站点工具

用户工具


看过这篇文章以后不要再提this

序言:本文是 《前端3小时速成指南 - 写代码啦!》中其中关于 this 讲解的文字版教程,本周日(12月10日晚8点)有 前端3小时速成指南后半部分免费直播课,讲解 继承、this、Web 安全、Http、Cookie 和 Session 等相关知识点,对于前端基本功不扎实的同学,一个半小时的直播课绝对让你收获满满

本文讲解了 this 在各个场景下表示什么值
讲解了 call、apply、bind、箭头函数对 this 的影响

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

以上就是原理,我们根据原理来发散做几道测试题

var name = '饥人谷'
var people = {
   name: '若愚',
   sayName: function(){
       console.log(this.name)
   }
}
var 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(){
       //点击的时候会执行 sayHello,sayHello 里面的 this 代表 body 对象
       this.container.addEventListener('click', this.sayHello)                  
 
       //点击的时候会执行 sayHello,sayHello 里面的 this 代表 app 对象
       this.container.addEventListener('click', this.sayHello.bind(this)) 
   }, 
   sayHello: function(){
      console.log(this)
   }
}
app.bind()

箭头函数

如果你经常用 ES6语法,你会发现函数有这三种写法

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 是什么?)          
        */
       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     
   }
}

写一篇文章挺辛苦,觉得不错给个赞,如需转载私信联系我

饥人谷一直致力于培养有灵魂的编程者,打造专业有爱的国内前端技术圈子。如造梦师一般帮助近千名不甘寂寞的追梦人把编程梦变为现实,他们以饥人谷为起点,足迹遍布包括facebook、阿里巴巴、百度、网易、京东、今日头条、大众美团、饿了么、ofo在内的国内外大小企业。 了解培训课程:加微信 xiedaimala03,官网:https://jirengu.com

本文作者:饥人谷若愚老师

若愚 · 2023/02/09 12:41 · 看过这篇文章以后不要再提this.txt