抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

Yuki 妙妙屋

不要因为走得太远,就忘了当初为什么出发。

在 Javascript 里,所有的数据类型都是「对象」(object),这一点与 Java 非常相似,所以很多人都会误以为 JavaScript 是一门面向对象的语言。

但从严格意义上来讲,Javascript 并不是一门面向对象语言,而是基于对象,也可以说是基于原型。Javascript 并没有「类」(class) 的概念,也没有提供像抽象、继承、重载等有关面向对象语言的许多特性。那么,Javascript 是如何通过 object 来实现 class 类似表现的?

对象原型

可以说 JavaScript 既是一个面向对象的语言又是一个面向过程的语言。

在 JavaScript 中,通过在运行时,给空对象附加方法和属性来创建对象,与编译语言如 C++ 和 Java 中常见的通过语法来定义类相反。对象构造后,它可以用作是创建相似对象的原型。

前面我们有说到,Javascript 里面所有的数据类型都是对象。在这其中,每个对象上都有 __proto__constructor 这两个属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const array = [114, 514];
const number = 1919810;
const string = '哼哼哼啊';

/**
* 上述代码与这种写法是等价的
*
* const array = new Array(114, 514);
* const number = new Number(1919810);
* const string = new String('哼哼哼啊');
*/

console.log(number instanceof Object); // -> true
console.log(array instanceof Object); // -> true
console.log(string instanceof Object); // -> true

函数也是对象,较为特殊的是,除了 __proto__constructor,函数还有 prototype 这个独有属性。prototype 是显式原型,__proto__ 是隐式原型。

1
2
3
4
5
6
// 等价于 var foo = new Function();
function Foo() {}

console.log(Foo instanceof Object); // -> true
console.log(Foo.__proto__ === Function.prototype); // -> true
console.log(Foo.prototype); // -> {constructor: ƒ Foo(), [[Prototype]]: Object}

原型链

对象的 __proto__ 属性,是在创建对象时自动添加的,默认值为其构造函数的 prototype。函数的 prototype 属性是定义时自动添加的,默认为 {} 空对象,一层一层的 __proto__ 便形成了原型链

1
2
3
4
5
6
7
8
function Foo() {}

Foo.prototype.test = function () {
console.log('hello world');
};

var foo = new Foo();
foo.test(); // -> hello world

prototype 的作用,就是包含可以由特定类型的所有实例共享的属性和方法,也就是让该函数所实例化的对象们,都可以找到公用的属性和方法。当我们试图去访问一个对象上的属性时,如果不存在,便会顺着原型链一直往下找,直到找到为止。

所以,我们可以在字符串上调用 replace()trim(),能在数组上调用 join()push(),正是因为顺着原型链找到了对应的方法。如果比较熟悉链表 (linked list),那么理解起来会特别快。

值得一提的是,Object 的原型是 null,因为它是原型链的最顶端

1
console.log(Object.prototype.__proto__ === null); // -> true

[[prototype]]__proto__

前面我们有说到,所有对象都有 __proto__ 属性,可是在浏览器里打印却显示的是 [[prototype]] 而非 __proto__

其实 [[prototype]]__proto__ 意义相同,均表示对象的内部属性,其值指向对象原型。前者在一些书籍、规范中表示一个对象的原型属性,后者则是在浏览器实现中指向对象原型。

__proto__ 并不是语言本身的特性,这是各大厂商具体实现时添加的私有属性。虽然目前很多现代浏览器的 JavaScript 引擎中都提供了这个私有属性,但依旧不建议在生产中使用该属性,避免对环境产生依赖。生产环境中,建议只使用 Object.getPrototypeOf() 方法来获取实例对象的原型。

构造函数

constructor 属性也是对象才拥有的,它是从一个对象指向一个函数,含义就是指向该对象的构造函数。

每个对象都有构造函数,不过 Function 对象比较特殊,它的构造函数就是它自己(因为 Function 可以看成是一个函数,也可以是一个对象),所有函数最终都是由 Function() 构造函数得来,所以 constructor 属性的指向就是 Function()

ES6

在 ES6 中,新引入了 class 关键字,让定义类更接近传统写法,不过其本质也只是构造函数的另一种写法,最后还是绕不开原型链。

有兴趣你可以查阅 深入浅出 function 与 class 一栏。

评论