JavaScript之this

来源:互联网 发布:淘宝客搜索产品推广 编辑:程序博客网 时间:2024/05/29 23:22

JavaScript之this

1. 为什么使用this


先看个例子:
function identity() {return this.name.toUpperCase();}function speak() {return "Hello, i'm " + identity.call(this);}var me = {name: 'rod chen'}var you = {name: "others in Aug"}console.log(identity.call(me));  //ROD CHENconsole.log(identity.call(you)); //OTHERS IN AUGconsole.log(speak.call(me));     //Hello, i'm ROD CHEN  console.log(speak.call(you));    //Hello, i'm OTHERS IN AUG
输出的结果很明显,对于call的用法前面文章有提到,第一个参数就是传入到函数里的this的值。
这段代码可以在不同的上下文对象( me 和 you )中重复使用函数 identify() 和 speak() ,如果我们不适用this的话,那就需要identity和speak显示传入一个上下文对象,就像下面的方式
function identity(context) {return context.name.toUpperCase();}function speak(context) {return "Hello, i'm " + identity(context);}var me = {name: 'rod chen'}var you = {name: "others in Aug"}console.log(identity(me));console.log(identity(you));console.log(speak(me));console.log(speak(you));
Summary:this 提供了一种更优雅的方式来隐式“传递”一个对象引用,因此可以将API设计得更加简洁
并且易于复用。随着使用模式越来越复杂,显式传递上下文对象会让代码变得越来越混乱,使用 this 则不会
这样

2. 关于this的误解


(1) 指向自身


误解:很容易将this理解成函数对象自身。
其实不是,先看了一个例子:

console.log 语句产生了 4 条输出,证明 foo(..) 确实被调用了 4 次,但是 foo.count 仍然是 0。显然从字面意思来理解 this 是错误的。执行 foo.count = 0 时,的确向函数对象 foo 添加了一个属性 count 。但是函数内部代码this.count 中的 this 并不是指向那个函数对象,所以虽然属性名相同,根对象却并不相
同,困惑随之产生,其实counter是window对象的,从上面的例子中也可以看到,执行的方式是window.foo(),所以this指向的是全局作用域window对象。

(2) this作用域

误解: this指向函数的作用域。
this 在任何情况下都不指向函数的词法作用域。在 JavaScript 内部,作用域确实和对象类似,可见的标识符都是它的属性。但是作用域“对象”无法通过 JavaScript代码访问,它存在于 JavaScript 引擎内部。但是我们是可以访问this对象的。
function foo() {var a = 2;this.bar();}function bar() {"use strict";console.log( this.a );}foo(); //undefined

3. this到底是什么


this 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。 this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。 this就是记录的其中一个属性,会在函数执行的过程中用到。

方法调用的方式: this指向调用这个方法的对象,或者call,apply函数显示传入的参数。
函数的方式调用: window或者undefined

4. this全面解析


(1). 调用位置


调用位置就是函数在代码中被调用的位置(而不是声明的位置),最重要的是要分析调用栈(就是为了到达当前执行位置所调用的所有函数)。我们关心的调用位置就在当前正在执行的函数的前一个调用中。
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 的调用位置
可以把调用栈想象成一个函数调用链,但是这种方式太累了,因为当我们在项目里方法可能存在不同的文件模块重,这样做的话太麻烦了。我们可以使用开发者工具来查看:如下图中红色框部分


(2) 绑定规则


a. 独立函数调用

可以把这条规则看作是无法应用其他规则时的默认规则。在代码中, foo() 是直接使用不带任何修饰的函数引用进行调用的,因此只能使用
默认绑定,无法应用其他规则。这个时候的this值为window或者undefined


b. 隐式绑定

1) 隐式绑定
另一条需要考虑的规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含。
function foo() {console.log( this.a );}var obj = {a: 2,foo: foo};obj.foo(); // 2
首先需要注意的是 foo() 的声明方式,及其之后是如何被当作引用属性添加到 obj 中的。但是无论是直接在 obj 中定义还是先定义再添加为引用属性,这个函数严格来说都不属于obj 对象,只是obj内的foo属性指向了全局作用域下foo函数的位置。
然而,调用位置会使用 obj 上下文来引用函数,因此你可以说函数被调用时 obj 对象“拥有”或者“包含”它。无论你如何称呼这个模式,当 foo() 被调用时,它的落脚点确实指向 obj 对象。

Note: 对象属性引用链中只有最顶层或者说最后一层会影响调用位置(也就是最靠近调用函数的位置)
function foo() {console.log( this.a );}var obj2 = {a: 42,foo: foo};var obj1 = {a: 2,obj2: obj2};obj1.obj2.foo(); // 42

2)隐式丢失
一个最常见的 this 绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把 this 绑定到全局对象或者undefined上。
function foo() {console.log( this.a );}var obj = {a: 2,foo: foo};var bar = obj.foo; // 函数别名!var a = "oops, global"; // a 是全局对象的属性bar(); // "oops, global"
虽然 bar 是 obj.foo 的一个引用,但是实际上,它引用的是 foo 函数本身,因此此时的bar() 其实是一个不带任何修饰的函数调用,因此应用了默认绑定,这也说明了this的值是调用方式来决定的。


c. 显示绑定

分析隐式绑定时,我们必须在一个对象内部包含一个指向函数的属性,并通过这个属性间接引用函数,从而把 this 间接(隐式)绑定到这个对象上。那么如果我们不想在对象内部包含函数引用,而想在某个对象上强制调用函数,该怎么做呢?
那就是 call(..) 和 apply(..) 。对于call和apply的用法这里就不多说了,前面的文章里有讲过,可以查看文章JavaScript函数,作用域以及闭包。

1) 硬绑定 bind
function foo(something) {console.log( this.a, something );return this.a + something;}var obj = {a:2};var bar = foo.bind( obj );var b = bar( 3 ); // 2 3console.log( b ); // 5
bind(..) 会返回一个硬编码的新函数,它会把参数设置为 this的上下文并调用原始函数,this的值有我创建的函数类决定,而不是调用者。

2)API调用的上下文
第三方库的许多函数,以及 JavaScript 语言和宿主环境中许多新的内置函数,都提供了一个可选的参数,通常被称为“上下文”(context),其作用和 bind(..) 一样,确保你的回调函数使用指定的 this 。
请看图示:


d. new 绑定

使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。
1). 创建(或者说构造)一个全新的对象。
2). 这个新对象会被执行 [[ 原型 ]] 连接。
3). 这个新对象会绑定到函数调用的 this 。
4). 如果函数没有返回其他对象,那么 new表达式中的函数调用会自动返回这个新对象。


(3). 优先级


现在我们可以根据优先级来判断函数在某个调用位置应用的是哪条规则。可以按照下面的
顺序来进行判断:
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()

(4). 箭头函数


 ES6 中介绍了一种无法使用这些规则的特殊函数类型:箭头函数。
 箭头函数并不是使用 function 关键字定义的,而是使用被称为“胖箭头”的操作符 => 定义的。箭头函数不使用 this的四种标准规则,而是根据外层(函数或者全局)作用域来决
定this.


箭头函数最常用于回调函数中,例如事件处理器或者定时器
function foo() {setTimeout(() => {// 这里的 this 在此法上继承自 foo()console.log( this.a );},100);}var obj = {a:2};foo.call( obj ); // 2function foo1() {setTimeout(function(){// 这里的 this 是windowconsole.log( this.a );},100);}var obj = {a:2};foo1.call( obj ); // undefined


0 0
原创粉丝点击