this的工作原理

this是JavaScript中的一个关键字。它代表函数运行时,自动生成的一个内部对象,只能在函数内部使用。比如:

一、问题的由来

如果一个函数被作为一个对象的方法调用,那么this将被指派为这个对象。

function  a(){
     this.x  = 1;
}

学懂 JavaScript 语言,一个标志就是理解下面两种写法,可能有不一样的结果。

复制代码 代码如下:

金沙国际官网,随着函数使用场合的不同,this的值会发生变化,所以要分析this所在的函数是当做哪个对象的方法调用的,则该对象就是this所引用的对象。
this的使用一般有以下四种情况:

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

var parent = {
    method: function () {
        console.log(this);
    }
};

普通函数调用

普通函数调用属于全局性调用,this就代表了全局变量。
比如:

const x = 1;
function a(){
     console.log(this.x);
}
a(); // 输出为1

此时的this指的就是全局变量global,最后一句其实就等价于

window.a();//输出为1

上面代码中,虽然obj.foo和foo指向同一个函数,但是执行结果可能不一样。请看下面的例子。

parent.method();
// <- parent

对象的方法调用

在 JavaScript
中,函数也是对象,因此函数可以作为一个对象的属性,此时该函数被称为该对象的方法,在使用这种调用方式时,this
被自然绑定到该对象。

const a = { 
 x : 1,
 y : function(x) { 
     console.log(this.x);
     } 
 }; 
 a.y(0); // 输出1,this绑定到当前对象,即a对象

在这段代码中,function(x){}这个函数就是a对象的一个方法,所以此时的this就是a对象。

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

注意这种行为非常“脆弱”,如果你获取一个方法的引用并且调用,那么this的值不会是parent了,而是window全局对象。这让大多数开发者迷惑。

构造函数调用

JavaScript
支持面向对象式编程,与主流的面向对象式编程语言不同,JavaScript
并没有类的概念,而是使用原型继承方式。相应的,JavaScript
中的构造函数也很特殊,如果不使用 new
调用,则和普通函数一样。一般构造函数以大写字母开头,如果调用正确,this
绑定到新创建的对象上。

function a(){
  this.x = 1;
}
const b = new a();
console.log(b.x); //输出1

这种差异的原因,就在于函数体内部使用了this关键字。很多教科书会告诉你,this指的是函数运行时所在的环境。对于obj.foo()来说,foo运行在obj环境,所以this指向obj;对于foo()来说,foo运行在全局环境,所以this指向全局环境。所以,两者的运行结果不一样。

复制代码 代码如下:

apply调用

apply()是函数对象的一个方法,它的作用是改变函数的调用对象,它的第一个参数就表示改变后的调用这个函数的对象。所以this指的就是这第一个参数。

const x = 0;
const a = { 
 x : 1,
 y : function() { 
     console.log(this.x);
     } 
 }; 
a.y.apply(); // 输出0

apply()的参数为空时,默认调用全局对象。因此,这时的运行结果为0,证明this指的是全局对象。
若将给apply()传入参数,即

a.y.apply(a); // 输出1

则表明此时的this指的是对象a

这种解释没错,但是教科书往往不告诉你,为什么会这样?也就是说,函数的运行环境到底是怎么决定的?举例来说,为什么obj.foo()就是在obj环境执行,而一旦var foo = obj.foo,foo()就变成在全局环境执行?

ThisClownCar();
// <- Window
 

本文就来解释 JavaScript
这样处理的原理。理解了这一点,你就会彻底理解this的作用。

改动this

二、内存的数据结构

.call、 .apply 和.bind
方法用来操作调用函数的方式,帮我们定义this的值和传递给函数的参数值。

JavaScript 语言之所以有this的设计,跟内存里面的数据结构有关系。

Function.prototype.call
可以有任意数量的参数,第一个参数被分配给this,剩下的被传递给调用函数。

var obj = { foo: 5 };

复制代码 代码如下:

