Twlig / issuesBlog

MIT License
3 stars 0 forks source link

原型与原型链 #11

Open Twlig opened 2 years ago

Twlig commented 2 years ago

原型

每个函数都会创建一个 prototype 属性,这个属性是一个对象(默认是空的Object对象),包含应该由特定引用类型(构造函数创建)的实例共享的属性和方法。上面提到的prototype属性就是通过调用构造函数创建的对象的原型。也就是说,只要创建一个函数,就会按照特定的规则为这个函数创建一个 prototype 属性(指向原型对象)。

函数的显式原型

如下所示,定义了一个Person函数,JS会为Person函数自动创建prototype属性(空Object对象),并prototype中添加constructor属性指向Person。prototype属性就是函数的显式原型。原型定义的属性和方法可以由所有实例共享。

function Person(){};    // 内部语句:this.prototype = {}
console.log(Person.prototype)  //每个函数function都有一个prototype,即显示原型属性,默认指向一个空的Object对象
/*  constructor: Person       
    [[Prototype]]: Object*/

JS内部操作类似于:

Person.prototype = new Object();
Person.prototype.constructor = Person;

对象实例的隐式原型

当对函数使用new关键字执行new Person()时,Person就成为了一个构造函数。而let p = new Person()创建出来的p就是Person的实例对象。实例对象有__proto__属性就是隐式原型,也就是实例的内部[[Prototype]]指针。实例对象有__proto__属性等于构造函数的原型对象Person.prototype)。也就是说:

//定义构造函数
function Person(){};
//1. 每个函数function都有一个prototype,即显示原型属性,默认指向一个空的Object对象
console.log(Person.prototype);
//2. prototype显示原型属性还有constructor属性指向构造函数本身
console.log(Person.prototype.constructor);  //Person
//3. 每个实例对象都有一个__proto__,称为隐式原型
let p = new Person();       // 内部语句: this.__proto__ = Person.prototype
//4. 对象的隐式原型的值为其对应的构造函数的显式原型的值
p.__proto__ == Person.prototype; //true
console.log(p.__proto__)
/*  constructor: Person
    [[Prototype]]: Object*/
Person.prototype.test = function() {
    console.log("test")
}
//通过实例调用原型方法
p.test()
/*5.总结:
    函数的prototype属性:定义函数时自动添加,默认为一个空的Object对象,有constructor属性指向构造函数本身
    对象的__proto__属性:创建对象时自动添加,默认值为构造函数的prototype属性值
    对象的隐式原型__proto__的值为其对应的构造函数的显式原型prototype的值
*/

此外,在实例对象上可以通过隐式原型访问到构造函数的原型,实例对象还能访问到实例的属性和方法。

function Person(){
    this.name = 'zzy';
    this.sayName = function(){ console.log(this.name)};
};
Person.prototype.sayHi = ()=>{console.log('Hi')};
let p = new Person();
console.log(p.__proto__) // == Person.prototype
/*  constructor: Person
    sayHi: function
    [[Prototype]]: Object*/
console.log(p) //查看p能访问到的属性和方法
/*  name: "zzy"  
    sayName: ƒ ()
    [[Prototype]]: Object    也就是上面的p.__proto__,也对应这Person.prototype
        constructor: Person
        sayHi: function
        [[Prototype]]: Object   
    */

注意,在构造函数Person中通过this创建的属性和方法,最后都是p实例的属性和方法和Person没有关系,因为在p被创建出来的时候调用new Person(),构造函数中的this指向的是当前实例p

原型链

原型链基本思想就是通过原型继承多个引用类型的属性和方法。实例对象的隐式原型等于对应构造函数的显示原型。前面的例子中Person函数的prototype是默认的Object对象,我们也可以更换为别的自定义对象,扩展原型链

function Person(){};
Person.prototype.name = 'Person'
function Student(){};
Student.prototype = new Person();
Student.prototype.sayName = function(){  //注意此处不能用箭头函数,否则this指向window对象
    console.log(this.name); //指向Greg
};
let Greg = new Student();
Greg.sayName();  //Person
console.log(Greg);
/*
    [[Prototype]]: Object    也就是上面的Greg.__proto__,也对应这Student.prototype
        //无constructor,因为Student.prototype = new Person()把原本默认的prototype覆盖了
        sayName: function
        [[Prototype]]: Object   Greg.__proto__.__proto__,对应Person.prototype
            name: 'Person'
            constructor: Person
            [[Prototype]]: Object   Greg.__proto__.__proto__.__proto__,Object.prototype
                constructor: Object
                __proto__: null
    */

再附上一张究极神图,吃透了这张图原型链就差不多了:

image

//1. 函数的显式原型指向对象默认是空Object实例对象(但Object不满足)
console.log(Foo.prototype instanceof Object) //true
console.log(Object.prototype instanceof Object) //false  很好理解Object已经是起源了那Object.prototype当然不是Object的实例,而是一部分
console.log(Function.prototype instanceof Object) //true
//2. 所有函数都是Function的实例(包括Function自身)
console.log(Function.__proto__ === Function.prototype) //true
//3. Object的原型对象是原型链的尽头
console.log(Object.prototype.__proto__) //null
function Foo() {}
var Foo = new Function();   //Foo.__proto__ = Function.prototype
Function = new Function();  //Function.__proto__ = Function.prototype

注意事项