简述原型

245 阅读8分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第7天,点击查看活动详情

原型(ptototype)

上一篇文章对原型做了一个概述,这一篇会对原型进行详细的剖析。

1.认识原型

函数的prototype属性

每个函数都有一个prototype属性,它默认指向一个Object空对象(我们即称为:原型对象)

        function Fun1() {

        }
        function Fun2() {

        }
        console.log(Fun1.prototype); //默认指向一个Object空对象
        console.log(Fun2.prototype); //默认指向一个Object空对象
        console.log(Fun1.prototype == FUN2.prototype); // false

见以上代码,每一个函数中的prototype都是唯一的,因为指向的是一个Object空对象,虽然是空的,但它是Object的实例,而每一个实例都是唯一的,所以我们说每一个函数中的prototype都是唯一的。

原型对象中有一个属性constructor,它指向函数对象(构造函数)

        console.log(Date.prototype.constructor === Date); //true
        console.log(Fun.prototype.constructor === Fun); //true

image.png
我们假设构造函数名叫“Type”,这个构造函数中有个属性叫prototype。
而这个prototype属性指向的是这个Type的原型对象 “Type prototype”。
而原型对象“Type prototype” 中又有个属性constructor,constructor又指向了构造函数Type
也就是说,这个构造函数和它的原型对象是相互引用的关系。我是A,你是B,A里面有个属性能找到B,B里面有个属性能找到A。

我们给原型添加方法,是给谁用的?
给原型对象添加属性(一般是方法) ===> 是给实例对象访问的

        Fun1.prototype.test = function () {
            console.log('我是test()');
        }
        var fun = new Fun();
        fun.test();

2.显式原型与隐式原型

见以下过程

       function Fn() { 
            //内部语句执行了:this.prototype = {}
        }

① 每个函数function都有一个prototype,即显示原型属性
console.log(Fn.prototype); // Object
② 每个实例对象都有一个__proto__ ,可称为隐式原型属性

        var fn = new Fn(); //创建时执行的内部语句:this.__proto__ = Fn.prototype
        console.log(fn.__proto__); // Object

③ 对象的隐式原型的值 为 其对应构造函数的显示原型的值

        console.log(Fn.prototype === fn.__proto__); //true
        //给原型对象添加方法
        Fn.prototype.test = function () {
            console.log("hello world");
        }
        //通过实例调用原型的方法
        fn.test()

总结:

  • 函数的prototype属性:是在定义函数时自动添加的,默认值是一个空Object对象
  • 对象的__proto__属性:创建对象时自动添加的,默认值为构造函数的prototype属性值(就是说对象.__proto__和 函数名(通常指构造函数).prototype 指向的是同一个原型对象)
  • 程序员能直接操作显示原型,但不能直接操作隐式原型(ES6之前),就是你不能直接 实例对象.__proto__去操作构造函数的原型对象(ES6之前)

见补充图:

image.png

以上的代码都干了些啥事呢,我们来看看。
首先就是创建了一个函数,函数对象在堆空间里面,函数名Fn在栈空间里,存放的值是这个函数对象的地址。 在定义函数时会自动给函数对象添加prototype属性,指向空Object实例对象。
然后我们创建一个Fn的实例,创建这个实例对象时会自动添加__proto__属性,也是指向这个空Object实例对象
通过操作显式原型给Fn对象添加方法,这个方法也是指向一个函数对象...


补充图Object部分解释:
对于任何构造函数,以及对应的实例,不管是他们的显式原型属性还是隐式原型属性指向的都是这个构造函数的原型对象。这个原型对象其实是一个空Object对象,是一个Object的实例,那么它既然是实例,就会有一个隐式原型属性__proto__,其指向的是Object构造函数的原型对象
我们可以试着console.log(Object),Object是一个引用变量,它指向的是Object函数对象,这个Object函数对象具有一个Prototype属性,即显式原型属性,指向的是Object的原型对象。


关于图中的Fn构造函数为什么会有隐式原型的补充
图中的函数对象中也存在着一个隐式原型属性__proto__,我们说所有的实例对象都有这个隐式原型属性,所有函数对象本质上是Function的实例
我们function Fn(){}的过程相当于 var Fn = new Function(),这个Fn是一个函数变量,所以创建这个Fn的过程,内部代码是这么实现的this.__proto__ =Function.prototype
所以,每个函数都有两个属性,一个显式原型属性,一个隐式原型属性,隐式原型的值和Function.prototype的值是相等的(因为每个函数都是Function的实例,实例对象的隐式原型属性的值等于构造函数显式原型属性的值),都是指向Function的原型对象。


Object与Function之间的联系补充图

