Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

你不知道的JavaScript《二》this语法 #10

Open
Hjw52 opened this issue Nov 8, 2020 · 0 comments
Open

你不知道的JavaScript《二》this语法 #10

Hjw52 opened this issue Nov 8, 2020 · 0 comments

Comments

@Hjw52
Copy link
Owner

Hjw52 commented Nov 8, 2020

你不知道的JavaScript《二》this语法

引言

this 关键字是 JavaScript 中最复杂的机制之一。它是一个很特别的关键字,被自动定义在所有函数的作用域中。我们很难说清它到底执行什么。相对于词法作用域在声明的时候在其代码所在的位置确定的那种“静态作用域”,this的指向就是“动态”。this 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调 用时的各种条件。this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。我们只有找到函数的调用位置,才能判断出它绑定的this。

为什么用this

我们可以看下面这段代码:

function identify() {
	return this.name.toUpperCase();
}
function speak() {
	var greeting = "Hello, I'm " + identify.call( this );
	console.log( greeting );
}
var me = {
	name: "Kyle"
};
var you = {
	name: "Reader"
};
identify.call( me ); // KYLE
identify.call( you ); // READER
speak.call( me ); // Hello, 我是 KYLE
speak.call( you ); // Hello, 我是 READER

这段代码函数针对不同的对象调用,显示出不同的结果,这也是this的用法之一,为函数指向调用它的上下文对象,隐式传递一个对象引用;如果不用this,则需要为函数定义形参,传入相应对象,如下:

function identify(context) {
	return context.name.toUpperCase();
}
function speak(context) {
	var greeting = "Hello, I'm " + identify( context );
	console.log( greeting );
}
identify( you ); // READER
speak( me ); //hello, 我是 KYLE

关于this

也许很多人认为this指向函数自身,但在JavaScript,this的指向却是灵活多变的。我们看下思考代码:

