JavaScript 的 this 原理

时间:2019-09-24 18:24来源:关于计算机
JavaScript 的 this 原理 2018/06/19 · JavaScript· this 初稿出处:阮一峰    1.含义 一、难点的因由 学懂 JavaScript 语言,二个申明正是知道上面三种写法,只怕有差异的结果。 var obj = { foo: fu

JavaScript 的 this 原理

2018/06/19 · JavaScript · this

初稿出处: 阮一峰   

1.含义

一、难点的因由

学懂 JavaScript 语言,二个申明正是知道上面三种写法,只怕有差异的结果。

var obj = { foo: function () {} }; var foo = obj.foo; // 写法一 obj.foo() // 写法二 foo()

1
2
3
4
5
6
7
8
9
10
11
var obj = {
  foo: function () {}
};
 
var foo = obj.foo;
 
// 写法一
obj.foo()
 
// 写法二
foo()

上边代码中,即便obj.foofoo针对同四个函数,不过进行结果可能不雷同。请看上边包车型地铁例子。

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

1
2
3
4
5
6
7
8
9
10
var obj = {
  foo: function () { console.log(this.bar) },
  bar: 1
};
 
var foo = obj.foo;
var bar = 2;
 
obj.foo() // 1
foo() // 2

这种反差的来头,就在于函数体内部采纳了this最首要字。非常多教科书会报告您,this指的是函数运维时所在的情形。对于obj.foo()来说,foo运行在obj环境,所以this指向obj;对于foo()来说,foo运作在全局情形,所以this本着全局蒙受。所以,两个的周转结果不均等。

这种解释没有错,不过教科书往往不报告您,为啥会那样?也便是说,函数的运维条件到底是怎么调控的?举个例子来讲,为何obj.foo()就是在obj情形进行,而只要var foo = obj.foofoo()就改成在大局遇到实行?

正文就来声明 JavaScript 那样管理的法则。精通了那或多或少,你就能干净领略this的作用。

this关键字是一个拾分关键的语法点。首先,this总是回到二个对象,简单说,正是回来属性或措施“当前”所在的对象。

二、内部存款和储蓄器的数据结构

JavaScript 语言之所以有this的计划,跟内部存款和储蓄器里面包车型客车数据结构有涉嫌。

var obj = { foo: 5 };

1
var obj = { foo:  5 };

地方的代码将四个对象赋值给变量obj。JavaScript 引擎会先在内部存款和储蓄器里面,生成一个目的{ foo: 5 },然后把这些指标的内部存款和储蓄器地址赋值给变量obj

永利皇宫463手机版 1

也正是说,变量obj是贰个地点(reference)。前边假若要读取obj.foo,引擎先从obj获得内部存款和储蓄器地址,然后再从该地址读出原始的靶子,重返它的foo属性。

原来的靶子以字典结构保留,每叁特性质名都对应三性情质描述对象。比如来讲,上边例子的foo属性,实际上是以上边包车型地铁样式保留的。

永利皇宫463手机版 2

{ foo: { [[value]]: 5 [[writable]]: true [[enumerable]]: true [[configurable]]: true } }

1
2
3
4
5
6
7
8
{
  foo: {
    [[value]]: 5
    [[writable]]: true
    [[enumerable]]: true
    [[configurable]]: true
  }
}

注意,foo天性的值保存在属性描述对象的value个性之中。

this.property // this就意味着property属性当前所在的对象。

三、函数

如此的协会是很显著的,难题在于属性的值大概是三个函数。

var obj = { foo: function () {} };

1
var obj = { foo: function () {} };

那时,引擎会将函数单独保存在内存中,然后再将函数的地点赋值给foo属性的value属性。

永利皇宫463手机版 3

{ foo: { [[value]]: 函数的地址 ... } }

1
2
3
4
5
6
{
  foo: {
    [[value]]: 函数的地址
    ...
  }
}

鉴于函数是贰个单独的值,所以它能够在差别的条件(上下文)施行。

var f = function () {}; var obj = { f: f }; // 单独实施 f() // obj 景况实行 obj.f()

1
2
3
4
5
6
7
8
var f = function () {};
var obj = { f: f };
 
// 单独执行
f()
 
// obj 环境执行
obj.f()

var person = {

四、景况变量

JavaScript 允许在函数体内部,援用当前条件的其它变量。

var f = function () { console.log(x); };

1
2
3
var f = function () {
  console.log(x);
};

地点代码中,函数体里面使用了变量x。该变量由运维意况提供。

于今主题素材就来了,由于函数能够在差异的周转条件进行,所以供给有一种体制,能够在函数体内部获得当前的运营条件(context)。所以,this就涌出了,它的布署目标就是在函数体内部,指代函数当前的运作境遇。

var f = function () { console.log(this.x); }

1
2
3
var f = function () {
  console.log(this.x);
}

上边代码中,函数体里面包车型地铁this.x便是指当前运作条件的x

var f = function () { console.log(this.x); } var x = 1; var obj = { f: f, x: 2, }; // 单独执行 f() // 1 // obj 景况进行 obj.f() // 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var f = function () {
  console.log(this.x);
}
 
var x = 1;
var obj = {
  f: f,
  x: 2,
};
 
// 单独执行
f() // 1
 
// obj 环境执行
obj.f() // 2

地方代码中,函数f在大局情况进行,this.x本着全局景况的x

永利皇宫463手机版 4

obj条件进行,this.x指向obj.x

永利皇宫463手机版 5

归来本文最早建议的标题,obj.foo()是通过obj找到foo,所以就是在obj条件举行。一旦var foo = obj.foo,变量foo就径直指向函数本人,所以foo()就成为在全局情况举行。

1 赞 4 收藏 评论

永利皇宫463手机版 6

name: '张三',

describe: function () {

return '姓名:'+ this.name;

}

};

 

