javascript:this 关键字

别急着格式化!DiskGenius恢复U盘/移动硬盘RAW格式数据的完整流程 本文详细介绍了使用DiskGenius恢复U盘或移动硬盘RAW格式数据的完整流程。当设备显示为RAW格式时,盲目格式化可能导致永久数据丢失。文章解析了RAW格式的成因,并提供了从紧急处理原则到分区表重建、数据提取的进阶技巧,帮助用户无损恢复重要文件。 阅读详情

前言

看过阮一峰的关于 this 的教程,讲了很多比较好的例子,但没有对其本质的东西解释清楚,而且部分例证存在问题;于是,打算重写本章节,从this的本质入手;

本文为作者的原创作品,转载需注明出处;

本文转载自笔者的私人博客,伤神的博客 http://www.shangyang.me/2017/03/24/javascript-lang-this/

References

Javascript中this关键字详解
jQuery Fundamentals Chapter - The this keyword

this 是什么?

this可以理解为一个指针,指向调用对象;

判断 this 是什么的四个法则

官网定义

先来看第一段官方的解释,

In JavaScript, as in most object-oriented programming languages, this is a special keyword that is used within methods to refer to the object on which a method is being invoked. The value of this is determined using a simple series of steps:

  1. If the function is invoked using Function.call or Function.apply, this will be set to the first argument passed to call/apply. If the first argument passed to call/apply is null or undefined, this will refer to the global object (which is the window object in Web browsers).
  2. If the function being invoked was created using Function.bind, this will be the first argument that was passed to bind at the time the function was created.
  3. If the function is being invoked as a method of an object, this will refer to that object.
  4. Otherwise, the function is being invoked as a standalone function not attached to any object, and this will refer to the global object.

大致翻译如下,
this是这么一个特殊的关键字,它是用来指向一个当前正在被调用( a being invoked )方法的调用对象的;( 等等,这句话其实隐藏了一个非常关键的信息,那就是this是在运行期 生效的,怎么生效的?在运行期,this被赋值,将某个对象赋值给this,与声明期无关,也就是说,this是运行期相关的 );this的赋值场景,归纳起来,分为如下四种情况,

  1. 如果方法是被Function.call或者Function.apply调用执行…. bla..bla..
    参考 function prototype call 小节

  2. 如果是被Function.bind… bla…bla
    参考 function prototype bind 小节

  3. 如果某个方法在运行期是被一个对象( an object )调用( 备注:这里为了便于理解,我针对这种情况,自己给起了个名称叫关联调用 ),在运行期,会将该 object 的引用赋值给该方法的this。
    备注:被一个对象调用?何解?其实就是指语句obj.func(),这个时候,func()方法内部的this将会被赋值为obj对象的引用,也就是指向obj;

  4. 如果该方法在运行期被当做一个没有依附在任何 object 上的一个独立方法被调用(is being invoked as a standalone function not attached to any object ),那么该方法内部的this将会被赋值为全局对象(在浏览器端就是 windows )
    独立方法 ( standalone function )?在运行期,如果func方法被obj关联调用的,既是通过obj.func()的方式,那么它就不是standalone的;如果是直接被调用,没有任何对象关联,既是通过func()调用,那么这就是standalone的。

this 运行期相关

官网定义 2

再来看另外一句非常精炼的描述,来加深理解

The this keyword is relative to the execution context, not the declaration context.

this关键字与运行环境有关而与声明环境无关;(补充,而作用域链和闭包是在函数的声明期创建的,参考创建时机)

补充,是如何与函数的运行期相关的,参考this 指针运行时赋值

我的补充

法则 #3 和 #4,大多数情况都非常容易理解,有几种情况需要特别注意,

  1. 函数嵌套
    需要注意的是object对象中的函数内部再次嵌套函数的情况,

    
          
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
          
    var name = "windows";
    var obj = {
    name: "object",
    f1: function(){
    console.log( "this: "+ this.name)
    function f2(){
    console.log( "this: " + this.name)
    }
    f2();
    }
    };

    执行

    
          
    1
    2
    3
    
          
    > obj.f1();
    this: object
    this: windows

    可以看到,在运行期,被调用函数 f1() 中的this指向 obj,而被调用函数 f2() 中的this指向的是 windows ( global object );因为 f1 函数在当前的运行时中是通过 obj.f1() 进行的关联调用,所以,根据定义 #3,在当前的运行期间,f1() 内部的 this 是指向 obj 对象的( 通过将 obj 的引用直接赋值给 this ),而, f2 函数在运行期是没有与其它 object 进行关联调用,所以,在当前的运行时期,f2 是一个 standalone 的函数,所以,根据定义 #4,在当前的运行期间,f2() 的内部this是指向 windows 的。(注意,这里我反复强调当前运行期间,是因为this是在运行时被赋值的,所以,要特别注意的是,即使某个函数的定义不变,但在不同的执行环境(运行环境)中,this是会发生变化;)

  2. 回调函数
    参看函数回调场景-1和函数回调场景-2

  3. 函数赋值
    参看将函数赋值-standalone以及相关变种章节

可见,要判断this在运行期到底指的是什么,并没有那么容易,但是,只要牢牢的把握好两点,就可以迎刃而解,

  • this是运行期相关的
    更确切的说,this是在运行期被赋值的,所以,它的值是在运行期动态确定的。
  • this是否与其它对象关联调用
    这里的关联调用指的是 javascript 的一种语法,既是调用语句显式的写为obj.func(),另外需要注意的是,javascript 方法的调用不会隐式的隐含 this。只要没有显式的关联调用,那么就是standalone的调用,就符合法则 #4,所以,this指向 Global Object。

this 的 Object

注意,this定义中所指的Object指的是 javascript 的 Object 类型,既是通过


    
1
2
3

    
var o1 = {};
var o2 = new Object();
var o3 = Object.create( Object.prototype);

这样的方式构建出来的对象;

