前端面向对象面试题大全

以下题目是根据网上多份面经收集而来的,题目相同意味着被问的频率比较高,有问题欢迎留言讨论,喜欢可以点赞关注。

1、你是怎么理解面向对象的,什么是面向对象,用面向对象做过什么

面向对象编程(OOP,Object Oriented Programming)
用一个对象去描述一些属性和行为


image.png

好处:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护

image.png
2、继承(ES5/ES6),es5的继承实现一下,手写一个类的继承

https://www.jianshu.com/p/dd7eb3464759

es5 构造函数继承 原型链继承 组合继承 原子继承 原型方法继承 寄生式继承 寄生组合式继承
原型链继承继承

    Cat.prototype = new Animal();
    //上面的代码把cat的prototype指向了Animal 现在要还原回来
    Cat.prototype.constructor = Cat;
    // 问题
    // 1、子类的构造函数的参数,没法传给父级的构造函数
    // 2、子类的原型的constructor会被改变,需要自己变回来
    // 3、父类使用this声明的属性被所有实例共享。 原因是实例化是父类一次性赋值到子类实例的原型上,它会将父类通过this声明的属性也赋值到子类原型上。例如在父类中一个数组值,在子类的多个实例中,无论哪一个实例去修改这个数组的值,都会影响到其他子类实例。

构造函数继承

 function Cat(name, age) {
        // Animal(age,name)//this===window; 
       //  Animal.call(this) 无参数
        Animal.call(this, name, age)//借用父类的构造函数 给子类创建实例属性
    }
//优点:
//1、可以向父类传递参数。
//2、解决父类this声明的属性会被实例共享的问题。即实例修改属性不会影响到父级属性
//缺点
//1、不能继承父类prototype上的属性/方法,只能继承父类通过this声明的属性/方法。
//2、每次实例化子类,都要执行父类函数。重新声明父类所定义的方法,无法复用。

组合继承规避了原型继承法(解决父类this声明的属性会被实例共享的问题)和构造函数继承法的缺点

 // 父类
    function Animal(name, age) {
        this.name = name;
        this.age = age;
        this.fruit = ['water', 'apple']
    }
    // 在父类的原型上 创建run方法
    Animal.prototype.run = function () {
        console.log(this.name + '  running')
    }

    function Cat(name, age) {
        // Animal(age,name)//this===window; 
        Animal.call(this, name, age)
    }
    Cat.prototype = new Animal();//组合原型继承模式
    Cat.prototype.constructor = Cat;
    var c = new Cat('Tom', 12)
    console.dir(Cat.prototype.constructor)

    console.log(c)// Tom
    console.log(c.name)// Tom
    console.log(c.age)// Tom
    console.log(c.fruit)// ['water', 'apple']

优点
1、解决原型链继承父类this声明的属性或者方法被共享的问题。
2、解决借用构造函数解决不能继承父类prototype对象上的属性/方法问题。
缺点
1、调用了父类函数两次,造成一定的性能问题。
2、因调用两次父类,导出父类通过this声明的属性和方法被生成两份的问题。
3、原型链上下文丢失,子类和父类通过prototype声明的属性和方法都存在与子类prototype上
原子继承

    var animal = { name: "qw", age: 8 };
    var a = Object.create(animal);//'qw'
    console.log(a.name);//{}
    console.log(a);
    // 注:此时a.name a.age可访问成功,但a本身并无此类属性,而a原型上有这些属性
// 优点:
// 不需要使用new构造函数就可以直接 构造另外其他对象
// 缺点:
// 所有构造函数出来的实例会共享 原型对象上的引用类型的属性

原型式继承
https://www.jianshu.com/p/ab6b8821e80f

    function Object(o) {
    function F(){};
    // 将被继承的对象作为空函数的prototype
    F.prototype = o;
    // 返回new期间创建的新对象,此对象的原型为被继承的对象, 
    // 通过原型链查找可以拿到被继承对象的属性
    return new F();
}
    var o = { name: 'liu', age: 23 }
    var m1 = Object(o)
    console.log(m1.name)

优点
1、兼容性好,最简单的对象继承。
缺点
1、多少实例共享被继承的属性,存在被篡改的情况,不能传递参数。

寄生式继承

function Person(name, age, job) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function() {
        alert(this.name);
    }
    return o;
}

var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
/* 重点:就是给原型式继承外面套了个壳子。
    优点:没有创建自定义类型,因为只是套了个壳子返回对象,这个函数顺理成章就成了创建的新对象。
 缺点:没用到原型,无法复用。 */

