有不少初学的小伙伴在调用函数给对象进行赋值的时候常常会出现一些关于this的错误,例如this找不到啊,或者没报错却没有生效啊之类的问题,即使是一些入门级的同窗在遇到这些问题时,也只是经过不断的尝试使用var _this = this
、.call()
等方法去实现效果,最后虽然达到了想要的效果,可是却并无明白问题所在,也懒得去仔细研究,那么今天我就来带你们一块儿看看js中this的庐山真面目es6
「舒适提示:内容较多,建议点赞收藏后阅读」数组
❝当一个函数被调用时,会建立一个活动记录(有时候也称为执行上下文)。这个记录会包 含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。 this 就是记录的 其中一个属性,会在函数执行的过程当中用到。markdown
——《你不知道的JavaScript上卷》app
❞
上面是《你不知道的JavaScript》一书中对于this的解释,可能看起来很难理解,但咱们不妨只看第一句话就能够了,「当一个函数被调用时,会建立一个活动记录」,这句话也就说明了,函数在定义时是没有this的,this只在函数被调用时产生,咱们也暂且能够粗略的理解为,this就是表明调用这个函数的对象,这句话在大多数状况下是行得通的,那什么状况下不能这么理解呢,下面咱们就来看看this的绑定规则函数
默认绑定就是直接调用函数,这也是最多见的一种状况,咱们能够看看下面一段代码oop
function foo() {
var a = 2 foo.a = 3 console.log(this) // Window console.log(this.a) // 4 } var a = 4 foo() 复制代码
*你们能够直接复制上述代码,而后打开控制栏,粘贴到console里运行一下ui
上述的结果能够看到,咱们直接运行函数时候,函数里的this是直接绑定到Window上的,其实按我我的的理解是,全部的全局变量都是默认绑定到Window上的,全局变量a,用Window.a也同样能够获取到,因此此处的直接调用函数 `foo()`咱们也能够理解为Window.foo(),因此默认绑定依然遵循咱们上述得出的结论:**this就是表明调用这个函数的对象**注意,在严格模式下,直接调用函数,this并不会被绑定到window上,而是被绑定到undefined上。this
隐式绑定咱们能够简单的理解为,当函数被调用时被一个对象所包裹或拥有,或者能够理解为,该函数定义在某对象的一个属性下面或被对象的的一个属性所引用。看文字比较枯涩,直接看代码spa
function foo() {
var a = 2 foo.a = 3 console.log(this) console.log(this.a) } var a = 4 var obj = { a: 5, foo: foo, // 此处函数foo()被引用给了对象obj的foo属性 } obj.foo() // 打印结果为 // {a: 5, foo: ƒ} // 5 复制代码
像上述方式,当函数做为对象的一个属性被调用时,咱们则能够称之为隐形绑定,这种状况也一样适应于咱们上面所说的,谁调用,this就是谁的说法。我的理解,这之因此叫作隐式绑定就是由于咱们没有给this指定绑定对象,而this自动绑定到了所属的上下文对象中[也就是所处的对象中]。code
对象属性引用链中只有最顶层或者说最后一层会影响调用位置。举例来讲:
function foo() {
console.log(this.a) } var obj2 = { a: 42, foo: foo, } var obj1 = { a: 2, obj2: obj2, } obj1.obj2.foo() // 42 复制代码
一个最多见的 this 绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把 this 绑定到全局对象或者 undefined 上,取决因而否是严格模式。
function foo() {
console.log(this.a) } var obj = { a: 2, foo: foo, } var bar = obj.foo // 函数别名! var a = 'oops, global' // a 是全局对象的属性 bar() // "oops, global" 复制代码
虽然 bar 是 obj.foo 的一个引用,可是实际上,它引用的是 foo 函数自己,所以此时的 bar() 实际上是一个不带任何修饰的函数调用,所以应用了默认绑定。若是不能理解这句话的话,咱们能够直接这么理解
var bar = boj.foo = foo var() 至关于 foo() 复制代码
另外一种很是类似的状况可能会令咱们感到迷惑,可是道理是同样的,代码以下
function foo() {
console.log(this.a) } var obj = { a: 2, foo: foo, } var a = 'oops, global' // a 是全局对象的属性 setTimeout(obj.foo, 100) // "oops, global" 复制代码
JavaScript 环境中内置的 setTimeout() 函数实现和下面的伪代码相似:
function setTimeout(fn, delay) {
// 等待 delay 毫秒 fn() // <-- 调用位置! } 复制代码
这样咱们就好理解多了,在这里obj.foo也就是foo做为回调函数被传入,执行的时候依然执行的是foo(),一样适用于默认绑定的原则
咱们经常使用的.apply()
、.call()
、.bind()
这种改变函数this指向的方法,由于咱们能够直接指定 this 的绑定对象,所以咱们称之为显式绑定。 这三个函数都用来改变this的指向,下面咱们就简单说一下这三个函数的用法和区别
function foo() {
console.log('我叫:' + this.name + ',我今年' + this.age) } var obj = { name: '小豪', age: '18', } foo.apply(obj) // 我叫:小豪,我今年18 foo.call(obj) // 我叫:小豪,我今年18 foo.bind(obj)() // 我叫:小豪,我今年18 复制代码
能够看出,三个函数接受的第一个参数都为要绑定的对象,用法上并没有区别,只不过.bind()方法返回的是一个函数,咱们须要加一个括号去调用它。
function foo(from, to) {
console.log('我叫:' + this.name + ',我今年' + this.age + ',来自' + from + ',要去' + to) } var obj = { name: '小豪', age: '18', } foo.apply(obj, ['河北','北京']) // 我叫:小豪,我今年18,来自河北,要去北京 foo.call(obj, '河北' , '北京') // 我叫:小豪,我今年18,来自河北,要去北京 foo.bind(obj, '河北' , '北京')() // 我叫:小豪,我今年18,来自河北,要去北京 复制代码
以上能够看出,call和bind的传参方式是同样的,只要用逗号隔开就能够了,而apply则必须把全部的参数都放到一个数组里。
「注意,此类函数不可叠加使用,如foo.call(obj1).call(obj2)是错误的行为,foo.call(obj1).apply(obj2)也是」
function foo(a) {
this.a = a } var bar = new foo(2) console.log(bar.a) 复制代码
使用new进行的this绑定将始终被绑定到建立时候赋值的对象bar上,后续也没法再修改他的this绑定。
关于用new调用函数后是如何执行的,《你不知道的JavaScript》一书中是这么说的
❝使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操做。
❞
- 建立(或者说构造)一个全新的对象。
- 这个新对象会被执行 [[ 原型 ]] 链接。
- 这个新对象会绑定到函数调用的 this 。
- 若是函数没有返回其余对象,那么 new 表达式中的函数调用会自动返回这个新对象。
咱们如今关心的是第 1 步、第 3 步、第 4 步,因此暂时跳过第 2 步,因为构造函数我也不是太懂,这里先不提,感兴趣的同窗能够本身去搜索,咱们只要知道new返回的是一个对象就能够了。不信打印一下代码
function foo(a) {
this.a = a } var bar = new foo(2) console.log(typeOf(bar)) // object 复制代码
这里咱们重点关注一下第四句话:「若是函数没有返回其余对象,那么 new 表达式中的函数调用会自动返回这个新对象」,正常的咱们在这一小节开头已经看过了,那下面咱们就来看看不正常的:当函数返回了其余对象
function foo(a) {
this.a = a return { b: 5 } } var bar = new foo(2) console.log(bar) // {b: 5} console.log(bar.a) // undefined 复制代码
看,这时候bar就等于函数里新返回的对象了,因此说,若是你在函数里返回了新的对象,那么 new 表达式中的函数调用会自动返回这个咱们手动建立的新对象,不然将返回自动建立的那个对象,注意当函数返回了非对象和undefind、null时,bar依然等于咱们建立的那个实例
毫无疑问,确定是默认绑定的优先级最低,由于它会在你不进行其余全部绑定的时候才会选择默认绑定。 其次的优先级为
❝new > 显示绑定(apply、call、bind) > 隐式绑定(对象调用)> 默认绑定
❞
咱们以前介绍的四条规则已经能够包含全部正常的函数。可是 ES6 中介绍了一种没法使用这些规则的特殊函数类型:箭头函数。
箭头函数并非使用 function 关键字定义的,而是使用被称为“胖箭头”的操做符 => 定 义的。箭头函数不使用 this 的四种标准规则,而是根据外层(函数或者全局)做用域来决 定 this 。
function foo() {
// 返回一个箭头函数 return () => { //this 继承自 foo() console.log(this.a) } } var obj1 = { a: 2, } var obj2 = { a: 3, } var bar = foo.call(obj1) bar.call(obj2) // 2, 不是 3 ! 复制代码
以上代码能够看出,箭头函数的this指向取决于它所在做用域的this,而且箭头函数没法被改变this指向,不管是用call仍是new。
其实箭头函数和咱们以前使用的一种方法几乎同样,那就是用一个变量暂存this
function foo() {
var self = this // lexical capture of this setTimeout(function () { console.log(self.a) }, 100) } var obj = { a: 2, } foo.call(obj) // 2 复制代码
看,在这种状况下,函数里的this指向也是取决于它所在的做用域,不一样的是,箭头函数的this指向取决于它所在的最近的做用域,而用变量暂存this的方法可让它指向任意做用域的this(前提是能够访问到那个变量)
因此,在不考虑es6的兼容问题或者已经作好兼容处理的状况下,咱们不妨使用箭头函数代替咱们之前var _this = this
的写法,这样写既简单也更美观。
function foo() {
var self = this // lexical capture of this setTimeout(function () { console.log(self.a) }, 100) } var obj = { a: 2, } foo.call(obj) // 2 // 改写为 function foo() { setTimeout(() => { console.log(this.a) }, 100) } var obj = { a: 2, } foo.call(obj) // 2 复制代码
不过,在《你不知道的JavaScript》一书中,编者建议,若是你在编写代码的过程当中有使用上面四种绑定规则的话,须要尽可能避免使用var _this = this
和箭头函数,由于在同一个函数或者同一个程序中混 合使用这两种风格一般会使代码更难维护,而且可能也会更难编写
❝做者基础较为薄弱,文章为本人参考相关技术博客和技术书籍所总结,因此可能并不会彻底准确,有些地方为了通俗易懂也没有使用专业词汇,可能会出现用词错误的状况,但愿各位同仁能够多加指正,共同进步,谢谢。
❞
❝本文大部份内容参考自书籍《你不知道的JavaScript》上卷,若是想了解完整内容,请阅读书籍。