备注,最开始,自己有个思维的误区,认为既然 javascript 一切皆为对象,那么this指针是指向对象的,那么是不是也可以指向Function,Number等对象?答案是否定的。

起初,我是按照上面的逻辑来理解的,直到当我总结到bind 是如何实现的小节后,发现Function对象在调用方法属性bind的时候,bind方法内部的this指向的是Function,这才恍然大悟,this的Object实际上是可以指向任何 javascript Object的,包括 Object、Function等。

this 是变化的

我们来看这样一个例子,


    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

    
var C = "王麻子";
var A = {
name: '张三',
describe: function () {
return '姓名:'+ this.name;
}
};
var B = {
name: '李四'
};
// 执行,
> A.describe();
'张三'
> B.describe = A.describe;
> B.describe()
'李四'
> var describe = A.describe;
> describe();
'王麻子'

可以看到,虽然 A.describe 方法的定义不变,但是其运行时环境发生了变化,this 的指向也就发生了变化。


    
1
2
3

    
> B.describe = A.describe;
> B.describe()
'李四'

在运行时,相当于运行的是 B 的 describe 方法


    
1
2
3

    
> var describe = A.describe;
> describe();
'王麻子'

在运行时,相当于运行的是 windows 的 describe 方法

方法调用没有隐含 this

经常写 Java 代码的原因,经常会习惯性的认为只要在对象方法里面调用某个方法或者属性,隐含了 this,比如


    
1
2
3
4
5
6
7
8
9
10
11
12
13

    
public class Person{
String name;
public String getName(){
return name;
}
public String getName2(){
return this.name;
}
}

而 Javascript 实际上并没有这种隐含的表达方式;详细验证过程参考将函数赋值-standalone

关联调用 - 容易混淆的场景

从this 是什么章节中,为了方便对 #3 进行描述,我起了个名字叫做 关联调用 ;那么有些情况看似是 关联调用,实则不然;

我们有一个标准的对象,定义如下,


    
1
2
3
4
5
6
7

    
var name = "windows";
var obj = {
name: "obj",
foo: function () {
console.log( "this: "+ this.name);
}
};

通过标准的 关联调用 的方式,我们进行如下的调用,


    
1
2

    
> obj.foo()
'this: obj'

根据法则 #3 既 关联调用 的定义,得到 this -> obj;如果事事都如此的简单,如此的标准,那可就好了,总会有些让人费解的情况,现在来看看如下的一些特殊的例子,加深对 关联调用 的理解。

将函数赋值 - standalone


    
1
2
3

    
> var fooo = obj.foo
> fooo();
'this: windows'

输出的 windows,既是 this -> global object,而不是我们期望的 obj;为什么?原因是,obj.foo 其实是 foo 函数的函数地址,通过 var fooo = obj.foo 将该函数的地址赋给了变量 fooo,那么当执行


    
1

    
> fooo();

的时候,fooo() 执行的是是一个standalone的方法,根据法则 #4,所以该方法内部的this指向的是 Global Object;注意,obj.foo 表示函数 foo 的入口地址,所以,变量 fooo 等价与 foo 函数。

备注:由于受到写 Java 代码习惯的原因,很容易将这里解释为默认执行的是this.fooo(),fooo() 的调用隐含了this,因此就会想到,由于this指向的 Global Object,所以这里当然返回的就是this: windows;但是,这样解释,是不对的,因为 Javascript 压根没有这种隐含this的概念,参看用例,


    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

    
var name = "windows";
var o = {
name : "o",
f2 : function(){
console.log( "o -> f2");
console.log( "this: " this.name );
},
f : function(){
console.log( "f.this -> " + this.name);
var f2 = function(){
console.log( "f -> f2");
console.log( this.name );
}
f2(); // f -> f2
this.f2(); // o -> f2
}
}

可以看到,在 o.f() 函数中,如果 f2() 的调用隐含了this,那么 f2() 和 this.f2() 两者调用应该是等价的;但是,在实际执行过程中,f2() 和 this.f2()执行的是两个截然不同的方法,因此 f2() ≠ this.f2(),所以 f2() 并没有隐示的表示为 this.f2();

将函数赋值变种 - 匿名 standalone 函数立即执行


    
1
2

    
> (obj.foo = obj.foo)()
'this: windows'

首先,立即执行 foo 函数,然后将 foo 函数赋值给对象 obj 对象的 foo 属性;等价于执行如下的代码,


    
1
2
3
4
5

    
var name = "windows";
var obj = { name : "obj" };
(obj.foo = function () {
console.log( "this: " + this.name);
})();

输出,


    
1

    
'this: windows'

可以看到,this -> global object,这里为什么指向的是 global object?其实这里的立即执行过程,就是执行的如下代码,


    
1
2
3

    
( function () {
console.log( "this: " + this.name);
}());

由此可以看出,实际上进行一个匿名函数的立即执行;也就是说执行过程中并没有使用 关联调用,而是一次 standalone 函数的自身调用,所以根据法则 #4,this -> global object。执行完以后,将该匿名函数赋值给 obj.foo。

再次执行,


    
1
2

    
> obj.foo();
'this: obj'

这次执行的过程是一次标准的 关联调用 过程,所以根据法则 #3,this -> obj。

作为判断条件 - 匿名函数立即执行


    
1
2

    
> ( false || obj.foo)()
'windows'

等价于执行,


    
1
2
3

    
( false || function () {
console.log( "this: " + this.name);
})()

原理和函数赋值变种-匿名 standalone 函数立即执行 一致,等价于立即执行如下的匿名函数


    
1
2
3

    
( function () {
console.log( "this: " + this.name);
})()

其实,把这个例子再做一个细微的更改,其中逻辑就看得更清楚了,为 foo 函数添加一个返回值 return true


    
1
2
3
4
5
6
7
8

    
var name = "windows";
var obj ={
name: "obj",
foo: function () {
console.log( "this: "+ this.name);
return true;
}
};

再次执行,


    
1
2
3

    
> ( false || obj.foo)()
'windows'
true

