this应该是一个讨论了好久的话题了。其中,关于this的文章,在不少的博客当中也有不少介绍,可是,之前我都是只知其一;不知其二的去了解它,就是看博客当中,只介绍了一些状况下的 this 的使用方式,可是也并无本身去作过总结。恰好是在掘金当中有看到一篇关于this的一些详细文章,文末会附上连接以及英文原文,这里纯粹是本身进行一个总结,之后方便本身进行回顾以及加深印象。但愿这篇文章对于你了解this有必定的帮助,文末还有一些练习题噢~但愿真的对大家有帮助。(由于写项目过程当中,一直被 this 坑过,却找了好久的 bug ,我真是 乐了狗)javascript
在了解this以前,相信你们都应该会知道做用域这个知识点的存在,函数在建立以后,会构建本身的执行环境以及做用域,这是一开始就肯定了。可是实际的上下文(context)环境,也能够理解为就是this,它是动态肯定的,即在函数运行时才肯定this所指向的对象,而非声明时所指向的对象。java
关于this,总结起来,主要有如下几个途径可以被运用到。闭包
若是函数被当中对象的一个方法进行调用,则this值指向该对象。app
var person = { name: 'Alice', sayName: function() { alert('welcome ' + this.name); } } person.sayName(); // this == person, alert: 'welcome Alice'
在这里,函数的this指向该对象(即 person);可是有一点须要注意,就是当对象的方法被赋予给一个变量时,其则变为了函数触发,此时的this为 window
或者 undefined(严格模式下)
,以下:函数
var name = 'Bob'; var person; // 即上面的定义,此不拓展详细,直接使用 var say = person.sayName; // this == window || undefined say(); // 'welcome Bob' || throw an error: Cannot read property 'name' of undefined(...)
在函数内部当中使用了 this
,即函数被当作方法使用,不一样于 1
当中做为对象的方法使用,此时调用,是在全局做用域下进行调用,即在window
下进行调用,由定义能够知道,在全局做用域下声明一个函数,其自动加为window
的一个属性。this
此时名正言顺的会指向window
,严格模式下为 undefined
this
function sayThis() { alert(this == window); // true }
结合第一点,函数做为对象的一个方法使用,这里存在一个小坑,即闭包,啥是闭包,这个在这里就不扯开了,最简单的理解就是 Function that returns function,若是不理解什么是闭包的话,能够去翻翻 《JavaScript 高级程序设计》第七章关于闭包的相关内容。第一点当中存在一个小坑,就是将对象的方法赋予给一个变量的时候,其变为函数触发,此时的 this
其实是指向 window(非严格模式)。prototype
那么,当函数中返回一个函数,此时在对象当中调用该方法,其就至关因而函数触发,此时的 this
,在不作任何上下文绑定的前提之下,其指向 window(非严格模式)。设计
var name = 'Bob', person = { name: 'Alice', sayName: function() { console.log(this === person); // true return function() { console.log(this === person); // false console.log(this === window); // true console.log(this.name); // Bob }; } }; person.sayName()();
固然,要解决这个问题的方法,很简单,就是给他绑定一个上下文。code
var name = 'Bob', person = { name: 'Alice', sayName: function() { console.log(this === person); // true return function() { console.log(this === person); // true console.log(this === window); // false console.log(this.name); // Alice }.bind(this); } }; person.sayName()();
咱们知道在使用 new
方法建立对象的时候,会通过以下这些个过程:orm
建立对象,将 this
值赋予新的对象
调用构造函数,为 this
添加属性和方法
返回 this
给当前的对象
function Person(name, age) { this.name = name; this.age = age; } var person1 = new Person('Alice', 29); console.log(person1.name); // Alice
这里要记得使用 new
运算符,不然,其只能算是普通的调用,而不是建立一个新的实例对象。而当作普通函数调用的话,实际上即 第 2 种状况下,对函数普通调用,此时的 this
指向 window
function Person(name, age) { this.name = name; this.age = age; return this; } var person1 = Person('Alice', 29); console.log(person1.name); // Alice console.log(window.name); // Alice console.log(person1 === window); // true
这是正常状况下,this 会正确返回而且指向该对象,可是在构造函数当中,若是返回了一个对象,那么 this 会指向返回的那个对象。
function Person(name, age) { this.name = name; this.age = age; return { name: 'Bob' }; } var person1 = new Person('Alice'); console.log(person1.name); // Bob console.log(person1.age); // undefined
题外话,相似的,联想到 var a = new Person()
,则 a instanceof Person
必定返回 true
吗?留给大家想想咯。
call
、apply
或 bind
改变 this
在引用类型 Function当中,函数存在两个方法属性,call
和 apply
,在 ECMAScript5当中,加入了 bind
方法。题外话,他们三者区别,应该都知道了吧,不知道的加紧补习呀。
var name = 'Bob'; var person = { name: 'Alice', age: 29 } function sayName() { console.log(this.name); } sayName.call(person); // Alice
这里是使用了 call
方法来改变了 this的执行环境,至于使用 apply
,效果同样,只是两者差异在于传入参数的不一样。
func.call(context, arg1, arg2, ...) func.apply(context, [arg1, arg2, ...])
使用 bind
方法进行上下文的改变,bind
方法与 call
和 apply
有着本质的不一样,其不一样点是,bind()
函数返回的是一个新的函数,即方法,然后二者则都是当即执行函数,使用的时候即调用了该函数,返回方法操做的结果。
而且,使用 bind()
方法建立的 上下文,其为永久的上下文环境,不可修改,即便是使用 call
或者 apply
方法,也没法修改 this
所指向的值。
var name = 'Bob'; var person = { name: 'Alice', age: 29 } function sayName() { console.log(this.name); } var say = sayName.bind(person); say(); // Alice sayName(); // Bob
箭头函数并不建立其自身的上下文,其上下文 this,取决于其在定义时的外部函数。
而且,箭头函数拥有静态的上下文,即一次绑定以后,便不可再修改,即便是用了 第 4 种用途当中的改变上下文的方法,也不为之动容。
var num = [1, 2, 3]; (function() { var showNumber = () => { console.log(this === num); // true console.log(this); // [1, 2, 3] } console.log(this === num); // true showNumber(); // true && [1, 2, 3] showNumber.call([1, 2]); // true && [1, 2, 3] showNumber.apply([1, 2]); // true && [1, 2, 3] showNumber.bind([1, 2])(); // true && [1, 2, 3] }).call(num);
因为箭头函数的外部决定上下文以及静态上下文等的特性,不太建议使用箭头函数在全局环境下来定义方法,由于不能经过其余方法改变其上下文。这很蛋疼。
function Period (hours, minutes) { this.hours = hours; this.minutes = minutes; } Period.prototype.format = () => { console.log(this === window); // => true return this.hours + ' hours and ' + this.minutes + ' minutes'; }; var walkPeriod = new Period(2, 30); console.log(walkPeriod.hours); walkPeriod.format(); // => 'undefined hours and undefined minutes'
此时的 this 其实是指向了 window,因此 this.hours 和 this.minutes实际上没有声明的,故为 undefined
。
在全局环境下,仍是选用 函数表达式 来进行函数的定义,能够保证正确的上下文环境
function Period (hours, minutes) { this.hours = hours; this.minutes = minutes; } Period.prototype.format = function() { console.log(this === walkPeriod); // => true return this.hours + ' hours and ' + this.minutes + ' minutes'; }; var walkPeriod = new Period(2, 30); walkPeriod.format(); // '2 hours and 30 minutes'
// 练习1 var func = (function(a) { this.a = a; return function(a) { a += this.a; return a; } })(function(a, b) { return a; }(1, 2)) func(4) // ? // 练习2 var x = 10, foo = { x: 20, bar: function() { var x = 30; return this.x; } } console.log(foo.bar()); console.log((foo.bar)()); console.log((foo.bar = foo.bar)()); console.log((foo.bar, foo.bar)());
但愿看完这篇文章,这两个练习题,你是可以作出来的呀~ 好好分析一下呗。若是不肯定的话,能够在留言板上,我们相互讨论一下呀~