Javascript 很全的this的用法

2018-11-11 admin

前言: 名字取得可能有点大,this 关键字是 JavaScript 中最复杂的机制之一。笔者也困扰已久,但自从阅读了《你不知道的Javascript》以后豁然开朗,整理成文。如需更更详细的解释,请阅读《你不知道的Javascript》第二部分第1章第2章。

绑定规则

默认绑定

最常用的函数调用类型:独立函数调用。可以把这条规则看作是无法应用 其他规则时的默认规则。

function foo() {      
    console.log( this.a ); 
} 
var a = 2; 
foo(); // 2

foo() 是直接使用不带任何修饰的函数引用进行调用的,因此只能使用 默认绑定,无法应用其他规则。 ps:不带任何修饰的函数的描述如果有疑惑的话请不要纠结,继续看下去就就会明白。

需要说明的一点是:如果使用严格模式(strict mode),那么全局对象将无法使用默认绑定,因此 this 会绑定 到 undefined:

  function foo() {  
         "use strict";     
        console.log( this.a ); 
    } 
    var a = 2; 
    foo(); // TypeError: this is undefined

隐式绑定

另一条需要考虑的规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或者包 含,不过这种说法可能会造成一些误导。

function foo() {      
    console.log( this.a ); 
    } 

var obj = {      
    a: 2,     
    foo: foo  
    }; 

obj.foo(); // 2  此处foo里的this就是指的.前面的对象

ps:啰嗦两句,为什么obj.foo()可以执行,这是一种属性的引用链的写法,因为obj 和foo 都挂在全局作用域上。如果还不明白再举一个例子。

function foo() {      
     console.log( this.a ); 
     } 

var obj2 = {      
    a: 42,     
    foo: foo  
    }; 

var obj1 = {      
    a: 2,     
    obj2: obj2  
    }; 

obj1.obj2.foo(); // 42

对象属性引用链中只有最顶层或者说最后一层会影响调用位置.此处也就是obj2

这里说一个比较容易出错的地方:

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

var a = "oops, global"; // a 是全局对象的属性 

 setTimeout( obj.foo, 100 ); // "oops, global

JavaScript 环境中内置的 setTimeout() 函数实现和下面的伪代码类似:

