你不知道的Javascript(上卷)-this笔记

关于this的误解

1.this并不指向函数本身

function foo(num){
	console.log(num);
	this.count++;
}

foo.count = 0;
for(var i=0;i < 10; i++){
	if(i>5){
		foo(i);
	}
}

console.log(foo.count); //0

执行foo.count = 0时,的确向函数对象foo添加了一个属性count。但是函数内部代码this.count中的this并不是指向那个函数对象,所以虽然属性名相同,根对象却并不相同。
 
2.this在任何情况下都不指向函数的词法作用域。

function foo(){
	var a = 2;
	this.bar();
}

function bar(){
	console.log(this.a);
}

foo(); // ReferenceError: a is not defined

每当你想要把this和词法作用域的查找混合使用时,一定要提醒自己,这是无法实现的。

this是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。

也就是说每个函数的this是在调用时被绑定的,完全取决于函数的调用位置(也就是函数的调用方法)

调用位置就是函数在代码中被调用的位置而不是声明的位置。

四条绑定规则

1.默认绑定

function foo(){
	console.log(this.a);
}

var a = 2;
foo(); // 2

当调用foo()时,this.a被解析成了全局变量a,因为在本例中,函数调用时应用了this的默认绑定,因此this指向全局对象。

怎样知道是否应用了默认绑定?

可以通过分析调用位置来看看foo()是如何调用的。在代码中,foo()是直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,无法应用其他规则。

如果使用严格模式(strict mode),则不能将全局对象用于默认绑定,因此this会绑定到undefined:

function foo(){
	"use strict";
	console.log(this.a);
}

var a = 2;
foo(); //TypeError:this is undefined

2.隐式绑定

调用位置时候有上下文对象,或者说是否被某个对象拥有或包含。

function foo(){
	console.log(this.a);
}

var obj = {
	a:2,
	foo:foo
};

obj.foo(); // 2

无论是直接在obj中定义还是先定义再添加为引用属性,这个函数严格来说都不属于obj对象。

然而,调用位置会使用obj上下文来引用函数,因此你可以说函数被调用时obj对象“拥有”或者“包含”它。当foo()被调用时,它的前面确实加上了对obj的引用。

因此,当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。

对象属性引用链中只有上一层或者说最后一层在调用位置中起作用。

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:42,
	foo:foo
};

var bar = obj.foo;

var a = "opps,global";

bar(); //"opps,global"

虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身,因此此时的bar()其实是一个不带任何修饰的函数调用,因此应用了默认绑定。

回调函数中的隐式赋值

function foo(){
	console.log(this.a);
}

function doFoo(fn){
	// fn其实引用的是foo
	fn(); // 调用位置
}

var obj = {
	a:2,
	foo:foo
};

var a = "oops,global";

doFoo(obj.foo); //"oops,global"

参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值

3.显式绑定

使用函数的call(…)和apply(…)方法。

它们的第一个参数是一个对象,是给this准备的,接着在调用函数时将其绑定到this。因为你可以直接指定this的绑定对象,因此我们称之为显式绑定。

function foo(){
	console.log(this.a);
}

var obj = {
	a:2
};

foo.call(obj); // 2

通过foo.call(…),我们可以在调用foo时强制把它的this绑定到obj上。

但是显示绑定仍然无法解决丢失绑定的问题

使用以下两种方法可以解决:

1.显式的强制绑定(硬绑定)

function foo(){
	console.log(this.a);
}

var obj = {
	a:2
};

var bar = function(){
	foo.call(obj);
};

bar(); // 2
setTimeout(bar,100); // 2

// 硬绑定的bar不可能再修改它的this
bar.call(window); // 2

我们创建了函数bar(),并在它的内部手动调用了foo.call(obj),因此强制把foo的this绑定到了obj。无论之后如何调用函数bar,它总会手动在obj上调用foo。这种绑定是一种显式的强制绑定,因此我们称之为硬绑定。

2.API调用的“上下文”

funtion foo(el){
	console.log(el,this.id);
}

var obj = {
	id:"awesome"
};

// 调用foo()时把this绑定到obj
[1,2,3].forEach(foo,obj);
// 1 awesome 2 awesome 3 awesome

这些函数实际上就是通过call(…)或者apply(…)实现了显式绑定,这样你可以少写一些代码。

4.new绑定

在JavaScript中,构造函数只是一些使用new操作符时被调用的函数。它们并不会属于某个类,也不会实例化一个类。实际上,它们甚至都不能说是一种特殊的函数类型,它们只是被new操作符调用的普通函数而已。

使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。

1.创建(或者说构造)一个全新的对象。
2.这个新对象会被执行[[Prototype]]连接。
3.这个新对象会绑定到函数调用的this。
4.如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。

function foo(a){
	this.a = a;
}

var bar = new foo(2);
console.log(bar.a); // 2

使用new来调用foo(…)时,我们会构造一个新对象并把它绑定到foo(…)调用中的this上。new是最后一种可以影响函数调用时this绑定行为的方法,我们称之为new绑定。

判断this

1.函数是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象。

var bar = new foo();

2.函数是否通过call、apply(显式绑定)或者硬绑定调用?如果是的话,this绑定的是指定的对象。

var bar  = foo.call(obj2);

3.函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象。

var bar = obj1.foo();

4.如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局对象。

var bar = foo();

对于正常的函数调用来说,这就是this的绑定原理,但总有例外。

绑定例外

1.被忽略的this

function foo(){
	console.log(this.a);
}

var a = 2;

foo.call(null); // 2

如果你把null或者undefined作为this的绑定对象传入call、apply或者bind,这些值在调用时会被忽略,实际应用的是默认绑定规则:

2.间接引用

function foo(){
	console.log(this.a);
}

var a = 2;
var o = {a:3,foo:foo};
var p = {a:4};

o.foo(); // 3
(p.foo = o.foo)(); // 2

赋值表达式p.foo = o.foo的返回值是目标函数的引用,因此调用位置是foo()而不是p.foo()或者o.foo()。根据我们之前说过的,这里会应用默认绑定。

3.软绑定

硬绑定会大大降低函数的灵活性,使用硬绑定之后就无法使用隐式绑定或者显式绑定来修改this。

如果可以给默认绑定指定一个全局对象和undefined以外的值,那就可以实现和硬绑定相同的效果,同时保留隐式绑定或者显式绑定修改this的能力。

this词法

箭头函数不使用this的四种标准规则,而是根据外层(函数或者全局)作用域来决定this。

function foo(){
	return (a) =>{
		console.log(this.a);
	};
}

var obj1 = {
	a:2
};

var obj2 = {
	a:3
};

var bar = foo.call(obj1);
bar.call(obj2); // 2

由于foo()的this绑定到obj1,bar(引用箭头函数)的this也会绑定到obj1,箭头函数的绑定无法被修改。(new也不行!)。

因此,箭头函数最常用于回调函数中,它可以像bind(…)一样确保函数的this被绑定到指定对象,此外,其重要性还体现在它用更常见的词法作用域取代了传统的this机制。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值