优点
1、兼容性好,最简单的对象继承。
缺点
1、多少实例共享被继承的属性,存在被篡改的情况,不能传递参数。

寄生组合继承
JS的继承方式有很多种,最理想的继承方式是寄生组合式继承。
组合继承(构造函数和原型的组合)会调用两次父类构造函数的代码,

function Person(name){
  this.name=name;
}
Person.prototype.sayName=function(){
  console.log(this.name+' '+this.gender+' '+this.age);
}
function Female(name,gender,age){
  Person.call(this,name);//第一次调用父类构造函数             
  this.age=age;
  this.gender=gender;
}
Female.prototype=new Person();//第一次调用父类构造函数
Female.prototype.constrcutor=Female;//因重写原型而失去constructor属性,所以要对constrcutor重新赋值

因此引入寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的方式来继承方法,而不需要为子类指定原型而调用父类的构造函数,我们需要拿到的仅仅是父类原型的一个副本。因此可以通过传入子类和父类的构造函数作为参数,首先创建父类原型的一个复本,并为其添加constrcutor,最后赋给子类的原型。这样避免了调用两次父类的构造函数,为其创建多余的属性。

function Parent(name) {
  this.name = name
}

Parent.sayHello = function (){
    console.log('hello')
}

Parent.prototype.sayName = function() {
    console.log('my name is ' + this.name)
    return this.name
}


function Child(name, age) {
    Parent.call(this, name)
    this.age = age
}

function _inherits(Child, Parent) {
  Child.prototype = Object.create(Parent.prototype)
  Child.prototype.constructor = Child
  Child.__proto__ = Parent

}

_inherits(Child, Parent)

Child.prototype.sayAge = function () {
    console.log('my age is ' + this.age)
    return this.age
}

var parent = new Parent('Parent')
var child = new Child('Child', 18)
console.log(parent)
Parent.sayHello()
parent.sayName()
console.log(child)
Child.sayHello()
child.sayAge()
child.sayName()

1、寄生组合式继承是当前最成熟的继承方法,也是先也常用的继承方法,在大多数Js框架中都是用这个作为继承方案。

寄生组合式继承相对组合继承的优点:
1、只调用了父类构造函数一次,节约了性能。
2、避免生成了不必要的属性。
3、使用原型式继承保证了原型链上下文不变,子类的prototype只有子类通过prototype声明的属性和方法,父类的prototype只有父类通过prototype声明的属性和方法。

es6通过extend
1、class 可以理解为function,由于class本质还是一个function,因此它也会拥有一个的prototype属性,当new一个class时,会把class的porototype属性赋值给这个新对象的 __proto属性。
2、constructor 方法是默认添加的方法,在new一个对象时,自动调用该方法,constructor里面定义自己的属性。
3、继承extends和super,class 子类名 extends 父类名实现继承,当然还得在constructor里面写上super(父类的参数),意思就是在子类中获得父类的this指针,相当于Animal.call(this)

// es6继承
  class Animal {
    //构造函数,里面写上对象的属性
    constructor(props) {
      this.name = props.name || 'Unknown';
    }
    //方法写在后面
    eat() {//父类共有的方法
      console.log(this.name + " will eat pests.");
    }
  }

  //class继承
  class Bird extends Animal {
    //构造函数
    constructor(props,myAttribute) {//props是继承过来的属性,myAttribute是自己的属性
      //调用实现父类的构造函数
      super(props)//相当于获得父类的this指向
      this.type = props.type || "Unknown";//父类的属性,也可写在父类中
      this.attr = myAttribute;//自己的私有属性
    }

    fly() {//自己私有的方法
      console.log(this.name + " are friendly to people.");
    }
    myattr() {//自己私有的方法
      console.log(this.type+'---'+this.attr);
    }
  }

//通过new实例化
  var myBird = new Bird({
    name: '小燕子',
    type: 'Egg animal'//卵生动物
  },'Bird class')
  myBird.eat()
  myBird.fly()
  myBird.myattr()

3、JS的继承方法x2,用原型实现继承有什么缺点,怎么解决

同2题,继承最大的优点就是能继承父类构造函数和原型的属性和方法
缺点:
1、子类的构造函数的参数,没法传给父级的构造函数
2、子类的原型的constructor会被改变,需要自己变回来
3、父类使用this声明的属性被所有实例共享。 原因是实例化是父类一次性赋值到子类实例的原型上,它会将父类通过this声明的属性也赋值到子类原型上。例如在父类中一个数组值,在子类的多个实例中,无论哪一个实例去修改这个数组的值,都会影响到其他子类实例。
方法:实用组合继承


