zoukankan      html  css  js  c++  java
  • JavaScript中的this(你不知道的JavaScript)

    JavaScript中的this,刚接触JavaScript时大家都在大肆渲染说其多么多么的灵巧重要,然而自己并不关心;随着自己对JavaScript一步步深入了解,突然恍然大悟,原来它真的很重要!所以,自己花费了大约2周的时间去查贴、翻阅之前读的书籍,将this的全貌展示如下。

    先出几道题:

    var a = {
    value: 'a',
    fn:function() {
    alert(this.value);
    }
    };
    var b = a.fn;
    b();

    //undefined

    var a = {
    value: 'a',
    fn:function() {
    alert(this.value);
    }
    };
    setTimeout(a.fn);
    //undefined

    可以认为是作为“函数”调用。换种形式写呢:

    var a = {
    value: 'a',
    fn:function() {
    alert(this.value);
    }
    };
    var b;
    (b = a.fn)();

    //undefined

    没问题吧?再来个:

    var a = {
    value: 'a',
    fn:function() {
    alert(this.value);
    }
    };
    var b = {
    value: 'b'
    };
    (b.fn = a.fn)();

    //undefined

    至于这个为啥this指向window,可以认为进行表达式求值时,返回的是个函数,因此作为函数调用,再看

    var a = {
    value: 'a',
    fn:function() {
    alert(this.value);
    }
    };
    (a.fn = a.fn)();
    (a.fn, a.fn, a.fn)();

    // undefined

    // undefined

    var a = {
    value: 'a',
    fn:function() {
    alert(this.value);
    }
    };
    (a.fn)();

    //"a"

    bind绑定问题

    var a = {
    value: 'a',
    fn:function() {
    alert(this.value);
    }
    };
    var b = {
    value: 'b'
    };
    a.fn = a.fn.bind(b);
    a.fn();

    //"b"

    绑定过了,就不能改了?

    var a = {
    value: 'a',
    fn:function() {
    alert(this.value);
    }
    };
    var b = {
    value: 'b'
    };
    a.fn = a.fn.bind(b);
    new a.fn();

    //undefined

    ES6的箭头函数
    我们知道如下的this指向window

    var fn = function() {
    setTimeout(function() {
    alert(this.value)
    });
    };
    fn.call({value:'a'});
    //undefined

    但对应的箭头函数呢?

    var fn = function() {
    setTimeout(() => {
    alert(this.value)
    });
    };
    fn.call({value:'a'});

    //

    以上总总有感于《你不知道的JavaScript》,
    书中关于this的介绍还是很详细的。
    还起了几个名字呢?
    默认绑定(函数调用),
    隐式绑定(方法调用),
    显式绑定(call或apply),
    new绑定(new构造函数)
    还像模像样的给出了调用优先级(上面四种逆序)。。。
    此书有英文版(共六本),可以去啃啃。

    一、this是什么--基于调用位置的上下文;调用位置不同,this值不同。

    大家都JavaScript中this存在两个误解:
    (1)this指向函数自身
    (2)this指向函数的作用域

    作用域无法通过JavaScript代码访问,它存在于JavaScript引擎内部。每当把this和词法作用域的查找混合使用时,一定要提醒自己,这是无法实现的!

    this是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this的绑定和函数声明的位置没有任何关系,只取决于函数的调用位置(也就是函数的调用方式)!

    示例:

    [javascript] view plain copy
    1. var foo = "golbal foo";  
    2. var myObj = {foo : 'myObj foo'};  
    3. var say = function(){  
    4.     console.log(this.foo);  
    5. }  
    6.   
    7. myObj.say = say;  
    8. myObj.say();    //结果:myObj foo  
    9. say();      //结果:golbal foo  

    二、为什么使用this

    [javascript] view plain copy
    1. var me = {  
    2.     name: "fenfei"  
    3. };  
    4.   
    5. //不使用this,调用  
    6. function speak(name){  
    7.     console.log("Hello, I'm "+ name);  
    8. }  
    9. speak(me.name);     //Hello, I'm fenfei  
    10.   
    11. //使用this,调用  
    12. function speak(){  
    13.     console.log("Hello, I'm "+ this.name);  
    14. }  
    15. speak.call(me);     //Hello, I'm fenfei  

    this提供了一种更优雅的方式来隐式“传递”对象引用,因此可以将API设计得更加简洁并易于复用。

    三. this的四大绑定规则

    1. 默认绑定--函数调用类型:独立函数调用,this指向全局对象。

    [javascript] view plain copy
    1. var a = "foo";  
    2. function foo(){  
    3.     console.log(this.a);  
    4. }  
    5. foo();  // "foo"  

    在严格模式下,全局对象将无法使用默认绑定,因此this会绑定到undefined。

    [javascript] view plain copy
    1. var a = "foo";  
    2. function foo(){  
    3.     "use strict";  
    4.     console.log(this.a);  
    5. }  
    6. foo();  // TypeError:this is undefined  

    2. 隐式绑定--调用位置是否有上下文对象,或者说被某个对象拥有或者包含

    [javascript] view plain copy
    1. function foo(){  
    2.     console.log(this.a);  
    3. }  
    4. var obj1 = {  
    5.     a : 2,  
    6.     foo : foo  
    7. }  
    8. var obj2 = {  
    9.     a : 1,  
    10.     obj1 : obj1  
    11. }  
    12. obj2.obj1.foo();    //结果:2  

    当foo()被调用时,它的落脚点指向obj1对象,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。
    对象属性引用链中只有最顶层或者说最后一次层会影响调用位置。

    注意:隐式丢失

    常见的this绑定问题就是“隐式绑定”的函数会丢失绑定对象,也就是“默认绑定”,从而把this绑定到全局对象(严格模式下为undefined)。

    [javascript] view plain copy
    1. var a = "foo";  
    2. function foo(){  
    3.     console.log(this.a);  
    4. }  
    5. var obj = {  
    6.     a : 2,  
    7.     foo : foo  
    8. }  
    9. var bar = obj.foo;  
    10. bar();  //"foo"  

    虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身,因此此时的bar()其实是不带任何修饰的函数调用,因此应用了默认绑定。

    [javascript] view plain copy
    1. var a = "foo";  
    2. function foo(){  
    3.     console.log(this.a);  
    4. }  
    5. function doFoo(fn){     //var fn = obj.foo  
    6.     fn();  
    7. }  
    8. var obj = {  
    9.     a : 2,  
    10.     foo : foo  
    11. }  
    12. doFoo(obj.foo); //"foo"  
    13.   
    14. setTimeout(obj.foo, 100);   //"foo"  

    参数传递其实就是一种隐式赋值,因此传入函数式会被隐式赋值(LHS)

    3. 显示绑定

    (1)call、apply
    (2)硬绑定

    [javascript] view plain copy
    1. function foo(){  
    2.     console.log(this.a);  
    3. }  
    4. var obj = { a:2 };  
    5. var bar = function(){  
    6.     foo.call(obj);  
    7. };  
    8. bar(); //2  
    9. setTimeout( bar, 1000); //2  
    10. bar.call( window ); //2  

    创建了函数bar(),在其内部手动调用了foo.call(obj),因此强制把foo的this绑定到了obj上。无论之后如何调用函数bar,总会手动在obj上调用foo。这种显示的强制绑定,称为“硬绑定”。

    4. new绑定

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

    四、优先级

    了解了函数调用中this绑定的四条规则,需要做的就是找到函数的调用位置并判断对应哪条规则。

    1. 函数是否是new绑定?如果是,this绑定的是新创建的对象。

    [javascript] view plain copy
    1. var bar = new Foo();  

    2. 函数是否通过call、apply显示绑定或硬绑定?如果是,this绑定的是指定的对象。

    [javascript] view plain copy
    1. var bar = foo.call(obj);  

    3. 函数是否在某个上下文对象中隐式调用?如果是,this绑定的是那个上下文对象。

    [javascript] view plain copy
    1. var bar = obj.foo();  

    4. 上述全不是,则使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局window对象。

    [javascript] view plain copy
    1. var bar = foo();  

    五、绑定this注意点

    1. 忽略this

    把null或undefined作为this的绑定对象传入call、apply、bind,调用时会被忽略,实际应用的是默认绑定规则!

    [javascript] view plain copy
    1. function foo(){  
    2.     console.log(this.a);  
    3. }  
    4. var a = 1;  
    5. foo.call(null, 2);          //1  
    6. foo.apply(undefined, [3]);  //1  

    2. 间接引用

    [javascript] view plain copy
    1. function foo(){  
    2.     console.log(this.a);  
    3. }  
    4. var a = 2;  
    5. var o = { a : 3,foo : foo};  
    6. var p = { a : 4};  
    7. o.foo();            //3  
    8. (p.foo = o.foo)();  //2 间接引用  
    9. var pfoo = o.foo;  
    10. pfoo();         //2 隐式丢失  

    注意:同上述“隐式丢失”结果一样,但是过程不太一样,区分!!

    3. ES6箭头函数

    箭头函数不使用this的四种标准规则,而是根据外层(函数或者全局)作用域来决定this。
    箭头函数的绑定无法被修改。常用于回调函数中,如事件处理器或定时器。和ES6之前代码中的this = self机制一样。

    [javascript] view plain copy
    1. function foo(){  
    2.     setTimeout(()=>{  
    3.         console.log(this.a);  
    4.     },100);  
    5. }  
    6. var obj = { a : 2};  
    7. foo.call(obj);  

    等价于:

    [javascript] view plain copy
    1. function foo(){  
    2.     var self = this;  
    3.     setTimeout(function(){  
    4.         console.log(self.a);  
    5.     },100);  
    6. }  
    7. var obj = { a : 2};  
    8. foo.call(obj);  

    六、详解如何确定this

    1.(隐式绑定)如果某个对象中某个成员是个function,当从这个对象上调用这个方法时this指向当前对象。

    [javascript] view plain copy
    1. var FenFei = {  
    2.     firstname:"li",  
    3.     lastname:"gang",  
    4.     timeTravel:function(year){  
    5.         console.log(this.firstname + " " + this.lastname + " is time traveling to " + year);  
    6.     }  
    7. }  
    8.   
    9. FenFei.timeTravel(2014);    //li gang is time traveling to 2014(父/拥有者对象:FenFei)  

    2.(隐私绑定)可以通过创建一个新的对象,来引用FenFei对象上的timeTravel方法。

    [javascript] view plain copy
    1. var Camile = {  
    2.     firstname:"li",  
    3.     lastname:"yunxia"  
    4. }  
    5.   
    6. Camile.timeTravel = FenFei.timeTravel;  
    7. Camile.timeTravel(2014);    //li yunxia is time traveling to 2014(父/拥有者对象:Camile)  

    注意:此示例同上述“隐式丢失”、“间接引用”区分!!!

    3.(隐式丢失)使用变量保存FenFei.timeTravel方法的引用

    [javascript] view plain copy
    1. var getTimeTravel = FenFei.timeTravel;  
    2. getTimeTravel(2014);    //undefined undefined is time traveling to 2014(父/拥有者对象:Window;window对象里并没有firstName和lastName属性)  

    PS:谨记方法中的this将指向调用它的那个父/拥有者对象!无论何时,当一个函数被调用,我们必须看方括号或者是圆括号左边紧邻的位置,如果我们看到一个引用(reference),那么传到function里面的this值就是指向这个方法所属于的那个对象,如若不然,那它就是指向全局对象的。

    4. 异步调用的方法内部的this

    [html] view plain copy
    1. <button id="async">点击我</button>  
    [javascript] view plain copy
    1. var btnDom = document.getElementById("async");  
    2. btnDom.addEventListener('click',FenFei.timeTravel);//undefined undefined is time traveling to [object MouseEvent](父/拥有者对象:button)  
    3.   
    4. btnDom.addEventListener('click',function(e){  
    5.     FenFei.timeTravel(2014);//li gang is time traveling to 2014(父/拥有者对象:FenFei)  
    6. });  

    5.(new绑定)构造函数里的this

    当使用构造函数创建一个对象的实例时,构造函数里的this就是新建的实例。

    [javascript] view plain copy
    1. var TimeTravel = function(fName, lName){  
    2.     this.firstname = fName;  
    3.     this.lastname = lName;  
    4. }  
    5.   
    6. var FenFei = new TimeTravel("li", "gang");  
    7. console.log(FenFei.firstname + " " + FenFei.lastname);      //li gang  

    6.(显示绑定)call和apply方法设定this值

    [javascript] view plain copy
    1. var Camile = {  
    2.     firstname:"li",  
    3.     lastname:"yunxia"  
    4. }  
    5.   
    6. FenFei.timeTravel.call(Camile,2014);        //li yunxia is time traveling to 2014(指定this对象为Camile)  
    7. FenFei.timeTravel.apply(Camile,[2014]); //li yunxia is time traveling to 2014(指定this对象为Camile)  

    PS:注意和上述“2”处做对比

    7.(显示绑定)bind将函数绑定至某个对象(ES5)

    [javascript] view plain copy
    1. function f(y){  
    2.     return this.x + y;  
    3. }  
    4. var o = {x:1};  
    5. /* f.bind(o)返回一个新函数,调用g(2)会把原始的函数f()当作o的方法来调用 */  
    6. var g = f.bind(o);    
    7. g(2);   // 3  

    补充:ES5之前方法模拟bind()方法

    [javascript] view plain copy
    1. function myBind(f, o){  
    2.     if(f.bind){  
    3.         return f.bind(o);  
    4.     }else{  
    5.         return function(){  
    6.             return f.apply(o, arguments);  
    7.         }  
    8.     }  
    9. }  

    bind的作用和apply,call类似都是改变函数的execute context,也就是runtime时this关键字的指向。但是使用方法略有不同。一个函数进行bind后可稍后执行。 

    七、补充

    1. 使用new为函数创建多个实例的时候,这些实例会共享prototype。

    当在一个实例里直接给this添加属性的时,会隐藏prototype中与之同名的属性。
    如果想访问prototype中的属性值而不是自己的设定的属性值:
    (1)删除实例自己添加的属性: delete 实例名.属性名
    (2)直接访问prototype中的属性:Thing.prototype.name

    [javascript] view plain copy
    1. function Thing(){}  
    2. Thing.prototype.name = "leegang";  
    3. Thing.prototype.getName = function(){  
    4.     console.log(this.name);  
    5. }  
    6. Thing.prototype.setName = function(newName){  
    7.     this.name = newName;  
    8. }  
    9. Thing.prototype.deleteName = function(){  
    10.     delete this.name;  
    11. }  
    12.   
    13. var thing = new Thing();    /*thing:Thing {}*/  
    14. thing.setName("liyunxia");  /*thing:Thing {name: "liyunxia"}*/  
    15. thing.getName();        //结果:liyunxia  
    16. thing.deleteName();     /*thing:Thing {}*/  
    17. thing.getName();        //结果:leegang    thing.__proto__为Thing {name: "leegang"}  
    18.   
    19. thing.name = "liyunxia";    /*Thing {name: "liyunxia"}*/  
    20. thing.getName();        //结果:liyunxia  
    21. delete thing.name;      /*thing:Thing {}*/  
    22. thing.getName();        //结果:leegang thing.__proto__为Thing {name: "leegang"}  

    2. 柯里化

    把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,如果其他的参数是必要的,返回接受余下的参数且返回结果的新函数

    [javascript] view plain copy
      1. var sum = function(x, y){  
      2.     return x + y;  
      3. }  
      4. var succ = sum.bind(null, 1);  
      5. succ(2);    //3  
      6.   
      7. function f(y, z){  
      8.     return this.x + y +z;  
      9. }  
      10. var g = f.bind({x:1}, 2);  
      11. g(3);   //6  
  • 相关阅读:
    poj 3616 Milking Time
    poj 3176 Cow Bowling
    poj 2229 Sumsets
    poj 2385 Apple Catching
    poj 3280 Cheapest Palindrome
    hdu 1530 Maximum Clique
    hdu 1102 Constructing Roads
    codeforces 592B The Monster and the Squirrel
    CDOJ 1221 Ancient Go
    hdu 1151 Air Raid(二分图最小路径覆盖)
  • 原文地址:https://www.cnblogs.com/wyaocn/p/5774429.html
Copyright © 2011-2022 走看看