可见,obj.foo 函数执行以后,返回 true。上述代码其实等价于执行如下的代码,


    
1
2
3
4

    
( false || function () {
console.log( "this: " + this.name);
return true;
})()

函数回调场景 0 - 基本原理


    
1
2
3
4
5
6
7
8
9
10
11
12
13
14

    
var counter = {
count: 0,
inc: function () {
'use strict';
this.count++;
}
};
function callIt(callback) {
callback();
}
> callIt(counter.inc)
TypeError: Cannot read property 'count' of undefined

可以看到,把一个定义有this关键字的函数作为其它函数的回调函数,是危险的,因为this在运行期会被重新赋值,上述例子很直观的描述了这一点,之所以报错,是因为this指向了 Global Object。要解决这样的问题,可以使用bind,调用的时候改为


    
1
2

    
> callIt(counter.inc.bind(counter))
1

函数回调场景 1 - setTimeout


    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

    
var name = "Bob";
var nameObj ={
name : "Tom",
showName : function(){
console.log( this.name);
},
waitShowName : function(){
setTimeout( this.showName, 1000);
}
};
// 执行,
> nameObj.waitShowName();
'Tom'
undefined

setTimeout(this.showName, 1000);将 nameObj.showName 函数作为回调函数参数传递给 setTimeout;那么为什么当 setTimeout 执行回调的时候,nameObj.showName 方法返回的是 undefined 呢?为什么不是返回全局对象对应的 name Bob?原因只有一个,那就是 setTimeout 有自己的 this 对象,而它没有 name 属性,而在回调 showName 函数的时候,showName 函数中的 this 正是 setTimeout 上下文中的 this,而该 this 并没有定义 name 属性,所以这里返回 undefined。

函数回调场景 2 - DOM 对象


    
1
2
3
4
5
6
7

    
var o = new Object();
o.f = function () {
console.log( this === o);
}
o.f() // true,得到期望的结果 this -> o

但是,如果将f方法指定给某个click事件,this的指向发生了改变,


    
1

    
$( '#button').on( 'click', o.f);

点击按钮以后,返回的是false,是因为在执行过程中,this不再指向对象o了而改为指向了按钮的DOM对象了;Sounds Good,但问题是,怎么被改动的?看了一下 jQuery 的源码,event.js,摘录重要的片段如下,


    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

    
function on( elem, types, selector, data, fn, one ) {
.......
if ( one === 1 ) {
origFn = fn;
fn = function( event ) {
// Can use an empty set, since event contains the info
jQuery().off( event );
return origFn.apply( this, arguments );
};
// Use same guid so caller can remove using origFn
fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
}
.......
}

o.f 函数的地址赋值给 fn 参数,fn -> origFn,最后是通过origFn.apply( this, arguments );来调用 o.f 函数的,而这里的 this 就是当前的 DOM对象,既是这个按钮 button;通过这样的方式,在执行过程中,通过回调函数 $(“button”).on(…) 成功的将新的 this 对象 button 注入了 o.f 函数。那么如何解决呢?参看function.prototype.apply())
的小节#3,动态绑定回调函数。

函数回调场景 3 - 数组对象方法的回调


    
1
2
3
4
5
6
7
8
9
10
11
12
13
14

    
var obj = {
name: '张三',
times: [ 1, 2, 3],
print: function () {
this.times.forEach( function (n) {
console.log( this.name);
});
}
};
> obj.print();
'undefined'
'undefined'
'undefined'

这里我们期望的是,依次根据数组 times 的长度,输出 obj.name 三次,但是实际运行结果是,数组虽然循环了三次,但是每次输出都是 undefined,那是因为匿名函数


    
1
2
3

    
function(n){
console.log( this.name);
}

作为数组 times 的方法 forEach 的回调函数执行,在 forEach 方法内部该匿名函数必然是作为 standalone 方法执行的,所以,this指向了 Global Object;

进一步,为什么“在 forEach 方法内部该匿名函数必然是作为 standalone 方法执行的”?为什么必然是作为 standalone 方法执行?是因为不能在 forEach 函数中使用 this.fn() 的方式来调用该匿名回调函数( fn 作为参数引用该匿名回调函数 ),因为如果这样做,在运行时期会报错,因为在 forEach函数的 this 对象中找不到 fn 这样的属性,而该 this 对象指向的是 obj.times 数组对象。因此,得到结论“在 forEach 方法内部该匿名函数必然是作为 standalone 方法执行的”

解决办法,使用 bind


    
1
2
3
4
5
6
7
8
9
10

    
obj.print = function () {
this.times.forEach( function (n) {
console.log( this.name);
}.bind( this));
};
> obj.print()
'张三'
'张三'
'张三'

将 obj 对象作为 this 绑定到该匿名函数上,然后再作为回调函数参数传递给 forEach 函数,这样,在 forEach 函数中,用 standalone 的方式调用 fn的时候,fn 中的 this 指向的就是数组对象 obj 对象,这样,我们就能顺利的输出 obj.name 了。

绑定 this

有上述描述可知,this的值在运行时根据不同上下文环境有不同的值,因此我们说this的值是变化的,这就给我们的编程带来了麻烦,有时候,我们期望,得到一个固定的this。Javascript 提供了call、apply以及bind这三个方法,来固定this的指向;这三个方法存储在 function.prototype 域中,
debug_function_call_bind_apply.pngdebug_function_call_bind_apply.png

function.prototype.call()

总结起来,就是解决函数在调用的时候,如何解决this动态变化的问题。

调用格式,


    
1

    
func.call(thisValue, arg1, arg2, ...)

第一个参数是在运行时用来赋值给 func 函数内部的 this 的。

通过f.call(obj)的方式调用函数,在运行时,将 obj 赋值给 this;


    
1
2
3
4
5
6
7
8

    
var obj = {};
var f = function () {
return this;
};
f() === this // true
f.call(obj) === obj // true

