JavaScript new 篇
前言
本文将对 JavaScript
语言中的 new
操作符进行全面讲解,从其使用方法、内部原理以及运算优先级等方面展开阐述。
定义及用法
定义
new
运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。
语法
new constructor[([arguments])]
constructor
: 构造器函数arguments
:用于被constructor
调用的参数列表
例如:
function Person(name) {
this.name = name;
}
const person = new Person('Jerry');
person.name; // Jerry
需要注意的是:如果你没有使用 new
运算符,构造函数会像其他的普通函数一样被调用,并不会创建一个对象。在这种情况下,this
的指向也是不一样的。
另外我们需要知道在使用
new
操作符的时候如果不需要传参,是可以省略掉()
的,但是这样有时候会出现问题,我们会在后面的操作符优先级中具体讨论。
下面我们来探究以下 new
操作符的原理,为什么可以创建一个实例对象。
内部原理
new
关键字会进行如下的操作:
- 创建一个空的简单的
javascript
对象,即{}
。 - 设置该对象的
__proto__
属性为构造函数的原型对象。 - 将
this
指向该空对象并且执行函数体。 - 若函数没有返回对象,则返回
this
。
对于第一步我们不需要验证,我们可以来验证剩下的几个步骤:
function Person(name) {
this.name = name;
}
const person = new Person('Jerry');
console.log(person);
console.log(person.__proto__ === Person.prototype);
浏览器中执行结果如下:
这样我们就验证了第二步。其实第三步也同时验证了,正因为执行了函数体,所以新创建的实例对象身上才有 name
属性值为 Jerry
。
对于第四步,我们可以举个反例,我们显式的手动返回一个对象类型的数据:
function Person(name) {
this.name = name;
return {
age: 18
};
}
const person = new Person('Jerry');
console.log(person);
可以看执行结果为返回的对象,并非 this
:
其实只要返回一个应用类型的值都会返回该值而非 this
,例如我们返回一个函数或者数组:
function Person(name) {
this.name = name;
return [1, 2, 3];
}
const person = new Person('Jerry');
console.log(person); // [1, 2, 3]
但是如果我们返回一个基本类型的值呢?
function Person(name) {
this.name = name;
return 123;
}
const person = new Person('Jerry');
console.log(person); // {name: 'Jerry'}
这个时候返回的就是 this
,因为函数内部没有返回一个对象。
手写 new
在了解了 new
的基本使用及其内部的原理后,我们可以手写一个类似于 new
运算符的 _new
函数:
function myNew(constructor, ...args) {
const obj = {};
obj.__proto__ = constructor.prototype;
const result = constructor.apply(obj, args);
return result instanceof Object ? result : obj;
}
解释一下上面的代码:
- 创建一个空对象
obj
。 - 将
obj
的__proto__
属性指向构造函数的prototype
。 - 使用
call
或者apply
绑定this
并执行函数体。 - 若函数没有返回对象就返回
obj
,否则返回函数返回的内容。
运算符优先级
通过上面的讲解,你应该对 new
运算符有了很深层面的认识。但是对于它的运算优先级,同样是我们要必须掌握的一个内容。
对于 new
运算符的优先级问题我们只需要搞清楚它和 .
运算符之间的优先级问题。
我们来看一种场景:
function Foo() {
console.log(1);
return this;
}
Foo.getName = function() {
console.log(2);
};
Foo.prototype.getName = function() {
console.log(3);
};
new Foo.getName();
new Foo().getName();
上面的代码其实就涉及到了 new
运算符与 .
运算符的优先级问题。
我们需要知道:
new
操作符带括号时与.
操作符的优先级平级。new
操作符无括号时优先级小于.
操作符的优先级。
知道了这两个特性后,上面代码的执行逻辑就很清楚了:
- 执行
new Foo.getName()
时,先访问Foo.getName
,然后new
这个函数,即执行结果打印2
。 - 执行
new Foo().getName()
时,先执行new Foo()
,返回Foo
的实例对象,同时打印1
。接着调用实例对象上的getName()
方法,打印3
。
所以,最终打印结果为:2
、1
、3
:
总结
在我们学习一个单独的知识点的时候尽量的了解其基本使用后去了解其内部实现的原理,尝试自己封装。这将有利于培养我们造轮子的能力,并且可以巩固知识点。最后也要自己主动去发现关于该知识点使用时可能会遇到的问题。
转载自:https://juejin.cn/post/6910474617391939592