您的位置:首页 > Web前端 > JavaScript

JS THIS 深入浅出 JavaScript 中的 this && 理解javascript函数调用和 this ** Javascript 普通函数和构造函数的区别

2017-09-29 11:25 1026 查看
//理解JS中的this非常重要,对理解闭包,看源码都有帮助,下面两篇文章非常不错,

//第一篇讲解不大懂的地方有,作为构造函数调用的部分,和作为函数调用的区别,只是构造函数名字首字母大写么?

深入浅出 JavaScript 中的 this

https://www.ibm.com/developerworks/cn/web/1207_wangqf_jsthis/

 理解javascript函数调用和 this

http://blog.csdn.net/littlechang/article/details/8180550

=》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》

王 群锋
2012 年 7 月 09 日发布

分享此页面

WeiboGoogle+用电子邮件发送本页面





11

在 Java 等面向对象的语言中,this 关键字的含义是明确且具体的,即指代当前对象。一般在编译期确定下来,或称为编译期绑定。而在 JavaScript 中,this 是动态绑定,或称为运行期绑定的,这就导致 JavaScript 中的 this 关键字有能力具备多重含义,带来灵活性的同时,也为初学者带来不少困惑。本文仅就这一问题展开讨论,阅罢本文,读者若能正确回答 JavaScript 中的 What ’s this 问题,作为作者,我就会觉得花费这么多功夫,撰写这样一篇文章是值得的。

Java 语言中的 this

在 Java 中定义类经常会使用 this 关键字,多数情况下是为了避免命名冲突,比如在下面例子的中,定义一个 Point 类,很自然的,大家会使用 x,y 为其属性或成员变量命名,在构造函数中,使用 x,y 为参数命名,相比其他的名字,比如 a,b,也更有意义。这时候就需要使用 this 来避免命名上的冲突。另一种情况是为了方便的调用其他构造函数,比如定义在 x 轴上的点,其 x 值默认为 0,使用时只要提供 y 值就可以了,我们可以为此定义一个只需传入一个参数的构造函数。无论哪种情况,this 的含义是一样的,均指当前对象。

清单 1. Point.java

JavaScript 语言中的 this

由于其运行期绑定的特性,JavaScript 中的 this 含义要丰富得多,它可以是全局对象、当前对象或者任意对象,这完全取决于函数的调用方式。JavaScript 中函数的调用有以下几种方式:作为对象方法调用,作为函数调用,作为构造函数调用,和使用 apply 或 call 调用。下面我们将按照调用方式的不同,分别讨论 this 的含义。

作为对象方法调用

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

清单 2. point.js

作为函数调用

函数也可以直接被调用,此时 this 绑定到全局对象。在浏览器中,window 就是该全局对象。比如下面的例子:函数被调用时,this 被绑定到全局对象,接下来执行赋值语句,相当于隐式的声明了一个全局变量,这显然不是调用者希望的。

清单 3. nonsense.js

对于内部函数,即声明在另外一个函数体内的函数,这种绑定到全局对象的方式会产生另外一个问题。我们仍然以前面提到的 point 对象为例,这次我们希望在 moveTo 方法内定义两个函数,分别将 x,y 坐标进行平移。结果可能出乎大家意料,不仅 point 对象没有移动,反而多出两个全局变量 x,y。

清单 4. point.js

这属于 JavaScript 的设计缺陷,正确的设计方式是内部函数的 this 应该绑定到其外层函数对应的对象上,为了规避这一设计缺陷,聪明的 JavaScript 程序员想出了变量替代的方法,约定俗成,该变量一般被命名为 that。

清单 5. point2.js

作为构造函数调用

JavaScript 支持面向对象式编程,与主流的面向对象式编程语言不同,JavaScript 并没有类(class)的概念,而是使用基于原型(prototype)的继承方式。

相应的,JavaScript 中的构造函数也很特殊,如果不使用 new 调用,则和普通函数一样,即区别在调用方式上,当用 new调用,则当做构造函数,不用 new调用,直接函数名调用,则为普通函数。

普通函数和构造函数的另一个区别为返回值,具体请参考如下文章。

作为又一项约定俗成的准则,构造函数以大写字母开头,提醒调用者使用正确的方式调用。如果调用正确,this 绑定到新创建的对象上。

清单 6. Point.js

=_____________________________________________________________________________________

** Javascript 普通函数和构造函数的区别 **

__________________________________

普通函数和构造函数的区别