call方法的参数是一个对象,如果参数为 空、null 或者 undefined,则使用默认的全局对象;


    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

    
var n = 123;
var obj = { n: 456 };
function a() {
console.log( this.n);
}
> a.call()
123
> a.call( null)
123
> a.call( undefined)
123
> a.call( window)
123
> a.call(obj)
456

如果call方法的参数是一个原始值,那么这个原始值会自动转成对应的包装对象,然后赋值给 this


    
1
2
3
4
5
6

    
var f = function () {
return this;
};
> f.call( 5)
[ Number: 5]

call方法可以接受多个参数,第一个参数就是赋值给 this 的对象,


    
1
2
3
4
5
6
7
8
9
10
11
12

    
var obj = {
name : 'obj'
}
function add(a, b) {
console.log( this.name);
return a + b;
}
> add.call(obj, 1, 2)
obj
3

call方法可以调用对象的原生方法;


    
1
2
3
4
5
6
7
8
9
10

    
var obj = {};
obj.hasOwnProperty( 'toString') // false
// “覆盖”掉继承的 hasOwnProperty 方法
obj.hasOwnProperty = function () {
return true;
};
obj.hasOwnProperty( 'toString') // true
Object.prototype.hasOwnProperty.call(obj, 'toString') // false

方法 hasOwnProperty 是对象 obj 从 Object.prototype 中继承的方法,如果一旦被覆盖,就不会得到正确的结果,那么,我们可以使用call的方式调用原生方法,将 obj 作为 this 在运行时调用,这样,变通的,我们就可以调用 obj 对象所继承的原生方法了。

function.prototype.apply()

总结起来,和call一样,就是解决函数在调用的时候,如何解决this动态变化的问题。

apply方法的作用与call方法类似,也是改变this指向,然后再调用该函数。唯一的区别就是,它接收一个数组作为函数执行时的参数,使用格式如下。


    
1

    
func.apply(thisValue, [arg1, arg2, ...])

apply方法的第一个参数也是this所要指向的那个对象,如果设为null或undefined,则等同于指定全局对象。第二个参数则是一个数组,该数组的所有成员依次作为参数,传入原函数。

原函数的参数,在call方法中必须一个个添加,但是在apply方法中,必须以数组形式添加


    
1
2
3
4
5
6

    
function f(x,y){
console.log(x+y);
}
f.call( null, 1, 1) // 2
f.apply( null,[ 1, 1]) // 2
  1. 找出数组最大的元素

    
          
    1
    2
    3
    
          
    var a = [ 10, 2, 4, 15, 9];
    Math.max.apply( null, a)
    // 15
  2. 将数组的空元素变为 undefined

    
          
    1
    2
    
          
    Array.apply( null, [ "a",, "b"])
    // [ 'a', undefined, 'b' ]

    空元素与undefined的差别在于,数组的forEach方法会跳过空元素,但是不会跳过undefined。因此,遍历内部元素的时候,会得到不同的结果。

    
          
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
          
    var a = [ 'a', , 'b'];
    function print(i) {
    console.log(i);
    }
    a.forEach(print)
    // a
    // b
    Array.apply( null, a).forEach(print)
    // a
    // undefined
    // b
  3. 绑定回调函数的对象
    函数回调场景-2我们看到this被动态的更改为了 DOM 对象 button,这往往不是我们所期望的,所以,我们可以再次绑定回调函数来固定this,如下,

    
          
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
          
    var o = new Object();
    o.f = function () {
    console.log( this === o);
    }
    var f = function (){
    o.f.apply(o);
    // 或者 o.f.call(o);
    };
    $( '#button').on( 'click', f);

    这样,我们用 f 函数封装原来的回调函数 o.f,并使用apply方法固定住this,使其永远指向 object o,这样,就达到了this不被动态修改的目的。

function.prototype.bind()

总结起来,其实就是在把函数作为参数传递的时候,如何解决this动态变化的问题。

解决的问题

在认识关联调用 - 容易混淆的场景中,我们浓墨重彩的描述了将函数赋值以后,导致this在运行期发生变化的种种场景,而且在编程过程当中,也是非常容易导致问题的场景;那么有没有这么一种机制,即便是在函数赋值后,在运行期依然能够保护并固定住我的this?答案是有的,那就是bind。下面,我们来看一个例子,


    
1
2

    
var d = new Date();
d.getTime() // 1481869925657

我们使用语句 d.getTime() 通过对象 d 关联调用函数 getTime(),根据法则 #3,函数 getTime() 内部的 this 指向的是对象 d,然后从 d 对象中成功获取到了时间。但是,我们稍加改动,将对象 d 中的函数 getTime 赋值给另外一个变量,在执行呢?


    
1
2

    
var print = d.getTime;
print() // Uncaught TypeError: this is not a Date object.

Wow~, 画风突变,得不到时间了,而且还抛出了一个程序异常,好玩,你的程序因此崩溃.. 这就是this在执行期动态变化所导致的,当我们将函数 d.getTime 赋值给 print,然后语句 print() 表示将函数 getTime 作为 standalone 的函数在运行期调用,所以,内部的this发生变化,指向了 Global Object,也因此,我们得不到时间了,但我们得到一个意想不到的异常..

Ok, 别怕,孩子,bind登场了,


    
1
2

    
var print = d.getTime.bind(d);
print() // 148186992565

在 赋值过程中,将函数通过bind语法绑定this对象 d 以后,再赋值给一个新的变量;这样,即便 print() 再次作为 standalone 的函数在运行期调用,this的指向也不再发生变化,而是固定的指向了对象 d。

bind 是如何实现的

    
1
2
3
4
5
6
7
8
9
10
11

    
if(!( 'bind' in Function.prototype)){
Function.prototype.bind = function(){
var fn = this; // 当前调用 bind 的当前对象 fn ( fn.bind(..) )
var context = arguments[ 0]; // 用来绑定 this 对象的参数
var args = Array.prototype.slice.call( arguments, 1);
var fnbound = function(){
return fn.apply(context, args);
}
return fnbound;
}
}

