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() // PersonPerson() // undefined复制代码
new.target
在函数体外是一个语法错误