美文网首页我爱编程
关于重写原型注意事项和instanceof内部机制

关于重写原型注意事项和instanceof内部机制

作者: DHFE | 来源:发表于2018-06-21 11:54 被阅读20次

无论什么时候,只要创建了一个新函数,就会根据一组特定规则为该函数创建一个prototype属性,这个属性指向原型对象。在默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性 是一个指向prototype属性所在函数的指针。
当调用函数创建一个新实例后,该实例内部将包含一个指针(内部属性),指向构造函数的原型对象。ECMA-262第5版管这个指针叫[[Prototype]]。虽然在脚本中没有标准方式访问[[Prototype]],但Firefox,Safari,Chrome在每个对象上都支持一个属性__proto__;而在其他实现中,这个属性对脚本是完全不可见的。这个连接存在于实例构造函数原型对象之间。
————《JS高程》

先看一段代码:

function Person() {};
// 创建 Person.prototype
// Person.prototype.constructor ==> Person


// 重新指定了Person.prototype 新对象来自Object构造函数
// Person.prototype(new).constructor ==> Object
Person.prototype = {    
    name: "Nicholas",
    age: 29,
    job: "Software Engineer",
    sayName: function() {
        console.log(this.name);
    }
};

var friend = new Person();      // friend.[[Prototype]] ==> Person.prototype


console.log(friend.constructor);    // function Object() {...}

画的一个示意图,1234表示对象创建顺序

这是JS高程里关于原型的一段简单语法, 采用对象字面量的形式重写了整个原型对象。原因是减少Person.prototype的频繁输入,也为了从视觉上更好封装原型的功能。

这里注意点在于constructor属性上。我们知道,每创建一个函数,就会同时创建它的prototype对象,这个对象内也会自动获得constructor属性,指向其原来的构造函数。正常情况下,constructor属性应该指向Person函数,但在这个例子中,不再是了。

Person.prototype = {...};这一句代码,就直接重写了默认的prototype对象,因此,constructor属性也就变成了新对象的constructor属性(指向Object构造函数),不再指向Person函数。

console.log(friend instanceof Object);      // true
console.log(friend instanceof Person);      // true

console.log(friend.constructor === Person); // false
console.log(friend.constructor === Object); // true

此时,尽管instanceof操作符还能返回正确结果,但通过constructor已经无法确定对象的构造函数(类型)了。

如果我们需要constructor的值,可以手动设置。

Person.prototype = {    
    constructor: Person,
    name: "Nicholas",
    age: 29,
    job: "Software Engineer",
    sayName: function() {
        console.log(this.name);
    }
};

之所以提到顺序,应该这关系到原型的动态性。

function Person() {};

var friend = new Person();

Person.prototype.sayHi = function() {
    console.log("Hi");
}

friend.sayHi();     // "Hi"

这里,我们先实例化的Person,然后才为Person原型中添加方法,但是依然生效可以使用,原因可以归结为实例与原型之间的松散连接关系。可以随时随地为原型添加属性和方法,并且修改能够在所有对象实例中反映出来。

但是,这样呢?

function Person() {};

var friend = new Person();

Person.prototype = {    
    constructor: Person,
    name: "Nicholas",
    age: 29,
    job: "Software Engineer",
    sayName: function() {
        console.log(this.name);
    }
};

friend.sayName();       // TypeError 

这里就是刚开始的例子,无非将实例化的顺序更改了一下,先实例,后重写原型,可是最后报错了。

我们知道,调用构造函数会为实例添加一个[[Prototype]]指针,而把原型修改为另外一个对象就等于切断了最初原型之间的联系。

是否在重写原型之前创建Person实例成了这两者的重要区别

function Person() {};
Person.prototype = {    
    name: "Nicholas",
    age: 29,
    job: "Software Engineer",
    sayName: function() {
        console.log(this.name);
    }
};

var friend = new Person();          // 重写后实例化

friend.sayName();       // "Nicholas"


function Person2() {};

var friend2 = new Person2();        // 实例化后重写

Person2.prototype = {    
    constructor: Person,
    name: "Nicholas",
    age: 29,
    job: "Software Engineer",
    sayName: function() {
        console.log(this.name);
    }
};
friend2.sayName();       // TypeError 

首先可以确定的是,原型prototype是可以修改和重写的,不管在哪个时期。而实例对象的[[Prototype]]也必然会连接到原型对象上,这是可以肯定的。

那么,我们可以将实例对象的[[Prototype]]看作一种唯一的硬连接,只能单一指向,那么,可以有两种情况。

  • 在未实例对象,硬连接发生前修改或重写构造函数的原型对象。
    此时,由于未实例化,那么情况与正常情况类似,修改或者重写原型都对实例无影响,唯一要注意的就是重写原型会导致constructor指向发生变化(Object)而已。

  • 在实例化对象后,重写构造函数的原型对象
    这里就比较严格,只针对重写情况(原因如下),因为只有重写才会使[[Prototype]]的指针(引用)发生变化。

function Person() {};
Person.prototype = {    
    name: "Nicholas",
    age: 29,
    job: "Software Engineer",
    sayName: function() {
        console.log(this.name);
    }
};

var friend = new Person();        

Person.prototype.sayHi = function() {       // 实例化后对原型属性(方法)添加修改或删除无大碍
    console.log("Hi");
}

friend.sayName();       // "Nicholas"
friend.sayHi();         // "Hi"

在实例化后,硬连接产生,[[Prototype]]指针指向实例化前最新一次的原型(即不管你修改多少次,以最后一次修改后的原型为准),此时相当于互相绑定了契约关系,那么此时若在重写原型对象,硬连接并不会随之改变,反而,指针依旧指向之前的原型对象

function Person2() {};
Person2.prototype = {
    before: function() {
        console.log("我是实例前最后一次的原型对象,实例对象的[[prototype]]指向我")
    }
};

var friend2 = new Person2();       // 实例化构造函数

Person2.prototype = {              // 实例化重写原型对象,但没有用,Person2.__proto__在实例化时就确定了。
    sayHi: function() {
        console.log("Hi");
    }
};

friend2.before();       // 我是实例前最后一次的原型对象,实例对象的[[prototype]]指向我
friend2.sayHi();        // TypeError: friend2.sayHi is not a function


总结:在通过原型链实现继承时,使用对象字面量创建原型方法时要注意是在实例化之前修改还是实例化之后修改。

重写原型对象,切断了现有原型和任何之前已经存在的对象实例之间的联系;它们引用的仍然是最初的原型。
——《JS高程》,注意这个最初的含义。

javascript instanceof 内部机制探析

JavaScript instanceof 运算符深入剖析

over~

相关文章

网友评论

    本文标题:关于重写原型注意事项和instanceof内部机制

    本文链接:https://www.haomeiwen.com/subject/tpzfyftx.html