给Function对象的prototype原型中新增一个属性bind,该bind是一个 function 函数;这里要特别特别注意,每次bind调用以后,返回的是一个新的function,


    
1
2
3
4

    
var fnbound = function(){
return fn.apply(context, args);
}
return fnbound;

通过 fnbound 函数套一层原函数 fn 作为闭包,然后返回这个新的 function fnbound;大部分教程就是这样介绍即止了;其实,我想问的是,为什么bind要这么设计,直接返回fn.apply(context, args);不是挺好吗?为什么还要在外面套一层新函数 fnbound?Ok,这里我就来试图解释下原因吧;

采用反证法,如果,我们不套这么一层新函数 fubound,看看,会怎样?于是,我们得到如下的实现,


    
1
2
3
4
5
6
7
8

    
if(!( 'bind' in Function.prototype)){
Function.prototype.bind = function(){
var fn = this; // 当前调用 bind 的当前对象 fn ( fn.bind(..) )
var context = arguments[ 0]; // 用来绑定 this 对象的参数
var args = Array.prototype.slice.call( arguments, 1);
return fn.apply(context, args);
}
}

直接返回fn.apply(context, args),oh,顿时,我明白了,fn.apply(...)这是一条执行命令啊,它会立即执行 fn,将 fn 执行的结果返回.. 而我们这里的bind的初衷只是扩充 fn 函数的行为(既绑定this对象),然后返回一个函数的引用,而正式因为我们无法在绑定以后,直接返回原有函数的引用,所以,这里,我们才需要创建一个新的函数并返回这个新的函数的引用,已达到bind的设计目的。Ok,这下总算是清楚了。

特性
绑定匿名函数

    
1
2
3
4
5

    
obj.print = function () {
this.times.forEach( function (n) {
console.log( this.name);
}.bind( this));
};

可见,我们可以直接改匿名函数执行bind,然后在将其赋值给某个对象;更详细的用例参考函数回调场景 3 - 数组对象方法的回调

作为函数直接调用

    
1
2

    
var altwrite = document.write;
altwrite( "hello");

在浏览器运行这个例子,得到错误Uncaught ReferenceError: alwrite is not defined,这个错误并没有真正保留底层的原因,真正的原因是,document 对象的 write 函数再执行的时候,内部this指向了 Global Object

为了解决上述问题,我们可以bind document 对象,


    
1

    
altwrite.bind( document)( "hello")

注意这里的写法,altwrite.bind(document)返回的是一个Function,所以可以直接跟参数调用。

绑定函数参数

除了绑定this对象意外,还可以绑定函数中的参数,看如下的例子,


    
1
2
3
4
5
6
7
8
9
10
11
12
13

    
var add = function (x, y) {
return x * this.m + y * this.n;
}
var obj = {
m: 2,
n: 2
};
var newAdd = add.bind(obj, 5);
newAdd( 5);
// 20

add.bind(obj, 5);除了绑定 add 函数的this对象为 obj 以外,将其固定为 obj 以外,还绑定了 add 函数的第一个参数 x,并将其固定为 5;这样,得到的 newAdd 函数只能接收一个参数,那就是 y 了,因为 x 已经被bind绑定且固定了,所以可以看到,随后执行的语句newAdd(5)传递的实际上是 y 参数。

若绑定 null 或者 undefined

如果bind方法的第一个参数是 null 或 undefined,等于将this绑定到全局对象,函数运行时this指向 Global Object。


    
1
2
3
4
5
6
7
8
9
10
11
12

    
var name = 'windows';
function add(x, y) {
console.log( this.name);
return x + y;
}
var plus = add.bind( null, 5); // 绑定了 x 参数
> plus( 10) // 赋值的是 y 参数,于是执行的是 5 + 10
'windows'
15

改写原生方法的使用方式

首先,


    
1
2

    
> [ 1, 2, 3].push( 4)
4 // 输出新增后数组的长度

等价于


    
1

    
Array.prototype.push.call([ 1, 2, 3], 4)

第一个参数 [1, 2, 3] 绑定 push 函数的this关键字,第二个参数 4,是需要被添加的值。

补充一下

为什么说这里是等价的?我们来解读一下


    
1
2

    
> [ 1, 2, 3].push( 4)
4 // 输出新增后数组的长度

的执行过程,[1, 2, 3] 作为数组对象,调用其原型中的 Array.prototype.push 方法,很明显,采用的是关联调用,因此 push 函数内部的 this 指向的是数组对象 [1, 2, 3];而这里,我们通过


    
1

    
Array.prototype.push.call([ 1, 2, 3], 4)

这样的调用方式,只是换汤不换药,同样是执行的数组中的原型方法 push,只是this的传递方式不同而已,这里是通过bind直接将this赋值为数组对象 [1, 2, 3],而不是通过之前的关联调用;所以,两种调用方式是等价的。

补充完毕

再次,

call 方法调用的是 Function 对象的原型方法既 Function.prototype.call(…),那么我们再来将它 bind 一下,看看会有什么结果


    
1
2
3
4
5
6
7
8
9
10
11
12

    
> var push = Function.prototype.call.bind( Array.prototype.push);
> push([ 1, 2, 3], 4);
4 // 返回数组长度
// 或者写为
> var a = [ 1, 2, 3];
> push(a, 4);
4
> a
[ 1, 2, 3, 4]

我们得到了一个具备数组 push 操作的一个新的函数 push(…) ( 注: bind 每次回返回一个新的函数 );

那是为什么呢?

可以看到,背后的核心是,


    
1

    
push([ 1, 2, 3], 4);

等价于执行


    
1

    
Array.prototype.push.call([1, 2, 3], 4)

