你不得不看的js面试基础之——原型链与继承

358 阅读3分钟

本文是笔者在备战秋招时总结所得,如有错误,欢迎指正,一起学习~~~

原型链

首先你需要清楚:

  1. 所有函数类型都天生具有**prototype(原型)**属性。

  2. prototype中有一个constructor属性,这个属性就是指向当前构造函数本身

    其实这个属性指向了**[[prototype]],但是 [[prototype]] 是内部**属性,我们并不能访问到,所以使用 _proto_ 来访问。

  3. 每一个对象都有一个_proto__属性,这个属性指向其构造函数的prototype (原型)

    obj.__proto__ = Constrcutor.prototype;
    

    原型链是基于proto向上查找的机制,当我们要查找一个实例对象的某个属性或者方法时,我们会先在其私有的空间上查找,若无,则会查找基于proto所属类的prototype,若无,再向上查找,直到最后找到Object.prototype, 再向上直到null。

    也就是: obj -> obj.proto->obj.prototype.proto -> ...-> Object.prototype -> Object.prototype.proto ->null

扩展原型链的几种方法:

  1. Object.setPrototypeOf(obj, newobj)

  2. obj.prototype =

  3. let obj = Object.create(foo.prototype)

(创建一个对象 并将原型链指向foo.prototype)

  1. obj. proto=

obj.[[Prototype]] 用于指向 obj 的原型

[[Prototype]] 从es6开始可以通过 Object.getPrototypeOf() 和 Object.setPrototypeOf() 来访问,等同于 __proto__

被构造函数创建的实例对象的[[Prototype]] 指向 func 的 prototype 属性。

继承

继承是基于原型链的继承,分为继承属性和继承方法。

    function inherit(parentConstructor, prototypeObj) {
        function Cat(...args) {
            // 继承属性 
            // 将Animal的this指向 cat1实例 => 继承了Animal
            parentConstructor.apply(this, args);
        }
        Cat.prototype = prototypeObj;//实例cat1的proto
        // 继承方法
        // Object.setPrototypeOf(Cat.prototype, parentConstructor.prototype);
          Cat.prototype.__proto__ = parentConstructor.prototype
        // 将Animal.prototype设置给Cat.prototype
        return Cat
    }
    let animalNum = 0;
    // 属性
    function Animal(name,size) {
        animalNum++;
        this.size = size
        this.name = name;
    }
    // 方法
    Animal.prototype.getName = function () {
        return this.name;
    };
    const Cat = inherit(Animal, {
        say: function () {
            console.log(`NO${animalNum}:${this.getName()}`);
        }
    });
    const cat1 = new Cat('小花',10);
    console.log(cat1)
    cat1.say(); // NO1:小花

instanceof

检测构造函数的protype是否在某个实例对象的原型链上。

function instance_of(L, R) {
        let O = R.prototype;
        L = L.__proto__;
        while (true) {
            if (L === O) {
                return true;
            } else if(L.__proto__ === null) {
                return false
            }
            L = L.__proto__;
        }
    }

new

实现new的过程,伪代码如下:

  1. 创建一个继承自Object.prototype的空对象

  2. 继承原型对象

  3. 将这个对象作为构造函数要运行时的this 作用:增加原型的属性给自身

  4. 返回结果, 若构造函数的返回值为基本数据类型,则忽略,若为引用数据类型,则直接返回这个对象。

    1.0(此为简单版本,未考虑new的传参问题)

        function myNew(Constructor) {
            // 1 创建一个 继承自构造函数 Object.prototype 的对象
            let obj = {}
            // 继承 原型对象
            obj.__proto__ = Constructor.prototype;
            // 2 这个对象 作为构造函数运行时的 this,作用:增加原型的属性到自己的属性上
            let res = Constructor.apply(obj)
            // let res = obj
            console.log(res)
            // 3 new 产生的结果: 3.1 如果构造函数返回一个对象了, new 结果就是这个对象
            // 3 new 产生的结果: 3.2 如果构造函数没有返回一个对象, new 结果就是我们刚才创建的这个对象
            // console.log(obj.hosts);
            // todo 3
            return typeof res === 'object' ? res : obj;
        }
        function Animal() {
            // return {
            //     a:1, b:2
            // }
            // 如果 返回的不是一个对象, return 对 new 的结果没有任何作用
            // return 3;
            this.name = 'cat';
        }
        let ani = new Animal();
        console.log(ani);
    

    2.0 (传参版本)

        function _new (Constructor, ...args) {
            let obj = Object.create(Constructor.prototype);
            let res = Constructor.apply(obj, args)
            return typeof res === "object" ? res : obj
        }
        function _Animal(name, size) {
            this.name = name,
            this.size = size
        }
        let cat = _new(_Animal, 'cat', 10)