昨晚做了一个梦,梦见有道关于new
的笔试题,梦中的情景让我很困惑!
所以今天再来彻底的理解new
关键字到底做了什么,然后手动封装其功能
首先,我们应该知道new
的基本作用是用于构造函数生产对象
function Person(name, age) {
this.name = name;
this.age = age;
}
let p1 = new Person('alice', 12);
console.log(p1)//Person {name: "alice", age: 12}
上述例子中,通过new
构造函数Person()
产生p1
对象,生产的对象可以继承Person
原型上的属性和方法
Person.prototype.show = function (name, age) {
console.log(`我叫${this.name},我今年${this.age}岁了!`)
}
p1.show()
上面代码执行结果是:我叫alice,我今年12岁了!
补充说明一下什么是原型
说白了原型(prototype
)其实就是function
对象的一个属性,它定义了构造函数构造出的对象的共有祖先,凡是该构造函数构造出的对象,都能继承原型上的属性和方法
接着来看new
关键字的内部原理
我们知道,在JavaScript
中,函数执行前面加new
和不加new
,执行的结果会变得不一样;那new
到底做了什么?
我们先来回顾一下,我之前提过通过new构造函数构造出对象内部原理
- 隐式创建this对象;在函数体最前面隐式的加上this = {}
- 执行this.xxx = xxx
- 隐式的返回this对象
昨晚那个梦告诉我这不太对,有几个问题
第一,生成的对象为什么会继承自构造函数的原型?如果按照上述步骤构建对象,构建过程跟原型并无关联;
第二,执行的只是this.xxx=xxx
这种格式的代码吗,那 其他语句执行吗?
第三,new
最终的结果都是返回this
对象?如果构造函数有毛病,自己显示的return
了呢,那结果是返回隐式的this
对象和是返回显示return
的东西呢?
针对上诉三个疑问,我们一起来看看
第一个问题,生成的对象为什么会继承自构造函数的原型?
其实最开始构建对象的时候,隐式创建的this
对象并非完全是空对象{}
,这个对象里面有个属性__proto__
指向其构造函数的prototype
,这就是对象能继承原型上的属性和方法的真实原因
this.__proto__ : Student.prototype;
第二个问题,执行的只是this.xxx=xxx这种格式的代码吗?
下面我在构造函数Person
中瞎填一些代码
function Person(name, age) {
console.log(this)//{name: "alice", age: 12}
this.name = name;
this.age = age;
alert(1)//弹出1
console.log(2);//控制台输出2
function inner() {
if (1) {
console.log('inner')//inner函数执行打印出:inner
}
}
inner();
}
let p1 = new Person('alice', 12);
上面代码中,我们发现并非指执行this.xxx=xxx这种格式的代码,而是跟正常函数执行一致,会执行函数中的每条代码
第三个问题,如果构造函数有显示return,结果返回的结果是显示的return还是隐式的this对象?
我来显示的返回一些东西
(1) 显示返回的类型为基础类型中原始值时,即number、string、boolean、null、undefined
中的一种
function Person(name, age) {
this.name = name;
this.age = age;
return 1;
//return 'abc';
//return true;
//return null;
//return undefined;
}
let p1 = new Person('alice', 12);
console.log(p1);
显示返回原始值数据时,最终返回都原本的this
对象
(2) 显示返回数组、对象、function
function Person(name, age) {
this.name = name;
this.age = age;
return [];
}
let p1 = new Person('alice', 12);
console.log(p1);//[]
若显示返回数组,返回的就是该数组
function Person(name, age) {
this.name = name;
this.age = age;
return {};
}
let p1 = new Person('alice', 12);
console.log(p1);//{}
若显示返回对象,返回的就是该对象
function Person(name, age) {
this.name = name;
this.age = age;
function fn(){
}
return fn;
}
let p1 = new Person('alice', 12);
console.log(p1);//fn(){}
若显示返回一个函数,最终返回的就是函数
以上几个例子,可以得出跟我们最开始讲得不一致的地方,就是:
如果构造函数本身有显示返回,显示返回的情况分两种
第一,如果显示返回的数据类型是原始类型(number、string、boolean、undefined、null
),那构造对象最终返回我们构建的this
对象;
第二,如果显示返回的值如果是引用值(比如array、object、function
),那么new
得出的结果不再是this
对象,而是显示返回的东西;需特别注意这点
所以,重新总结梳理一下new的内部原理:
1. 在函数体最前头隐式创建this对象;相当于 let this = {}
2. 给this对象身上添加__proto__属性指向构造函数的原型prototype
3. 执行构造函数中的每一条语句(注意构造函数执行时,里面this指向绑定为新对象哦)
4. 判断构造函数本身是否有显示的return数据:
如果没有显示return数据,那么返回隐式的this对象
如果有显示return数据,判断return的数据的是原始值还是引用值,如果是原始值,返回结果还是隐式的this对象;如果是引用值,返回的就是显示返回的东西
经过上述总结梳理,可以模拟实现new
的功能如下:
Function.prototype._new = function (...arg) {
let _this = {};//第一步,创建this对象
_this.__proto__ = this.prototype;//第二步,添加属性指向构造函数原型
let _constructorRes = this.apply(_this, arg);//第三步,让构造函数执行,并绑定构造函数中的this指向新对象
//第四步,判断返回结果是什么类型
if (typeof _constructorRes === 'number'
|| typeof _constructorRes === 'string'
|| typeof _constructorRes === 'boolean'
|| _constructorRes === null
|| _constructorRes === undefined) {//判断为原始值,返回_this对象
return _this;
} else {
return _constructorRes;
}
}
测试一下
function Person(name, age) {
this.name = name;
this.age = age;
}
let p1 = Person._new('alice', 12);
console.log(p1);
以上测试代码结果打印{name: "alice", age: 12}
,即let p1 = Person._new('alice', 12)
和let p1 = new Person('alice', 12)
执行结果一致。
经过多次测试以上模拟实现new
没有问题!
最后来解决我梦见的那道题目(大概)
function Foo() {
getName = function () {
alert(1)
}
return this;
}
Foo.getName = function () {
alert(2)
}
Foo.prototype.getName = function () {
alert(3)
}
var getName = function () {
alert(4)
}
function getName() {
alert(5)
}
Foo.getName();
getName()
Foo().getName()
getName()
new Foo.getName()
new Foo().getName()
new new Foo().getName()
前面四个问题,都比较容易,分别得出:2,4,1,1
主要来看后面三个
- new Foo.getName()
这里将Foo上的静态方法getName函数作为构造函数执行,所以得出结果为:2
注意:这里是先Foo.getName,然后再new - new Foo().getName()
这里是先计算new Foo(),即调用的是Foo.prototype上的getName(),即得出结果为:3
- new new Foo().getName()
以上代码相当于new((new Foo()).getName)();答案输出:3
这题除了明白new的内部原理之外,得注意js运算符的优先级;
以 new new Foo().getName()为例,小结一下上述new的优先级顺序:
- 先
new Foo()
- 然后new Foo()
.getName
- 最后
new
new Foo().getName()
以上就是关于new的全部内容!
网友评论