所以,我们得证明Function.prototype.call.bind(Array.prototype.push)([1, 2, 3], 4)与Array.prototype.push.call([1, 2, 3], 4)两个函数的执行过程是等价的( 注意,为什么比较的是执行过程等价,因为call函数是立即执行的,而bind返回的是一个函数引用,所以必须比较两者的执行过程 );其实,要证明这个问题,最直接方法就是去查看函数Function.prototype.call的源码,可惜,我在官网 MDN Function.prototype.call() 上面也没有看到源码;那么这里,其实可以做一些推理,

Function.prototype.call.bind(Array.prototype.push)([1, 2, 3], 4)

通过bind,这里返回一个新的 call 函数,该函数绑定了 Array.prototype.push Function 对象做为其this对象;那么Function.prototype.call函数内部会怎么执行呢?我猜想应该就是执行this.apply(context, params)之类的,this表示的是 Array.prototype.push,context表示的既是这里的数组对象 [1, 2, 3], params表示的既是这里的参数 4

Array.prototype.push.call([1, 2, 3], 4)
同理,由上述Function.prototype.call函数内部的执行过程是执行this.apply(context, params)的推断来看,this依然是指向的 Array.prototype.push,context表示的既是这里的数组对象 [1, 2, 3], params表示的既是这里的参数 4;所以,这里的调用方式与 Function.prototype.call.bind(Array.prototype.push)([1, 2, 3], 4) 的方式等价;所以,我们得出如下结论,
Array.prototype.push.call([1, 2, 3], 4) <=> Function.prototype.call.bind(Array.prototype.push)([1, 2, 3], 4) <=> push([1, 2, 3], 4)

使用 bind 的一些注意事项
每次返回一个新函数

bind方法每运行一次,就返回一个新函数,这会产生一些问题。比如,监听事件的时候,不能写成下面这样。


    
1

    
element.addEventListener( 'click', o.m.bind(o));

上面代码中,click 事件绑定bind方法新生成的一个匿名函数。这样会导致无法取消绑定,所以,下面的代码是无效的。


    
1

    
element.removeEventListener( 'click', o.m.bind(o));

正确的方法是写成下面这样,使得 add 和 remove 使用的是同一个函数的引用。


    
1
2
3
4

    
var listener = o.m.bind(o);
element.addEventListener( 'click', listener);
// ...
element.removeEventListener( 'click', listener);

use strict

使用严格模式,该部分可以参考阮一峰的教程严格模式,说得非常详细;不过应用到面向对象编程里面,主要就是为了避免this在运行期动态指向 Global Object,如果发生这类的情况,报错;例如


    
1
2
3
4
5
6

    
function f() {
'use strict';
this.a = 1;
};
f(); // 报错,this未定义

当执行过程中,发现函数 f 中的this指向了 Global Object,则报错。

构造函数中的 this

this -> Object.prototype instance

构造函数比较特别,javascript 解析过程不同于其它普通函数;

假如我们有如下的构造函数,


    
1
2
3
4

    
var Person = function(name, age){
this.name = name;
this.age = age;
}

当 javascript 语法解析器解析到如下语句以后,


    
1

    
var p = new Person( '张三', 35);

实际上执行的是,


    
1
2
3
4
5
6
7
8
9
10
11
12

    
function new( /* 构造函数 */ constructor, /* 构造函数参数 */ param1 ) {
// 将 arguments 对象转为数组
var args = [].slice.call( arguments);
// 取出构造函数
var constructor = args.shift();
// 创建一个空对象,继承构造函数的 prototype 属性
var context = Object.create( constructor.prototype);
// 执行构造函数
var result = constructor.apply(context, args);
// 如果返回结果是对象,就直接返回,则返回 context 对象
return (typeof result === 'object' && result != null) ? result : context;
}

备注:arguments 可表示一个函数中所有的参数,也就是一个函数所有参数的结合。

下面,我们一步一步的来分析该构造函数的实现,弄清楚this指的是什么,

constructor

就是 Person 构造函数,

context

var context = Object.create(constructor.prototype);通过 constructor.prototype 创建了一个新的对象,也就是 Person.prototype 的一个实例 Person.prototype isntance;

constructor.apply(context, args);

注意,这步非常关键,context 作为 constructor 构造函数的this,所以


    
1
2
3
4

    
var Person = function(name, age){
this.name = name;
this.age = age;
}

中的this在执行过程中指向的实际上就是该 context 对象。

result

是constructor.apply(context, args);方法调用的返回值,我们当前用例中,Person 构造函数并没有返回任何东西,所以,这里是 null。

return (typeof result === ‘object’ && result != null) ? result : context;

new方法的最后返回值,如果 result 不为 null,则返回 result 否则返回的是 context;我们这个用例,当初始化构造函数完成以后,返回的是 context既 Person.prototype instance,也就是构造函数中的this指针;这也是大多数构造函数应用的场景。

Object.prototype instance -> Object.prototype


    
1
2
3
4
5
6
7

    
var Obj = function (p) {
this.p = p;
};
Obj.prototype.m = function() {
return this.p;
};

执行,


    
1
2
3
4
5
6
7

    
> var o = new Obj( 'Hello World!');
> o.p
'Hello World!'
> o.m()
'Hello World!'

说实话,当我第一次看到这个例子的时候,o.p 还好理解,o 就是表示构造函数 Obj 内部的this对象,是一个通过 Object.create(Obj.prototype) 得到的一份 Obj.prototype 的实例对象;但是,当我看到 o.m 的时候,还是有点懵逼,Obj.prototype 并不是代表的this呀,Object.create(Obj.prototype)才是( 既 Obj.prototype instance ),所以在 Obj.prototype 上定义的 m 方法,怎么可以通过 o.m() 既通过 Obj.prototype instance 来调用呢?( 注意,关系 o-> Object.create(Obj.prototype) -> Obj.prototype instance -> this != Obj.prototype ) 当理解到 prototype 的涵义有,才知道,Obj.prototype instance 会继承 Obj.prototype 中的公共属性的,所以,这里通过 Obj.prototype 对象定义的 m 函数可以通过 Object.prototype instance 进行调用。