在命名规则上,构造函数一般是首字母大写,普通函数遵照小驼峰式命名法。

在函数调用的时候:

function fn() { }

     构造函数:1. new fn( )

                     2 .构造函数内部会创建一个新的对象,即f的实例

                     3. 函数内部的this指向 新创建的f的实例

                     4. 默认的返回值是f的实例

     普通函数:1. fn( )

                     2. 在调用函数的内部不会创建新的对象

                     3. 函数内部的this指向调用函数的对象(如果没有对象调用,默认是window)

                     4. 返回值由return语句决定 

//构造函数
function Egperson (name,age) {
this.name = name;
this.age = age;
this.sayName = function () {
alert(this.name);
}
}
var person = new Egperson('mike','18'); //this-->person
person.sayName();  //'mike'

//普通函数
function egPerson (name,age) {
this.name = name;
this.age = age;
this.sayName = function () {
alert(this.name);
}
}
egPerson('alice','23'); //this-->window
window.sayName();  //'alice'

可以看出:

1)构造函数内部会创建一个实例,调用普通函数时则不会创建新的对象。

2)构造函数内部的this指向是新创建的person实例,而普通函数内部的this指向调用函数的对象(如果没有对象调用,默认为window)

 构造函数的返回值:

     有一个默认的返回值,新创建的对象(实例);

     当手动添加返回值后(return语句):

          1. 返回值是基本数据类型-->真正的返回值还是那个新创建的对象(实例)

          2. 返回值是复杂数据类型(对象)-->真正的返回值是这个对象 

看一个常见的面试题

?
=_____________________________________________________________________________________

使用 apply 或 call 调用

让我们再一次重申,在 JavaScript 中函数也是对象,对象则有方法,apply 和 call 就是函数对象的方法。这两个方法异常强大,他们允许切换函数执行的上下文环境(context),即 this 绑定的对象。很多 JavaScript 中的技巧以及类库都用到了该方法。让我们看一个具体的例子:

清单 7. Point2.js

在上面的例子中,我们使用构造函数生成了一个对象 p1,该对象同时具有 moveTo 方法;使用对象字面量创建了另一个对象 p2,我们看到使用 apply 可以将 p1 的方法应用到 p2 上,这时候 this 也被绑定到对象 p2 上。另一个方法 call 也具备同样功能,不同的是最后的参数不是作为一个数组统一传入,而是分开传入的。

换个角度理解

如果像作者一样,大家也觉得上述四种方式不方便记忆,过一段时间后,又搞不明白 this 究竟指什么。那么我向大家推荐 Yehuda Katz 的这篇文章:Understanding JavaScript Function Invocation and
“this”。在这篇文章里,Yehuda Katz 将 apply 或 call 方式作为函数调用的基本方式,其他几种方式都是在这一基础上的演变,或称之为语法糖。Yehuda Katz 强调了函数调用时 this 绑定的过程,不管函数以何种方式调用,均需完成这一绑定过程,不同的是,作为函数调用时,this 绑定到全局对象;作为方法调用时,this 绑定到该方法所属的对象。

结束?

通过上面的描述,如果大家已经能明确区分各种情况下 this 的含义,这篇文章的目标就已经完成了。如果大家的好奇心再强一点,想知道为什么 this 在 JavaScript 中的含义如此丰富,那就得继续阅读下面的内容了。作者需要提前告知大家,下面的内容会比前面稍显枯燥,如果只想明白 this 的含义,阅读到此已经足够了。如果大家不嫌枯燥,非要探寻其中究竟,那就一起迈入下一节吧。

函数的执行环境

JavaScript 中的函数既可以被当作普通函数执行,也可以作为对象的方法执行,这是导致 this 含义如此丰富的主要原因。一个函数被执行时,会创建一个执行环境(ExecutionContext),函数的所有的行为均发生在此执行环境中,构建该执行环境时,JavaScript 首先会创建
arguments
变量,其中包含调用函数时传入的参数。接下来创建作用域链。然后初始化变量,首先初始化函数的形参表,值为
arguments
变量中对应的值,如果
arguments
变量中没有对应值,则该形参初始化为
undefined
。如果该函数中含有内部函数,则初始化这些内部函数。如果没有,继续初始化该函数内定义的局部变量,需要注意的是此时这些变量初始化为
undefined
,其赋值操作在执行环境(ExecutionContext)创建成功后,函数执行时才会执行,这点对于我们理解
JavaScript 中的变量作用域非常重要,鉴于篇幅,我们先不在这里讨论这个话题。最后为
this
变量赋值,如前所述,会根据函数调用方式的不同,赋给
this
全局对象,当前对象等。至此函数的执行环境(ExecutionContext)创建成功,函数开始逐行执行,所需变量均从之前构建好的执行环境(ExecutionContext)中读取。

