最近刷到一道JavaScript的题目
var tmp = {};
var A = function() {};
A.prototype = tmp;
var a = new A();
A.prototype = {};
var b = Object.create(tmp);
b.constructor = A.constructor;
console.log(a instanceof A);
console.log(b instanceof A);
在公布答案之前,先来看看JavaScript里面的instance of 是个啥玩意
根据定义 instanceof 运算符用来检测 constructor.prototype 是否存在于参数 object 的原型链上。
他可以用在如下几个方面:
- 1.instanceof的普通的用法,obj instanceof Object 检测Object.prototype是否存在于参数obj的原型链上。
- 2.继承中判断实例是否属于它的父类
但是有时候会遇到像下面这样的代码判断:
console.log(Object instanceof Object);//true
console.log(Function instanceof Function);//true
console.log(Number instanceof Number);//false
console.log(String instanceof String);//false
console.log(Function instanceof Object);//true
console.log(Foo instanceof Function);//true
console.log(Foo instanceof Foo);//false
先来看看instanceof运算符的判断逻辑:
function instance_of(L, R) {//L 表示左表达式,R 表示右表达式
var O = R.prototype;// 取 R 的显示原型
L = L.__proto__;// 取 L 的隐式原型
while (true) {
if (L === null)
return false;
if (O === L)// 当 O 严格等于 L 时,返回 true
return true;
L = L.__proto__;
}
}
好了,这里又涉及到了另一个问题: proto 和 prototype
在 JavaScript 原型继承结构里面,规范中用 [[Prototype]] 表示对象隐式的原型,在 JavaScript 中用 proto 表示,
- 对象有属性__proto__,指向该对象的构造函数的原型对象。
- 方法除了有属性__proto__,还有属性prototype,prototype指向该方法的原型对象。
而对于原型链有几点需要注意的:
1.关于constructor
在默认情况下,所有原型对象都会自动获得一个 constructor(构造函数)属性,这个属性包含一个指向 prototype 属性所在函数的指针。例如,Person.prototype.constructor 指向 Person。而通过这个构造函数,我们还可继续为原型对象添加其他属性和方法。
虽然可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值。
如果我们在实例中添加了一个属性,而该属性与实例原型中的一个属性同名,那我们就在实例中创建该属性,该属性将会屏蔽原型中的那个属性。来看下面的例子。
function Person(){}
Person.prototype.name = "Stone";
Person.prototype.age = 28;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
console.log(this.name);
};
var person1 = new Person();
var person2 = new Person();
person1.name = "Sophie";
console.log(person1.name); // "Sophie",来自实例
console.log(person2.name); // "Stone",来自原型
在这个例子中,person1 的 name 被一个新值给屏蔽了。但无论访问 person1.name 还是访问 person2.name 都能够正常地返回值,即分别是 “Sophie”(来自对象实例)和 “Stone”(来自原型)。当访问 person1.name 时,需要读取它的值,因此就会在这个实例上搜索一个名为 name 的属性。这个属性确实存在,于是就返回它的值而不必再搜索原型了。当访问 person2. name 时,并没有在实例上发现该属性,因此就会继续搜索原型,结果在那里找到了 name 属性。
当为对象实例添加一个属性时,这个属性就会屏蔽原型中保存的同名属性;换句话说,添加这个属性只会阻止我们访问原型中的那个属性,但不会修改那个属性。即使将这个属性设置为 null ,也只会在实例中设置这个属性,而不会恢复其指向原型的连接。
不过,使用 delete 操作符则可以完全删除实例属性,从而让我们能够重新访问原型中的属性。
2.constructor属性其实就是一个拿来保存自己构造函数引用的属性,没有其他特殊的地方。
思考个问题:new Person( ) 出来的千千万万个实例中如果都有constructor属性,并且都指向创建自己的构造函数,那它们都拥有一个效果相同但却都各自占用一部分内存的属性?
onstructor是完全可以被当成一个共享属性存放在原型对象中,作用也依然是指向自己的构造函数,而实际上也是这么处理的。对象的constructor属性就是被当做共享属性放在它们的原型对象中。
但是如果是共享属性,那我将两个实例其中一个属性改了,为啥第二个实例没同步?如下面代码:
function Person() {}
var person1 = new Person()
var person2 = new Person()
console.log(person1.constructor) // [Function: Person]
console.log(person2.constructor) // [Function: Person]
person1.constructor = Function
console.log(person1.constructor) // [Function: Function]
console.log(person2.constructor) // [Function: Person] !不是同步为[Function: Function]
这个是因为person1.constructor = Function改的并不是原型对象上的共享属性constructor,而是给实例person1加了一个constructor属性。
console.log(person1) // 结果:Function { constructor: [Function: Function] }
我们根本不能通过一个对象.constructor找回创建自己的构造函数(之间没有箭头链接)!
function Person() {}
console.log(Object instanceof Object); //true
//第一个Object的原型链:Object=>
//Object.__proto__ => Function.prototype=>Function.prototype.__proto__=>Object.prototype
//第二个Object的原型:Object=> Object.prototype
console.log(Function instanceof Function); //true
//第一个Function的原型链:Function=>Function.__proto__ => Function.prototype
//第二个Function的原型:Function=>Function.prototype
console.log(Function instanceof Object); //true
//Function=>
//Function.__proto__=>Function.prototype=>Function.prototype.__proto__=>Object.prototype
//Object => Object.prototype
console.log(Person instanceof Function); //true
//Person=>Person.__proto__=>Function.prototype
//Function=>Function.prototype
console.log(String instanceof String); //false
//第一个String的原型链:String=>
//String.__proto__=>Function.prototype=>Function.prototype.__proto__=>Object.prototype
//第二个String的原型链:String=>String.prototype
console.log(Boolean instanceof Boolean); //false
//第一个Boolean的原型链:Boolean=>
//Boolean.__proto__=>Function.prototype=>Function.prototype.__proto__=>Object.prototype
//第二个Boolean的原型链:Boolean=>Boolean.prototype
console.log(Person instanceof Person); //false[添加链接描述](https://juejin.im/post/5ced143cf265da1bcb4f0d06#heading-11)
//第一个Person的原型链:Person=>
//Person.__proto__=>Function.prototype=>Function.prototype.__proto__=>Object.prototype
//第二个Person的原型链:Person=>Person.prototype
所以再回顾原题目 答案不难理解了
参考文章