4、解释一下原型和原型链有什么特点x2

原型
每个函数都有一个prototype属性,这个属性指向的就是原型对象. 实例有一个proto指向它构造函数的原型对象.

原型链
当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,会去对象的proto属性(隐式原型对象)里去找 , (这里的隐式原型对象指向的就是它构造函数的prototype(显示原型对象))然后原型本身也是一个对象 , 拥有proto 属性 , 所以会继续向上查找 ,一直找到Object.prototype.proto===null 这样的链条称之为原型链

特点
原型对象上的方法是被不同实例共有的 ,当我们修改原型时,与之相关的对象也会继承这一改变。原型链就是为了实现继承

5、介绍this和原型

一:到底什么是this呢

概念:执行上下文,this一般存在于函数中,表示当前函数的执行上下文, 如果函数没有执行,那么this没有内容,只有函数在执行后this才有绑定。注意:this的指向只能是对象,当然别忘记了数组也是一个特殊的对象。

二:this到底指向的是谁呢

this的指向其实是跟this的执行位置是有关的,不同的位置执行位置,this的指向就可能发生改变。this被谁执行了,this就是执行谁的,这时候一定要看清楚this是被谁直接执行的! 默认执行:this指向了window,在严格模式下,this指向了undefined

image.png
6、使用原型最大的好处

原型的作用一:数据共享,节省空间,继承
利用原型可以使得每一个实例对象都可以调用原型上的say方法,可以让每个实例对象只是得到函数say的一个指针,指向同一个say函数,节省了空间
原型的作用二:继承
在子类构造函数中借用父类构造函数,再通过原型继承父类的原型属性和方法,模拟继承的效果

7、prototype和_proto_区别x2
image.png

image.png
image.png

对象并不具有prototype属性,只有函数才有prototype属性。
总结:
js里所有的对象都有proto属性(对象,函数),指向构造该对象的构造函数的原型。
只有函数function才具有prototype属性。这个属性是一个指针,指向一个对象,这个对象的用途就是包含所有实例共享的属性和方法(我们把这个对象叫做原型对象)。原型对象也有一个属性,叫做constructor,这个属性包含了一个指针,指回原构造函数。

8、construct是什么

construct构造属性构造属性存储着这个对象所对应的函数的引用。由于JS对象是通过call调用并执行堆的声明函数实现的JS对象,所以JS对象的construct存储着这个JS对象所调用的函数的函数引用

<script type="text/javascript">

function employee(name,job,born)
{
this.name=name;
this.job=job;
this.born=born;
}

var bill=new employee("Bill Gates","Engineer",1985);

document.write(bill.constructor);
console.log(bill.__proto__.constructor);//结果同上
</script>
输出:

function employee(name, job, born)
{this.name = name; this.job = job; this.born = born;}
9、new 的过程,new的原理是什么?通过new的方式创建对象和通过字面量创建有什么区别,实现new
image.png
image.png
10、ES5对象 vs ES6对象(es6 class 的new实例和es5的new实例有什么区别?)

ES6中(和ES5相比),classnew实例有以下特点:

  • class的构造参数必须是new来调用,不可以将其作为普通函数执行
  • es6class不存在变量提升
  • 最重要的是:es6内部方法不可以枚举。es5的prototype上的方法可以枚举。

为此我做了以下测试代码进行验证:

console.log(ES5Class()) // es5:可以直接作为函数运行
// console.log(new ES6Class()) // 会报错:不存在变量提升

function ES5Class(){
  console.log("hello")
}

ES5Class.prototype.func = function(){ console.log("Hello world") }

class ES6Class{
  constructor(){}
  func(){
    console.log("Hello world")
  }
}

let es5 = new ES5Class()
let es6 = new ES6Class()

console.log("ES5 :")
for(let _ in es5){
  console.log(_)
}

// es6:不可枚举
console.log("ES6 :")
for(let _ in es6){
  console.log(_)
}
复制代码

这篇《JavaScript创建对象—从es5到es6》对这个问题的深入解释很好,推荐观看!

11、介绍class和ES5的类以及区别

ES5的继承实质是先创建子类的实例对象this,然后将父类的方法添加到this上。
ES6的继承实质是先将父类实例对象的方法和属性加到this上面,然后在用子类的构造函数修改this。