person.describe()

// "姓名:张三"

地点代码中,this.name表示describe方法所在的脚下目的的name属性。调用person.describe方法时,describe方法所在的如今目的是person,所以正是调用person.name。

出于指标的性质可以赋给另二个指标,所以属性所在的此时此刻目的是可变的,即this的针对是可变的。

var A = {

name: '张三',

describe: function () {

return '姓名:'+ this.name;

}

};

 

var B = {

name: '李四'

};

 

B.describe = A.describe;

B.describe()

// "姓名:李四"

地点代码中,A.describe属性被赋给B,于是B.describe就表示describe方法所在的此时此刻指标是B,所以this.name就对准B.name

function f() {

return '姓名:'+ this.name;

}

 

var A = {

name: '张三',

describe: f

};

 

var B = {

name: '李四',

describe: f

};

 

A.describe() // "姓名:张三"

B.describe() // "姓名:李四"

上边代码中,函数f内部使用了this关键字,随着f所在的指标差异,this的针对性也分化。

只要函数被赋给另三个变量,this的针对性就能够变。

var A = {

name: '张三',

describe: function () {

return '姓名:'+ this.name;

}

};

永利皇宫463手机版, 

var name = '李四';

var f = A.describe;

f() // "姓名:李四"

地方代码中,A.describe被赋值给变量f,内部的this就能够指向f运营时所在的目的(本例是顶层对象)。

能够相近地以为,this是兼具函数运维时的叁个藏匿参数,指向函数的运维情况。

 

 

 

2.选择场面

1)全局景况

在全局情况使用this,它指的正是顶层对象window。

this === window // true

function f() {

console.log(this === window); // true

}

2)构造函数

构造函数中的this,指的是实例对象。

var Obj = function (p) {

this.p = p;

};

Obj.prototype.m = function() {

return this.p;

};

地点代码定义了四个结构函数Obj。由于this指向实例对象,所以在构造函数内部定义this.p,就相当于概念实例对象有贰个p属性;然后m方法可以回到那么些p属性。

var o = new Obj('Hello World!');

o.p // "Hello World!"

o.m() // "Hello World!"

3)对象的章程

当 A 对象的方式被赋予 B 对象,该措施中的this就从指向 A 对象形成了指向 B 对象。所以要极度当心,将有个别对象的办法赋值给另贰个目标,会变动this的针对性。

var obj ={

foo: function () {

console.log(this);

}

};

obj.foo() // obj

地方代码中,obj.foo方法施行时,它其中的this指向obj。

然则,唯有这一种用法(直接在obj对象上调用foo方法),this指向obj;别的用法时,this都指向代码块当前各省对象(浏览器为window对象)。

