new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。
new 运算符实际上接收一个构造器和一组参数创建了一个实例。它做了以下操作:
-
创建一个空的 JavaScript 对象,即 {} ;
-
空对象的原型指针指向构造函数的原型对象(设置新对象的原型为 new 运算符后的构造器的 prototype 属性);
-
利用函数的call方法改变this指向,在空对象上挂载属性或方法(把新对象作为构造函数的 this 上下文和接收的参数一起传入并调用构造函数);
-
如果调用构造函数没有 return,则返回 this ,也就是返回第一步创建的对象。
function constructor(a, b) { this.a = a this.b = b } var obj = new constructor('a', 'b') obj.a // 'a' obj.b // 'b' // 1. var obj = {} // 2. obj.proto = constructor.prototype // 3 4. constructor.call(obj, 'aaa', 'bbb')()()
这里先声明了一个构造函数 constructor,然后使用 new 运算符创建 obj,这种通过构造器在对象上添加属性的方式,在语法上和类十分相似。除此之外,JavaScript 还提供了另外一种在构造器的 prototype 属性上添加属性的方式。
function constructor() {
}
constructor.prototype.p1 = 'p1'
constructor.prototype.p2 = function () {
console.log(this.p1)
}
var obj = new constructor
obj.p2() // 'p1'
这就是早年间,常用的利用 new运算符和构造函数模拟基于类的面向对象的方法了。
庆幸的是,随着 ES6的发布,我们可以直接使用 class 关键字来直接定义类了,而不需要再使用 new + function 的方式来模拟类了。
ES6 class
class 关键字是一个语法糖,只是为了让对象原型的写法更加清晰、更像面向对象编程的语法而已。背后的实现逻辑其实和上面介绍的使用 new + function 的方式是完全一致的。
class Fun {
constructor(x, y) {
this.x = x
this.y = y
}
toString() {
return '(' + this.x + ', ' + this.y + ')'
}
}
var p = new Fun(1, 2)
p.x // 1
等同于
function Fun(x, y) {
this.x = x
this.y = y
}
Fun.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')'
};
var p = new Fun(1, 2)
p.x // 1
在现有的类的语法中,使用 getter/setter 和 method 来创建类的兼容性是最好的。
也就是通过 get 和 set 关键字来创建 getter,通过括号和大括号来创建 method,数据型成员写在构造器里面。
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
// Getter
get area() {
return this.calcArea();
}
// Method
calcArea() {
return this.height * this.width;
}
}
类的写法实际上也是由原型运行时来承载的,逻辑上 JavaScript 认为每个类是有共同原型的一组对象,类中定义的方法和属性则会被写在原型对象之上。除此之外,class 同样也提供了继承的能力。
class Animal {
constructor(name) {
this.name = name
}
speak() {
console.log(this.name + ' makes a noise.')
}
}
class Dog extends Animal {
constructor(name) {
super(name)
}
speak() {
console.log(this.name + ' barks.')
}
}
let dog = new Dog('Mitzie')
dog.speak(); // Mitzie barks.
上面代码创建了一个 Animal 类,并且通过 extends 关键字使 Dog 类继承它;最后使用子类去调用父类的 name。
这种方式使用了 extends 关键字自动设置了 constructor,并且自动调用了父类的构造函数。我们在使用类的方式来编码的时候,应该尽量用 class 来声明类,而不是用函数来模拟类,这样可以尽可能地减少一些书写的语法错误。
参考:
【建议👍】再来40道this面试题酸爽继续(1.2w字用手整理)