JavaScript中的new要区别于传统的面向对象语言的new。
我们要明确一下,在JavaScript 中,构造函数是什么?
在JavaScript中的构造函数,只是写在new
操作符后面的一个普通的函数,并不属于某个类,更不会实例化一个类。“构造函数“ 这个说法更确切的应该成为 “构造调用” 。后面为了习惯说法,还是称为构造函数。
new的时候,发生了什么呢?
new会劫持所有普通函数并用构造对象的形式来调用它。
function Foo() {
console.log('rookie dog');
}
let a = new Foo(); // rookie dog
a; // {}
new通过构造函数调用,创建了的构造函数的“实例”,使其含有构造函数的属性,并且__proto__
可以访问到造函数原型链中的属性:
- 内部创建一个新的空对象
- 把新对象的
__proto__
与构造函数的prototype
联系上 - 把新对象this绑定到构造函数上成为新的非空对象
- 判断返回的新对象是简单类型还是复杂类型,复杂类型的话要返回构造函数本身(用过
apply
指向构造函数的那个对象),简单类型返回新的对象(__proto__
与构造函数的prototype
联系的对象,没有把this指向构造函数的那个对象)- (因为new 的时候就是这个么返回的,实例本来就是希望继承构造函数的属性,含有构造函数的原型的,并不希望得到的是构造函数的返回值)
让我们手动实现一个new
function create(Con, ...args) {
let obj = {};
obj.__proto__ = Con.prototype;
let result = Con.apply(obj, args);
if (result instanceof Object) {
return result;
} else {
return obj;
}
}
function Test(name, age) {
this.name = name
this.age = age
}
Test.prototype.sayName = function () {
console.log(this.name)
}
const a = create(Test, 'rookie dog', 23);
console.log(a.name) // 'rookie dog'
console.log(a.age) // 23
a.sayName() // 'lmh'
注意事项
让我们看到上面new
过程的第二点并联系上面的代码:将a内部的[[prototype]]
连接到Test.prototype
所指的对象。
在传统的面向对象的语言中,类可以被复制。但是在JavaScript中,并没有这种复制,而是通过[[prototype]]
进行关联。如果要进行复制,需要使用Object.create()
Test.prototype.sayName = function () {
console.log(this.name)
}
let a = new Test('rookie dog', 23)
a.sayName() // rookie dog
Test.prototype.sayName = function () {
console.log(1)
}
a.sayName() // 1 --> 这是关联,而不是复制
当然,如果你改变了a
的sayName
,实际上是在a上面新加了一个sayName
方法,这并不会影响到Test
,因为sayName
本是通过[[prototype]]
来调用到的
参考文章
- 《你不知道的JavaScript 上卷》