function setTimeout(fn,delay) { // 等待 delay 毫秒

    fn(); // <-- 调用位置! }

setTimeout的参数fn可以看做调用时候传入的函数复制给fn,这个是挂在全局作用域上的(此处的说法不严谨,很多时候框架的不同此处的this被绑定到哪儿很不确定).所以,回调函数丢失 this 绑定是非常常见的。es6 的=>很好的解决了这个问题。

显式绑定

JavaScript 提供的绝大多数函数以及你自 己创建的所有函数都可以使用 call(…) 和 apply(…) 方法。它们会把这个对象绑定到 this,接着在调用函数时指定这个 this。因为你可以直接指定 this 的绑定对象,因此我 们称之为显式绑定。

function foo() {      
    console.log( this.a ); 
    } 

var obj = {      
    a:2 
    }; 

foo.call( obj ); // 2

通过 foo.call(…),我们可以在调用 foo 时强制把它的 this 绑定到 obj 上。 如果你传入了一个原始值(字符串类型、布尔类型或者数字类型)来当作 this 的绑定对 象,这个原始值会被转换成它的对象形式(也就是 new String(…)、new Boolean(…) 或者 new Number(…))。这通常被称为“装箱”。

ps:硬绑定只能执行一次,之后再绑别的都是绑不上去的。

由于硬绑定是一种非常常用的模式,所以在 ES5 中提供了内置的方法 Function.prototype. 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 3  

console.log( b ); // 5

bind(…) 会返回一个硬编码的新函数,它会把参数设置为 this 的上下文并调用原始函数。

new绑定

对于js的new 初学者可能会把它和JAVA类的语言混淆 首先我们重新定义一下 JavaScript 中的“构造函数” 。在 JavaScript 中,构造函数只是一些 使用 new 操作符时被调用的函数。它们并不会属于某个类,也不会实例化一个类。实际上, 它们甚至都不能说是一种特殊的函数类型,它们只是被 new 操作符调用的普通函数而已。

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

2涉及到原型链的知识,本篇可忽视。

function foo(a) {      
    this.a = a; 
    }  

var bar = new foo(2); 
console.log( bar.a ); // 2

使用 new 来调用 foo(…) 时,我们会构造一个新对象并把它绑定到 foo(…) 调用中的 this 上。new 是最后一种可以影响函数调用时 this 绑定行为的方法,我们称之为 new 绑定。

ps:此处可能看得比较费解,请阅读《你不知道的js》第二部的关于类与原型链的章节,还是比较复杂的,在此就不展开了。此处你可以理解为用函数new 了一个新对象,然后绑定到 函数的this上使用。

在 new 中使用硬绑定函数的一般用处

主要目的是预先设置函数的一些参数,这样在使用 new 进行初始化时就可以只传入其余的参数。bind(…) 的功能之一就是可以把除了第一个 参数(第一个参数用于绑定 this)之外的其他参数都传给下层的函数(这种技术称为“部 分应用”,是“柯里化”的一种)。举例来说:

function foo(p1,p2) {          
    this.val = p1 + p2; 
    } 

// 之所以使用 null 是因为在本例中我们并不关心硬绑定的 this 是什么 
// 反正使用 new 时 this 会被修改 
var bar = foo.bind( null, "p1" ); 

var baz = new bar( "p2" );  

baz.val; // p1p2

优先级

我们可以根据优先级来判断函数在某个调用位置应用的是哪条规则。可以按照下面的 顺序来进行判断: (数值越小优先级越高)

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()

绑定例外

在某些场景下 this 的绑定行为会出乎意料,你认为应当应用其他绑定规则时,实际上应用 的可能是默认绑定规则。

被忽略的this

如果你把 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 
(p.foo = o.foo)(); // 2

赋值表达式 p.foo = o.foo 的返回值是目标函数的引用,因此调用位置是 foo() 而不是 p.foo() 或者 o.foo()。

1

软绑定

用硬绑定之后就无法使用隐式绑定或者显式绑定来修改 this。 如果可以给默认绑定指定一个全局对象和 undefined 以外的值,那就可以实现和硬绑定相 同的效果,同时保留隐式绑定或者显式绑定修改 this 的能力。

if (!Function.prototype.softBind) {

 Function.prototype.softBind = function(obj) {         
 var fn = this;         // 捕获所有 curried 参数         
 var curried = [].slice.call( arguments, 1 );          
 var bound = function() {             
 return fn.apply(                 
 (!this || this === (window || global)) ?obj : this                                     
     curried.concat.apply( curried, arguments )             
     );          
  };         
  bound.prototype = Object.create( fn.prototype );         
     return bound;      
     }; }

关于=>有话说

箭头函数并不是使用 function 关键字定义的,而是使用被称为“胖箭头”的操作符 => 定 义的。箭头函数不使用 this 的四种标准规则,而是根据外层(函数或者全局)作用域来决 定 this。 之前我们有说过setTimeout的this绑定丢失的问题es6和es6以前的解决办法如下,可以比较着看 es6:

function foo() {      
setTimeout(() => {         // 这里的 this 在此法上继承自 foo()         
console.log( this.a ); },100); } 

var obj = {      
        a:2 
        }; 

foo.call( obj ); // 2

es5

function foo() {     
        var self = this; // lexical capture of this      
        setTimeout( function(){ console.log( self.a );     }, 100 ); }  

var obj = {     
    a: 2 
    }; 

foo.call( obj ); // 2

ES6 中的箭头函数并不会使用四条标准的绑定规则,而是根据当前的词法作用域来决定 this,具体来说,箭头函数会继承外层函数调用的 this 绑定(无论 this 绑定到什么)。这 其实和 ES6 之前代码中的 self = this 机制一样。

原文链接:https://segmentfault.com/a/1190000016973682

本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处。

转载请注明:文章转载自 JavaScript中文网 [https://www.javascriptcn.com]

本文地址:https://www.javascriptcn.com/read-45092.html

文章标题:Javascript 很全的this的用法

相关文章
html5+JavaScript教程-微信打飞机小游戏源码
js &#x2F;&#x2F; JavaScript Document var c = document.getElementById(&quot;dotu&quot;); var cxt = c.getContext(&quot;2d&q...
2015-11-12
JavaScript编辑器推荐
主流编辑器有SublimeText,Notepad++,webstorm等,是使用最广泛的编辑器,但也有一些JavaScript编辑器提供有着各自的特性和功能,适应不同人的需求,以下是几款优秀的编辑器,相信你一定能找到自己喜欢的。 1. W...
2015-11-12
js性能优化 如何更快速加载你的JavaScript页面
确保代码尽量简洁 不要什么都依赖JavaScript。不要编写重复性的脚本。要把JavaScript当作糖果工具,只是起到美化作用。别给你的网站添加大量的JavaScript代码。只有必要的时候用一下。只有确实能改善用户体验的时候用一下。 ...
2015-11-12
10个强大的纯CSS3动画案例分享
我们的网页外观主要由CSS控制,编写CSS代码可以任意改变我们的网页布局以及网页内容的样式。CSS3的出现,更是可以让网页增添了不少动画元素,让我们的网页变得更加生动有趣,并且更易于交互。本文分享了10个非常炫酷的CSS3动画案例,希望大家...
2015-11-16
2015年JavaScript或“亲库而远框架”
2014年过去了,作为一个JavaScript开发者很难满怀信心的去“挽回”一个特定的库或技术,即便是强大的Angular,似乎也因为最近的一些事情而动摇。 2014年10月的ng-europe会议上,Angular开发者团队透露了一个关于...
2015-11-12
JavaScript实现PC手机端和嵌入式滑动拼图验证码三种效果
PC和手机端网站滑动拼图验证码效果源码,同时包涵了弹出式Demo,使用ajax形式提交二次验证码所需的验证结果值,嵌入式Demo,使用表单形式提交二次验证所需的验证结果值,移动端手动实现弹出式Demo三种效果 首先要确认前端使用页面,比如...
2017-03-17
JavaScript常用特效chm下载
下载地址:JavaScript常用特效chm下载 对了,如果打开空白,在手册上右键属性解除锁定即可。 ...
2015-11-12
从2014年的发展来展望JS的未来将会如何
&lt;font face=&quot;寰�杞�闆呴粦, Arial, sans-serif &quot;&gt;2014骞达紝杞�浠惰�屼笟鍙戝睍杩呴€燂紝鍚勭�嶈��瑷€灞傚嚭涓嶇┓锛屼互婊¤冻鐢ㄦ埛涓嶆柇鍙樺寲鐨勯渶姹傘€傝繖浜涜��...
2015-11-12
12个你未必知道的CSS小知识
虽然CSS并不是一种很复杂的技术,但就算你是一个使用CSS多年的高手,仍然会有很多CSS用法/属性/属性值你从来没使用过,甚至从来没听说过。 1.CSS的color属性并非只能用于文本显示 对于CSS的color属性,相信所有Web开发人员...
2015-11-12
ajax为什么令人惊异?ajax的优缺点
使用Ajax的最大优点,就是能在不更新整个页面的前提下维护数据。这使得Web应用程序更为迅捷地回应用户动作,并避免了在网络上发送那些没有改变的信息。 Ajax不需要任何浏览器插件,但需要用户允许JavaScript在浏览器上执行。就像DHT...
2015-11-12
回到顶部