彻底掌握js原型和原型链

2021-03-21  本文已影响0人  哭不是罪

这块知识点比较抽象,我会尽量用代码+示意图的方式梳理。如果你有疑惑,请耐心看完,可能会有帮助。

构造函数和普通函数
        function Person() { }
        let zhangsan = new Person()

这块代码创建了一个Person构造函数和一个由Person构造函数创建的zhangsan实例对象。

其实Person构造函数和普通的函数并没有什么区别,只是在使用的时候用法不同,会有不同的叫法。

普通函数在进行new操作的时候,它就称为构造函数,否则它就是普通函数。

另外一点,为了在视觉上便于区分,通常会在定义当做构造函数使用的普通函数时,函数名的首字母用大写。这个不用太纠结。

prototype和__ proto __
prototype

在函数中有一个prototype属性,为方便区分,通常叫做显式原型对象。
而实例对象则没有。

这个属性比较简单了,它就是一个对象{}(new Object()),但是这个对象下包含着一个指向函数本身的constructor方法。这个对象是可以随意更改的,尤其在实现继承的时候,注意改变函数的prototype指向。

        function Person(){
          // prototype: { constructor: function Person() }
        }
        let zhangsan = new Person()  
        console.log(Person.prototype.constructor === Person) // true Person有prototype,且Person.prototype.constructor 指向函数本身
        console.log(zhangsan.prototype) // undefined 实例对象没有prototype
        
__ proto __
        function Person() { }
        let zhangsan = new Person()
        console.dir(Person.__proto__) // {...}
        console.dir(zhangsan.__proto__) // {...}

无论是Person构造函数还是由该函数创建的zhangsan实例对象都会有一个叫做"__ proto __"的属性。它们是怎么来的呢?

函数/实例在创建的时候都会有一个__ proto __属性,它是由创建该函数/实例对象的父级构造函数的prototype赋值而来的。

为了方便区分,__ proto __通常叫做隐式原型

zhangsan是由Person函数创建出来的,zhangsan的父级构造函数就是Person。

那Person是由谁创建的呢,Person的父级构造函数是谁呢?

其实函数也是有父级构造函数创建出来的,这个父级构造函数就是Function。Function是一个系统内置的函数。

很多系统内置的函数也是由它创建的,如Array()、String()、Boolean()、Date()、Object()等等

        // function Person(){}  ==> var Person = new Function()
        // var str = '123' ==> var str = new String('123')
        // var obj = {} ==> var obj = new Object()
        // ......
        var Person = new Function()
        let zhangsan = new Person()
        // 这样看是不是就很像了呢。
        console.dir(Person.__proto__ === Function.prototype) // true
        console.dir(zhangsan.__proto__ === Person.prototype) // true

        // 先就__proto__讨论,上面就发生了类似于下面的过程
        // 创建Person时
        // function Person() {
        //     __proto__: Function.prototype
        // }
        
        // new Person时
        // function Person() {
        //     var this = {
        //         __proto__: Person.prototype
        //     }
        //     return this
        // }

上面关于prototype 和 __ proto __ 的来龙去脉说完了,我觉得是有必要在前面就把它们讲清楚,因为原型和原型链就是围绕着这两个属性展开的。

原型链
function Person() { }
        Person.prototype.job = 'student'
        let  zhangsan = new Person()
        console.log(zhangsan.job)  // student
        console.log(zhangsan) // {__proto: {job:student}}
        console.log(zhangsan.toString()) // "[object Object]"

从打印结果可以看出,zhangsan自身并没有job这个属性,倒是只有一个__ proto __ 属性,但是为什么zhangsan能访问到job呢?

对象访问变量的时候会首先从自身找,如果自身找不到就从自身的__ proto __ (隐式原型)属性指向的父级函数的prototype(显式原型)中寻找,如果还找不到就沿着该显示原型的__ proto __继续寻找,形成的这个链状关系就是原型链,直到到达原型链的顶端Object.prototype(null)。

示例中,zhangsan.__ proto __ 是在new Person创建zhangsan的时候,由Person的prototype赋值而来

        // new Person时
        // function Person() {
        //     var this = {
        //         __proto__: Person.prototype
        //     }
        //     return this
        // }

zhangsan.__ proto __ 下面是有一个job属性的,所以zhangsan能访问job

zhangsan.job(no value) ==> zhangsan.__proto__ .job ==> Person.prototype.job (has value) ==> success

对照此图会更容易理解

aaa.png

这个图看似复杂,其实记住根本的两句话,并不难理解。
函数和实例对象都会有一个__ proto __,它们的来源其实很简单

谁创建了函数,谁就得把自己的prototype属性赋给新创建函数的 __ proto __
谁创建了实例对象,谁就得把自己的prototype属性赋给新对象的__ proto __

还以上面的代码举例,
Person创建了zhangsan,Person的prototype就得赋给zhangsan.__ proto __ ,所以zhangsan.__ proto __ === Person.prototype

Function创建了Person,Function的prototype就得赋给Person.__ proto ,所以Person. proto __ === Function.prototype

Person.prototype也是一个对象(类似{}),谁创建了它呢,Object创建了它(new Object()),那么Person.prototype.__ proto __ === Object.prototype

说了这么多,感觉还是苍白无力,这块比较抽象,需要多实践对比,自己画画图理解一下会好很好。

个人总结,如有错误,请指正。

上一篇下一篇

猜你喜欢

热点阅读