上一节我们了解到了 call 和 apply,通过他俩 我们来看看函数执行的本质是什么
当我们执行一个函数,有以下几种调用方式等价
函数直接调用情况下
"use strict"
function fn(a, b) {
console.log(this)
}
fn(1, 2)
// 等价于
fn.call( undefined, 1, 2 )
fn.apply( undefined, [1, 2] )
可以看到:
- 在严格模式下,fn 里的 this 就是 call 的第一个参数,也就是 undefined
- 在非严格模式下,(不加 "use strict") call 传递的第一个参数如果是 undefined 或者 null,那 this 会自动替换为 window 对象
对象方法调用的情况下
var obj = {
fn: function(a, b){
console.log(this)
},
child: {
fn2: function(){
console.log(this)
}
}
}
obj.fn(1, 2)
//等价于
obj.fn.call(obj, 1, 2) // 所以 this 是 obj
obj.fn.apply(obj, [1, 2])
obj.child.fn2()
//等价于
obj.child.fn2.call(obj.chid) // 所以 this 是 obj.child
通过 call 和 apply 我们理解了原理之后来看看下面的测试题
let name = '饥人谷'
let people = {
name: '若愚',
sayName: function(){
console.log(this.name)
}
}
let sayAgain = people.sayName
function sayName(){
console.log(this.name)
}
sayName()
/*
解析:相当于 `sayName.call(undefined)` ,因为是非严格模式,所以 this 被替换成 Window,所以这里输出全局的 name 即 "饥人谷"
*/
people.sayName()
/*
解析: 相当于 `people.sayName.call(people)` ,所以这里输出 `people.name` 即 "若愚"
*/
sayAgain()
/*
解析: 相当于 `sayAgain.call(undefined)` ,,因为是非严格模式,所以 this 被替换成 Window,所以这里输出全局的 name 即 "饥人谷"
*/
var arr = []
for(var i=0; i<3; i++){
arr[i] = function(){ console.log(this) }
}
var fn = arr[0]
arr[0]()
/*
解析: 因为函数是个特殊的对象,所以 arr 相当于 { '0': function(){}, '1': function(){}, '2': function(){}, length:3}
arr[0]相当于 `arr['0']` 相当于 `arr.0` (当然这种写法不符合规范),所以 arr[0]等价于 arr.0.call(arr), this就是 arr
*/
fn()
/*
解析: 相当于 `fn.call(undefined)`, 所以 fn 里面的 this 是 Window
*/
bind
bind 的作用和 call 与 apply 类似,区别在使用上
bind 的执行结果返回的是绑定了一个对象的新函数
先看一个简单一点的例子
var obj = {name: '饥人谷'}
function sayName(){
console.log(this.name)
}
var fn = sayName.bind(obj) // 注意 这里 fn 还是一个函数,功能和 sayName 一模一样,区别只在于它里面的 this 是 obj
fn() // 输出: '饥人谷'
再看一个实际一点的例子
var app = {
container: document.querySelector('body'),
bind: function(){
this.container.addEventListener('click', this.sayHello) //点击的时候会执行 sayHello,sayHello 里面的 this 代表 body 对象
this.container.addEventListener('click', this.sayHello.bind(this)) //点击的时候会执行 sayHello,sayHello 里面的 this 代表 app 对象
},
sayHello: function(){
console.log(this)
}
}
app.bind()
箭头函数
ES6 标准中,函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
同样先看一个简单点的例子
let app = {
fn1: function(a){
console.log(this) //app
},
fn2(a) {
consoel.log(this) //app
},
fn3: (a)=>{
console.log(this) //window
}
}
粗略一看,fn1、fn2、fn3 貌似都一样,实际上 fn1和 fn2完全等价,但 fn3是有区别的
以上代码等同于
app.fn2.call(app)
app.fn3.call( 它的上一级的 this )
再看一个复杂点的例子就清楚了
var app = {
init() {
var menu = {
init: ()=>{
console.log(this)
},
bind() {
console.log(this)
}
}
menu.init()
/*相当于 menu.init.call(menu 所在的环境下的 this) , 所以 init 里面的 this 也就是 app。
(假设 app.init 也是箭头函数,想想 menu.init 里面的 this 是什么?)
(答案是:window)
*/
menu.bind()
/*相当于 menu.bind.call(menu),也就是 menu,所以 bind 里面的 this 就是 menu
*/
}
}
app.init()
理解了原理之后,我们再来看看下面的题目
var app = {
fn1() {
setTimeout(function(){
console.log(this)
}, 10)
},
fn2() {
setTimeout(()=>{
console.log(this)
},20)
},
fn3() {
setTimeout((function(){
console.log(this)
}).bind(this), 30)
},
fn4: ()=> {
setTimeout(()=>{
console.log(this)
},40)
}
}
app.fn1()
app.fn2()
app.fn3()
app.fn4()
以上代码相当于
var app = {
fn1() {
function fn(){
console.log(this)
}
//过10ms 后执行
//fn.call(undefined) ,所以输出 Window
},
fn2() {
//过20ms 执行箭头函数
//箭头函数里面没资格有 自己的 this,借用 setTimeout 外面的 this,也就是 app
},
fn3() {
// 创建了一个新函数,这个新函数里面绑定了 外面的this,也就是 app
// 20 ms 后执行新函数,输出 this,也就是刚刚绑定的 app
}
fn4: ()=> {
//过40ms 执行箭头函数
//箭头函数里面没资格有 this,用 setTimeout 外面的 this
//setTimeout 所在的 fn4也是箭头函数,没资格拥有自己的 this,借用外面的 this ,也就是 Window
}
}
转载自 饥人谷this 常见面试题汇总
网友评论