Function.bind

有了前面对于函数执行环境的描述,我们来看看 this 在 JavaScript 中经常被误用的一种情况:回调函数。JavaScript 支持函数式编程,函数属于一级对象,可以作为参数被传递。请看下面的例子 myObject.handler 作为回调函数,会在 onclick 事件被触发时调用,但此时,该函数已经在另外一个执行环境(ExecutionContext)中执行了,this 自然也不会绑定到 myObject 对象上。

清单 8. callback.js

这是 JavaScript 新手们经常犯的一个错误,为了避免这种错误,许多 JavaScript 框架都提供了手动绑定 this 的方法。比如 Dojo 就提供了 lang.hitch,该方法接受一个对象和函数作为参数,返回一个新函数,执行时 this 绑定到传入的对象上。使用 Dojo,可以将上面的例子改为:

清单 9. Callback2.js

在新版的 JavaScript 中,已经提供了内置的 bind 方法供大家使用。

eval 方法

JavaScript 中的 eval 方法可以将字符串转换为 JavaScript 代码,使用 eval 方法时,this 指向哪里呢?答案很简单,看谁在调用 eval 方法,调用者的执行环境(ExecutionContext)中的 this 就被 eval 方法继承下来了。

结束语

本文介绍了 JavaScript 中的 this 关键字在各种情况下的含义,虽然这只是 JavaScript 中一个很小的概念,但借此我们可以深入了解 JavaScript 中函数的执行环境,而这是理解闭包等其他概念的基础。掌握了这些概念,才能充分发挥 JavaScript 的特点,才会发现 JavaScript 语言特性的强大。

相关主题

JavaScript: The Good Parts:第四章中关于函数的介绍。
Understanding JavaScript Function Invocation and“this”:另一种角度理解
JavaScript 中的 this。
Avoiding and exploiting JavaScript’ s warts:中关于 JavaScript 中的 this
一节。
Making Functions with hitch and partial:介绍了 Dojo 中提供的绑定 this 的方法。
Functions and execution contexts in JavaScript:深入讲解了 JavaScript 中函数,及其执行环境的概念。
Understanding JavaScript
s this keyword:通过实例讲解了 this 在各种情况下的含义。
JavaScript Closures:深入讲解了 JavaScript 中闭包的概念,关于函数执行环境一节的介绍,对于理解 this 的含义很有帮助。
developerWorks Web development 专区:通过专门关于 Web 技术的文章和教程,扩展您在网站开发方面的技能。
developerWorks Ajax 资源中心:这是有关 Ajax 编程模型信息的一站式中心,包括很多文档、教程、论坛、blog、wiki 和新闻。任何 Ajax 的新信息都能在这里找到。
developerWorks Web 2.0 资源中心,这是有关 Web 2.0 相关信息的一站式中心,包括大量 Web 2.0 技术文章、教程、下载和相关技术资源。您还可以通过Web
2.0 新手入门 栏目,迅速了解 Web 2.0 的相关概念。
查看
HTML5 专题,了解更多和 HTML5 相关的知识和动向
=》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》

=》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》

原文:

http://yehudakatz.com/2011/08/11/understanding-javascript-function-invocation-and-this/

2011.8.11

多年以来,我看到大量关于javascript函数调用的困惑。尤其,许多人抱怨函数调用中“this”的语意是混乱的。
在我看来,大量这样的混乱可以通过理解核心函数调用原语被清理,然后再看所有其他在原语之上进行包装的调用函数的方法。实际上,这正好是ECMAScript规格对这个问题的考虑。在某些领域,这个是一个规格的简化,但基本思想是一样的。
核心原语
首先,我们来看核心函数调用原语,一个函数的调用方法[1]。这个调用方法是相对直线向前的(The call method is relatively straightforward.)。
1.     构造参数列表(argList)从参数1到最后
2.     第一个参数是thisValue
3.     把this 赋值给thisValue 并用argList 作为参数列表调用函数
例如:

[javascript]
view plain
copy
print?

function hello(thing) {  
  console.log(this + " says hello " + thing);  
}  
   
