最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 详细剖析JavaScript原型和原型链机制并重写new与Object.create

    正文概述 掘金(mttt)   2021-04-04   624

    承接上一篇面向对象基础知识——原型与原型链基础知识

    下面从两个角度分析解释原型是什么,原型链机制又是如何运作的

    从两个角度先说说概念

    函数数据类型角度分析

    大部分函数数据类型的值都具备prototype(原型/显式原型)属性,属性值本身是一个对象。浏览器会默认为其开辟一个堆内存,用来存储当前类所属实例可以调用的公共的属性和方法。在浏览器默认开辟的这个堆内存(原型对象)中,有一个默认的属性 constructor(构造函数/构造器),属性值是当前函数/类本身。

    函数数据类型分类

    • 普通函数(实名或者匿名函数)
    • 箭头函数
    • 构造函数/类「内置类/自定义类」
    • 生成器函数 Generator
    • ...

    不具备prototype的函数

    • 箭头函数 const fn=()=>{}
    • 基于ES6给对象某个成员赋值函数值的快捷操作
          let obj = {
           fn1: function () {
              // 常规写法  具备prototype属性
           },
           fn2() {
              // 快捷写法  不具备prototype属性
           }
        };
        class Fn {
           fn() {} //这样的也不具备prototype属性
        };
      

    对象数据类型值角度分析

    每一对象数据类型的值都具备一个属性__proto__(原型链/隐式原型),属性值指向自己所属类的原型 prototype

    ps:这里先不考虑函数类型的对象

    对象数据类型值分类

    • 普通对象
    • 特殊对象:数组、正则、日期、MathError...
    • 函数对象
    • 实例对象
    • 构造函数。prototype

    面向对象的底层都是基于这两个角度:类(构造函数)和实例对象

    总结

    记住这两个最重要的概念:

    1. 大部分函数数据类型的值都具备prototype(原型/显式原型)属性,属性值本身是一个对象。浏览器会默认为其开辟一个堆内存,用来存储当前类所属实例可以调用的公共的属性和方法。在浏览器默认开辟的这个堆内存(原型对象)中,有一个默认的属性 constructor(构造函数/构造器),属性值是当前函数/类本身
    2. 每一对象数据类型的值都具备一个属性__proto__(原型链/隐式原型),属性值指向自己所属类的原型 prototype

    举例理解概念

    举例一个内置类(数组类Array和其实例)来理解以上概念

    函数数据类型角度理解

    我们知道,任何函数在内存中会存在:作用域,代码字符串,属性键值对,那我们来看看Array

    1. 作用域 详细剖析JavaScript原型和原型链机制并重写new与Object.create

      因为是内置的,作用域不清楚,但是是有作用域的

    2. 代码字符串 详细剖析JavaScript原型和原型链机制并重写new与Object.create

      [native code] 为代码字符串,可能为C++写的,浏览器内置代码不让我们不看

    3. 属性键值对中的proptotype属性 每一个函数都天生具备一个proptotype,所以Array中也是有的

      详细剖析JavaScript原型和原型链机制并重写new与Object.create

      并且proptotype对象中有一个constructor属性指向函数本身,另外包含很多Array的公共属性(concat,forEach等),如图:

      详细剖析JavaScript原型和原型链机制并重写new与Object.create

      总结:

      详细剖析JavaScript原型和原型链机制并重写new与Object.create

    同样,Object函数也是一样

    详细剖析JavaScript原型和原型链机制并重写new与Object.create

    对象数据类型角度理解

    ps:函数也是对对象,关于函数多种角色的问题这里暂时先不讨论,后面写文章进行补充

    实例对象,prototype(原型对象),函数对象,都是对象,所有的对象都有__proto__属性,这个属性指向所属类的原型对象。

    指向关系如下

    详细剖析JavaScript原型和原型链机制并重写new与Object.create

    注意容易混淆的点:

    原型对象并不是他所属的构造函数的一个实例,比如Array.prototype不是Array的一个实例。因为既然作为原型,他是所有实例用来共享属性和方法的对象,所以不会是其中的一个实例。只有new出来的才算是某个构造函数的实例。Array的原型对象是浏览器默认开辟出来的,这里不是Array构造函数的实例。 详细剖析JavaScript原型和原型链机制并重写new与Object.create

    通过浏览器我们发现Array.prototype.__proto__指向Object.proptotype,所以Array.prototypeObject的实例

    详细剖析JavaScript原型和原型链机制并重写new与Object.create

    注意更加特殊的Object.prototype.__proto__,指向null。因为Object是所有对象的基类,Object.prototype本身就是一个对象,Object.prototype.__proto__最后只能指向自己,即Object.prototype,这是没有意义的,所以最后浏览器让其指向null,这样也更合理一点。

    详细剖析JavaScript原型和原型链机制并重写new与Object.create

    详细剖析JavaScript原型和原型链机制并重写new与Object.create 详细剖析JavaScript原型和原型链机制并重写new与Object.create

    那么以上原型链机制的定义明确了以后,平常是怎么运作的呢?举个例子理解:

    执行arr.length,查看arr的属性length,或者执行arr.push的过程:

    首先访问自己的私有属性,如果私有属性中是存在的,则直接使用。如果访问的成员在私有属性中没有,默认会基于__proto__找到所属类的prototype上的属性/方法。

    所以Array.prototype上的方法,例如push等,相对于Array的实例来说,算是共有属性

    执行或使用arr.hasOwnProperty的过程:

    私有没有,Array.prototype上也没有,所以会继续基于Array.prototype.__proto__继续往上寻找,最终在Object.prototype上找到该方法,如果再找不到就返回undefined

    我们把上面这种成员访问的查找机制叫做原型链机制

    如下图:

    详细剖析JavaScript原型和原型链机制并重写new与Object.create

    以上是最常用的成员访问机制,还有一些其他方法进行成员访问:

    例如arr.forEach(),除了直接这样使用,还可以:

    1. 可以直接使用arr.__proto__.forEach()这样可以直接跳过私有属性的查找,直接使用原型对象上的方法(此方法平时一般不会自己手动操作,因为ie浏览器没法使用这个方法,ie将其保护起来,不允许我们访问)
    2. 也可以Array.prototype.forEach(),也可以直接使用原型对象上的方法

    那么这几种方法的区别是什么?三者都是找到Array.prototype.forEach,并且让其执行,区别在于forEach方法中的this指向不一样。谁调用了方法,点前面是什么,this就是什么。以上三种方式的this分别是arr,arr__proto__Array。prototype

    如下例子:

    let arr = [10, 20, 30];
    console.log(arr.hasOwnProperty('forEach')); //->false
    

    简单来看,上面的代码是 验证forEach是否为arr对象的私有属性

    那么整个的执行过程实际上是:

    arr按照原型链查找机制,找到的是Object.prototype.hasOwnProperty方法「@A」,并且把找到的@A执行,注意:

    • @A方法中的this应该是arr「我们要操作的对象」
    • 传递的实参应该是forEach「我们要验证的属性」 @A方法的作用是,验证“实参”是否为当前this的一个私有属性 ,那么最终同等效果的执行方式为:===> Object.prototype.hasOwnProperty.call(arr,'forEach')

    公有属性和私有属性的"相对"

    另外需要注意的点:公有属性和私有属性是"相对"的。

    例如相对于arr这个实例对象,Array.prototype这个对象里面的push等方法是各个arr实例的公有属性。而Array.prototype他自己本身就是一个对象,push在他这里就是他自己的私有方法,所以对象上的属性是共有还是私有,得要相对来说。当做公有属性,是相对于类的实例来说,当做私有属性是相对于自身来说

    详细剖析JavaScript原型和原型链机制并重写new与Object.create

    详细剖析JavaScript原型和原型链机制并重写new与Object.create

    所以没有公有属性,私有属性这个严格的概念,而只是相对来说,当不同的角色有了不同的功能,最后才区分了公有,私有的概念。

    这样明白以后,我们在写代码进行操作的时候,例如我们做一个字符串截取工作,那么我们可以直接查看String.prototype上有什么公有方法可以直接调用来使用,然后还可以顺着__proto__继续往上找公有方法,按照原型连一级级往上找,只要出现在原型链上的方法都可以使用。

    例如我们一直往上找document.body实例的方法,发现其有事件相关的方法

    详细剖析JavaScript原型和原型链机制并重写new与Object.create

    这样我们就可以使用document.body.addEventListener,再例如dom操作都有的classList方法,用来操作class

    详细剖析JavaScript原型和原型链机制并重写new与Object.create

    都可以找到相关的方法

    所以,整个JS就是基于面向对象思想构建的

    加深理解的例子

    function Fn() {
        this.x = 100;
        this.y = 200;
        this.getX = function () {
            console.log(this.x);
        };
    }
    Fn.prototype.getX = function () {
        console.log(this.x);
    };
    Fn.prototype.getY = function () {
        console.log(this.y);
    };
    let f1 = new Fn;
    let f2 = new Fn;
    console.log(f1.getX === f2.getX);//false
    console.log(f1.getY === f2.getY);//true
    console.log(f1.__proto__.getY === Fn.prototype.getY);//true
    console.log(f1.__proto__.getX === f2.getX);//false
    console.log(f1.getX === Fn.prototype.getX);//false
    console.log(f1.constructor);//Fn
    console.log(Fn.prototype.__proto__.constructor);//Object
    f1.getX();//100
    f1.__proto__.getX();//undefined
    f2.getY();//200
    Fn.prototype.getY();//undefined
    

    详细剖析JavaScript原型和原型链机制并重写new与Object.create 注:new Fn()new Fn是一样的效果,只是前者优先级是20,可传参数,后者优先级是19,不传参数

    分析:

    f1和f2都如下,迷惑点在于,其私有属性和原型上都有getX函数,两个私有属性地址不一样,返回false,而原型是同一个对象,其中的getX函数是同一个地址

    __proto__返回原型

    详细剖析JavaScript原型和原型链机制并重写new与Object.create

    详细剖析JavaScript原型和原型链机制并重写new与Object.create

    重写内置new

    function Dog(name) {
        this.name = name;
    }
    Dog.prototype.bark = function () {
        console.log('wangwang');
    }
    Dog.prototype.sayName = function () {
        console.log('my name is ' + this.name);
    }
    /*
    let sanmao = new Dog('三毛');
    sanmao.sayName();
    sanmao.bark();
    */
    function _new() {
        //=>完成你的代码   
    }
    let sanmao = _new(Dog, '三毛');
    sanmao.bark(); //=>"wangwang"
    sanmao.sayName(); //=>"my name is 三毛"
    console.log(sanmao instanceof Dog); //=>true
    

    new 关键字做了什么:

    1. 创建Ctor的一个实例对象(创建一个实例对象,并将实例对象的__proto__指向Ctor.prototype
    2. 把构造函数当做普通函数执行,并且让方法中的this指向实例对象
    3. 确认方法执行的返回值。如果没有返回值或者返回的是原始值,让其默认返回实例对象即可。

    注:

    1. Ctor -> constructor缩写 构造函数

    2. params -> 后期给Ctor传递的所有的实参信息

    function _new(Ctor, ...params) {
        // 1.创建Ctor的一个实例对象 
        // 实例.__proto__===Ctor.prototype
        let obj = {};//这里仅仅只创建了Object的一个实例对象 
        obj.__proto__ = Ctor.prototype;
    
        // 2.把构造函数当做普通函数执行「让方法中的THIS->实例对象」
        let result = Ctor.call(obj, ...params);
    
        // 3.确认方法执行的返回值「如果没有返回值或者返回的是原始值,我们让其默认返回实例对象即可」
        if (result !== null && /^(object|function)$/.test(typeof result)) return result;
        return obj;
    } 
    

    创建Ctor的一个实例对象 如果用

     let obj = {};//这里仅仅只创建了Object的一个实例对象 
        obj.__proto__ = Ctor.prototype;
    

    上面这种方法不太好,下面说说新的方法

    Object.create()

    Object.create([obj]):创建一个空对象,并且让空对象.__proto__指向 obj

    官方解释:把obj作为新实例对象的原型。

    也这样解读:以obj为原型,创造一个新对象

    注意:

    • 参数obj可以是一个对象或者是null,但是不能是其他的值
    • Object.create(null) 创建一个不具备__proto__属性的对象(不是任何类的实例)

    重写Object.create()

    Object._create = function create(prototype) {
        if (prototype !== null && typeof prototype !== "object") throw new TypeError('Object prototype may only be an Object or null');
        var Proxy = function Proxy() {}
        Proxy.prototype = prototype;
        return new Proxy;
    };
    

    效果: 详细剖析JavaScript原型和原型链机制并重写new与Object.create

    需要注意的是,我们自己写的这个_create,在传入null的时候是无法消除__proto__

    详细剖析JavaScript原型和原型链机制并重写new与Object.create

    详细剖析JavaScript原型和原型链机制并重写new与Object.create __proto__是没办法消除的,无法删除,并且直接指向Object

    详细剖析JavaScript原型和原型链机制并重写new与Object.create

    允许使用Object.create()时重写new

    使用Object.create()可替代重写new的第一步

    将变量前置声明,更加规范,并注意一些校验规则 详细剖析JavaScript原型和原型链机制并重写new与Object.create

    function _new(Ctor, ...params) {
        let obj,
            result,
            proto = Ctor.prototype,
            ct = typeof Ctor;
        // 构造函数的校验规则
        //前提不能是Symbol或者BigInt,然后Ctor不是函数或者proto不存在(箭头函数)就抛出一个类型错误
        if (Ctor === Symbol || Ctor === BigInt || ct !== 'function' || !proto) throw new TypeError(`${Ctor} is not a constuctor!`);
        
        //符合规则后按逻辑来
        //1. 
        obj = Object.create(Ctor.prototype);
        //2. 
        result = Ctor.call(obj, ...params);
        //3. 
        if (result !== null && /^(object|function)$/.test(typeof result)) return result;
        return obj;
    }
    

    起源地下载网 » 详细剖析JavaScript原型和原型链机制并重写new与Object.create

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元