JS 中关于this 执行环境的问题_part2
2017-07-29 23:36
381 查看
今天阅读了《你不知道JavaScript(上)》, 对this又有了新的理解.
1. 指向自身
2. 指向函数的作用域
针对第一个误解,来看一个例子
结果是0的原因是,这里函数foo在调用的时候,this指向的是全局对象.全局中并没有声明count变量,在非严格模式下会自动创建全局变量count, 值为undefined, foo函数中的this.count++的操作都是针对undefined,因此全局变量count的值为NaN. this在这里指向全局对象在这本书中的解释是 this进行了默认绑定.
要想希望上面代码中foo函数的this指向其本身, 这里提供两个方法.
1. 使用foo.count++ 代替this.count++ ,函数foo是具名函数,不是匿名函数,可以用foo指向其自身.(arguments.callee已经被弃用?)
2. 强制this指向foo函数对象,调用foo函数的时候使用foo.call(foo,i);
第二个误解是this指向函数的作用域
针对第二个误解,看下面的例子
bar()在全局作用域中, 但在foo()中无法用this调用. bar()也无法访问foo()作用域中的的变量a.
this实际上是在函数被调用时发生的,它指向什么完全取决于函数在哪里被调用.
看下面这个例子
调用栈是baz -> bar -> foo, 因此foo的调用位置是bar,bar的调用位置在baz, baz的调用位置在全局作用域
看下面的代码
调用栈 ->foo
foo()的调用位置在全局作用域,直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,无法应用其他规则。
如果使用严格模式(strict mode),那么全局对象将无法使用默认绑定,因此 this 会绑定到 undefined
看下面的代码
调用栈obj.foo->foo
调用位置在obj.foo. 调用位置会使用 obj 上下文来引用函数,因此你可以说函数被调用时 obj 对象“拥有”或者“包含”它.当函数引用有上下文对象时,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象.因为调用 foo() 时 this 被绑定到 obj,因此 this.a 和 obj.a 是一样的。
对象属性引用链中只有最顶层或者说最后一层会影响调用位置
函数调用栈: obj1.obj2.foo -> foo
这里引用链的顶层是obj2,因此this指向obj2对象
调用栈看起来像 obj.foo(bar)->foo,其实调用栈为->foo, 调用位置在全局作用域
虽然 bar 是 obj.foo 的一个引用,但是实际上,它引用的是 foo 函数本身,因此此时的bar() 其实是一个不带任何修饰的函数调用,因此应用了默认绑定.
另一个例子
foo()的调用栈其实是doFoo->foo, fn引用foo函数本身
doFoo()的调用栈->doFoo
doFoo( obj.foo );中的obj.foo是隐式赋值,因此fn()在调用的时候和上一个例子一样,应用了默认绑定. doFoo()中的this同样也是默认绑定.
var bar = new foo()
函数是否通过 call、apply(显式绑定)或者硬绑定调用?如果是的话,this 绑定的是
指定的对象。
var bar = foo.call(obj2)
函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this 绑定的是那个上
下文对象。
var bar = obj1.foo()
如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到 undefined,否则绑定到
全局对象。
var bar = foo()
如果你把 null 或者 undefined 作为 this 的绑定对象传入 call、apply 或者 bind,这些值在调用时会被忽略,实际应用的是默认绑定规则.
赋值表达式 p.foo = o.foo 的返回值是目标函数的引用,因此调用位置是 全局作用域而不是p.foo() 或者 o.foo().(书上说调用位置在foo() 感觉有问题)根据我们之前说过的,这里会应用默认绑定.
就可以顺序应用下面这四条规则来判断 this 的绑定对象。
1. 由 new 调用?绑定到新创建的对象。
2. 由 call 或者 apply(或者 bind)调用?绑定到指定的对象。
3. 由上下文对象调用?绑定到那个上下文对象。
4. 默认:在严格模式下绑定到 undefined,否则绑定到全局对象。
http://www.fangbinwei.cn/articleDetail?id=59ab975ef1906c31d6c6924a
误解
经常有人会误解this, 两种常见的对this的误解1. 指向自身
2. 指向函数的作用域
针对第一个误解,来看一个例子
function foo(num) { console.log( "foo: " + num ); // 记录 foo 被调用的次数 this.count++; } foo.count = 0; var i; for (i=0; i<10; i++) { if (i > 5) { foo( i ); } } // foo: 6 // foo: 7 // foo: 8 // foo: 9 // foo 被调用了多少次? console.log( foo.count ); // 0 -- WTF?
结果是0的原因是,这里函数foo在调用的时候,this指向的是全局对象.全局中并没有声明count变量,在非严格模式下会自动创建全局变量count, 值为undefined, foo函数中的this.count++的操作都是针对undefined,因此全局变量count的值为NaN. this在这里指向全局对象在这本书中的解释是 this进行了默认绑定.
要想希望上面代码中foo函数的this指向其本身, 这里提供两个方法.
1. 使用foo.count++ 代替this.count++ ,函数foo是具名函数,不是匿名函数,可以用foo指向其自身.(arguments.callee已经被弃用?)
2. 强制this指向foo函数对象,调用foo函数的时候使用foo.call(foo,i);
第二个误解是this指向函数的作用域
针对第二个误解,看下面的例子
function foo() { var a = 2; this.bar(); } function bar() { console.log( this.a ); } foo(); // ReferenceError: a is not defined
bar()在全局作用域中, 但在foo()中无法用this调用. bar()也无法访问foo()作用域中的的变量a.
this实际上是在函数被调用时发生的,它指向什么完全取决于函数在哪里被调用.
调用位置
this 是在调用时被绑定的,完全取决于函数的调用位置(个人认为这句话的意思是: this是在调用的时候被绑定的,所以需要找到函数的调用位置,找到位置后,观察函数符合什么调用规则,this的指向取决于函数在调用位置的调用模式).因此需要分析调用栈,我们关心的调用位置就在当前正在执行的函数的前一个调用中。看下面这个例子
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 的调用位置
调用栈是baz -> bar -> foo, 因此foo的调用位置是bar,bar的调用位置在baz, baz的调用位置在全局作用域
绑定规则
函数在不用的调用位置(可能存在不用的调用模式),其this的指向不同.要判断函数在该调用位置的this指向,需要应用下面四条规则中的一条.默认绑定
this 的默认绑定指向全局对象看下面的代码
function foo() { console.log( this.a ); } var a = 2; foo(); // 2
调用栈 ->foo
foo()的调用位置在全局作用域,直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,无法应用其他规则。
如果使用严格模式(strict mode),那么全局对象将无法使用默认绑定,因此 this 会绑定到 undefined
隐式绑定
观察调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含.看下面的代码
function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; obj.foo(); // 2
调用栈obj.foo->foo
调用位置在obj.foo. 调用位置会使用 obj 上下文来引用函数,因此你可以说函数被调用时 obj 对象“拥有”或者“包含”它.当函数引用有上下文对象时,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象.因为调用 foo() 时 this 被绑定到 obj,因此 this.a 和 obj.a 是一样的。
对象属性引用链中只有最顶层或者说最后一层会影响调用位置
function foo() { console.log( this.a ); } var obj2 = { a: 42, foo: foo }; var obj1 = { a: 2, obj2: obj2 }; obj1.obj2.foo(); // 42
函数调用栈: obj1.obj2.foo -> foo
这里引用链的顶层是obj2,因此this指向obj2对象
隐式丢失
看例子function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; var bar = obj.foo; // 函数别名! var a = "oops, global"; // a 是全局对象的属性 bar(); // "oops, global"
调用栈看起来像 obj.foo(bar)->foo,其实调用栈为->foo, 调用位置在全局作用域
虽然 bar 是 obj.foo 的一个引用,但是实际上,它引用的是 foo 函数本身,因此此时的bar() 其实是一个不带任何修饰的函数调用,因此应用了默认绑定.
另一个例子
function foo() { console.log( this.a ); } function doFoo(fn) { // fn 其实引用的是 foo fn(); // <-- 调用位置! console.log(a)// "oops, global" } var obj = { a: 2, foo: foo }; var a = "oops, global"; // a 是全局对象的属性 doFoo( obj.foo ); // "oops, global"
foo()的调用栈其实是doFoo->foo, fn引用foo函数本身
doFoo()的调用栈->doFoo
doFoo( obj.foo );中的obj.foo是隐式赋值,因此fn()在调用的时候和上一个例子一样,应用了默认绑定. doFoo()中的this同样也是默认绑定.
显式绑定
是指利用apply(),call() 数组的forEach()等方法强制绑定thisnew 绑定
是指用new调用函数的时候, 绑定this的情况优先级
函数是否在 new 中调用(new 绑定)?如果是的话 this 绑定的是新创建的对象。var bar = new foo()
函数是否通过 call、apply(显式绑定)或者硬绑定调用?如果是的话,this 绑定的是
指定的对象。
var bar = foo.call(obj2)
函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this 绑定的是那个上
下文对象。
var bar = obj1.foo()
如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到 undefined,否则绑定到
全局对象。
var bar = foo()
例外
被忽略的this
function foo() { console.log( this.a ); } var a = 2; foo.call( null ); // 2
如果你把 null 或者 undefined 作为 this 的绑定对象传入 call、apply 或者 bind,这些值在调用时会被忽略,实际应用的是默认绑定规则.
间接引用
function foo() { console.log( this.a ); } var a = 2; var o = { a: 3, foo: foo }; var p = { a: 4 }; o.foo(); // 3 调用栈 o.foo ->foo (p.foo = o.foo)(); // 2 调用栈 ->foo
赋值表达式 p.foo = o.foo 的返回值是目标函数的引用,因此调用位置是 全局作用域而不是p.foo() 或者 o.foo().(书上说调用位置在foo() 感觉有问题)根据我们之前说过的,这里会应用默认绑定.
小结
如果要判断一个运行中函数的 this 绑定,就需要找到这个函数的直接调用位置。找到之后就可以顺序应用下面这四条规则来判断 this 的绑定对象。
1. 由 new 调用?绑定到新创建的对象。
2. 由 call 或者 apply(或者 bind)调用?绑定到指定的对象。
3. 由上下文对象调用?绑定到那个上下文对象。
4. 默认:在严格模式下绑定到 undefined,否则绑定到全局对象。
http://www.fangbinwei.cn/articleDetail?id=59ab975ef1906c31d6c6924a
相关文章推荐
- JS 中关于this 执行环境的问题_part3
- JS 中关于this 执行环境的问题_part1
- 关于js延迟执行问题
- js 回调函数中 this对象的执行环境(上下文)
- 关于js的this上下文环境绑定
- 关于easyui加载子页面js不执行的问题
- 关于JS调用外部可执行程序中路径空格问题
- [安卓开发]关于WebView执行js 会输出的问题解决
- asp.net关于Page_Load方法和执行js脚本顺序的不同所带来的问题
- 关于Android4.4以下版本Webview执行LoadUrl加载js导致键盘隐藏的问题(富文本编辑器)
- js 关于this的一些语法问题
- 关于JS中for循环时,作用域问题和this指针指向的总结
- 关于js中this的指向问题
- 关于js中循环遍历中顺序执行多个嵌套ajax的问题
- 关于this 方法中套方法中找 立即执行的问题 等问题
- 关于JS调用Applet的执行权限的问题
- js对象字面量属性间相互访问的问题 和 这个问题与执行环境的关系
- 关于在页面里加载和执行JS顺序的问题
- 关于js在html中执行顺序的问题
- 关于js的this上下文环境绑定