CCS软件仿真插件移植避坑指南:从CCS5.5到7.4的完整流程 本文详细介绍了将CCS软件仿真插件从CCS5.5迁移至CCS7.4的完整流程与避坑指南。针对TMS320F28335等C2000系列芯片,重点解析了仿真核心文件、配置数据库、连接定义及驱动文件的移植步骤,并通过两个实战Demo验证了移植后仿真环境的可用性,帮助开发者在无硬件条件下继续高效进行算法验证与调试。 阅读详情

相关推荐

android技术--------如何解决弹出键盘只影响当前fragment输入的窗口

题目比较长,也可能有很多人不明所以这东西有啥可写的,简单来说就是实现近似于Android Pad 上QQHD聊天的界面效果,搞了很长的事件,今天终于搞定。 android3.0新加了组件Fragment称之为碎片,对这个一无所知的请自觉补习。今天的阐述将基于此组件。 fragment可以放入activity中,并随activity的生命周期变化而变化,但当相应键盘事件的时候,就比较让人挠头了,

3506

DiskGenius帮你恢复系统无法识别的U盘数据

DiskGenius帮你恢复系统无法识别的U盘数据

梦凝哲雪 1万+

CS231n+assignment2(一)

cs231n作业二详解+代码

qq_56551150的博客 2411

U盘出问题怎么修

1.准备一个不要钱的diskgenius。问题描述电脑识别不到U盘

m0_72532016的博客 2754

STC8单片机低功耗延时优化实践:从循环等待到定时唤醒

本文深入探讨了STC8单片机低功耗延时优化的核心方法,即利用掉电唤醒定时器(WKT)替代传统的循环等待,实现从“空转耗电”到“定时唤醒”的转变。通过实战代码演示了如何编写低功耗延时函数,将待机电流从mA级降至μA级,并分析了精度、唤醒时间等关键权衡点,为电池供电的物联网设备提供了有效的续航优化方案。

pea55的博客 324

uniapp原生安卓插件开发之路

声明: 我的功能需求:全局悬浮框,也就是在其他应用上依旧能悬浮。查遍uni文档,无法实现,甚至native.js也无法实现,无奈只能走上原生插件开发这条不归路了。(吐槽:天天写前端js,突然写了下android简直要炸,public static String啥的,var不香嘛,不过别人严谨肯定还是有别人道理的,加油吧,争取能入个门!) 本人不懂安卓开发,完全安卓小白,所以安卓方面可能表达的不是很好,安卓代码可能写的不优美,但我功能最终还是实现了。但是为了需求,能力总能被“逼”出来。有描述错误的还望指正

Jone's Area 1万+

台电睿动U盘修复记录

使用DiskGenius5.5检查U盘,可以显示U盘盘符、厂商、型号、容量等基本信息,但无法看到分区,由于U盘中有数据,第一步要做的是将数据读取出来。2、U盘量产最主要的是找到主控型号,不得已只能开盘查看,但是,知道型号依然有可能找不到合适的量产工具,这一点只能依赖互联网资源了。1、数据恢复是最重要的,如果是重要数据,还是应该找专业厂家来处理,他们有更专业的工具和软件,数据恢复的可靠性更有保障。U盘的故障现象为只能识别盘符,但无法显示容量,点击后显示需要格式化,点击格式化,报错并提示无法进行格式化。

peterlu126的博客 2519

Cadence Virtuoso ADE L保姆级教程:从DC到Noise,一次搞定6种仿真设置

本文提供Cadence Virtuoso ADE L的全面仿真教程,涵盖DC工作点分析、瞬态响应、AC特性、零极点分布和噪声性能等6种仿真设置。通过详细的步骤和实用技巧,帮助工程师高效完成从基础配置到高级后处理的完整工作流,显著提升IC设计效率。

weixin_26841743的博客 595

用 DiskGenius 修复 U 盘

最近,为了检测U盘是否为扩容盘,不小心把U盘弄坏,不能格式化,windows系统不能识别U盘. 怀疑是引导区坏了;找了很多工具,试了很多方法,在 win7 下均失败。   所幸,找到 DiskGenius 将问题解决,   恢复主引导区 如果主引导区恢复失败,可以试着 清楚扇区数据和保留扇区后,再进行重建主引导区。 快速分区 最后格式化 即修复完毕 ...

DGenerationX的专栏 1万+

鸿蒙接入微信支付 微信H5网页在鸿蒙系统的适配方法(含支付兼容方案) 微信H5适配鸿蒙系统 | ArkWeb识别 | 鸿蒙浏览器UA | 微信支付鸿蒙适配 | 鸿蒙微信判断方式

微信H5网页鸿蒙系统适配指南 针对微信H5网页在鸿蒙系统出现的兼容问题,本文提供完整适配方案。关键点包括: 环境识别:

代码简单说 Vue、JAVA、PHP、Node.js 熟练运用,接口、架构、性能全搞定。 1300

海康威视iVMS-8700综合安防管理平台 文件读取漏洞复现

HIKVISION iVMS 综合安防管理平台download接口处存在任意文件读取漏洞,攻击者通过构造token绕过身份认证,读取服务器中的目录信息与敏感文件。使系统处于极不安全的状态。

0xSec 3304

modern-js-cheatsheet this关键字:JavaScript中最令人困惑的概念终极指南

JavaScript中的`this`关键字无疑是学习过程中最令人困惑的概念之一,它与其他编程语言中的行为完全不同。在本文中,我们将深入探讨`this`关键字的工作原理,帮助你彻底理解这个现代JavaScript开发中至关重要的概念。😊 ## 为什么`this`如此令人困惑? `this`关键字的行为取决于函数被调用的方式,而不是函数定义的位置。这种动态绑定的特性让许多开发者感到头疼,但一旦掌

gitblog_00867的博客 484

基于YOLOv8的建筑材料分类系统:从数据集构建到UI界面实现

