1 写在前面
JavaScript中除了基本类型外的数据类型,都是对象。但是由于其没有 类(class,ES6引入了class,但只是语法糖)的概念,如何将所有对象联系起来就成了一个问题,于是就有了原型和原型链的概念。
2 为什么会有原型和原型链
1994年,网景公司(Netscape)发布了Navigator浏览器0.9版,但是刚开始的Js没有继承机制,更别提像同时期兴盛的C++和Java这样拥有面向对象的概念。在实际的开发过程中,工程师们发现没有继承机制很难解决一些问题,必须有一种机制能将所有的对象关联起来。Brendan Eich鉴于以上情况,但不想把Js设计得过为复杂,于是引入了new关键词和constructor构造函数来简化对象的设计,引入了prototype函数对象来包含所有实例对象的构造函数的属性和方法,引入了proto和原型链的概念解决继承的问题。
3 相关名词概念
原型
- JavaScript的所有对象中都包含了一个 [proto] 内部属性,这个属性所对应的就是该对象的原型
- JavaScript的函数对象,除了原型 [proto] 之外,还预置了 prototype 属性
- 当函数对象作为构造函数创建实例时,该 prototype 属性值将被作为实例对象的原型 [proto]。
原型链
- 当一个对象调用的属性/方法自身不存在时,就会去自己 [proto] 关联的前辈 prototype 对象上去找
- 如果没找到,就会去该 prototype 原型 [proto] 关联的前辈 prototype 去找。依次类推,直到找到属性/方法或 undefined 为止。从而形成了所谓的“原型链”
也就是说
JavaScript中的对象,都有一个内置属性[[Prototype]],指向这个对象的原型对象。当查找一个属性或方法时,如果在当前对象中找不到定义,会继续在当前对象的原型对象中查找;如果原型对象中依然没有找到,会继续在原型对象的原型中查找(原型也是对象,也有它自己的原型);如此继续,直到找到为止,或者查找到最顶层的原型对象中也没有找到,就结束查找,返回undefined。可以看出,这个查找过程是一个链式的查找,每个对象都有一个到它自身原型对象的链接,这些链接组件的整个链条就是原型链。拥有相同原型的多个对象,他们的共同特征正是通过这种查找模式体现出来的。
在上面的查找过程,我们提到了最顶层的原型对象,这个对象就是Object.prototype,这个对象中保存了最常用的方法,如toString、valueOf、hasOwnProperty等,因此我们才能在任何对象中使用这些方法。
4 创建对象的方式
1、字面量方式
当通过字面量方式创建对象时,它的原型就是Object.prototype。虽然我们无法直接访问内置属性[[Prototype]],但我们可以通过Object.getPrototypeOf()或对象的__proto__获取对象的原型。
var obj = {};
Object.getPrototypeOf(obj) === Object.prototype; // true
obj.__proto__ === Object.prototype; // true
2、构造函数的调用
通过函数的构造调用(注意,我们不把它叫做构造函数,因为JavaScript中同样没有构造函数的概念,所有的函数都是平等的,只不过用来创建对象时,函数的调用方式不同而已)也是一种常用的创建对象的方式。基于同一个函数创建出来的对象,理应可以共享一些相同的属性或方法,但这些属性或方法如果放在Object.prototype里,那么所有的对象都可以使用它们了,作用域太大,显然不合适。于是,JavaScript在定义一个函数时,同时为这个函数定义了一个 默认的prototype属性,所有共享的属性或方法,都放到这个属性所指向的对象中。由此看出,通过一个函数的构造调用创建的对象,它的原型就是这个函数的prototype指向的对象。
var f = function(name) { this.name = name };
f.prototype.getName = function() { return this.name; } //在prototype下存放所有对象的共享方法
var obj = new f('JavaScript');
obj.getName(); // JavaScript
obj.__proto__ === f.prototype; // true
3、Object.create()
第三种常用的创建对象的方式是使用Object.create()。这个方法会以你传入的对象作为创建出来的对象的原型。
var obj = {};
var obj2 = Object.create(obj);
obj2.__proto__ === obj; // true
这种方式还可以模拟对象的“继承”行为。
function Foo(name) {
this.name = name;
}
Foo.prototype.myName = function() {
return this.name;
};
function Bar(name,label) {
Foo.call( this, name ); //
this.label = label;
}
// temp对象的原型是Foo.prototype
var temp = Object.create( Foo.prototype );
// 通过new Bar() 创建的对象,其原型是temp, 而temp的原型是Foo.prototype,
// 从而两个原型对象Bar.prototype和Foo.prototype 有了"继承"关系
Bar.prototype = temp;
Bar.prototype.myLabel = function() {
return this.label;
};
var a = new Bar( "a", "obj a" );
a.myName(); // "a"
a.myLabel(); // "obj a"
a.__proto__.__proto__ === Foo.prototype; //true
5 __proto__和prototype
这是容易混淆的两个属性。__proto__指向当前对象的原型,prototype是函数才具有的属性,默认情况下,new 一个函数创建出的对象,其原型都指向这个函数的prototype属性。
每个实例对象(object )都有一个私有属性(称之为 __proto__)指向它的原型对象(prototype)。该原型对象也有一个自己的原型对象 ,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。
6 Javascript如何实现继承?
1.构造函数绑定:使用 call 或 apply 方法,将父对象的构造函数绑定在子对象上
function Cat(name,color){
Animal.apply(this, arguments);
this.name = name;
this.color = color;
}
- 实例继承:将子对象的 prototype 指向父对象的一个实例
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
- 拷贝继承:如果把父对象的所有属性和方法,拷贝进子对象
function extend(Child, Parent) {
var p = Parent.prototype;
var c = Child.prototype;
for (var i in p) {
c[i] = p[i];
}
c.uber = p;
}
- 原型继承:将子对象的 prototype 指向父对象的 prototype
function extend(Child, Parent) {
var F = function(){};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
Child.uber = Parent.prototype;
}
- ES6 语法糖 extends:class ColorPoint extends Point {}
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}
如有侵权,请联系删除
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。