上面的代码将一个对象赋值给变量obj。JavaScript
引擎会先在内存里面,生成一个对象{ foo: 5
},然后把这个对象的内存地址赋值给变量obj。

Array.prototype.slice.call([1, 2, 3], 1, 2)
// <- [2]

也就是说,变量obj是一个地址(reference)。后面如果要读取obj.foo,引擎先从obj拿到内存地址,然后再从该地址读出原始的对象,返回它的foo属性。

Function.prototype.apply
的行为和.call类似,但它传递给函数的参数是一个数组,而不是任意参数。

原始的对象以字典结构保存,每一个属性名都对应一个属性描述对象。举例来说,上面例子的foo属性,实际上是以下面的形式保存的。

String.prototype.split.apply(‘13.12.02’, [‘.’])
// <- [’13’, ’12’, ’02’]
 

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

Function.prototype.bind
创建一个特殊的函数,该函数将永远使用传递给.bind的参数作为this的值,以及能够分配部分参数,创建原函数的珂里化(curride)版本。

注意,foo属性的值保存在属性描述对象的value属性里面。

 

三、函数

复制代码 代码如下:

这样的结构是很清晰的,问题在于属性的值可能是一个函数。

var arr = [1, 2];
var add = Array.prototype.push.bind(arr, 3);

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

// effectively the same as arr.push(3)
add();

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

// effectively the same as arr.push(3, 4)
add(4);

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

console.log(arr);
// <- [1, 2, 3, 3, 4]

由于函数是一个单独的值,所以它可以在不同的环境(上下文)执行。

作用域链中的this

var f = function () {};
var obj = { f: f };
// 单独执行
f()
// obj 环境执行
obj.f()

在下面的例子,this将无法在作用域链中保持不变。这是规则的缺陷,并且常常会给业余开发者带来困惑。

四、环境变量

复制代码 代码如下:

JavaScript 允许在函数体内部,引用当前环境的其他变量。

function scoping () {
  console.log(this);

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

  return function () {
    console.log(this);
  };
}

上面代码中,函数体里面使用了变量x。该变量由运行环境提供。

scoping()();
// <- Window
// <- Window

现在问题就来了,由于函数可以在不同的运行环境执行,所以需要有一种机制,能够在函数体内部获得当前的运行环境(context)。所以,this就出现了,它的设计目的就是在函数体内部,指代函数当前的运行环境。

有一个常见的方法,创建一个局部变量保持对this的引用,并且在子作用域中不能有同命变量。子作用域中的同名变量将覆盖父作用域中对this的引用。

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

复制代码 代码如下:

上面代码中,函数f在全局环境执行,this.x指向全局环境的x。

function retaining () {
  var self = this;

在obj环境执行,this.x指向obj.x。

  return function () {
    console.log(self);
  };
}

回到本文开头提出的问题,obj.foo()是通过obj找到foo,所以就是在obj环境执行。一旦var
foo =
obj.foo,变量foo就直接指向函数本身,所以foo()就变成在全局环境执行。

retaining()();
// <- Window
 

总结

除非你真的想同时使用父作用域的this,以及当前this值,由于某些莫名其妙的原因,我更喜欢是使用的方法.bind函数。这可以用来将父作用域的this指定给子作用域。

以上所述是小编给大家介绍的JavaScript 中的 this
工作原理,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

 

您可能感兴趣的文章:

  • JavaScript中this关键词的使用技巧、工作原理以及注意事项
  • javaScript中的this示例学习详解及工作原理
  • Angular.JS中的this指向详解
  • 浅析JavaScript中var
    that=this
  • 深入理解Javascript箭头函数中的this
  • 详解JS中定时器setInterval和setTImeout的this指向问题

复制代码 代码如下:

function bound () {
  return function () {
    console.log(this);
  }.bind(this);
}

bound()();
// <- Window

如果一个函数被作为一个对象的方法调用,那么this将被指派为这个对象。
复制代码 代码如下: var parent = { method: function ()…

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图