// 情况一

(obj.foo = obj.foo)() // window

 

// 情况二

(false || obj.foo)() // window

 

// 情况三

(1, obj.foo)() // window

地点代码中,obj.foo先运算再实践,固然值根本未有调换,this也不再指向obj了。那是因为那时候它就退出了运营情形obj,而是在大局情状实行。

能够这么敞亮,在 JavaScript 引擎内部,obj和obj.foo储存在八个内存地址,简称为M1和M2。唯有obj.foo()那样调用时,是从M1调用M2,因而this指向obj。可是,上面两种状态,都以一向抽出M2举办演算,然后就在全局情状实行运算结果(依旧M2),由此this指向全局碰着。

地点二种情形一致下边包车型大巴代码。

// 情况一

(obj.foo = function () {

console.log(this);

})()

// 等同于

(function () {

console.log(this);

})()

 

// 情况二

(false || function () {

console.log(this);

})()

 

// 情况三

(1, function () {

console.log(this);

})()

要是有个别方法位于多层对象的中间,那时this只是指向当前一层的靶子,而不会持续更下面的层。

var a = {

p: 'Hello',

b: {

m: function() {

console.log(this.p);

}

}

};

a.b.m() // undefined

地点代码中,a.b.m方法在a对象的第二层,该方法内部的this不是指向a,而是指向a.b。这是因为实在执行的是上边的代码。

var b = {

m: function() {

console.log(this.p);

};

 

var a = {

p: 'Hello',

b: b

};

(a.b).m() // 等同于 b.m()

若果要完毕预期功效,独有写成下边那样。

var a = {

b: {

m: function() {

console.log(this.p);

},

p: 'Hello'

}

};

 

 

 

3.施用注意点

1)防止多层 this

由于this的针对是不分明的,所以切勿在函数中含有多层的this。

var o = {

f1: function () {

console.log(this);

var f2 = function () {

console.log(this);

}();

}

}

 

o.f1()

// Object

// Window

八个消除办法是在其次层改用三个对准外层this的变量。

var o = {

f1: function() {

console.log(this);

var that = this;

var f2 = function() {

console.log(that);

}();

}

}

 

o.f1()

// Object

// Object

2)幸免数组管理方法中的this

数组的map和foreach方法,允许提供二个函数作为参数。这么些函数内部不应当利用this。

var o = {

v: 'hello',

p: [ 'a1', 'a2' ],

f: function f() {

this.p.forEach(function (item) {

console.log(this.v + ' ' + item);

});

}

}

 

o.f()

// undefined a1

// undefined a2

一种是是在第二层改用多少个针对外层this的变量。如上3.1

另一种方法是将this当作foreach方法的第三个参数,固定它的运营条件。

3)制止回调函数中的this

4.绑定 this 的方法

this的动态切换,就算为JavaScript创制了光辉的八面后珑,但也使得编制程序变得紧Baba和歪曲。不经常,必要把this固定下来,幸免出现意想不到的景况。JavaScript提供了call、apply、bind那多少个主意,来切换/固定this的针对。

1)function.prototype.call()

函数实例的call方法,可以钦定函数内部this的对准(即函数奉行时所在的作用域),然后在所钦点的机能域中,调用该函数

var obj = {};

var f = function () {

return this;

};

f() === this // true

f.call(obj) === obj // true

地点代码中,在大局遭逢运营函数f时,this指向全局情状;call方法能够转移this的对准,钦定this指向对象obj,然后在对象obj的功用域中运作函数f。

call方法的参数,应该是三个目的。假若参数为空、null和undefined,则暗许传入全局对象。

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

上边代码中,a函数中的this关键字,假如指向全局对象,再次回到结果为123。若是使用call方法将this关键字指向obj对象,再次来到结果为456。可以看出,倘使call方法未有参数,也许参数为null或undefined,则一点差别也没有于指向全局对象。

call方法还足以承受多个参数。

call的率先个参数就是this所要指向的不行目的,前边的参数则是函数调用时所需的参数。

function add(a, b) {

return a + b;

}

add.call(this, 1, 2) // 3

2)function.prototype.apply()

apply方法的成效与call方法类似,也是改换this指向,然后再调用该函数。独一的区分就是,它接受贰个数组作为函数试行时的参数,使用格式如下。

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

