当面试官问你面向对象的时候,你真的都会了吗?

233 阅读6分钟

首先声明,我不是标题党,这是菜鸡本鸡的扪心自问。。今天被追问对面向对象的理解,并引申问了几个问题,没想到准备了那么久的八股文,居然也被问住了,看来是真的高估自己了,被挂得也心服口服。所以今天准备完整做个回顾,权当记录了。

请你谈谈你对面向对象的理解吧

面向对象其实是将问题抽象成具体的对象,这一个对象有自己的属性及方法,在解决问题的时候就是通过将对象结合在一起。建立对象的目的不是为了完整的完成某一个步骤,而是描述某个事物在整个解决问题的过程中的方法及行为。

请你说说面向对象的三个特性分别是什么

封装:把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。

继承:通过继承创建的新类称为“子类”或“派生类”。继承的过程,就是从一般到特殊的过程。

多态:同一个方法,面对不同的对象有不同的表现形式就叫做多态。( JS 中同一作用域下的同名函数,前者会被后者覆盖

请说说实现继承的方式

推荐一篇个人感觉写的不错的:面试官:Javascript如何实现继承? | web前端面试 - 面试官系列 (vue3js.cn)

可以先来几个问题测试下:

1)有几种继承的方式?优缺点是什么?

2)只有构造函数没有原型链的继承有什么优缺点?

3)为什么要有寄生式组合继承?

1. 法一:原型链继承

子类可以利用prototype将所有在父类中通过prototype追加的方法和属性都追加到子类。 下面是例子:

    //声明父类 
    var SuperClass = function () { 
        this.name = "Mike";
    }; 
    
    //为父类添加共有方法 
    SuperClass.prototype.getSuperValue = function () { 
        return this.name(); 
    }; 
    
    //声明子类 
    var SubClass = function () { 
        this.sex = "female";
    } }; 
    
    //核心:继承父类,通过原型形成链条 
    SubClass.prototype = new SuperClass() ; 
    
    //为子类添加共有方法 
    SubClass.prototype.getSubValue= function () { 
        return this.sex() 
    };

缺点

1)使用类继承的方法,如果父类的构造函数中有引用类型,就会在子类中被所有实例共用,因此一个子类的实例如果更改了这个引用类型,就会影响到其他子类的实例。

2)子类型实例不能给父类型构造函数传参

2. 法二:构造函数继承

​ 通过使用call()apply()方法,Parent构造函数在为Child的实例创建的新对象的上下文执行了,就相当于新的Child实例对象上运行了Parent()函数中的所有初始化代码,结果就是每个实例都有自己的info属性。

好处是即使改变了某一个对象的属性或方法,不会影响其他的对象(因为每一个对象都是复制的一份)。

function Parent() { 
this.name = "Mike";
} 
function Child() { 
    Parent.call(this) 
}

缺点

1)内存浪费。

2)call方法仅仅调用了父级构造函数的属性及方法,没有办法调用父级构造函数原型对象的方法。

3. 法三:组合继承

基本的思路就是使用原型链继承原型上的属性和方法,而通过构造函数继承实例属性,这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性。

    //声明父类
    var SuperClass = function (name) {
        this.name = name;
        this.sex=['male','female'];
    };
    //声明父类原型上的方法
    SuperClass.prototype.sexs = function () {
        console.log(this.books)
    };
    //声明子类
    var SubClass = function (name) {
        SuperClass.call(this, name)
    };
    //子类继承父类(链式继承)
    SubClass.prototype = new SuperClass();
    //实例化子类
    var subclass1 = new SubClass('Mike');
    var subclass2 = new SubClass('Tom');

法四:寄生式组合继承

引用:juejin.cn/post/699344…

父类的构造函数会被创建两次(call()的时候一遍,new的时候又一遍),所以为了解决这个问题,又出现了寄生组合继承。组合继承存在这一定的效率问题,它的父类构造函数始终会被调用俩次,一次在创建字类原型时调用,另一次在子类构造函数中调用。本质上子类只需要在执行时重写自己的原型就行了。

     function inheritPrototype(subType, superType) {
        let prototype = Object(superType.prototype); // 创建对象
        prototype.constructor = subType; // 增强对象
        subType.prototype = prototype; // 赋值对象
    }

这个 inheritPrototype()函数实现了寄生式组合继承的核心逻辑。这个函数接收两个参数:子类构造函数和父类构造函数。在这个函数内部,第一步是创建父类原型的一个副本。然后,给返回的 prototype 对象设置 constructor 属性,解决由于重写原型导致默认 constructor 丢失的问题。最后将新创建的对象赋值给子类型的原型。如下例所示,调用 inheritPrototype()就可以实现前面例子中的子类型原型赋值:

//声明父类
function SuperType(name) {
        this.name = name;
        this.colors = ["red", "blue", "green"];
    }
//声明父类原型上的方法
SuperType.prototype.sayName = function () {
        console.log(this.name);
    };
//声明子类
function SubType(name, age) {
        SuperType.call(this, name);
        this.age = age;
    }
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function () {
        console.log(this.age);
    };

这里只调用了一次 SuperType 构造函数,避免了 SubType.prototype 上不必要也用不到的属性, 因此可以说这个例子的效率更高。而且原型链仍然保持不变。

法五:寄生继承

寄生式继承就是用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象。object.create()就是这个原理。

  // 寄生式继承
    function subobject(obj) {
        let clone = Object(obj);
        clone.sayName = function(){
            console.log("Mike")
        };
        return clone;
    }
    let sub = {
        name:"Tom"
    }
    let sup = subobject(sub);
    sup.sayName();//Mike

请说说封装的方式

通过构造函数添加

构造函数其实就是普通的函数,只不过有以下的特点

  • 首字母大写
  • 内部使用this
  • 使用 new生成实例

通过this添加的属性和方法只在当前对象上添加,是该对象自身拥有的。 缺点:我们实例化一个新对象的时候,this指向的属性和方法都会得到相应的创建,即会在内存中复制一份,这样就造成了内存的浪费。

function Cat(name,color){
        this.name = name;
        this.color = color;
        this.eat = function () {
            alert('吃老鼠')
        }
    }

通过原型链

对于那些不变的属性和方法,我们可以直接将其添加在类的prototype 对象上,再生成实例即可。

在类的外部通过.语法添加

在实例化对象的时候,并不会执行到在类外部通过. 语法添加的属性和方法,所以实例化之后的对象是不能访问到. 语法所添加的对象和属性的,只能通过类自身访问。

用于js代码封装的设计模式有哪些?

参考:segmentfault.com/a/119000002…

主要有工厂模式创建者模式单例模式原型模式四种。