建筑材料分类数据集需要包含各种常见建筑材料的图像,并对每种材料进行标注。砖块(Brick)混凝土(Concrete)钢筋(Rebar)木材(Wood)瓷砖(Tile)玻璃(Glass)钢材(Steel)塑料(Plastic)石膏板(Drywall)绝缘材料(Insulation)如果没有找到合适的公开数据集,可以构建自己的数据集。使用智能手机或专业相机拍摄各种建筑材料的照片确保在不同光照条件、角度和背景下拍摄每类材料至少收集300-500张图像。

YOLO 488

掌握JavaScript中的this关键字:33-js-concepts中的call、apply与bind

在JavaScript开发中,`this`关键字、`call`、`apply`和`bind`是每个开发者必须掌握的核心概念。它们不仅影响函数的执行上下文,还在面向对象编程和函数式编程中扮演着关键角色。本文将结合33-js-concepts项目中的知识点,详细解析这些概念的工作原理和实际应用场景。 ## 什么是this关键字? `this`是JavaScript中的一个特殊关键字,它指向函数执行

gitblog_00714的博客 438

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

JavaScript31:this关键字

JavaScript this 关键字 面向对象语言中 this 表示当前对象的一个引用。 但在 JavaScript 中 this 不是固定不变的,它会随着执行环境的改变而改变。 在方法中,this 表示该方法所属的对象。 如果单独使用,this 表示全局对象。 在函数中,this 表示全局对象。 在函数中,在严格模式下,this 是未定义的(undefined)。 在事件中,this 表示接收事件的元素。 类似 call() 和 apply() 方法可以将 this 引用到任何对象。 对

menglong0329的博客 243

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

JavaScript实战:this关键字

JavaScript this 关键字 面向对象语言中 this 表示当前对象的一个引用。 但在 JavaScript 中 this 不是固定不变的,它会随着执行环境的改变而改变。 在方法中,this 表示该方法所属的对象。 如果单独使用,this 表示全局对象。 在函数中,this 表示全局对象。 在函数中,在严格模式下,this 是未定义的(undefined)。 在事件中,this 表示接收事件的元素。 类似 call() 和 apply() 方法可以将 this 引用到任何对象。 事

wwwkm123的博客 165

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

JavaScript特性三:this关键字

一,全局变量和全局函数属于全局对象,故通过this关键字调用全局变量或在全局函数中使用this关键字时,this关键字指向全局对象 二,构造函数中的this关键字指向新创建的对象 三,对象方法中的this关键字指向调用这个方法的对象 四,事件处理程序中的this关键字需要分两种情况讨论 1,如果使用硬编码,this关键字指向发生事件的对象 2,如果使用函数调用方式,this关键字指向全局对象

怀揣梦想,努力前行 1045

JS:this关键字

this使用全景,常规绑定 this 的方法

weixin_43932309的博客 201

Javascript :this关键字 详解

箭头函数相信大家在日常开发中用到的地方非常之多,因为它很简洁,可读性强,但是它最大的好处,其实是解决了匿名函数的this指向问题,有利于封装回调函数。 var name = 'window'; // 其实是window.name = 'window' var A = { name: 'A', sayHello: function(){ console.log(thi...

鸡血博客 196

【JavaScript】this关键字:绑定this的方法

JavaScript 提供了call、apply、bind这三个方法,来切换/固定this的指向。

weixin_45980065的博客 1126

Javascript:this关键字

this this 是 JavaScript 语言的一个关键字。它是函数运行时,在函数体内部自动生成的一个对象,只能在函数体内部使用。 function test() { this.x = 1; } this 的多种指向: 在 JavaScript 中 this 不是固定不变的,它会随着执行环境的改变而改变。 在对象方法中, this 指向该方法所属的对象。 单独使用 this, this指向全局对象。 函数使用中,this 指向函数的所属者。 严格模式下函数是没有绑定到 t..

q1472750149的博客 306

Javascript知识点详解:this关键字的指向问题

在JavaScript中,指向问题通常指的是关于this关键字的使用。this关键字指向当前执行代码的上下文对象。 在全局作用域中,this指向全局对象(通常是window对象)。 在函数中,this的值取决于函数的调用方式: 如果函数作为对象的方法被调用,this指向该对象。 如果函数作为普通函数被调用,this指向全局对象(在严格模式下为undefined)。 如果函数作为构造函数被调用,this指向新创建的对象。 如果使用call、apply或bind方法显式地改变this的指向。

qq_68163788的博客 3160

JS基础:this关键字详解

首先,我们需要搞清楚在JS里面,函数的几种调用方式: 普通函数调用 作为方法来调用 作为构造函数来调用 使用apply/call方法来调用 Function.prototype.bind方法 es6箭头函数 但是不管函数是按哪种方法来调用的,请记住一点:谁调用这个函数或方法,this关键字就指向谁 接下来就分情况来讨论下这些不同的情况: ...

weixin_41350225的博客 250

转:js中this关键字详解

this指向哪里? 一般而言,在Javascript中,this指向函数执行时的当前对象。 In JavaScript, as in most object-oriented programming languages,thisis a special keyword that is used within methods to refer to the object on ...

aaa2549769750的博客 104

JavaScript:一文搞懂this关键字指向

在JS中,this的指向会随着执行的环境不同,从而指向不同的对象,本文就是对this关键字指向来一波总结,以便那些,对于this指向比较迷的前端人 目录 1. 单独使用,this表示全局对象 1.1 即使在严格模式下,this也依然表示全局对象 2. 在function函数中 2.1 常规模式 2.2 严格模式 3. 在对象的属性方法中,指向当前所处的对象 3.1 常规模式 3.2 严格模式 4. 在html中,指向接收事件的html元素 总结 1. 单独使用,this表示全局.

本以为成功很简单,没想到活成普通人都需拼尽全力! 908
上一篇: Spring Core Container 源码分析七:注册 Bean Definitions
下一篇: Macbook 开发之器,Mac OSX 应用软件,开发工具必备网站推荐
伤神v
博客等级 码龄19年 17粉丝 129原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值