apply方法的率先个参数也是this所要指向的不行目的,借使设为null或undefined,则等同钦点全局对象。第二个参数则是多个数组,该数组的装有成员相继作为参数,传入原函数。原函数的参数,在call方法中必得二个个丰富,但是在apply方法中,必得以数组情势足够。

function f(x,y){

console.log(x+y);

}

f.call(null,1,1) // 2

f.apply(null,[1,1]) // 2

风趣的施用

1)找寻数组最大因素

var a = [10, 2, 4, 15, 9];

Math.max.apply(null, a)

// 15

2)将数组的空成分变为undefined

透过apply方法,利用Array构造函数将数组的空成分形成undefined。

Array.apply(null, ["a",,"b"])

// [ 'a', undefined, 'b' ]

空成分与undefined的差别在于,数组的forEach方法会跳过空元素,然则不会跳过undefined。由此,遍历内部因素的时候,会得到差异的结果。

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)调换类似数组的对象

除此以外,利用数组对象的slice方法,能够将二个看似数组的指标(举个例子arguments对象)转为真正的数组。

Array.prototype.slice.apply({0:1,length:1})

// [1]

Array.prototype.slice.apply({0:1})

// []

Array.prototype.slice.apply({0:1,length:2})

// [1, undefined]

Array.prototype.slice.apply({length:1})

// [undefined]

上面代码的apply方法的参数都是指标,可是回到结果都是数组,那就起到了将对象转成数组的指标。从地方代码能够观察,这一个办法起成效的前提是,被拍卖的靶子必得有length属性,以及相对应的数字键。

function.prototype.bind()

bind方法用于将函数体内的this绑定到有些对象,然后回来二个新函数。

var d = new Date();

d.getTime() // 1481869925657

var print = d.getTime;

print() // Uncaught TypeError: this is not a Date object.

上边代码中,我们将d.getTime方法赋给变量print,然后调用print就报错了。那是因为getTime方法内部的this,绑定Date对象的实例,赋给变量print现在,内部的this已经不指向Date对象的实例了。

bind方法能够消除那个标题,让log方法绑定console对象。

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

print() // 1481869925657

地点代码中,bind方法将getTime方法内部的this绑定到d对象,那时就足以高枕而卧地将以此办法赋值给其余变量了。

bind比call方法和apply方法更进一竿的是,除了绑定this以外,还是能够绑定原函数的参数。

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

上边代码中,bind方法除了绑定this对象,还将add函数的率先个参数x绑定成5,然后回到一个新函数newAdd,那一个函数只要再承受一个参数y就会运转了。

一旦bind方法的第二个参数是null或undefined,等于将this绑定到全局对象,函数运转时this指向顶层对象(在浏览器中为window)。

function add(x, y) {

return x + y;

}

var plus5 = add.bind(null, 5);

plus5(10) // 15

bind方法有部分施用注意点。

1)每三回回到一个新函数

2)结合回调函数使用

回调函数是JavaScript最常用的格局之一,但是二个广大的荒谬是,将包括this的章程直接当做回调函数。

var counter = {

count: 0,

inc: function () {

'use strict';

this.count++;

}

};

function callIt(callback) {

callback();

}

callIt(counter.inc)

// TypeError: Cannot read property 'count' of undefined

地点代码中,counter.inc方法被用作回调函数,传入了callIt,调用时其内部的this指向callIt运维时所在的对象,即顶层对象window,所以得不到预想结果。注意,上边的counter.inc方法内部使用了凶横方式,在该形式下,this指向顶层对象时会报错,一般情势不会。

缓慢解决形式就是使用bind方法,将counter.inc绑定counter。

callIt(counter.inc.bind(counter));

counter.count // 1

var obj = {

name: '张三',

times: [1, 2, 3],

print: function () {

this.times.forEach(function (n) {

console.log(this.name);

});

}

};

 

obj.print()

// 未有其他输出

obj.print = function () {

this.times.forEach(function (n) {

console.log(this.name);

}.bind(this));

};

 

obj.print()

// 张三

// 张三

// 张三

 

编辑:关于计算机 本文来源:JavaScript 的 this 原理

关键词:

  • 上一篇:没有了
  • 下一篇:没有了