JavaScript面向对象——原型和继承

Posted by luoway on October 14, 2015

基本概念:《详解js面向对象编程》

对象

《JavaScript权威指南》关于“对象”的定义是:

对象是一种复合值:它将很多值(原始值或其他对象)聚合在一起,可通过名字访问这些值。
对象也可以看成是属性的无序集合,每个属性都是一个名/值对。属性名是字符串,因此我们可以把对象看成是从字符串到值的映射。

对象形如:

var student = {
	name: "Lisa",
	age: "18",
	todo: function(){
		var sum;
		sum = 100 + 20;
		return sum;
	}
}

《JavaScript权威指南》关于“类”的描述是:

在JavaScript中也可以定义对象的类,让每个对象都共享某些属性。 类的成员或实例都包含一些属性,用以存放或定义它们的状态,其中有些属性定义了它们的行为(通常称为方法)。这些行为通常是由类定义的,而且为所有实例所共享。

原型

《JavaScript权威指南》: 6.1.3 原型

所有通过对象直接量创建的对象都具有同一个原型对象,可以通过Object.prototype获得对原型对象的引用

直接做试验来帮助理解。

var a = {};
var b = Object();
var c = new Object();
console.log(a,b,c); // 在chrome控制台上显示一致,原型链为:Object>>{}
console.log(Object.prototype); // 上述空对象{}的原型,Object。

通过关键字 new 和构造函数调用创建的对象的原型就是构造函数的prototype属性的值

Object()作为构造函数,new Object()创建了一个对象,该对象的原型是Object.prototype的值。

类的所有实例对象都从同一个原型对象上继承属性。因此,原型对象是类的核心。

上例的new干了什么?用代码试验一下

var A = function(){
    var a = "this is A";
    this.say = 'this is A' ;
} ;
var B = new A();
console.log(B); // { say:"this is A"}

new 后面的函数 A 称为构造函数,任何函数都可以作为构造函数。
B是A的实例。用代码表示,可将 var B = new A(); 替换为

var B = (function(){
    var this = {};
    var a = = "this is A";
    this.say = 'this is A' ;
    return this;
})();

感谢文章《JS中的 new 操作符简单理解》,它直接地描述了new的实现。

继承

《JavaScript权威指南》例6-1:通过原型继承创建一个新对象

//inherit()返回了一个新对象,它继承自原型对象p的属性
//这里使用Object.create()函数
//如果不存在Object.create(),则退化用其他方法
function inherit(){
	if(p == null) throw TypeError();	//p是对象,null也是对象。
	if(Object.create){
		return Object.create(p);
	}

	var t = typeof p;
	if(t !== "object" && t !== "function"){
		throw TypeError();	//函数也是对象,但typeof返回function
	}

	function f(){};
	f.prototype = p;	//原型继承
	return new f();		//使用f()创建p的继承对象
}

Object.create()能干什么?试试看

var a = {word:'This is a'}, b = Object.create(a);
console.log(a,b);       //原型链为:Object>>{word:'This is a'},Object>>{word:'This is a'}>>{}
console.log(a.__proto__);       //Object
console.log(a.__proto__.__proto__); //null
console.log(b.__proto__);       //Object>>{word:"This is a"}
console.log(b.__proto__.__proto__); //Object

可见b的原型是a,b继承了a。

Object.create(obj [, attr]) 创建一个新对象,其中第一个参数是这个对象的原型,第二个可选参数进一步描述对象属性。

MDN: Object.prototype.__proto__

一个对象的__proto__属性和对象的内部属性[[Prototype]]指向一个相同的值,通常称这个值为原型。原型的值可以是一个对象值也可以是null(比如说Object.prototype.__proto__的值就是null)。
该属性可能会引发一些错误,因为用户可能会不知道该属性的特殊性,而给它赋值,从而改变了这个对象指向的原型。

__proto__属性已经被添加在了ES6草案 §B.3.1中。

用__proto__改变指向原型:

var a = {word:"This is a"}, b = Object.create(a),c ={word:"This is c"};
console.log(b.__proto__);//{word:"This is a"}
b.__proto__ = c;
console.log(b.__proto__);//{word:"This is c"}  

用__proto__实现原型继承:

var b = {},c ={word:"This is c"};
b.__proto__ = c;
console.log(b.__proto__);//{word:"This is c"}
console.log(b.word);//This is c

prototype

在上节,我们可以很轻松地用b = Object.create(a)来实现原型继承。 那么另一种new Constructor()怎么实现原型继承?

var A = {
    say: "This is A"
}, B = {};
function F(){}
F.prototype = A;    //构造函数的prototype属性
B.prototype = A;    //自定义对象的prototype属性
var C = new F();    //使用f()创建A的继承对象
console.log(A);     //原型链为:Object>>{say: "This is A"}
console.log(B);     //原型链为:Object>>{prototype: A}, 即prototype指向A
console.log(C);     //原型链为:Object>>{say: "This is A"}>>F
console.log(C.__proto__===A);   //true

我们可以看到,F.prototype = A使得 A 成为 F 的原型,而B.prototype = A 是定义了一个名为“prototype”的属性。

更具体一点:

var A = {
    say: "This is A"
}, B = {};
function F(){}
console.log(B.prototype);   //undefined
console.log(F.prototype);   //{constructor: function F()}
console.log(B.__proto__);   //Object
console.log(B.__proto__.prototype);   //undefined
console.log(Function.prototype.prototype);   //undefined
console.log(Object.__proto__);//function()
console.log(Object.prototype);//Object

console.log(Object.prototype);   //Object
console.log(Array.prototype);   //Array
console.log(Function.prototype);   //function()

自定义对象、对象的属性没有prototype属性,其__proto__属性指向其原型; 构造函数有原生prototype属性,其__proto__属性指向匿名的构造函数。

Object 内部属性及方法

所有对象都有一个叫做 [[Prototype]] 的内部属性。此对象的值是 null 或一个对象,并且它用于实现继承。一个原生属性是否可以把宿主对象作为它的 [[Prototype]] 取决于实现。所有 [[Prototype]] 链必须是有限长度(即,从任何对象开始,递归访问 [[Prototype]] 内部属性必须最终到头,并且值是 null)。

结合上例归纳:构造函数有原生的prototype属性,即内部属性[[Prototype]]

小结

  • 原型: 所有对象都有一个叫做 [[Prototype]] 的内部属性,它用于实现继承。
    构造函数有原生属性prototype暴露内部属性[[Prototype]]prototype指向被继承的原型。构造函数的__proto__指向匿名的构造函数。
    自定义对象的__proto__属性指向该对象继承的原型,可以改变__proto__以改变指向哪个原型。自定义对象的prototype不存在。

  • 继承: 三种方法实现继承。

    1. Object.create(obj [, properties])
    2. 使构造函数的prototype指向被继承原型
    3. 使对象的__proto__属性指向被继承原型(__proto__不是标准,不推荐)。