hello.call("Yehuda", "world") //=> Yehuda says hello world  

function hello(thing) {
console.log(this + " says hello " + thing);
}

hello.call("Yehuda", "world") //=> Yehuda says hello world

正如你看到的,我们通过把this赋值给 "Yehuda"和一个单一参数来调用hello 方法。这就是javascript函数调用核心原语。你能想象所有其他的函数调用都是对这个原语包装。(包装是使用一个便利的语法和按照更基本的核心原语描述它)

[1] In the ES5 spec, the call method isdescribed in terms of another, more
low level primitive, but it’s a very thinwrapper on top of that primitive, so I’m simplifying a bit here. See the end ofthis post for more information.

简单函数调用
很明显,任何时候使用call 调用函数都是相当的烦人的。Javascript允许我们使用括弧直接调用函数(hello("world"))。我们这样做的时候,调用包装为:

[javascript]
view plain
copy
print?

function hello(thing) {  
  console.log("Hello " + thing);  
}  
   
// this:  
hello("world")  
   
// desugars to:  
hello.call(window, "world");  

function hello(thing) {
console.log("Hello " + thing);
}

// this:
hello("world")

// desugars to:
hello.call(window, "world");

这个行为在ECMAScript中只有当使用严格模式时改变了:

[javascript]
view plain
copy
print?

// this:  
hello("world")  
   
// desugars to:  
hello.call(undefined, "world");  

// this:
hello("world")

// desugars to:
hello.call(undefined, "world");

