美文网首页
前端面向对象面试题大全

前端面向对象面试题大全

作者: Aniugel | 来源:发表于2020-03-09 15:14 被阅读0次

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

    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
    

    相关文章

      网友评论

          本文标题:前端面向对象面试题大全

          本文链接:https://www.haomeiwen.com/subject/vvlufhtx.html