《你不知道的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。

时间: 2024-10-12 16:15:55

《你不知道的JavaScript》整理(二)——this的相关文章

《你不知道的JavaScript》整理(一)——作用域、提升与闭包

最近在读一本进阶的JavaScript的书<你不知道的JavaScript(上卷)>,里面分析了很多基础性的概念. 可以更全面深入的理解JavaScript深层面的知识点. 一.函数作用域 1)函数作用域 就是作用域在一个“Function”里,属于这个函数的全部变量都可以在整个函数的范围内使用及复用. function foo(a) { var b = 2; function bar() { // ... } var c = 3; } bar(); // 失败 console.log( a,

javascript 基础学习整理 二 之 html对象总结,参考W3C

Anchor 对象 更改一个链接的文本.URL 以及 target 使用 focus() 和 blur() 向超链接添加快捷键 Document 对象 使用 document.write() 向输出流写文本 使用 document.write() 向输出流写 HTML 返回当前文档的标题 返回当前文档的 URL 返回当前文档的 referrer 返回下载当前文档的服务器域名 使用 getElementById() 使用 getElementsByName() 打开一个新的文档,添加一些文本,然后

JavaScript作用域闭包(你不知道的JavaScript)

JavaScript闭包,是JS开发工程师必须深入了解的知识.3月份自己曾撰写博客<JavaScript闭包>,博客中只是简单阐述了闭包的工作过程和列举了几个示例,并没有去刨根问底,将其弄明白! 现在随着对JavaScript更深入的了解,也刚读完<你不知道的JavaScript(上卷)>这本书,所以乘机整理下,从底层和原理上去刨一下. JavaScript并不具有动态作用域,它只有词法作用域.词法作用域是在写代码或者说定义时确定的,而动态作用域是在运行时确定的.了解闭包前,首先我

java面试题整理二(转灰灰+灰灰)

java 开发面试题小整理(二) 51.Anonymous Inner Class(匿名内部类)是否可以继承其它类?是否可以实现接口? 答:可以继承其他类或实现其他接口,在Swing编程和Android开发中常用此方式来实现事件监听和回调. 52.内部类可以引用它的包含类(外部类)的成员吗?有没有什么限制? 答:一个内部类对象可以访问创建它的外部类对象的成员,包括私有成员. 53.Java 中的final关键字有哪些用法? 答:(1)修饰类:表示该类不能被继承: (2)修饰方法:表示方法不能被重

你不知道的javascript(中卷)笔记

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>你不知道的javascript(中卷)</title> </head> <body> <script type="text/javascript"> /* //封装对象包装 var a = new Boolean(false); if(!a)

你不知道的JavaScript(1)LHS查询和RHS查询

打算把<你不知道的JavaScript>中的知识点整理下,写点自己的心得,同时也敦促自己看书. 先做个整体的介绍,最后会再给个综合的例子. RHS 查询与简单地查找某个变量的值别无二致,而LHS 查询则是试图找到变量的容器本身,从而可以对其赋值. LHS查询 LHS查询指的是找到变量的容器本身,从而可以对其进行赋值.也就是找到赋值操作的目标. LHS查询的时候会沿着作用域链进行查询,找到的话就会将值赋值给这个变量,如果到达作用域顶端仍然找不到,就会在作用域链顶端创建这个变量. 举个例子 var

《你不知道的JavaScript》系列分享专栏

<你不知道的JavaScript>系列分享专栏 你不知道的JavaScript"系列就是要让不求甚解的JavaScript开发者迎难而上,深入语言内部,弄清楚JavaScript每一个零部件的用途 <你不知道的JavaScript>已整理成PDF文档,点击可直接下载至本地查阅https://www.webfalse.com/read/205515.html 文章 你不知道的JavaScript--Item3 隐式强制转换 你不知道的JavaScript--Item4 基本

你不知道的Canvas(二)

你不知道的Canvas(二) 一.色彩Colors 到目前为止,我们只看到过绘制内容的方法.如果我们想要给图形上色,有两个重要的属性可以做到:fillStyle 和 strokeStyle. fillStyle = color 设置图形的填充颜色. strokeStyle = color 设置图形轮廓的颜色. color 可以是表示 CSS 颜色值的字符串,渐变对象或者图案对象.默认情况下,线条和填充颜色都是黑色(CSS 颜色值 #000000). 输入的应该是符合 CSS3 颜色值标准 的有效

初探JavaScript(二)——JS如何动态操控HTML

除去五一三天,我已经和<JavaScript Dom编程艺术>磨合了六天,第一印象很好.慢慢的,我发现这是一块排骨,除了肉还有骨头.遇到不解的地方就会多看几遍,实在不懂的先跳过,毕竟,初次接触JS,没有必要花费过多时间去钻死胡同,先混个脸熟,以后再来拜访也未尝不可嘛.就这样,踉踉跄跄.囫囵吞枣似的已经过五关斩六将,到达第十一章. 书中有几个章节并没有从语法.技术等层面介绍JavaScript,而是站在一个全局的角度,立足编程原则和习惯道破了我们该如何看待和使用这门语言,主要有以下几点: Jav