说起原型,前端的小伙伴们不会陌生,说这个感觉都烂大街了,我为啥说这个?(还不是我不会,呜呜呜~~~,留下了技术的泪水),下面是我对原型及原型链的一些理解,如有不对的地方请大佬指出,谢谢~
JavaScript 中的对象有一个特殊的 [[Prototype]]
内置属性,其实就是对于其他对象的引用。几乎所有的对象在创建时 [[Prototype]]
属性都会被赋予一个非空的值【摘自你不知道的JS-5.1】。这个内置属性即为对象的原型。这个属性也是一个对象值。
起源: 因为JS中没有类的概念(class只是个语法糖),但是,Javascript里面都是对象,必须有一种机制,将所有对象联系起来。所以,Brendan Eich(十天设计出JS的那个男人)最后还是设计了"继承"【JS继承机制阮一峰】。然后JS也就成了完整的面向对象语言了,参考 java 和 c++ ,使用 new 命令生成对象与实例并且会调用类的构造函数,在JS中他设计的是 new 命令后面跟的不是类,而是构造函数。由构造函数来生成实例对象来共享原型对象的方法与属性。
开始
来点你平常常见的代码:
var person = new Object({
age: 12;
})
person.age // 12
person intanceof Object // true
Object即为构造函数,person是一个实例对象, person使用.age
时。调用的自身的的age属性
小提示:a instanceof b,检测a是否是b的实例
person能调用tostring
,但是该方法却不是自己的方法。
person.hasOwnProperty("age") // true
person.tostring() // "[object Object]"
person.hasOwnProperty("tostring") // false
小提示:Object.prototype.hasOwnproperty(),检测的是自身的属性。 in 关键字会检测原型链上的。
对象上的方法(tostring)为啥实例能够引用? 其实是 new 的功劳,大概是这个样子。(关于new, 文章后面有一个简易实现)
person.__proto__ = Object.prototype;
//这里的__proto__指的就是前面提到的[[prototype]],
// __proto__更像是是一个指针,一种关联
person.tostring时,自身并没有找到,于是在__proto__上找,那么Object.prototype上有没有呢?
很明显是有的,大家可以通过控制台去验证.如果没有,会在Object.prototype.__ proto__上去找,
当然 Object.prototype的__proto __是null
我们把__proto__
当做一个属性来使用(不是所有浏览器都支持,代码环境也不要用),其实更像一个getter / setter
Object.defineProperty( Object.prototype, "__proto__", {
get: function() {
return Object.getPrototypeOf( this );
},
set: function(o) {
// ES6 中的 setPrototypeOf(..)
Object.setPrototypeOf( this, o );
return o;
}
} );
除了tostring
,大家看到还有其他的方法,平常我们重写原型上的方法就是这个意思。
原型链说的直白点就是利用__proto__
产生的关联关系。
再看看下面代码
var p1 = { age: 12 }
var p2 = { sex: '男' }
p1.__proto__ = p2;
p1.age // 12
p1.sex //"男"
p1自身找不到sex,会在.__proto__
上找,这时找到了p2,发现有就打印出来了
小提示,在原型链上查找时,当找到时即不再继续查找,即使它的上游有同名属性,此谓原型屏蔽
前面提到的构造函数我是以Object来讲解的,当然String也是一样。下面为了更方便大家理解,用函数来举例
function person(sex = "male"){
this.age = 12;
this.sex = sex
}
var p1 = new person();
var p2 = new person("female");
p1.sex //"male";
p2.sex //"female";
上面产生的原型链:
p1.__proto__ = person.prototype
p2.__proto__ = person.prototype
大家可以通过 getPrototypeOf 或者 isPrototypeOf 验证
Object.getPrototypeOf(p1) === person.prototype //true
person.prototype.isPrototypeOf(p2) //true
为什么上面person.protype能用对象的 isPrototypeOf 方法,因为还有另外的原型
p1.__proto__.__proto__ === person.prototype.__proto__ === Object.prototype
p2.__proto__.__proto__ === person.prototype.__proto__ === Object.prototype
在Object.prototype这个对象上存在一些常用的对象方法,如isPrototypeOf
, toString
,toLocalString
等,如下
Object.prototype再往上查找就是null,前面提到过
p1.__proto__.__proto__.__proto__ === null;
Object.create
更多详情可以去【MDN】上查看更多细节)
现有如下代码:
var O = { porp1: "xxxx" }
var o1 = Object.create(O);
他的作用是返回一个新的对象 o1,且存在
o1.__proto__ = O
这样 o1 不就能调用 O 上的属性了吗。
一个简单的polyfill
// proto也就是相当于上面的O
Object.create = function (proto, propertiesObject) {
function F() {}
F.prototype = proto;
return new F();
};
关于Object.create() :
------详解Object.create(null)
------Object.create()、new Object()和{}的区别
------深入理解Object.create
为了更深入的理解,接下来我们看看面向对象和对象关联
的两个小demo【你不知道的js-6.1.3】
demo1: 面向对象风格(“原型”)
function Foo(who) {
this.me = who;
}
Foo.prototype.identify = function() {
return "I am " + this.me;
};
function Bar(who) {
Foo.call( this, who );
}
Bar.prototype = Object.create( Foo.prototype );
Bar.prototype.speak = function() {
console.log( "Hello, " + this.identify() + "." );
};
var b1 = new Bar( "b1" );
var b2 = new Bar( "b2" );
b1.speak();
b2.speak();
有了Object.create的了解,我们来分析 b1.speak() 发生了什么事,
- 调用speak,也就是b1.__ proto __ .speak().原型链上向上查找一层
- 运行speak()里面的this.identify(),此时this = Bar.prototype,因为Bar.prototype__proto__ = Foo.prorotype,找到identify方法。
- 最后查找this.me,me是b1的自身属性,,直接使用。
上面第三步可能有点难以理解,参考下面示例
function b(){ this.b = 2; console.log(this) }
var a1 = { a1: 11 }
b.call(a1)
//{a1: 11, b: 2}
a1 //{a1: 11, b: 2}
使用call
之后,原对象(a1)会与b内部 this
对象合并(属性合并),原函数的this
也会变为传入的对象。再来看看 new,下面是一个简易版
function New1(){
//获取构造函数
const con = [].shift.call(arguments);
// 创建一个对象且.__proto__指向构造函数.prototype
//为了能使用到con.prototype的属性
const obj = Object.create(con.prototype);
con.apply(obj, arguments);
// 确保最后返回的是个对象
return con instanceof Object ? con : obj;
}
// 这样使用
New1(Bar, "b1");
注意到了吗,新产生一个对象 obj,且这个对象的原型是Bar.prototype,由于前面对 object.create
的了解,有
obj.__proto__ = Bar.prototype
那么最后不难理解b1是含有一个me属性等于"b1"的对象了。
如果想了解更多,建议参考 阮一峰老师的 JS面向对象编程—封装
再介绍另外一个demo
demo2: 对象关联风格
Foo = {
init: function(who) {
this.me = who;
},
identify: function() {
return "I am " + this.me;
}
};
Bar = Object.create( Foo );
Bar.speak = function() {
alert( "Hello, " + this.identify() + "." );
};
var b1 = Object.create( Bar );
b1.init( "b1" );
var b2 = Object.create( Bar );
b2.init( "b2" );
b1.speak();
b2.speak();
上面两个demo都是一样的功能,建议先自行分析一下,两种实现方式的差别。
b1 能使用 init 与 speak 方法是因为原型链
至于更多的详情,大家可以利用控制台用上图中打印去展开查找。
关于以上两个demo的关系图:
如果把上面的关系图理清楚了,原型,原型链的理论方面就没问题了。
小提示:利用控制台,以及 isPropertyOf 和 Object.getPropertyOf 去验证,去理解
补充一些寄生组合继承(业界公认的继承的方法,babel 转class 也是 这种方式)
function Parent(value) {
this.value = value;
}
Parent.prototype.getValue = function() {
console.log(this.value);
}
function Child(value) {
Parent.call(this, value)
}
Child.prototype = Object.create(Parent.prototype, {
constructor: {
value: Child,
enumerable: false, // 不可枚举该属性
writable: true, // 可改写该属性
configurable: true // 可用 delete 删除该属性
}
})
const child = new Child(1)
child.getValue();
child instanceof Parent;
总结:
constructor 是指向实例对象的构造函数,只存在原型对象中;
prototype 是函数(Function,Object是内置的构造函数)独有的原型对象,存放共享属性和方法,函数申明时创建。
__ proto__ 指向自己的原型对象,从第一个到到最后的null的指向链称为原型链。