每天一小步 <如何写出专一的构造函数>

873 阅读3分钟

构造函数的工作原理

这已经是一个老生常谈的问题了,想要通过构造函数创建一个对象,需要经过下面几步👇:

  • 创建一个 空对象newObj
  • 将 空对象newObj的原型 绑定为 构造函数constructor的prototype
  • 然后通过 call或者apply方法 将 构造函数constructor 的 this指针以及传入的参数 绑定到 空对象newObj上,生成新对象resultObj
  • 最后判断 新对象resultObj 是否属于 对象类型或者函数类型 ,如果属于则 直接返回,不属于则 返回空对象newObj

被诱导的构造函数

当还没有Class的时候,我们使用的构造函数都是由Function改造而来的,为Function加上new操作符,通过改变this的指向把属性值绑定到新对象上,然后返回新对象。

那如果不加上new操作符,构造函数还会正常运行吗?答案是不会,它在没有new的帮助下依旧是一个Function。下面是一个例子👇

constructor例子1.png

如何写写出专一的构造函数

其实上面例子的问题很简单,就是Person函数中this指向的问题:

  1. 使用newPerson函数,this指向创造出来的实例
  2. 直接使用的Person函数,严格模式下的this指向undefined,非严格模式下指向window 那么接下来我们解决问题的关键就是判断构造函数的指针是否指向实例

判断this的指向

最简单的方式,直接在构造函数中判断this的指向,当this指向undefined或者window是抛出错误

function Person(name, age) {
    if (this == undefined || this == window) {
        throw new Error("wrong way of using constructor");
    }
    this.name = name;
    this.age = age;
}
new Person('god',123) // Person {name: 'god', age: 123}
Person('dog',321) // Uncaught Error: wrong way of using constructor

也可以使用instanceof的方式来判断

function Person(name, age) {
    if (!(this instanceof Person)) {
        throw new Error("wrong way of using constructor");
    }
    ...
}
new Person('god',123) // Person {name: 'god', age: 123}
Person('dog',321) // Uncaught Error: wrong way of using constructor

new.target

这个方法是由官方提供的,这里的new并不是指一个对象,只不过new.target指向到被new调用的构造函数,于是new.成为了一个虚拟的上下文。

  • 构造函数中使用new.target,返回一个指向构造方法或函数的引用
  • 普通函数中使用new.target,返回undefined
function Person(name, age) {
    if (!new.target) throw new Error("wrong way of using constructor");
    this.name = name;
    this.age = age;
}
console.log(new Person("god", 123)) // Person {name: 'god', age: 123}
Person("dog", 123) // Uncaught Error: wrong way of using constructor

Class

Class在推出伊始就被规定为必须结合new使用,因此也是最简单的解决方法

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
}
console.log(new Person("god", 123)) // Person {name: 'god', age: 123}
Person("dog", 123) // Uncaught TypeError: Class constructor Person cannot be invoked without 'new'

使用new.target实现一个抽象类

抽象类指一个不能被单独使用,必须被继承后才能使用的类

JS本身并没有提供抽象类的实现方法,但是可以通过new.target曲线救国

class grandFather {
    constructor(address) {
        if (new.target === grandFather)
            throw new Error("wrong of using abstract class");
        this.address = "this is a address";
    }
}
            
class Person extends grandFather {
    constructor(name, age) {
        super();
        this.name = name;
        this.age = age;
    }
}
console.log(new Person("god", 123)) // Person {address: 'this is a address', name: 'god', age: 123}
console.log(new grandFather()) // Uncaught Error: wrong of using abstract class

结语

要是有任何错误和需要修改的地方,恳请指出~