1. 函数的多重用途
函数可以搭配new使用,函数内部的this会指向一个新对象,最终函数会返回这个新对象
function fn(){
this.count = 1
}
const obj = new fn() // {count: 1}
fn() // undefined 全局对象上会添加一个属性count,值为 1
js函数有两个不同的内部方法:
[[Call]]和[[construct]]。
- 使用
new调用函数时,会执行[[construct]]方法:
- 在内存中创建一个继承自
fn.prototype的新对象- 把函数体的
this指向新对象,并执行函数体。- 默认返回新对象。如果构造函数指定的返回值为复杂类型,会覆盖掉默认返回值。
- 直接调用函数,会执行
[[Call]]方法,直接执行函数体。
使用代码模拟new的操作:
function New(fn, ...arg) {
// 创建新对象,原型为构造函数的原型
let res = Object.create(fn.prototype)
// 修改this指向为新对象,并执行函数体
let ret = fn.apply(res, arg)
// 如果返回值不是有效对象,则返回新对象
return (typeof ret === 'object' && typeof ret === 'function' && ret !== null) ? ret : res
}
function Fn(a) {
this.a = a
}
Fn.prototype = {
sayHello() {
console.log('hello')
}
}
console.log(new Fn(2))
console.log(new(Fn, 2))
new操作符创造的对象会有一个Fn。并不太清楚这是什么,后来猜测这是实例对象的构造函数名,于是尝试给Fn的原型加上了constructor属性:
Fn.prototype = {
constructor: Fn,
sayHello() {
console.log(1)
}
}
那么问题来了:为什么
new操作符创建的实例在原型没有constructor属性时,也能够知道实例的构造函数,而且并没有设置constructor属性,在控制台第一次的输出中可以看到。望有缘人能帮忙解答!!!
2. es5中判断函数被调用的方式
根据上述原理,es5经常依据this是否为构造函数的实例,来判断函数被调用的方式
function Person(){
if(this instanceof Person){
alert('使用new,函数被当做构造函数调用')
}else{
alert('当做普通函数调用')
}
}
以前经常 使用面向对象开发
jquery插件,发现有些插件初始化时,可以使用new也可以不使用new,比如let swiper = new Swiper(options)、let swiper = Swiper(options)均可,观察后发现,插件结构是这样的:// 忘记 Swiper是否可以不使用new初始化,这里就拿它举例说明了 function Swiper(option){ if(this instanceof Swiper){ // 初始化操作 }else{ return new Swiper(option) } }把函数的两种调用方式,都过滤为构造函数式调用,所以是否使用
new都可以。
题外话: 当时自己也偷学了这种方式,来写插件,觉得很高级,但总是忘记具体写法。学习的高潮之处,就在于新知识与自己的旧知识体系碰撞并融合的过程,最后融为了一声 "奥~~,TM原来如此"
但这种方式也有缺陷,因为可以用call或者apply修改函数内的this
指向到函数的实例上,比如let person = new Person(); Person.call(person),那么就不能区分是否通过new调用
3. es6判断函数调用方式
为了判断函数是否通过new调用,es6引入了new.target这个元属性。
元属性:是指非对象的属性,可以提供非对象目标的补充信息
- 使用
new调用函数时,会执行[[construct]]方法,new.target被赋值为函数本身- 直接调用函数,会执行
[[Call]]方法,new.target为undefined
function Person(){
if(new.target === Person){
// 使用 new调用
console.log(new.target)
}else{
console.log(new.target)
}
}
new Person() // Person
Person() // undefined
new.target在函数体外是一个语法错误