配合另一篇博文「对 JS 创建对象的一些记录」一起食用效果会更好
JS 本身是面向对象的,所以不需要模拟,但是因为它面向对象的方式和主流流派不太一样,所以可以理解为常说的「模拟面向对象」是「模拟基于类的面向对象」
ES6 提供了 class 关键表面上模拟类,但任然是基于原型运行。
什么是原型?
「照猫画虎」,这里的猫就是虎的原型,这就是用原型来描述对象。
而「基于类」的编程提倡使用一个关注分类和类之间关系开发模型(不是很明白这句话)。这类语言,是先有类,再从类去实例化一个对象,类与类之间又可能形成继承、组合等关系。
「基于原型」更提倡程序员去关注一系列对象实例的行为,而后才去关心如何将这些对象划分到最近的使用方式相似的原型对象(还不是很清楚两者之间的区别)。
是通过「复制」的方法创建新对象,实际就是创建一个全新的对象。
原型系统的两种实现思路:
- 使新对象持有一个原型的引用,并不是真的复制
- 深复制,然后两个对象没有关联
JS 是前者。
JS 的原型
- 如果所有对象都有私有字段[[prototype]],就是对象的原型。(我猜测这里的意思是:对象的 prototype 属性就是对象的原型(指向对象的原型对象),函数的 prototype 属性指向该函数的原型)
- 读一个属性,如果对象本身没有,则会继续访问对象的原型,直到原型为空或者找到为止。
ES6 提供了一系列内置函数,来访问操纵原型:
- Object.create,根据指定的原型对象创建新对象,原型可以是 null
- Object.getPrototypeOf,获得一个对象的原型
- Object.setPrototypeOf,设置一个对象的原型
// 一个 cat 对象
let cat = {
say() {
console.log("meow")
},
jump() {
console.log('jump')
}
}
// 在 cat 对象的基础上画了一个 tiger 对象
let tiger = Object.create(cat, {
say: {
writable: true,
configurable: true,
enumerable: true,
value: function() {
console.log('roar')
}
}
})
// cat 这个对象生出了一个具体的实例/对象
let anotherCat = Object.create(cat)
// 因为是 cat 生的,所以也有 cat 的 say 方法
anotherCat.say()
// 同上
let anotherTiger = Object.create(tiger)
anotherTiger.say()
以上是 ES6 的写法,早期版本是怎么写的呢?
早期版本的类与原型
类的定义是一个私有属性 [[class]],语言标准为内置类型例如 Number、String、Date 等指定了 [[class]] 属性,以表示它们的类,访问这个属性的方式是: Objecg.prototype.toString
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)));
ES5 后,[[class]] 私有属性被 Symbol.toStringTag 代替
var o = { [Symbol.toStringTag]: "MyObject" }
console.log(o + '') // [object MyObject]
上面隐性调用了 Object.prototype.toString
(这里不是特别明白,为什么 [Symbol.toStringTag] 这个属性会对结果产生影响)
new 操作做的事情:
- 以构造器的 prototype 属性(这里的构造器应是指构造函数,函数的 prototype 属性指向原型对象,对象的私有属性 proto 指向它的原型对象)为原型,创建新对象;
- 将 this 和调用参数传给构造器,执行;
- 如果构造器返回的是对象,则返回,否则返回第一步创建的对象
《JS高级程序设计》里可能说得更容易理解一些:
- 创建一个新对象
- 将构造函数的作用域赋给新对象(将 this 指向这个新对象)
- 执行构造函数里的代码(为新对象添加属性)
- 返回新对象
new 试图让函数对象在语法上跟类变得相似,但是,在客观上提供了两种方式
- 在构造器中添加属性
- 在构造器的 prototype 属性上添加属性
function c1(){
this.p1 = 1;
this.p2 = function(){
console.log(this.p1);
}
}
var o1 = new c1;
o1.p2();
function c2(){
}
c2.prototype.p1 = 1;
c2.prototype.p2 = function(){
console.log(this.p1);
}
var o2 = new c2;
o2.p2();
ES6 中的类
ES6 引入了 class 关键字,可以像基于类的语言一样写,虽然运行还是以原型运行:
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
// Getter
get area() {
return this.calcArea();
}
// Method
calcArea() {
return this.height * this.width;
}
}
- constructor 方法,就是构造方法
- this关键字则代表实例对象
- 定义“类”的方法的时候,通过括号和大括号来创建方法,前面不需要加上function这个关键字,直接把函数定义-放进去了就可以了。方法之间不需要逗号分隔,加了会报错
- 通过 get/set 关键字来创建 getter,数据型成员最好写在构造器里面。
类也有继承
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.
网友评论