JavaScript是基于原型的? 嗯。

那new、this是什么? 额。。。

什么是原型?

照猫画虎。

你要建一座房,照着设计图建就是“面向对象”,照着邻居建就是“基于对象”。

之前提到过,基于原型就是基于已有的对象,描述不一样的特征,所以相同的特征就直接“复制”过来。

基于原型的面向对象系统通过“复制”的方式来创建新对象。一些语言的实现中,还允许复制一个空对象。这实际上就是创建一个全新的对象。

Brendan的设计JS的思路是:

1
2
3
4
1. 借鉴C语言的基本语法
2. 借鉴Java的数据类型和内存管理
3. 借鉴Scheme语言,"first-class" function
4. 借鉴Self语言,使用基于原型(prototype)的继承机制

原型的”复制”实现:

  1. 新对象引用原型
  2. 完整地复制一份,两个对象不再有关联

JavaScript用的是第一种

JavaScript的原型

简单地说

  1. 对象有私有字段[[prototype]],就是原型
  2. 查找一个属性,当前对象没有,继续访问对象原型,直到空或者找到

ES6中提供三个操作原型的API

使用原型抽象猫和虎

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var cat = {
say() {
console.log("meow~")
},
jump() {
console.log("jump")
}
}

var tiger = Object.create(cat, {
say: {
writable: true,
configurable: true,
enumerable: true,
value: function() {
console.log("roar!")
}
}
})

var anotherCat = Object.create(cat)
anotherCar.say() // meow~

var anotherTiger = Object.create(tiger)
anotherCar.say() // roar!

当然,这是使用ES6语法操作的原型。再来看一下之前的原型操作

早期的原型

1. ES3之前的

早期的“类”是一个私有属性[[class]],内置类型如Number、String、Date等指定[[class]]属性,唯一可以访问[[class]]的方式是Object.prototype.toString

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var o = new Object;
var n = new Number;
var s = new String;
var b = new Boolean;
var d = new Date;
var arg = function(){ return arguments }();
var r = new RegExp;
var f = new Function;
var arr = new Array;
var e = new Error;
console.log([o, n, s, b, d, arg, r, f, arr, e].map(v => Object.prototype.toString.call(v)));
//
0: "[object Object]"
1: "[object Number]"
2: "[object String]"
3: "[object Boolean]"
4: "[object Date]"
5: "[object Arguments]"
6: "[object RegExp]"
7: "[object Function]"
8: "[object Array]"
9: "[object Error]"

2. ES5之后

[[class]]Symbol.toStringTag替代,[[Symbol.toStingTag]]可以自定义Object.prototype.toString的行为

1
2
var o = { [Symbol.toStringTag]: "MyObject" }
console.log(o + ""); // [object MyObject]

3. new、this

当然,JavaScript作为Brendan Eich在网景公司任职时创造的成果,受到公司政治的影响,被要求模仿JAVA(当时的JAVA Applet真是太。。。),引入了newthis

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function c1(){
this.p1 = 1;
this.p2 = function(){
console.log(this.p1);
}
}
var o1 = new c1;
o1.p2(); // 1



function c2(){
}
c2.prototype.p1 = 1;
c2.prototype.p2 = function(){
console.log(this.p1);
}

var o2 = new c2;
o2.p2(); // 1

new干了什么

  1. 以构造器的prototype属性(不是私有字段[[prototype]])为原型,创建新对象
  2. 将this和调用参数传给构造器,执行
  3. 若构造器返回的是对象,返回;否则,返回第一步创建的新对象

像上面的代码一样,new使得函数对象变得和”类”相似,但却提供了两种方法:

  1. 直接在constructor中修改this,给this添加属性
  2. 修改constructorprototype属性指向的对象,他是从该constructor构造出来的所有对象的原型

所以,Object.create算是new的语法糖??反了吧?按时间,好像是合理的。

1
2
3
4
5
6
7
8
9
// 用new实现 Object.create
Object.create = function (prototype) {
var ptype = function() {

}
ptype.prototype = prototype

return new ptype
}

ES6之前(没有Object.createObject.setPrototypeOf),运算符new是唯一一个可以指定[[prototype]]的方法(不是所有环境都支持__proto__

4. ES6中的类

ES6引入了class关键字,并且在标准中删除了所有[[class]]相关的私有属性描述。

所以,当用“类”的思想来写代码时,就要用class而不是function来模拟;让function回归原本的函数语义。(那就清晰了)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Rectangle {    
// 数据成员写在构造器里
constructor(height, width) {
this.width = width
this.height = height
}

// get关键字来创建getter
get area() {
return this.calcArea()
}

// Method 方法就这么写
calcArea() {
return this.width * this.height
}
}

what’s more

类提供了继承能力

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Animal { 
constructor(name) {
this.name = name;
}

speak() {
console.log(this.name + ' makes a noise.');
}
}

class Dog extends Animal {
constructor(name) {
super(name); // call the super class constructor and pass in the name parameter
}

speak() {
console.log(this.name + ' barks.');
}
}

let d = new Dog('Mitzie');
d.speak(); // Mitzie barks.

调用子类的speak方法获取到父类的name

Object API / Grammar分类

  • {}.[]Object.defineProperty 增改查对象
  • Object.create Object.setPrototypeOf Object.getPrototypeOf 原型操作
  • newclassextends 类操作
  • newfunction prototype 历史包袱,别用了

虽然class在编译后仍是基于原型的,但从语法和抽象能力来看,属于“分类”的面向对象思路