this是什么
在函数调用的时候,会创建一个活动对象(active object,有时也称为执行上下文)。这个记录会包括函数在哪里被调用(调用栈)
、函数的调用方法
、传入的参数等信息
。this
就负责记录其中的一个属性,会在函数函数调用的过程中用到。
this解决了什么问题
- 记录当前执行上下文信息,如调用栈、函数的调用方式、传入的参数。
- 改变this执行可以让不具备某个方法的对象,调用该方法(即:隐式’传递’一个对象的引用);
this取决于什么?
- this在函数调用的时候发生绑定,指向完全取决于函数在哪里执行。
- 始终坚持一个原理:
this 永远指向最后调用它的那个对象
、this 永远指向最后调用它的那个对象
、this 永远指向最后调用它的那个对象
来个测试题看看你对this了解情况:
栗子1:
var number = 5;
var obj = {
number: 3,
fn1: (function () {
var number;
this.number *= 2;
number = number * 2;
number = 3;
return function () {
var num = this.number;
this.number *= 2;
console.log(num);
number *= 3;
console.log(number);
}
})()
}
var fn1 = obj.fn1;
fn1.call(null);
obj.fn1();
console.log(window.number);
复制代码
误解指向自身
栗子2:
function countor(num){
console.log('countor',num)
//记录foo被调用的次数
this.count ++;
}
countor.count = 0;
var i = 0;
for(i = 0; i < 10 ; i++) {
if(i > 5) {
countor(i)
}
}
// foo:6
// foo:7
// foo:8
// foo:9
console.log(countor.count) // 0
复制代码
-
为什么countor明明调用了4次但是值还是0?
-
如果希望countor可以具备记录函数的调用次数有哪些解决方案?
方法一:
function countor(num){
console.log('countor',num);
countor.count ++;
}
countor.count = 0;
for(i = 0 ;i < 10; i++){
if(i > 5 ){
countor(i)
}
}
console.log(countor.count)
复制代码
方法二:
function countor(num){
console.log('countor',num);
data.count ++;
}
var data = {
count: 0
}
var i ;
for(i = 0 ;i < 10; i++){
if(i > 5 ){
countor(i)
}
}
console.log(data.count)
复制代码
虽然解决了问题,但是避开了this
通过直接将 countor.count的执行上下文直接指向当前的函数
即 直接调用 countor.count 替代this
,但还是避开了this
方案三:
function countor() {
console.log('countor',num)
//记录foo被调用的次数
this.count ++;
}
countor.count = 0;
var i = 0;
for(i = 0; i < 10 ; i++) {
if(i > 5) {
//直接改变函数调用时的上文,将this指向 countor本身
countor.bind(countor,i)
}
}
复制代码
上面的案例说明了一个误区;
this在任何情况下都不指向函数的词法作用域
现在我们来回答为什么countor明明调用了4次但是值还是0?
-
- 从上面的3个解决方案中可以得知,全局函数的执行上下文不是指向函数本身而是window
-
- 可以通过
改变函数调用时的this
指向来改变函数执行上下文
- 可以通过
this的调用位置
this的指向始终坚持一个原理:this永远指向最后调用它的那个对象
、this永远指向最后调用它的那个对象
、this永远指向最后调用它的那个对象
重要的事情说三遍。
根据不同的调用位置,确定使用下面4种绑定规则的哪一种:
绑定规则
默认绑定、隐式绑定、显式绑定、new绑定
默认绑定
全局方法默认绑定为window,严格模式,不能将全局对象用于默认绑定,因此this会绑定到undefined
1.1 题目一
function foo() {
console.log(this.a);
}
var a = 2;
复制代码
1.2 题目二
function foo(){
'use strict';
ocnsole.log(this.a);
}
var a =2;
foo();
复制代码
如果改为调用的地方为’use strict’呢?
function foo() {
console.log(this.a);
}
var a = 2;
(function(){
'use strict';
foo()
})()
复制代码
结论:默认绑定(非严格模式下this指向全局对象, 严格模式下this会绑定到undefined)
。
隐式绑定
2.1 题目一
function foo() {
console.log(this.a);
}
var obj = {
a:2,
foo:foo
}
obj.foo();
复制代码
2.1 题目二
如果引用2层呢?
function foo() {
console.log(this.a);
}
var obj2 = {
a:2,
foo:foo
}
var obj1 = {
a:2,
obj2:obj2
}
obj1.obj2.foo()
复制代码
隐式丢失:常见在回调函数中
为什么隐式绑定的this会丢失?
3.1 题目一
function foo() {
console.log(this.a);
}
var obj = {
a:2,
foo:foo
}
var bar = obj.foo;
var a = "global";
bar();
复制代码
由于foo.bar引用的是函数本身,bar()调用的位置在全局,所以this.a输出 global
下面这个栗子更微妙、更出乎意料
3.2 题目二
function foo() {
console.log(this.a);
}
function doFoo(fn){
fn();
}
var obj = {
a:'local',
foo:foo
}
var a = "global";
doFoo(obj.foo)
复制代码
3.3 题目三
现在我们不用window调用doFoo,而是放在对象obj2里,用obj2调用:
function foo(){
console.log(this.a);
}
function doFoo(fn){
console.log(this);
fn();
}
var obj = {a:"local",foo};
var a = "global";
var obj2 = {a:"obj2", doFoo};
ojb2.doFoo(obj.foo);
复制代码
3.4 题目四
var length = 10;
function fn () {
console.log(this.length);
}
var obj = {
length: 5,
method: function (fn) {
console.log(this.length)
fn();
arguments[0]();
}
};
obj.method(fn, 1);
复制代码
为什么arguments[0]()
的值是2呢?
显示绑定
- 通过
call()
或者apply()
、bind()
方法直接指定this的绑定对象, 如foo.call(obj)
使用显示绑定有三点需要注意:
- 使用
.call()、apply()
的函数会直接执行 - 使用
.bind() 会创建一个新的函数
,需要手动调用才能执行 - .call() 、.apply()用法基本类似,不过
call接受若干个参数
,apply接受一个数组。
4.1 题目一
function foo() {
console.log(this.a);
}
var obj = {a: 1};
var a = 2;
foo();
foo.call(obj);
foo.apply(obj);
foo.bind(obj);
复制代码
第四个foo,原因是因为bind创建了一个新函数需要用变量接收并调用,因此此处不会执行。
注意:如果call、apply、bind接收到的第一个参数是空或者null、undefined的话,则会忽略这个参数
。
function foo() {
console.log(this.a);
}
var a = "global";
foo.call();
foo.call(null);
foo.call(undefined);
复制代码
知道显示绑定后,我们来看一个它的妙用
4.2 题目二
var obj1 = {
a:1
}
var obj2 = {
a:2,
foo1:function(){
console.log(this.a);
}
foo2:function() {
console.log(this);
setTimeout( function (){
console.log(this);
},0)
}
}
var a = "global";
obj2.foo1();
obj2.foo2();
复制代码
如果希望定时器里this指向 obj1 如何改造?
4.3 题目三
setTimeout(function(){
console.log(this);
}.call(obj1),0)
// 只需要在上例的回调函里面 .bind(obj1)
复制代码
所以有小伙伴就会问了,我下面的这种写法不可以吗?
obj2.foo2.bind(obj1)
复制代码
注意⚠️:这种写法实际上是改变了foo2
函数内部的this
, 而 setTimeou
t里的函数this
与 foo2
函数里面的this
是没有关系的,定时器调用里面的this始终都是window
4.4 题目四
OK?,我们不用定时器,把它干掉,换成一个函数:
var obj1 = {
a:"obj1"
}
var obj2 = {
a:"obj2",
foo1:function() {
console.log(this.a);
},
foo2:function() {
function inner() {
console.log(this);
console.log(this.a);
}
inner()
}
}
var a = 3;
obj2.foo1();
obj2.foo2()
复制代码
如果将 inner() 改为显示绑定呢?
inner.call(obj1)
复制代码
4.5 题目五
看看下题会输出什么?
function foo() {
console.log(this.a);
}
var obj = {
a:1
}
var a = "global";
foo();
foo.call(obj);
foo().call(obj)
复制代码
注意⚠️:此处会报错Uncaught TypeError: Cannot read property 'call' of undefined
,因为 call必须要被函数调用。
那如果函数foo里面返回一个函数呢?
4.6 题目六
function foo() {
console.log(this.a);
return function() {
console.log(this.a)
}
}
var obj = {
a:1
}
var a = "global";
foo();
foo.call(obj);
foo().call(obj);
复制代码
如果把上面的call
换成bind
会如何?
4.7 题目七
function foo() {
console.log(this.a);
return function () {
console.log(this.a)
}
}
var obj = {
a:1
}
var a = "global";
foo();
foo.bind(obj1);
foo().bind(obj)
复制代码
注意⚠️:foo.bind(obj)
不会执行,因为返回的新的函数需要变量接收并调用才可以。
4.8 题目八
函数内层的this与函数外层的this有关系?内层this到底指向谁?我们重要的口诀再来一遍:this由最后一个调用它的对象决定
function foo () {
console.log(this.a)
return function () {
console.log(this.a)
}
}
var obj = { a: 1 }
var a = 2
foo.call(obj)()
复制代码
如果将上面的函数返回函数放入对象中呢?
4.9 题目九
var obj = {
a:"obj",
foo:function() {
console.log('foo',this.a);
return function() {
console.log('inner',this.a)
}
}
}
var obj2 = { a: "obj2"};
var a = 2
obj.foo();
obj.foo.call(obj2)();
obj.foo().call(obj2)
复制代码
加个参数玩玩
4.10 题目十
var obj = {
a:1,
foo:function(b) {
b = b || this.a;
return function(c) {
console.log(this.a + b + c)
}
}
}
var a = 2;
var obj2 = { a: 3};
obj.foo(a).call(obj2,1);
obj.foo.call(obj2)(1)
复制代码
new绑定
new做了什么?
function myNew (Person) {
const context = Object.create(Person);
let result = context.call(context);
if(typeof result !== 'null' && (typeof result === 'object' || typeof result === 'function')){
return result;
}else {
return context;
}
}
复制代码
总结4句话:
- new一个新对象,
- 新对象原型指向构造函数
- 并将this指向创建的新对象
- 函数如果没有返回其他对象,则返回新创建的对象,如果构造函数返回的为对象或者函数,则将该对象或函数返回
new绑定的优先级问题
5.1 题目一
function foo() {
console.log(this.a);
}
var obj1 = {
a:2,
foo:foo
}
var obj2 = {
a:3,
foo:foo
}
obj1.foo.call(obj2);
obj2.foo.call(obj1);
复制代码
可以看到显示绑定优先级更高
下面看看new绑定
与隐式绑定
优先级
5.2 题目二
function foo(something){
this.a = something;
}
var obj1 = {
foo:foo
}
var obj2 = {};
obj1.foo(1);
console.log(obj1.a);
obj1.foo.call(obj2,3);
console.log(obj2.a);
var bar = new obj1.foo(4);
console.log(obj1.a);
console.log(bar.a);
复制代码
可以看出new绑定比隐式绑定优先级更高
,现在我们看看 new绑定与显示绑定
哪个优先级更高??
5.3 题目三
由于我们无法 通过 new foo.call(obj1)
测试,所以我们间接通过硬绑定实现
function foo(something){
this.a = something;
}
var obj1 = {};
var bar = foo.bind(obj1);
bar(2);
console.log(obj1.a);
var baz = new bar(3);
console.log(baz.a);
console.log(bar.a);
复制代码
new bar并没有像我们预计的那样把obj1.a修改为3.
bind的内部实现:会判断是否被new 调用,如果是的话就会使用新创建的this替换硬绑定的this
5.4 题目四
复制代码
总结,判断this:
根据优先级来判断函数在某个调用位置应该用的是哪条规则,可以按照下面的顺序来判断
-
函数是否在new中调用(new绑定)?如果是this绑定就是新创建的对象。var bar = new foo():
-
函数是通过call、apply、(显示绑定)或者硬绑定调用?如果是的话,this绑定的是指定的对象。
-
函数是否在某个上下文中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象。var bar = obj1.foo();
-
如果都不是的话,使用默认绑定,在非严格模式下绑定到undefined,否则绑定到全局对象 var var = foo();
箭头函数
-
对于上面的问题,
this永远指向最后一个绑定它的对象
,但是对于箭头函数就不一样了。 -
箭头函数的this 由外层的作用域决定的
,指向定义时的this,而非执行时。
它里面的this由外层的作用域决定的是什么意思呢?
箭头函数中没有this的,必须通过查找作用域链来决定它的值,如果箭头函数被非箭头函数包含,则this就指向
最近的一层非箭头函数的this
, 否则this为undefined
6.1 题目一
function obj = {
name:"obj",
foo1:() => {
console.log(this.name);
},
foo2:function (){
console.log(this.name);
return => {
console.log(this.name);
}
}
}
var name = "galbol";
obj.foo1();
obj.foo2()();
复制代码
解题:
-
对于
obj.foo1()
函数的调用,它的外层作用域是window,对象obj当然不属于作用域,(作用域只有全局作用域与函数创建的局部作用域),所以输出为galbol
6.2 题目2
总结一下箭头函数需要注意的点吧:
-
它里面的this由外层来决定,且指向函数定义时的this而非执行时
-
字面量创建的对象,作用域是window,如果里面有箭头函数是属性的话,this指向的是window
-
构造函数创建的对象,作用域可以理解为这个构造函数,且这个构造函数
this是指向新创建的对象
-
箭头函数里面的this是无法通过bind、apply、call来修改的,但是可以通过改变作用域中的this指向来间接修改。
综合栗子
function sayHi(){
console.log('Hello,', this.name);
}
var person = {
name: 'YvetteLau',
sayHi: sayHi
}
var name = 'Wiliam';
var Hi = function(fn) {
fn();
}
Hi.call(person, person.sayHi);
复制代码
改变setTimeout的thsi
var obj1 = {
a: 1
}
var obj2 = {
a: 2,
foo1: function () {
console.log(this.a)
},
foo2: function () {
setTimeout(function () {
console.log(this)
console.log(this.a)
}.call(obj1), 0)
}
}
var a = 3
obj2.foo1()
obj2.foo2()
复制代码
参考阅读