探究构造函数与 new 命令

夜深了......

1. 前言

​ 在探究 JavaScript 中的构造函数之前，我们需要搞清楚什么是构造函数，正如其名字一样 “构造” ，它的作用就是构造，接下来我们来看看它是在构造什么。

​ 在继续往下说之前，我们需要明确所谓的构造函数其实就是一个函数，但是它具有自己的特征和用法，但是我们会发现如何区分和使用构造函数，那就不得不离不开 JavaScript 中的new关键字了。

2.new 关键字

在说明 JavaScript 中的构造函数时，我们必然不可能绕过它的两个特点：

函数体内部使用了 this 关键字，代表所要生成的对象实例。

生成对象的时候，必须使用 new 关键字。

1.new 命令的基础用法

以下就是一个最基础的使用new命令来实例化一个构造函数

Copy
let MyNew = function (){
this.say_hello = function (){
console.log('hello world')
}
}
let mynew = new MyNew

上述的操作就是通过 new 命令，让构造函数MyNew生成一个实例对象，并将结果交给mynew，mynew也就得到了构造函数中的属性

2. 传参构造函数

在实际中我们不可能通过一个构造函数来构造出一大堆相同对象，所以我们可以在new一个构造函数时传入参数。

Copy
let MyNew_1 = function (name){
this.name = name
}
let mynew_1 = new MyNew_1('张三')
console.log(mynew_1.name) // '张三'

上述的操作就是通过new关键字在实例化一个对象的时候传入对应的参数。

3.new 关键字的规范

因为在使用 new 关键字对构造函数进行实例化的时候，在最后它会自动的执行，所以在使用 new 关键字的可以发现。

Copy
let mynew_2 = new MyNew_1 // 不推荐

let mynew_3 = new MyNew_1() // 推荐

上述的两种的方式虽然都可以，但是更推荐使用第二种，相较于第一种来说，更容易被识别。

4. 注意

在使用构造函数的时候，如果我们在使用构造函数时忘记了使用new关键字，那么它将会被当成一个函数来执行。

Copy
let MyNew_1 = function (name){
this.name = name
}
let test = MyNew_1()
console.log(test.name) // undefined

上述之所以会输出undefined，使用为MyNew_1在被当成函数后，它的作用域就是全局的，而内部的this就指向的时window而window并没有声明这个变量，所以最后输出了undefined。

那么我们应该怎么避免这样的事情发生呢？

1. 使用严格模式

除了正常运行模式，ECMAscript5 添加了第二种运行模式：“严格模式”（strict mode）。顾名思义，这种模式是的Javascript在更严格的条件下运行。

Copy
function Fubar(foo, bar){
'use strict';
this._foo = foo;
this._bar = bar;
}
Fubar()

使用严格模式后，如果你将构造函数当成函数使用，那么将会直接报错。结果如下：

之所以会报错，主要归功于在严格模式中，函数内部的 this 不能指向全局对象，默认等于undefined，导致不加new调用会报错（JavaScript 不允许对undefined添加属性。

2. 使用 instanceof 关键字

我们可以通过instanceof关键字来判断。

Copy
function Fubar_1(foo, bar) {
if (!(this instanceof Fubar_1)) {
return new Fubar(foo, bar);
}

this._foo = foo;
this._bar = bar;
}
console.log(Fubar_1(1, 2)._foo)// 1
console.log((new Fubar_1(1, 2))._foo) // 1

如上述代码，我们只需要判断this是否为Fubar_1的实例对象就可以知道该构造函数是否使用了new关键字，如果没有，则帮你new，并将实例化的对象返回。

3.new 关键字的原理

在我们使用new关键字的时候，实际上它执行了下面的几步：

创建一个空对象，作为将要返回的对象实例。

将这个空对象的原型，指向构造函数的prototype属性。

将这个空对象赋值给函数内部的this关键字。

开始执行构造函数内部的代码。

转化为代码就是如下的操作：

Copy
function _new(/* 构造函数 */ constructor, /* 构造函数参数 */ params) {
// 将 arguments 对象转为数组
var args = [].slice.call(arguments);
// 取出构造函数
var constructor = args.shift();
// 创建一个空对象，继承构造函数的 prototype 属性
var context = Object.create(constructor.prototype);
// 执行构造函数
var result = constructor.apply(context, args);
// 如果返回结果是对象，就直接返回，否则返回 context 对象
return (typeof result === 'object' && result != null) ? result : context;
}

// 实例
var actor = _new(Person, '张三', 28);

后面继续了解下🧐🧐🧐

4. 构造函数中的返回值

当构造函数中有return语句的时候，我们只需要记住一点如果返回的是一个对象，则最终返回的就是这个对象，如果不是一个对象，则会返回this。

Copy
let MyNew_2 = function (){
this.count = 0
return this.count
}
(new MyNew_2()) === 0 // false

Copy
let MyNew_3 = function (){
this.count = 0
return {count:this.count}
}
(new MyNew_3()).count === 0 // true

5.new.target

这是 ECMASript 6 引入一个 新的属性它主要有两个作用：

当我们使用 new 操作符调用构造函数时，new.target 属性的值为构造函数，否则为 undefined

Copy
function Person (fullName) {
if (typeof new.target !== 'undefined') {
this.fullName = fullName;
} else {
throw new Error('须通过 new 关键字来调用 Person。');
}
}
let student = new Person('aa');
let student_1 = Person('aa'); // 报错

可以检查 new.target 是否被某个特定构造函数所有调用。

Copy
function Person (fullName, age) {
if (typeof new.target === Person) {
this.fullName = fullName;
this.age = age;
} else {
throw new Error('必须通过 new 关键字来调用 Person。');
}
}

function Dog (fullName, age) {
Person.call(this, fullName, age)
}

let dog = new Dog('HeHe', 3)

console.log(dog)

在 es5 中如果我们也要确定一个构造函数是否使用，那么我们将第一时间想到instanceof。

如下：

Copy
function Person (name) {
if(this instanceof Person){
this.name = name
} else {
throw new Error('未通过new调用')
}
}
var person2 = Person('Hello') //抛出异常

通过上述的操作，我们就可以判断构造函数是否使用new关键字。但是它不够好，比如下面这个例子：

Copy
function Person (name) {
if(this instanceof Person){
this.name = name
} else {
throw new Error('未通过new调用')
}
}
var person1 = new Person('Hello')
var person2 = Person.call(person1,'Hello') //正常执行

上述代码中使用call方法将 this 设为person1的实例，对于函数本身，无法通过区分Person.call还是new关键字得到Person的实例。但是通过new.target 就可以实现😍😍😍

6. 参考链接

王昱潇 --- 判断函数被调用的方法

网道 --- 实例对象与 new 命令

萌新小吉 --- 严格模式详解