0 写在前面的话
大多数的面向对象编程语言中,比如C++和Java,在使用他们完成任务之前,必须创建类(class)。但在JavaScript中并不需要或者说不强制使用类。
面向对象都有如下几种特性:
-
封装
数据可以和数据操作的功能组织在一起 -
聚合
一个对象可以引用另一个对象 -
继承
一个新创建的对象和另一个对象拥有同样的特性,而无需显示复制功能 -
多态
一个接口可以被多个对象实现
1 一切都是对象
“一切都是对象”这句话的重点在于如何去理解“对象”这个概念。
——当然,也不是所有的都是对象,值类型就不是对象。
首先咱们还是先看看javascript中一个常用的运算符——typeof。typeof应该算是咱们的老朋友,还有谁没用过它?
typeof函数输出的一共有几种类型,在此列出:
console.log(typeof x); // undefined
console.log(typeof 10); // number
console.log(typeof 'abc'); // string
console.log(typeof true); // boolean
console.log(typeof function () {}); //function
console.log(typeof [1, 'a', true]); //object
console.log(typeof { a: 10, b: 20 }); //object
console.log(typeof null); //object
console.log(typeof new Number(10)); //object
以上代码列出了typeof输出的集中类型标识,其中上面的四种(undefined, number, string, boolean)属于简单的值类型,不是对象。剩下的几种情况——函数、数组、对象、null、new Number(10)都是对象。他们都是引用类型,也就是我们说的对象。
1.1 对象的创建
ES5里面有两种方法可以创建对象,或者说实例化对象
new构造函数
对象字面量
在ES6当中, 允许直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。
var birth = '2000/01/01';
var Person = {
name: '张三',
//等同于birth: birth
birth,
// 等同于hello: function ()...
hello() { console.log('我的名字是', this.name); }
};
1.2 函数和对象的关系
函数是一种对象,但是函数却不像数组一样——你可以说数组是对象的一种,因为数组就像是对象的一个子集一样。但是函数与对象之间,却不仅仅是一种包含和被包含的关系,函数和对象之间的关系比较复杂,甚至有一点鸡生蛋蛋生鸡的逻辑。
function Fn() {
this.name = '张三';
this.year = 1988;
}
var fn1 = new Fn();
这个例子很简单,它能说明:对象可以通过函数来创建。对!也只能说明这一点。
但是我要说——对象都是通过函数创建的——有些人可能反驳:不对!因为:
var obj = { a: 10, b: 20 };
其实以上代码的本质是:
var obj = new Object();
obj.a = 10;
obj.b = 20;
所以,可以很负责任的说——对象都是通过函数来创建的。现在是不是糊涂了—— 对象是函数创建的,而函数却又是一种对象。天哪!函数和对象到底是什么关系啊?
2 原型对象
我们可以把原型对象看作是对象的基类。几乎所有的函数都有一个名为prototype的属性,该属性是一个原型对象用来创建新的对象实例。所有创建的对象实例共享该原型对象。
2.1 prototype属性
这个prototype的属性值是一个对象(属性的集合,再次强调!),默认的只有一个叫做constructor的属性,指向这个函数本身。
原型既然作为对象,属性的集合,不可能就只弄个constructor来玩玩,肯定可以自定义的增加许多属性。例如这位Object大哥,人家的prototype里面,就有好几个其他属性。
一个对象实例通过内部属性[[prototype]]追踪原型对象。该属性是一个指向该实例使用的原型对象的指针,当new一个新的对象时,构造函数的原型对象就会赋给该对象的prototype属性。
function Fn() { }
Fn.prototype.name = '张三';
Fn.prototype.getYear = function () {
return 1988;
};
var fn = new Fn();
console.log(fn.name);
console.log(fn.getYear());
Fn是一个函数,fn对象是从Fn函数new出来的,这样fn对象就可以调用Fn.prototype中的属性。
因为每个对象都有一个隐藏的属性——“proto”,这个属性引用了创建这个对象的函数的prototype。即:
fn.proto === Fn.prototype
2.2 隐式原型 proto
每个函数function都有一个prototype,即原型。这里再加一句话——每个对象都有一个proto,可成为隐式原型。
该属性没有写入 ES6 的正文,而是写入了附录,原因是__proto__前后的双下划线,说明它本质上是一个内部属性,而不是一个正式的对外的 API,只是由于浏览器广泛支持,才被加入了 ES6。标准明确规定,只有浏览器必须部署这个属性,其他运行环境不一定需要部署,而且新的代码最好认为这个属性是不存在的。因此,无论从语义的角度,还是从兼容性的角度,都不要使用这个属性,而是使用下面的Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)、Object.create()(生成操作)代替。
在实现上,__proto__调用的是Object.prototype.proto
那么上图中的“Object prototype”也是一个对象,它的proto指向哪里?
好问题!
在说明“Object prototype”之前,先说一下自定义函数的prototype。自定义函数的prototype本质上就是和 var obj = {} 是一样的,都是被Object创建,所以它的proto指向的就是Object.prototype。
但是Object.prototype确实一个特例——它的__proto__指向的是null,切记切记!
对象的proto指向的是创建它的函数的prototype,就会出现:Object.proto === Function.prototype。用一个图来表示。
上图中,很明显的标