先记住这个答案
fn() {} 是 ES6 的方法简写,和 fn: function () {} 在调用时的表现一样:obj.fn() 调用时 this 都是 obj,都有自己的 arguments,单独拿出来调用都会丢 this。真正的区别有两个。第一,简写方法里可以用 super.xxx() 调用原型对象上的方法,function 写法里用 super 会直接报语法错误。第二,简写方法没有 prototype 属性,不能当构造函数,new obj.fn() 会抛 TypeError,而 function 写法可以。
this和arguments规则两者一样- 只有简写方法里能用
super - 简写方法没有
prototype,不能new - 箭头函数属性才是真正没有自己
this的写法
用代码对比 super 和 new
让对象继承一个 base,两种写法各写一个方法,再分别试试 new。
const base = {
greet() {
return 'base'
}
}
const obj = {
short() {
return `${super.greet()} + short` // 简写方法里可以用 super
},
long: function () {
return 'long' // 这里写 super 会报语法错误
}
}
Object.setPrototypeOf(obj, base)
console.log(obj.short())
console.log(obj.short.prototype, typeof obj.long.prototype)
try {
new obj.short()
} catch (err) {
console.log(err.name)
}查看输出与解释
base + short
undefined object
TypeErrorshort 里的 super.greet() 找到了 base 上的 greet。short 没有 prototype,long 有,因为 long 是一个完整的普通函数,理论上可以当构造函数用。new obj.short() 直接抛 TypeError。
super 为什么只在简写方法里能用
super 需要知道「从哪个对象的原型开始找」。简写方法在创建时会记下自己定义在哪个对象上,规范里叫 [[HomeObject]],super.greet 就是去这个对象的原型上找 greet。fn: function () {} 只是把一个普通函数赋给属性,函数本身不知道自己属于哪个对象,所以引擎在解析阶段就不允许里面出现 super。class 里的方法也是方法定义,所以同样能用 super。
- 复制方法后
super不跟着变 - 把
obj.short赋给另一个对象other.short = obj.short再调用,this变成了other,但super仍然从obj的原型,也就是base开始找。这种this和super指向不同对象的情况很难读,实际代码里要避免把用了super的方法到处复制。
和箭头函数属性别搞混
还有第三种写法 fn: () => {},它和前两种都不一样。箭头函数没有自己的 this,用的是定义对象字面量时外层的 this,通常是模块顶层的 undefined 或者 window,而不是这个对象。所以对象方法需要用 this 读对象上的数据时,只能用简写方法或 function 写法,不能用箭头函数。
容易答错的地方
- 以为简写方法的
this是固定的 - 简写方法只是语法更短,
this规则和普通函数完全一样。const f = obj.short再调用f(),this一样会丢,作为事件回调传出去也要bind或者包一层箭头函数。 - 以为简写方法没有
arguments - 没有
arguments的是箭头函数。简写方法是普通方法,有自己的arguments。
面试官还会怎么问?
Vue 选项式 API 里 methods 为什么不能写箭头函数?
Vue 会把 methods 里的函数的 this 绑定到组件实例上。简写方法和 function 写法都能被绑定,箭头函数的 this 在定义时就定死了,绑定不上去,里面的 this 读不到组件数据。
getter 和 setter 算方法简写吗?
算,get size() {} 和 set size(v) {} 都是方法定义,里面同样能用 super,也同样不能 new。
两种写法在属性描述符上有区别吗?
没有。两者都是普通的数据属性,可写、可枚举、可配置,Object.keys 都能列出来。这一点和 class 里的方法不同,class 的方法定义在原型上,而且不可枚举。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。