有没有真正的了解过Javascript的 proto 和 prototype ? 它们在起什么重要的作用?
在 ES2015/ES6 中引入了 class 关键字,但那只是语法糖,JavaScript 仍然是基于原型的 本身不提供一个 class 实现
基于原型链的继承
JavaScript 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。
let func = function(){
this.a = 1;
this.b = 2;
}
func.prototype.b = 3;
func.prototype.c = 4;
let o = new func();
//整个原型链如下:
// {a:1, b:2} ---> {b:3, c:4} ---> Object.prototype---> null
console.log(o.a); // 1
// a是o的自身属性吗?是的,该属性的值为 1
console.log(o.b); // 2
// b是o的自身属性吗?是的,该属性的值为 2
// 原型上也有一个'b'属性,但是它不会被访问到。
// 这种情况被称为"属性遮蔽 (property shadowing)"
console.log(o.c); // 4
// c是o的自身属性吗?不是,那看看它的原型上有没有
// c是o.[[Prototype]]的属性吗?是的,该属性的值为 4
console.log(o.d); // undefined
// d 是 o 的自身属性吗?不是,那看看它的原型上有没有
// d 是 o.[[Prototype]] 的属性吗?不是,那看看它的原型上有没有
// o.[[Prototype]].[[Prototype]] 为 null,停止搜索
// 找不到 d 属性,返回 undefined
给对象设置属性会创建自有属性。获取和设置属性的唯一限制是内置 getter 或 setter 的属性。
继承方法
let f = function(){
this.a = 1;
add(){
console.log(" value ", this.a + 1);
}
}
console.log(f.add()); // 2
// 当调用 f.add 时,'this' 指向了 f.
let o = Object.create(f);
// o是一个继承自 f 的对象
o.a = 2; // 创建 o 的自身属性 'a'
o.add(); //3
// 调用 o.add 时,'this' 指向了 o
// 又因为 o 继承了 f 的 m 函数
// 所以,此时的 'this.a' 即 o.a,就是 o 的自身属性 'a'
在 JavaScript 中使用原型
function doSomething(){}
console.log(doSomething.prototype);
输出内容:
{constructor: ƒ}
constructor: ƒ doSomething()
__proto__:
constructor: ƒ Object()
hasOwnProperty: ƒ hasOwnProperty()
isPrototypeOf: ƒ isPrototypeOf()
propertyIsEnumerable: ƒ propertyIsEnumerable()
toLocaleString: ƒ toLocaleString()
toString: ƒ toString()
valueOf: ƒ valueOf()
__defineGetter__: ƒ __defineGetter__()
__defineSetter__: ƒ __defineSetter__()
__lookupGetter__: ƒ __lookupGetter__()
arguments: (...)
caller: (...)
length: 1
name: "__lookupGetter__"
__proto__: ƒ ()
[[Scopes]]: Scopes[0]
__lookupSetter__: ƒ __lookupSetter__()
get __proto__: ƒ __proto__()
set __proto__: ƒ __proto__()
这个时候我们可以给doSomething函数的原型对象添加新属性,如下:
function doSomething(){}
doSomething.prototype.foo = "bar";
console.log(doSomething.prototype);
输出如下:
{foo: "bar", constructor: ƒ}
foo: "bar"
constructor: ƒ doSomething()
arguments: null
caller: null
length: 0
name: "doSomething"
prototype: {foo: "bar", constructor: ƒ}
__proto__: ƒ ()
[[FunctionLocation]]: VM1773:1
[[Scopes]]: Scopes[2]
__proto__:
constructor: ƒ Object()
hasOwnProperty: ƒ hasOwnProperty()
isPrototypeOf: ƒ isPrototypeOf()
propertyIsEnumerable: ƒ propertyIsEnumerable()
toLocaleString: ƒ toLocaleString()
toString: ƒ toString()
valueOf: ƒ valueOf()
__defineGetter__: ƒ __defineGetter__()
__defineSetter__: ƒ __defineSetter__()
__lookupGetter__: ƒ __lookupGetter__()
__lookupSetter__: ƒ __lookupSetter__()
get __proto__: ƒ __proto__()
set __proto__: ƒ __proto__()
现在我们可以通过new操作符来创建基于这个原型对象的doSomething实例。使用new操作符,只需在调用doSomething函数语句之前添加new。这样,便可以获得这个函数的一个实例对象。一些属性就可以添加到该原型对象中。
function doSomething(){}
doSomething.prototype.foo = "bar"; // add a property onto the prototype
let doSomeInstancing = new doSomething();
doSomeInstancing.prop = "some value"; // add a property onto the object
console.log( doSomeInstancing );
//log
doSomeInstancing {prop: "some value"}
{
prop: "some value"
__proto__:
foo: "bar"
constructor: ƒ doSomething()
__proto__:
constructor: ƒ Object()
hasOwnProperty: ƒ hasOwnProperty()
isPrototypeOf: ƒ isPrototypeOf()
propertyIsEnumerable: ƒ propertyIsEnumerable()
toLocaleString: ƒ toLocaleString()
toString: ƒ toString()
valueOf: ƒ valueOf()
__defineGetter__: ƒ __defineGetter__()
__defineSetter__: ƒ __defineSetter__()
__lookupGetter__: ƒ __lookupGetter__()
__lookupSetter__: ƒ __lookupSetter__()
get __proto__: ƒ __proto__()
set __proto__: ƒ __proto__()
}
如上所示, doSomeInstancing 中的__proto__是 doSomething.prototype. 但这是做什么的呢?当你访问doSomeInstancing 中的一个属性,浏览器首先会查看doSomeInstancing 中是否存在这个属性。
如果 doSomeInstancing 不包含属性信息, 那么浏览器会在 doSomeInstancing 的 proto 中进行查找(同 doSomething.prototype). 如属性在 doSomeInstancing 的 proto 中查找到,则使用 doSomeInstancing 中 proto 的属性。
否则,如果 doSomeInstancing 中 proto 不具有该属性,则检查doSomeInstancing 的 proto 的 proto 是否具有该属性。默认情况下,任何函数的原型属性 proto 都是 window.Object.prototype. 因此, 通过doSomeInstancing 的 proto 的 proto ( 同 doSomething.prototype 的 proto (同 Object.prototype)) 来查找要搜索的属性。
如果属性不存在 doSomeInstancing 的 proto 的 proto 中, 那么就会在doSomeInstancing 的 proto 的 proto 的 proto 中查找。然而, 这里存在个问题:doSomeInstancing 的 proto 的 proto 的 proto 其实不存在。因此,只有这样,在 proto 的整个原型链被查看之后,这里没有更多的 proto , 浏览器断言该属性不存在,并给出属性值为 undefined 的结论。
function doSomething(){}
doSomething.prototype.foo = "bar";
var doSomeInstancing = new doSomething();
doSomeInstancing.prop = "some value";
console.log("doSomeInstancing.prop: " + doSomeInstancing.prop);
console.log("doSomeInstancing.foo: " + doSomeInstancing.foo);
console.log("doSomething.prop: " + doSomething.prop);
console.log("doSomething.foo: " + doSomething.foo);
console.log("doSomething.prototype.prop: " + doSomething.prototype.prop);
console.log("doSomething.prototype.foo: " + doSomething.prototype.foo);
doSomeInstancing.prop: some value
doSomeInstancing.foo: bar
doSomething.prop: undefined
doSomething.foo: undefined
doSomething.prototype.prop: undefined
doSomething.prototype.foo: bar
使用不同的方法来创建对象和生成原型链
使用语法结构创建的对象
var o = {a: 1};
// o 这个对象继承了 Object.prototype 上面的所有属性
// o 自身没有名为 hasOwnProperty 的属性
// hasOwnProperty 是 Object.prototype 的属性
// 因此 o 继承了 Object.prototype 的 hasOwnProperty
// Object.prototype 的原型为 null
// 原型链如下:
// o ---> Object.prototype ---> null
var a = ["yo", "whadup", "?"];
// 数组都继承于 Array.prototype
// (Array.prototype 中包含 indexOf, forEach 等方法)
// 原型链如下:
// a ---> Array.prototype ---> Object.prototype ---> null
function f(){
return 2;
}
// 函数都继承于 Function.prototype
// (Function.prototype 中包含 call, bind等方法)
// 原型链如下:
// f ---> Function.prototype ---> Object.prototype ---> null
使用构造器创建的对象
在 JavaScript 中,构造器其实就是一个普通的函数。当使用 new 操作符 来作用这个函数时,它就可以被称为构造方法(构造函数)。
function Graph() {
this.vertices = [];
this.edges = [];
}
Graph.prototype = {
addVertex: function(v){
this.vertices.push(v);
}
};
var g = new Graph();
// g 是生成的对象,他的自身属性有 'vertices' 和 'edges'。
// 在 g 被实例化时,g.[[Prototype]] 指向了 Graph.prototype。
使用 class 关键字创建的对象
ECMAScript6 引入了一套新的关键字用来实现 class。使用基于类语言的开发人员会对这些结构感到熟悉,但它们是不同的。JavaScript 仍然基于原型。这些新的关键字包括 class, constructor,static,extends 和 super。
class Polygon {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
class Square extends Polygon {
constructor(sideLength) {
super(sideLength, sideLength);
}
get area() {
return this.height * this.width;
}
set sideLength(newLength) {
this.height = newLength;
this.width = newLength;
}
}
var square = new Square(2);
性能
在原型链上查找属性比较耗时,对性能有副作用,这在性能要求苛刻的情况下很重要。另外,试图访问不存在的属性时会遍历整个原型链。
遍历对象的属性时,原型链上的每个可枚举属性都会被枚举出来。要检查对象是否具有自己定义的属性,而不是其原型链上的某个属性,则必须使用所有对象从 Object.prototype 继承的 hasOwnProperty 方法。下面给出一个具体的例子来说明它:
console.log(g.hasOwnProperty('vertices'));
// true
console.log(g.hasOwnProperty('nope'));
// false
console.log(g.hasOwnProperty('addVertex'));
// false
console.log(g.__proto__.hasOwnProperty('addVertex'));
// true
hasOwnProperty 是 JavaScript
中唯一一个处理属性并且不会遍历原型链的方法。(译者注:原文如此。另一种这样的方法:Object.keys())注意:检查属性是否为 undefined 是不能够检查其是否存在的。该属性可能已存在,但其值恰好被设置成了 undefined。
深入了解Javascript原型链及用法
强力推荐 Developer : https://developer.mozilla.org