《你不知道的JavaScript》整理(二)——this

简介: 最近在读一本进阶的JavaScript的书《你不知道的JavaScript(上卷)》,这次研究了一下“this”。当一个函数被调用时,会创建一个活动记录(执行上下文)。

最近在读一本进阶的JavaScript的书《你不知道的JavaScript(上卷)》,这次研究了一下“this”。

当一个函数被调用时,会创建一个活动记录(执行上下文)。

这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。

this就是记录的其中一个属性,会在函数执行的过程中用到。

this既不指向函数自身也不指向函数的作用域

this实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用

 

一、调用位置


调用位置就在当前正在执行的函数的前一个调用中,源码查看


function baz() {
  // 当前调用栈是:baz
  // 因此,当前调用位置是全局作用域
  console.log("baz");
  bar(); // <-- bar 的调用位置
}
function bar() {
  // 当前调用栈是 baz -> bar
  // 因此,当前调用位置在 baz 中
  console.log("bar");
  foo(); // <-- foo 的调用位置
}
function foo() {
  // 当前调用栈是 baz -> bar -> foo
  // 因此,当前调用位置在 bar 中
  console.log("foo");
}
baz(); // <-- baz 的调用位置


二、绑定规则


你必须找到调用位置,然后判断需要应用下面四条规则中的哪一条。

1)默认绑定

最常用的函数调用类型:独立函数调用。可以把这条规则看作是无法应用其他规则时的默认规则


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


2)隐式绑定

隐式绑定的规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含


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


但有时候会出现隐式丢失


function foo() {
  console.log(this.a);
}
var obj = {
    a: 2,
    foo: foo
};
var bar = obj.foo; // 函数  
var a = "oops, global"; //
bar(); // "oops, global"


虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身

因此此时的bar()其实是一个不带任何修饰的函数调用,应用了默认绑定

 

3)显式绑定

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


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


在很多库中经常能看到bind方法,这是一种硬绑定,一种显式的强制绑定,下面是一种bind实现。


function foo(something) {
  console.log(this.a, something);
  return this.a + something;
}
// 简单的辅助绑定函数 
function bind(fn, obj) {
  return function() {
    return fn.apply(obj, arguments);
  };
}
var obj = {
  a: 2
};
var bar = bind(foo, obj);
var b = bar(3); // 2 3
console.log(b); // 5


4)new绑定

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

1. 创建(或者说构造)一个全新的对象。

2. 这个新对象会被执行[[原型]]连接。

3. 这个新对象会绑定到函数调用的this

4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。


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


三、优先级


默认绑定的优先级是四条规则中最低的。


1)显式绑定优先级比隐式绑定要更高


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


2)new绑定比隐式绑定优先级高


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


3)new绑定会修改显示绑定中this


function bind(fn, obj) {
  return function() {
    return fn.apply(obj, arguments);
  };
}
function foo(something) { 
  this.a = something;
}
var obj1 = {};
var bar = foo.bind(obj1); //bar被硬绑定到obj1上
bar(2);
console.log( obj1.a ); // 2
var baz = new bar(3); //
console.log( obj1.a ); // 没有把obj1.a 修改为3,还是为2 
console.log( baz.a ); // 3


4)判断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()

 

四、绑定例外


1)被忽略的this

如果你把null或者undefined作为this的绑定对象传入call、apply或者bind。

这些值在调用时会被忽略,实际应用的是默认绑定规则。


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

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)软绑定

如果可以给默认绑定指定一个全局对象和undefined以外的值。

那就可以实现和硬绑定相同的效果,同时保留隐式绑定或者显式绑定修改this的能力。

可以通过一种被称为软绑定的方法来实现我们想要的效果。


Function.prototype.softBind = function(obj) {
  var fn = this;
  // 捕获所有 curried 参数
  var curried = [].slice.call(arguments, 1);
  var bound = function() {
    return fn.apply((!this || this === (window || global)) ? obj : this, curried.concat.apply(curried, arguments));
  };
  bound.prototype = Object.create(fn.prototype);
  return bound;
};


function foo() {
  console.log("name: " + this.name);
}
var obj = { name: "obj" }, 
    obj2 = { name: "obj2" }, 
    obj3 = { name: "obj3" };
var fooOBJ = foo.softBind( obj ); 
fooOBJ(); // name: obj <---- 应用了软绑定
obj2.foo = foo.softBind(obj); 
obj2.foo(); // name: obj2 
fooOBJ.call( obj3 ); // name: obj3
setTimeout( obj2.foo, 10 );// name: obj   <---- 应用了软绑定

软绑定版本的foo()可以手动将this绑定到obj2或者obj3上。

但如果应用默认绑定,则会将this绑定到obj。


相关文章
|
12天前
|
JavaScript 前端开发
javascript中的this
javascript中的this
|
12天前
|
JavaScript 前端开发
错综复杂的this:理清你的JavaScript代码中的指向问题
错综复杂的this:理清你的JavaScript代码中的指向问题
|
12天前
|
JavaScript
JS中改变this指向的六种方法
JS中改变this指向的六种方法
|
9天前
|
前端开发 JavaScript
JavaScript:this-关键字,2024中级前端开发面试解答
JavaScript:this-关键字,2024中级前端开发面试解答
|
12天前
|
自然语言处理 JavaScript 前端开发
在JavaScript中,this关键字的行为可能会因函数的调用方式而异
【5月更文挑战第9天】JavaScript中的`this`关键字行为取决于函数调用方式。在非严格模式下,直接调用函数时`this`指全局对象,严格模式下为`undefined`。作为对象方法调用时,`this`指向该对象。用`new`调用构造函数时,`this`指向新实例。通过`call`、`apply`、`bind`可手动设置`this`值。在回调和事件处理中,`this`可能不直观,箭头函数和绑定方法可帮助管理`this`的行为。
17 1
|
12天前
|
JavaScript 前端开发
深入探索JavaScript:如何改变this的指向
深入探索JavaScript:如何改变this的指向
10 2
|
12天前
|
JavaScript 前端开发
【专栏】`Function.prototype.apply` 在JavaScript中用于动态设定函数上下文(`this`)和参数列表
【4月更文挑战第29天】`Function.prototype.apply` 在JavaScript中用于动态设定函数上下文(`this`)和参数列表。它接受两个参数:上下文对象和参数数组。理解`apply`有助于深入JS运行机制。文章分三部分探讨其原理:基本概念和用法、工作原理详解、实际应用与注意事项。在应用中要注意性能、参数类型和兼容性问题。`apply`可用于动态改变上下文、传递参数数组,甚至模拟其他语言的调用方式。通过深入理解`apply`,能提升代码质量和效率。
|
12天前
|
JavaScript 前端开发 数据安全/隐私保护
|
12天前
|
自然语言处理 JavaScript 前端开发
js_关键字this指向哪里?
js_关键字this指向哪里?
7 0
|
12天前
|
JavaScript 前端开发
js开发:请解释this关键字在JavaScript中的用法。
【4月更文挑战第23天】JavaScript的this关键字根据执行环境指向不同对象:全局中指向全局对象(如window),普通函数中默认指向全局对象,作为方法调用时指向调用对象;构造函数中指向新实例,箭头函数继承所在上下文的this。可通过call、apply、bind方法显式改变this指向。
10 1