一.是什么
在JavaScript
中,new
操作符用于创建一个给定构造函数的实例对象
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.sayName = function () {
console.log(this.name)
}
const person1 = new Person('Tom', 20)
console.log(person1) // Person {name: "Tom", age: 20}
t.sayName() // 'Tom'
从上面可以看到:
new
通过构造函数Person
创建出来的实例可以访问到构造函数中的属性new
通过构造函数Person
创建出来的实例可以访问到构造函数原型链中的属性(即实例与构造函数通过原型链连接了起来)
现在在构建函数中显式加上返回值,并且这个返回值是一个原始类型
function Test(name) {
this.name = name
return 1
}
const t = new Test('xxx')
console.log(t.name) // 'xxx'
可以发现,构造函数中返回一个原始值,然而这个返回值并没有作用
下面在构造函数中返回一个对象
function Test(name) {
this.name = name
console.log(this) // Test { name: 'xxx' }
return { age: 26 }
}
const t = new Test('xxx')
console.log(t) // { age: 26 }
console.log(t.name) // 'undefined'
从上面可以发现,构造函数如果返回值为一个对象,那么这个返回值会被正常使用
二、流程
从上面介绍中,我们可以看到new
关键字主要做了以下的工作:
- 创建一个新的对象
obj
- 将对象与构建函数通过原型链连接起来
- 将构建函数中的
this
绑定到新建的对象obj
上 - 根据构建函数返回类型作判断,如果是原始值则被忽略,如果是返回对象,需要正常处理
三、手写new操作符
function mynew(Func, ...args) {
// 1.创建一个新对象
const obj = {}
// 2.新对象原型指向构造函数原型对象
obj.__proto__ = Func.prototype
// 3.将构建函数的this指向新对象
let result = Func.apply(obj, args)
// 4.根据返回值判断
return result instanceof Object ? result : obj
}
测试:
function mynew(func, ...args) {
const obj = {}
obj.__proto__ = func.prototype
let result = func.apply(obj, args)
return result instanceof Object ? result : obj
}
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function () {
console.log(this.name)
}
let p = mynew(Person, "huihui", 123)
console.log(p) // Person {name: "huihui", age: 123}
p.say() // huihui
总结流程:
-
因为new的结果是创建一个新对象,所以在模拟实现时,我们也要建立一个新对象,假设这个对象是
obj
; -
因为obj会具有Person构造函数的属性,所以按照继承,
我们可以使用
Person.apply(obj,arguments)
来给obj添加新的属性; -
因为我们要实现实例与构造函数通过原型链连接起来,可以通过实例的
__proto__
属性会指向构造函数的 prototype来实现 -
最后还要注意构造函数的返回值是对象还是基本数据类型,判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。
代码如下:
function objectFactory() {
//用new Object()的方式创建一个对象obj
var obj = new Object();
//取出第一个参数,就是构造函数
var Constructor = [].shift.call(arguments);
//实现实例与构造函数通过原型链连接起来
obj.__proto__ = Constructor.prototype;
//使用 apply,改变构造函数 this 的指向到新建的对象,这样 obj 就可以访问到构造函数中的属性
var ret = Constructor.apply(obj, arguments);
return typeof ret === 'object' ? ret : obj;
};
解释:
-
为什么
argements
的第一个参数是构造函数: 比如
let p = mynew(Person, "huihui", 123)
, 此时
arguments
的内容是Person, "huihui", 123
,所以第一项是构造函数。 -
解释
[].shift.call(arguments)
①[].shift
其实
Array.prototype.shift === [].shift
,两者其实是同一个函数,只是调用的方式 存在差异,一个是通过原型的方式直接在类上调用;一个是通过实例化,继承过来,然后再 调用。 ②
call
方法将this指向了arguments。[].shift.call(arguments)
的意思就是:拿到arguments
的第一项,并将它付给一个变量来保存。
实现new
的另一种写法:
// (1)首先创建了一个新的空对象
// (2)设置原型,将对象的原型设置为函数的 prototype 对象。
// (3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)
// (4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。
// 实现:
function objectFactory() {
let newObject = null,
constructor = Array.prototype.shift.call(arguments),
result = null;
// 参数判断
if (typeof constructor !== "function") {
console.error("type error");
return;
}
// 新建一个空对象,对象的原型为构造函数的 prototype 对象
newObject = Object.create(constructor.prototype);
// 将 this 指向新建对象,并执行函数
result = constructor.apply(newObject, arguments);
// 判断返回对象
let flag =
result && (typeof result === "object" || typeof result === "function");
// 判断返回结果
return flag ? result : newObject;
}
// 使用方法
// objectFactory(构造函数, 初始化参数);