一、什么是原型?
用原型来描述对象,一像二,一就是原型,二是对象。
对于原型的复制操作有两种
-
一个是并不真的去复制一个原型对象,而是使得新对象持有一个原型的引用;
-
另一个是切实地复制对象,从此两个对象再无关联。
二、JavaScript原型
- 如果所有对象都有私有字段 [[prototype]],就是对象的原型;
- 读一个属性,如果对象本身没有,则会继续访问对象的原型,直到原型为空或者找到为止。
ES6以来,JavaScript提供了一系列的内置函数,以便于更直接的访问操作原型
- Object.create 根据指定的原型创建新对象,原型可以是 null;
- Object.getPrototypeOf 获得一个对象的原型;
- Object.setPrototypeOf 设置一个对象的原型。
var water={ color(){console.log('none-color')} } var sea={ color(){console.log('blue-color')} } var waterRiver=Object.create(water); console.log(waterRiver.color())//none-color var waterSea=Object.create(sea) console.log(waterSea.color())//blue-color复制代码
最早之前,语言使用者唯一可以访问 [[class]] 属性的方式是 Object.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))); //复制代码
var o = { [Symbol.toStringTag]: "MyObject" } console.log(o + ""); //[object MyObject]复制代码
创建了一个新对象,并且给它唯一的一个属性 Symbol.toStringTag,用字符串加法触发了 Object.prototype.toString 的调用,发现这个属性最终对 Object.prototype.toString 的结果产生了影响。
三、new
- 以构造器的 prototype 属性(注意与私有字段 [[prototype]] 的区分)为原型,创建新对象;
- 将 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();复制代码
第一种方法是直接在构造器中修改 this,给 this 添加属性。
第二种方法是修改构造器的 prototype 属性指向的对象,它是从这个构造器构造出来的所有对象的原型。
四、ES中的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; }}new Rectangle(2,5).calcArea() //10new Rectangle(2,6).area //12复制代码
我们通过 get/set 关键字来创建 getter,通过括号和大括号来创建方法,数据型成员最好写在构造器里面。
**继承extends**1、当做函数使用
class A {}class B extends A { constructor() { super(); //ES6 要求,子类的构造函数必须执行一次super函数。 }}复制代码
super虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super内部的this指的是B,因此super()在这里相当于A.prototype.constructor.call(this)。
class A { constructor() { console.log(new.target.name); //new.target指向当前正在执行的函数 }}class B extends A { constructor() { super(); }}new A() // Anew B() // B复制代码
2、当做对象使用
在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
class A { c() { return 2; }}class B extends A { constructor() { super(); console.log(super.c()); // 2 }}let b = new B();复制代码
上面代码中,子类B当中的super.c(),就是将super当作一个对象使用。这时,super在普通方法之中,指向A.prototype,所以super.c()就相当于A.prototype.c()。
通过super调用父类的方法时,super会绑定子类的this。
class A { constructor() { this.x = 1; } s() { console.log(this.x); }}class B extends A { constructor() { super(); this.x = 2; } m() { super.s(); }}let b = new B();b.m() // 2复制代码
上面代码中,super.s()虽然调用的是A.prototype.s(),但是A.prototype.s()会绑定子类B的this,导致输出的是2,而不是1。也就是说,实际上执行的是super.s.call(this)。
由于绑定子类的this,所以如果通过super对某个属性赋值,这时super就是this,赋值的属性会变成子类实例的属性。
class A { constructor() { this.x = 1; }}class B extends A { constructor() { super(); this.x = 2; super.x = 3; console.log(super.x); // undefined console.log(this.x); // 3 }}let b = new B();复制代码
上面代码中,super.x赋值为3,这时等同于对this.x赋值为3。而当读取super.x的时候,读的是A.prototype.x,所以返回undefined。
注意,使用super的时候,必须显式指定是作为函数、还是作为对象使用,否则会报错。
class A {}class B extends A { constructor() { super(); console.log(super); // 报错 }}复制代码
上面代码中,console.log(super)当中的super,无法看出是作为函数使用,还是作为对象使用,所以 JavaScript 引擎解析代码的时候就会报错。这时,如果能清晰地表明super的数据类型,就不会报错。