短版本:函数调用fn(...args)和fn.call(window [ES5-strict: undefined], ...args)等同。
需要注意的是,函数内联声明也是正确的:(function() {})()和(function(){}).call(window [ES5-strict: undefined)等同。
[2]实际上,我说了点谎。ECMAScript 5规格说一般(大多情况)传递的是undefined ,但被调用的函数在非严格模式时应该改变它的thisValue 为全局对象。这允许严格模式调用者避免破坏现存的非严格模式库。
成员函数
下一个非常常见的方法是调用作为对象的成员方法(person.hello())。在这种情况下,调用包装为:

[javascript]
view plain
copy
print?

var person = {  
  name: "Brendan Eich",  
  hello: function(thing) {  
    console.log(this + " says hello " + thing);  
  }  
}  
   
// this:  
person.hello("world")  
   
// desugars to this:  
person.hello.call(person, "world");  

var person = {
name: "Brendan Eich",
hello: function(thing) {
console.log(this + " says hello " + thing);
}
}

// this:
person.hello("world")

// desugars to this:
person.hello.call(person, "world");

注意,和hello 方法是怎么以这种方式附加到对象上的没有关系。 记住我们之前作为独立函数定义的hello 。我们来看看如果动态的附加到对象上发生了什么:

[javascript]
view plain
copy
print?

function hello(thing) {  
  console.log(this + " says hello " + thing);  
}  
   
person = { name: "Brendan Eich" }  
person.hello = hello;  
   
person.hello("world") // still desugars to person.hello.call(person, "world")  
   
hello("world") // "[object DOMWindow]world"  

function hello(thing) {
console.log(this + " says hello " + thing);
}

person = { name: "Brendan Eich" }
person.hello = hello;

person.hello("world") // still desugars to person.hello.call(person, "world")

hello("world") // "[object DOMWindow]world"

注意这个函数没有确定的“this”概念。它总是在调用时基于它的调用者的调用方式设置。

使用Function.prototype.bind
因为有时有一个确定this 值的函数引用会方便一些,人们使用一个简单的封闭把戏来转换一个函数为一个不变的this:

[javascript]
view plain
copy
print?

var person = {  
  name: "Brendan Eich",  
  hello: function(thing) {  
    console.log(this.name + " says hello " + thing);  
  }  
}  
   
var boundHello = function(thing) { return person.hello.call(person, thing); }  
   
boundHello("world");  

var person = {
name: "Brendan Eich",
hello: function(thing) {
console.log(this.name + " says hello " + thing);
}
}

var boundHello = function(thing) { return person.hello.call(person, thing); }

boundHello("world");

尽管通过我们的boundHello 调用仍然解释为boundHello.call(window,"world"),我们转了一圈然然后使用我们原语调用方法来修改this 值为我们期望的值。
我们可以进行一些调整达到这个通用的目的:

[javascript]
view plain
copy
print?

var bind = function(func, thisValue) {  
  return function() {  
    return func.apply(thisValue, arguments);  
  }  
}  
   
var boundHello = bind(person.hello, person);  
boundHello("world") // "Brendan Eich says hello world"  

var bind = function(func, thisValue) {
return function() {
return func.apply(thisValue, arguments);
}
}

var boundHello = bind(person.hello, person);
boundHello("world") // "Brendan Eich says hello world"

为了理解这一点,你只需要再多了解两条信息。首先,arguments 是一个代表所有传递到函数的参数的类数组对象。其次,apply方法和call 原语工作原理完全一样,除了它带了一个类数组对象代替每一次列出参数。
我们的bind 方法简单的返回一个新的函数。当它被调用时,我们的新函数简单的调用了传入的原始函数,设置原始值为this。它也通过参数传递。
因为这是一个比较常见的习语,ES5为所有的Function 对象引入一个新的bind 方法,它实现下面的行为:

[javascript]
view plain
copy
print?

var boundHello = person.hello.bind(person);  
boundHello("world") // "Brendan Eich says hello world"  

var boundHello = person.hello.bind(person);
boundHello("world") // "Brendan Eich says hello world"

当你需要一个原始函数作为回调传递时这更有用:

[javascript]
view plain
copy
print?

var person = {  
  name: "Alex Russell",  
  hello: function() { console.log(this.name + " says hello world"); }  
}  
   
$("#some-div").click(person.hello.bind(person));  
   
// when the div is clicked, "Alex Russell says hello world" is printed  

var person = {
name: "Alex Russell",
hello: function() { console.log(this.name + " says hello world"); }
}

$("#some-div").click(person.hello.bind(person));

// when the div is clicked, "Alex Russell says hello world" is printed


当然,这有点笨拙,并且TC39(委员会正在制定的下一个ECMAScript版本)继续致力于更优雅的,向后兼容的方案。
在Jquery中
因为jQuery大量使用匿名回调函数,它内部使用call方法设置那些回调的this 值为更有用的值。例如, 在所有的事件处理中替代接收window 作为this(如你没有特别的处理),jQuery在回调中使用建立事件处理器的元素作为它的第一个参数调用call 。
这非常有用,因为匿名调用中this 的默认值不是特别的有用,但它可以给JavaScipt初学者的印象,this 通常是奇怪的,常常改变,很难确定的概念。 (原文:but it can give beginners to JavaScriptthe impression that this is, ingeneral a strange, often mutated concept that is hard
to reason about.)
如果你理解了基本规则,转换一个包装的函数调用为一个非包装的func.call(thisValue,...args),你应该能导航不那么变化莫测的Javascript this 值水域。(原文:you should be able to navigate the not sotreacherous waters of the JavaScript this value.)



附录:我行骗了
在一些地方,我从规格精确的语法简化了现实一点。可能最重要欺骗是我称func.call为“原语”的习惯。实际上,该规格有一个原语(内部引用为[[Call]])是func.call 和[obj.]func()使用。
不管怎样,一起来看看func.call定义:
1.    如果IsCallable(func)是false,则抛出一个TypeError异常。
2.    置argList为空列表。
3.    如果方法被调用时使用超过一个参数,那么以从左到右顺序从arg1开始附加每个参数作为argList最后的元素。
4.    返回调用func的[[Call]]内部方法的结果,提供thisArg作为this值,和argList作为参数列表。
正如你看到的,这个定义是本质非常简单的对原语[[Call]] 操作的JavaScript语言绑定。
如果你看一下调用函数的定义,七步的第一步是建立thisValue和argList,最后一步是:“返回在func中调用[[Call]]内部方法的结果,提供thisValue作为this值并提供参数列表作为argument的值。”
它的本质是相同的语法,一旦argList 和thisValue 被确定。
我撒了一点谎,在把call 叫做原语,但它的意思的本质是相同的,所以在本文的开始和引用的章节与段落我拉出规格。
这也有一些附加的情况(尤其包括)在这里没有提到情况。
这个条目在2011年8月11日星期四上午2:54被张贴,并在JavaScript下存档。你可以通过RSS2.0反馈跟随任意响应到这个入口。你可以跳到最后离开响应。Pinging当前不允许(原文:Pinging
is currently not allowed.没有理解是什么意思)。

原文地址:http://yehudakatz.com/2011/08/11/understanding-javascript-function-invocation-and-this/

译注:sugary 我译作了“包装”;primitive译作“原语”。

限于本人水平,如有不当,欢迎批评指正

=》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》




内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: