JS之Prototypal Inheritance
本文解释了:
prototype concept
inheritance concept
原型链的概念及用法
修改原型的危害
非基本数据类型自带一些属性和方法
基本数据自带对象包装Object wrapper
-
prototype concept
By default, every function has a property called prototype. This property by default is empty and you can add properties and methods to it. When you create object from this function, e.g. if I create X1 from X, it would inherit these properties and methods that define X's prototype.
-
inheritance concept
- One object gets access to the properties and methods of another object.
- All JavaScript objects inherit properties and methods from a prototype.
-
原型链 prototype chain
每个对象都有一个 prototype,每个 prototype 也是一个对象。每个对象是通过自己的 prototype 来继承属性和方法的;
JavaScript自带原型链搜索功能,如上图的obj.prop2实际上是obj.prototype.prop2, obj.prop3也是obj.prototype.prototype.prop3, 但是我们不用手动地这样层层地去找这个属性究竟是在哪一个prototype的对象里,JS直接帮我们找了,所以直接调用obj.prop2等就可以。
同样,我们也可以**手动修改对象的原型,使它指向别的对象(by reference):
var person = {
firstName: 'default',
lastName: 'default',
getFullName: function() {
return this.firstName + ' ' + this.lastName;
}
}
var john = {
firstName : 'John',
lastName : 'Doe'
}
//手动设置把john的原型指向person object
john.__proto__ = person;
console.log(john.getFullName()); //John Doe
console.log(john.firstName); //John
注意,最后的console.log(john.firstName);
的结果是John而非person
里的default
,是因为john
对象本身就自带firstName
这个属性,在原型链的顶端,JS引擎在原型从上到下层层递找,所以当JS引擎在原型链顶端找到方法和属性后,就停止再往下找,直接调用再顶端的(也就是john
对象)的firstName
属性。此外,也要注意,当person
对象的getFullName()
方法加入John
对象的原型后,里面的this
就指向了john对象。
同理,如果在上方的代码块下再加上以下代码,会得到:
var jane = {
firstName : 'Jane'
}
jane.__proto__ = person;
console.log(jane.getFullName()); //Jane default
-
修改原型的危害
修改对象的原型在实际生活中是一个十分危险的举动,一旦被修改,之后所声明拥有同一原型的新对象将受到牵连。例如:
const num = 42;
=> undefined
num.toString();
=> '42'
Number.prototype.toString = function() {return '100'};
=> [Function]
num.toString();
=> '100'
num
=> 42
const num2 = 50;
=> undefined
num2.toString();
=> '100'
从上例可以看出,当把整个Number对象原型中的toString()
方法修改后,所有的number对象里的方法也都被改写。
-
非基本数据类型自带一些属性和方法
- Array.prototype.push()
- String.prototype.toUpperCase()
比如说我们在浏览器自带的console里声明一个变量const arr = ['value', 'value1']
可以得到以下的结果
*#### Each object stores a reference to its prototype.每个对象都存储了对其原型的引用。(即每个对象都知道原型自带的属性和方法)
-
Properties/Methods defined most tightly to the instance have priority.
比如说在当arr.proto和arr.proto.proto两个object都包含同样的功能toString,但当我们在调用arr.toString的时候,实际调用的是arr.proto.toString,就如arr虽然既是array又是object,但是相较之下称呼其为array更具体,arr.proto.toString也比arr.proto.proto.toString更specific,离arr这个instance更近。
-
Most primitive types have object wrappers
- String()
- Number()
- Boolean()
- Object()
- (Symbol())
这里要注意的是,虽然我们之前说只有非基本数据类型才自带一些属性和方法,但是基本数据类型却自带对象包装(object wrapper),而这些包装也是自带原型的,所以可以调用原型的方法。即JS will automatically box(wrap) primitive values so you have access to the methods.
42.toString(); // Errors(还未包装)
const x = 42;(包装后)
x.toString(); //'42'
x.__proto__; //[Number: 0]
x instanceof Number // false
这里也要注意!!!最后一个例子中的instanceof
operator**运算符用于测试构造函数的prototype属性是否出现在对象的原型链中的任何位置。而此处的x只是为了引用方法的对Number对象的包装(It's just boxed around that number object for your reference)。
同理"foo" instanceof String //false;String("foo") instanceof String //true
。