image.png
上图描述了什么内容?
我们说任何函数都是new Function()产生的,那么Object()这个构造函数也是如此,也就是说Object构造函数是Function的一个实例,实例拥有隐式原型属性__proto__,它的值与实例对应的构造函数的显式原型属性的值相等,也就是说Object构造函数的内部执行了这样一条语句 this.__proto__ = Function.prototype

“函数的显式原型指向的对象默认是空Object实例对象” 但是,Object本身不满足
见下面代码

    console.log(Foo.prototype instanceof Object) //true
    console.log(Object.prototype instanceof Object) //false
    console.log(Function.prototype instanceof Object) //true

也就是说,Function的原型对象,实际上也是一个Object实例,也存在隐式原型属性,指向Object的原型对象

书接上一张图,我们说每个函数不仅有prototype显式原型属性,还有 __proto__ 隐式原型属性 ,而隐式原型属性是实例才有的,我们可以推出,每个函数都是Function的实例。
好,我们再看看Function,这个图中,Function既有显式原型属性也有隐式原型属性,说明它即是函数也是实例,得出:Function是new自己创建的: Function = new Function()
得出结论: 所有函数都是Function的实例,包括它自身
噢,对了,上面那张图的Object.prototype上少画了一个__proto__隐式原型属性,它的值是null,也就是原型链的尽头.


3.原型链

什么是原型链?
原型链又叫隐式原型链,因为它本质上是沿着隐式原型找的。
作用
查找对象的属性(方法) 是用来查询属性的,不是查询变量的,查询变量看的作用域链

访问一个对象的属性时,先在自身属性中查找,找到则返回;如果没有,再沿着__proto__这条链向上查找,找到则返回,如果最终没找到,返回undefined。(最多到 实例对象.__proto__.__proto__,找不到就返回undefined了)

注意:在对象的继承模式中,有一个原型继承,构造函数的实例对象自动拥有构造函数原型对象的属性(方法),利用的就是原型链

4.原型链属性问题

        function Fn() {

        }
        Fn.prototype.a = 'xxx';
        // 1. 读取对象的属性值时:会自动到原型链中查找
        var fn1 = new Fn();
        console.log(fn1.a, fn1);

        var fn2 = new Fn();
        // 2. 设置对象的属性值时:不会查找原型链,如果当前对象中没有此属性,直接添加此属性并设置其值
        fn2.a = 'yyy';
        console.log(fn1.a, fn2.a, fn2);

        // 3. 方法一般定义在原型中,属性一般通过构造函数定义在对象本身
        function Person(name, age) {
            this.name = name;
            this.age = age;

        }
        Person.prototype.setName = function (name) {
            this.name = name;
        }
        var p1 = new Person('tom', 12);
        p1.setName('bob');
        console.log(p1);
        console.log(p1.name, p1.age);

5. instanceof是如何判断的?

表达式:A instanceof B
原理就是:如果B函数的显示原型在A对象的原型链上,返回true,否则返回false

案例1

        function Foo() {}
        var f1 = new Foo();
        console.log(f1 instanceof Foo); //true,这行很简单,不解释了
        console.log(f1 instanceof Object);//true,这行的解释在下面

f1是一个实例对象,具有隐式原型属性__proto__,指向Foo的显示原型对象Foo.prototype,而Foo的原型对象本质上是一个空的Object实例对象,这个空的Object实例对象是Object的实例也具有隐式原型__proto__属性,指向构造函数Object的显示原型对象,即原型链的尽头Object.prototype 所以Object构造函数的显示原型对象在f1的原型链上,所以返回true

案例2

        console.log(Object instanceof Function); //true
        console.log(Object instanceof Object); //true  

第二个是true是因为,前者将Object做为实例,Object.__proto__的值等于其构造函数的显示原型的值Object.prototype
后者被判断为构造函数,instanceof去判断后者的显示原型对象在不在前者的原型链上,是则返回true。
我们知道Object做为Function的实例,那么Object.__proto__的值等于Function显示原型的值,也就指向Function的原型对象(Function.prototype)
而我们说函数的原型对象本质上是一个空Object实例对象,也就是说Function.prototype是Object的实例,Function.prototype.__proto__的值等于Object.prototype,在同一链上,返回true (其实就是后面做为构造函数的Object,它的显式原型在前面那个做为实例的Object的原型链上)

        console.log(Function instanceof Function); //true
        console.log(Function instanceof Object); //true

        console.log(Object instanceof Foo); //false
        //以上只要遵循了,后者的显式原型,在前者的原型链上,instanceof就会返回true

以上大概就是原型的一些内容,很多东西都涉及到原型,感觉原型还是蛮重要的,而且也蛮好理解的。
我上面讲的有点啰嗦,但我又怕讲少了,哈啊哈哈,有点矛盾心理