序言:本文是 《前端3小时速成指南 - 写代码啦!》中其中关于 this 讲解的文字版教程,本周日(12月10日晚8点)有 前端3小时速成指南后半部分免费直播课,讲解 继承、this、Web 安全、Http、Cookie 和 Session 等相关知识点,对于前端基本功不扎实的同学,一个半小时的直播课绝对让你收获满满
本文讲解了 this 在各个场景下表示什么值
讲解了 call、apply、bind、箭头函数对 this 的影响
当我们执行一个函数,以下几种调用方式等价
"use strict" function fn(a,b){ console.log(this) } fn(1, 2) //等价于 fn.call(undefined, 1, 2) fn.apply(undefined, [1, 2])
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 的作用和 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
本文作者:饥人谷若愚老师