ES5的继承是通过prototype或构造函数机制来实现。ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.apply(this))。ES6的继承机制实质上是先创建父类的实例对象this(所以必须先调用父类的super()方法),然后再用子类的构造函数修改this。具体为ES6通过class关键字定义类,里面有构造方法,类之间通过extends关键字实现继承。子类必须在constructor方法中调用super方法,否则新建实例报错。因为子类没有自己的this对象,而是继承了父类的this对象,然后对其调用。如果不调用super方法,子类得不到this对象。注意:super关键字指代父类的实例,即父类的this对象。在子类构造函数中,调用super后,才可使用this关键字,否则报错。

12、call,apply,bind 三者用法和区别:角度可为参数、绑定规则(显示绑定和强绑定),运行效率、运行情况,原生实现bind

https://juejin.im/post/59bfe84351882531b730bac2

fun.apply(thisArg, [argsArray])

thisArg:在 fun 函数运行时指定的 this 值。需要注意的是,指定的 this 值并不一定是该函数执行时真正的 this 值,如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象。
argsArray:一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 fun 函数。如果该参数的值为null 或 undefined,则表示不需要传入任何参数。从ECMAScript 5 开始可以使用类数组对象。浏览器兼容性请参阅本文底部内容。

首先说明call,apply是ES5中的语法,bind是ES6新引入的,它们三者的相似之处为:

  • 都是用来改变函数的this对象的指向(即函数运行时的上下文(context))
  • 第一个参数都是this要指向的对象
  • 都可以利用后续参数进行传参
    apply方法实际上是与call方法用法相同,只不过apply方法传进去的参数是以数组形式(或者类数组)bind()的作用其实与call()以及apply()都是一样的,都是为了改变函数运行时的上下文,bind()与后面两者的区别是,call()和apply()在调用函数之后会立即执行,而bind()方法调用并改变函数运行时的上下文的之后,返回一个新的函数,在我们需要调用的地方去调用他。

call,apply,bind 三者用法和区别:参数、绑定规则(显示绑定和强绑定)、运行效率(最终都会转换成一个一个的参数去运行)、运行情况(call,apply 立即执行,bind 是return 出一个 this “固定”的函数,这也是为什么 bind 是强绑定的一个原因)。
注:“固定”这个词的含义,它指的固定是指只要传进去了 context,则 bind 中 return 出来的函数 this 便一直指向 context,除非 context 是个变量。

实现bind

Function.prototype.myBind = function (obj) {
    const object = obj || window; //如果第一个参数为空则默认指向window对象
    let self = this;
    let args = [...arguments].slice(1); //存放参数的数组

    return function () {
        let newArgs = [...arguments]
        return self.apply(object, args.concat(newArgs))
    }
}

personOne.say.myBind(personTwo, "女", 24)();

前面的知识不重复说,return function是因为bind返回的是一个函数,并且这个函数不会执行,需要我们再次调用,那么当我们调用的时候,我们依旧可以对这个函数进行传递参数,即为支持柯里化形式传参,所以需要在返回的函数中声明一个空的数组接收调用bind函数返回的函数时传递的参数,之后对两次的参数使用concat()方法进行连接,调用ES5中的apply方法。

介绍下原型链(解决的是继承问题吗)
请简述原型链
https://juejin.im/post/5d282541e51d4577523f2422

image.png
image.png

image.png
    function employee(name, job, born) {
        this.name = name;
        this.job = job;
        this.born = born;
    }
    var b = new employee("Bill Gates", "Engineer", 1985);
    employee.prototype.age = 200
    console.log('age' in b);//true
    console.log(b.hasOwnProperty('name'));//true
    console.log(b.hasOwnProperty('age'));//false 原型中的属性不能检测到
    console.log(b.age !== undefined);//true
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容

  •   面向对象(Object-Oriented,OO)的语言有一个标志,那就是它们都有类的概念,而通过类可以创建任意...
    霜天晓阅读 2,096评论 0 6
  • JavaScript面向对象程序设计 本文会碰到的知识点:原型、原型链、函数对象、普通对象、继承 读完本文,可以学...
    moyi_gg阅读 759评论 0 2
  • 博客内容:什么是面向对象为什么要面向对象面向对象编程的特性和原则理解对象属性创建对象继承 什么是面向对象 面向对象...
    _Dot912阅读 1,401评论 3 12
  • > 此篇总结与《JavaScript设计模式》github地址 [YOU-SHOULD-KNOW-JS](http...
    Nealyang阅读 306评论 0 1
  • 我从镜中看到 天使的泪水 她满身伤痕 背着残败的翅膀 低着头 任凭衣衫在风中飘摇 惨淡而空洞的脸上 挂着一双落寞的...
    江晚吟wilu阅读 304评论 0 3