function foo(num) {
	console.log( "foo: " + num );
	// 记录 foo 被调用的次数
	this.count++; //如果foo.count++;呢?
}
foo.count = 0;
var i;
for (i=0; i<10; i++) {
	if (i > 5) {
	foo( i );
	}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
// foo 被调用了多少次?
console.log( foo.count ); // 0 --疑惑?

console.log 语句产生了 4 条输出,证明 foo(..) 确实被调用了 4 次,但是 foo.count 仍然 是 0。为什么呢?其实这边this并不是指向foo函数,所以foo.count自然不会增加。(这边的this真正指向是window!具体的下面会讲

我们再看一个例子:

function foo() {
	var a = 2;
	this.bar();
}
function bar() {
	console.log( this.a );
}
foo(); // ReferenceError: a is not defined

这段代码按道理来说,随着作用域向上查找,应该可以输出a的值,然而这边却是引用错误,无法找到a变量。为什么呢?首先,这段代码试图通过 this.bar() 来引用 bar() 函数。这是绝对不可能成功的,必须省略前面的this。其次,让 bar() 可以访问 foo() 作用域里的变量 a。这是不可能实现的,(这不像闭包)你不能使用 this 来引用一 个词法作用域内部的东西。

看了上面也许你有很多疑惑,其实这也是this最容易被误解的两点:

  1. this指向函数自身
  2. this指向函数的作用域(this 在任何情况下都不指向函数的词法作用域。在 JavaScript 内部,作用 域确实和对象类似,可见的标识符都是它的属性。但是作用域“对象”无法通过 JavaScript 代码访问,它存在于 JavaScript 引擎内部。)

下面我们将从这两点出发,探索this的真正奥秘。

this绑定

函数在执行过程中的调用位置决定了this的绑定对象,如何绑定通常有以下四条绑定规则。

  1. 默认绑定——无人调用指向全局
function foo() {
	console.log( this.a );
}
var a = 2;
foo(); // 2

这边函数应用了默认绑定,因为在代码中,foo()直接使用,不带任何修饰调用,无法应用其他规则。this指向全局对象。所以调用foo()时,this.a被解析成全局变量,输出2。但要注意:在严格模式下,全局对象无法使用

默认绑定,this指向undefined,这也算是JavaScript的一种保护机制。

function foo() {
	"use strict";
	console.log( this.a );//this在严格模式下
}
var a = 2;
foo(); // TypeError: this is undefined

//但下面的情况又是可以
function foo() {
	console.log( this.a );//this不在严格模式下
}
var a = 2;
(function(){
	"use strict";
	foo(); // 2
})();

​ 2 .隐式绑定——谁调用指向谁

function foo() {
	console.log( this.a );
}
var obj = {
	a: 2,
	foo: foo
};
obj.foo(); // 2

这段代码当 foo() 被调用时,它的落脚点确实指向 obj 对象。当函数引用有上下文对象时,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象。因为调 用 foo() 时 this 被绑定到 obj,因此 this.a 和 obj.a 是一样的。但隐式绑定通常会存在绑定丢失的问题,从而把 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() 其实是一个不带任何修饰的函数调用,因此应用了默认绑定。一种更常见的情况比如:

function foo() {
	console.log( this.a );
}
function doFoo(fn) {
	// fn 其实引用的是 foo
	fn(); // <-- 调用位置!
}
var obj = {
	a: 2,
	foo: foo
};
var a = "oops, global"; // a 是全局对象的属性
doFoo( obj.foo ); // "oops, global"

函数的传递容易造成绑定丢失的问题。

  1. 显式绑定——固定绑定的对象

当我们想在某个对象上强制调用函数,这就需要使用显式绑定。可以使用函数的 call(..) 和 apply(..) 方法强制把this绑定到某个对象。它们接收的第一个参数是一个对象,它们会把这个对象绑定到 this,接着在调用函数时指定这个 this。

eg1:

function foo() {
	console.log( this.a );
}
var obj = {
	a:2
};
foo.call( obj ); // 2

eg2:

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

通过 foo.call(),我们可以在调用 foo 时强制把它的 this 绑定到 obj 上。这种绑定难以更改,我们称之为硬绑定。

​ 4. new绑定

在这边需要明确知道:JavaScript 中 new 的机制实 际上和面向类的语言完全不同。在 JavaScript 中,构造函数只是一些 使用 new 操作符时被调用的函数。它们并不会属于某个类,也不会实例化一个类。

function foo(a) {
	this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); // 2

当使用 new 来调用 foo(...) 时,我们会构造一个新对象并把它绑定到 foo(..) 调用中的 this 上。就像上面代码中的this指向了bar。

绑定规则优先级

我们已经了解了函数调用中 this 绑定的四条规则,你需要做的就是找到函数的调用位 置并判断应当应用哪条规则。但是,如果某个调用位置可以应用多条规则该怎么办?为了解决这个问题就必须给这些规则设定优先级。可以明确的是,默认绑定的优先级是四条规则中最低的。同时显式绑定比隐式绑定优先级高,那么显式绑定、隐式绑定。new绑定三者谁高谁低呢?

**new 绑定 **VS 隐式绑定

function foo(something) {
	this.a = something;
}
var obj1 = {
	foo: foo
};
var obj2 = {};
obj1.foo( 2 );
console.log( obj1.a ); // 2
obj1.foo.call( obj2, 3 );
console.log( obj2.a ); // 3
var bar = new obj1.foo( 4 );
console.log( obj1.a ); // 2
console.log( bar.a ); // 4

可以看出bar.a覆盖了obj1.a,所以new绑定的优先级比隐式绑定高。

**new 绑定 **VS 显式绑定

function foo(something) {
	this.a = something;
}
var obj1 = {};
var bar = foo.bind( obj1 );
bar( 2 );
console.log( obj1.a ); // 2
var baz = new bar(3);
console.log( obj1.a ); // 2
console.log( baz.a ); // 3

bar 被硬绑定到 obj1 上,但是 new bar(3) 并没有像我们预计的那样把 obj1.a 修改为 3。new 修改了硬绑定(到 obj1 的)调用 bar(..) 中的 this。因为使用了 new 绑定,我们得到了一个名字为 baz 的新对象,并且 baz.a 的值是 3。所以,优先级:new绑定大于显式绑定。

综上

优先级从高到低排列为:

  1. new绑定
  2. 显式绑定
  3. 隐式绑定
  4. 默认绑定

箭头函数的this

ES6 中加入了一种无法使用上面绑定规则的特殊函数类型:箭头函数。它的this是根据外层(函数或者全局)作用域来决 定 this。它继承外层函数调用的 this 绑定(无论 this 绑定到什么)。这 其实和 ES6 之前代码中的 self = this 机制一样。相比普通绑定规则的动态this,箭头函数this显得有点**”静态“**。

function foo() {
	// 返回一个箭头函数
	return (a) => {
	//this 继承自 foo()
	console.log( this.a );
};
}
var obj1 = {
a:2
};
var obj2 = {
a:3
};
var bar = foo.call( obj1 );
bar.call( obj2 ); // 2, 不是 3 !

foo() 内部创建的箭头函数会捕获调用时 foo() 的 this。由于 foo() 的 this 绑定到 obj1, bar(引用箭头函数)的 this 也会绑定到 obj1,箭头函数的绑定无法被修改。(new 也无法修改)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant