资讯专栏INFORMATION COLUMN

JavaScript学习之Object(下)this

liuyix / 2320人阅读

摘要:为什么用能够动态的取值,实现了隐式传递一个对象的引用上面的例子中想在中取的值用就能简单搞定一定程度上实现了动态取值这个就是绑定了,这个就是上下文对象也就是调用栈是什么始终是一个对象,函数在被调用时发生绑定,具体绑定了什么看调用的位置,有一定

this 为什么用this
this能够动态的取值,实现了隐式传递一个对象的引用
        var obj = {
            a : {

                b : 2,
                c : function () {
                        console.log(this.b);
                    }
            }
        }

上面的例子中想在c中取b的值用this就能简单搞定,一定程度上实现了动态取值;obj.a.c(),这个this就是绑定了obj.a,这个就是上下文对象也就是调用栈

this是什么

this始终是一个对象,函数在被调用时发生绑定,具体绑定了什么看调用的位置,有一定的绑定规则

绑定规则

默认绑定
当函数不带任何修饰被调用时,运用默认绑定,this就是window全局对象;但要注意的是在严格模式下,则不行,this会绑定成undefined

function f() {
    //"use strict"
    console.log(this.a);
}
var a = 123;
f();        //123
function f() {
    "use strict"
    console.log(this.a);
}
var a = 123;
f();        //报错,undefined不能够添加属性

隐式绑定
当上下文对象(调用栈)调用函数时,会隐式绑定this,哪个对象调用就绑定谁

    function f() {
        console.log(this.a);
    }
    var obj = {
        a : 123,
        b : f,
    }
    obj.b();
  

上面例子实际上就是this = obj,而不是this = obj.a
这是最简单的,但是这种绑定常常会发生隐式丢失,而采用默认绑定

    function f() {
        console.log(this.a);
    }
    var obj = {
        a : 123,
        b : f,
    }
    var a = 345
    var f1 = obj.b;   //采取函数别名而发生了隐式丢失
    f1();             //345,这等同于直接执行了f函数

我的理解是函数取别名是最好赋值上下文对象,也就是调用栈(var f1 = b)
例如下面例子也是隐式丢失

    function f() {
        console.log(this.a);
    }
    var obj = {
        a : 123,
        b : f,
    }
    function fn(f) {        //等同于setTimeout(obj.b, 100)
        f();
    }
    fn(obj.b);

要注意这种别名:

function f() {
    console.log(this.a);
}
var obj = {a : 1};
obj.fn = f;
obj.fn()

实际上就是:

function f() {
    console.log(this.a);
}
var obj = {
    a : 1,
    fn : f,
};
obj.fn()

显示绑定
javascript提供了3种call,apply,bind;作用都是改变this的指向

call
可以填入多个参数

func.call(thisValue, arg1, arg2, arg3,...)

第一个参数是this要绑定的对象,若参数为空,undefined,null,则默认绑定window;若参数为原始值,则this绑定对应的包装类对象
后面参数则是传入func的参数

    function f(n, m) {
        console.log(n, m, this.a);
    }
    var obj = {a : 1};
    f.call(obj, 3, 4);     //3 4 1
    function f() {
        console.log(this);
    }
    f.call("123");       //String {"123"}

apply
apply与call类似只是参数不同,就是将传入原函数的参数变成数组形式

func.call(thisValue, [arg1, arg2, arg3,...])

apply与call可以这样理解:就是thisValue借用func函数功能来实现效果
类似于thisValue.fn(arg1, arg2, arg3...)
利用这一特点可以实现有趣的效果

找数组中最大的数

var arr = [11, 2, 3, 4, 5];
var nmax = Math.max.apply(null, arr);   //11

null的作用是因为用不到this,用null代替,仅用后面的参数,有柯里化的感觉
建议我们一般定义一个比null还空的空对象:var ø = Object.create(null)

将数组中的空元素变成undefined,这个样就可遍历了,其属性描述的enumerable:true

var arr = [11, , 3, 4, 5];
var arr1 = Array.apply(null, arr);  //[11, undefined, 3, 4, 5]

转换类数组对象,利用Array的实例方法slice,前提是必须有length属性

var arr = Array.prototype.slice.apply({0 :1, 1 : 2, length : 3});  //[1, 2, empty]
var arr = Array.prototype.slice.apply({0 :1});  //[]
var arr = Array.prototype.slice.apply({length : 1});  //[empty]

bind
bind函数参数形式和call类似

func.bind(thisValue, arg1, arg2, arg3,...)

我对他的理解是对原函数进行改造,生成自己的新函数,主要改造就是this绑定成thisValue,并且可以固定部分参数,当然后面arg选填
类似于thisValue.fn
我们可以自定义一个简单bind函数:

function myBind(fn, obj) {
    return function () {
        fn.apply(obj, arguments);
    }    
}
function f(n) {
    console.log(n + this.a)
}
var obj = {a : 1};
var bind = myBind(f, obj);
bind(4);               //5
    

原生态bind用法也类似

function f(n,m) {
    console.log(this.a + n + m);
}
var obj = {a : 1};
var bar = f.bind(obj, 2);
bar(3);    //6

注意点bind每次返回一个新函数,在监听事件时要注意,不然romove不掉

element.addEventListener("click", o.m.bind(o));
element.removeEventListener("click", o.m.bind(o));

因为o.m.bind(o)返回的时新函数,所以remove的也不是开启时的函数了
正确做法:添加一个值,记录开启时的函数

var listener = o.m.bind(o)
element.addEventListener("click", listener);
element.removeEventListener("click", listener);

有趣的地方:
利用call与bind实现原始方法

var slice = Function.prototype.call.bind(Array.prototype.slice);
slice([1, 2, 3], 1);   //[2, 3]

可以拆分bind看

Array.prototype.slice.Function.prototype.call([1, 2, 3], 1)

其中Function.prototype.call 就是call方法

Array.prototype.slice.call([1, 2, 3], 1)

拆分call

[1, 2, 3].Array.prototype.slice(1)

而Array.prototype.slice就是slice
所以

[1, 2, 3].slice(1)

个人理解可以看成,Array.prototype.slice实现了slice功能,Function.prototype.call实现了arguments中this的绑定以及参数的带入。所以函数最总调用时显示:slice([1, 2, 3], 1);

同理
var push = Function.prototype.call.bind(Array.prototype.push);
var pop = Function.prototype.call.bind(Array.prototype.pop);

同时bind也能被改写

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

var obj = { a : 123 };
var bind = Function.prototype.call.bind(Function.prototype.bind);
bind(f, obj)() // 123

new的this绑定
当用new来指向函数F时,函数变成构造函数F,this也会发生变化
this = Object.create(F.prototype)
具体new的功能可看我的new篇

绑定优先级
new绑定 > 显示绑定 > 隐式绑定 > 默认绑定


隐式绑定与默认绑定比较

function f() {
        console.log(this.a);
    }
    var obj = {
    a : 123,
        f : f,
    }
var a = 456;
    obj.f();  // 123

obj.f()覆盖了f(),因此隐式大于默认

隐式绑定与显示绑定比较

function f() {
    console.log(this.a);
}
var obj = {
    a : 123,
    f : f,
}
var obj1 = {
    a : 1123
}
obj.f.call(obj1);   //1123

由输出结果可以看出:call绑定的obj1覆盖了obj,所以显示大于隐式

显示绑定与new绑定

function f(n) {
    this.a = n;
    console.log(this);
}
var obj = { b : 2};
var bar = f.bind(obj);
console.log(bar(2));
console.log(new bar(2));
//{b:2,a:2}
//undfined
//{a: 2}
//{a: 2}

由输出结果可以看出:new bar(2),{a:2}说明是新生成的空对象,添加了a的属性,其次输出两个说明函数返回了一个this对象也就是{a:2},而不是undefined
所以new大于显示

使用注意点

注意多层this*

var obj = {
  f: function () {
    console.log(this);
    var f1 = function () {
      //console.log(this);
    }();
  }
}

obj.f();
//{f:func}
//winodw

因为传统的this没有继承机制,所以这个匿名函数的this没有任何修饰,采取默认绑定
有两种方法解决

var obj = {
  f: function () {
    var self= this
    console.log(self);
    var f1 = function () {
      //console.log(self);
    }();
  }
}

obj.f();
//{f:func}
//{f:func}
var obj = {
      f: function () {
        console.log(this);
        return () => {
              console.log(this);
        };
      }
}

obj.f()();
//{f:func}
//{f:func}

注意处理数组时使用this

var obj = {
    a : 123,
    arr : [1, 2, 3],
    f : function () {
        this.arr.forEach(function (elem) {
            console.log(this.a, elem);
        });
        
    }
}
var a = "this是window";
obj.f();
//this是window 1
//this是window 2
//this是window 3

forEach中的this指向的是window
解决办法:

   var obj = {
   a : 123,
   arr : [1, 2, 3],
   f : function () {
       this.arr.forEach(function (elem) {
           console.log(this.a, elem);
       },this);
       
   }
   }
   var a = "this是window";
   obj.f();
   //123 1
   //123 2
   //123 3

还有一种就是赋值给一个变量self暂时保存,给遍历时用,如同上面的self

注意回调函数中的this

var obj = new Object();
obj.f = function () {
  console.log(this === obj);
}


$("#button").on("click", obj.f);   //false

this指向的是DOM对象而不是obj,这个有点难以察觉,需要注意
解决办法:硬绑定obj对象

  var obj = new Object();
  obj.f = function () {
    console.log(this === obj);
  }
  function fn(){
    obj.f.apply(obj);
  }
  
  $("#button").on("click", fn);   //true
  //$("#button").on("click", o.f.bind(obj));   //true

上面的硬绑定相当于固定了this的对象,不会变了。
我们可以做个软绑定

if(!Function.prototype.sorfBind) {
    Function.prototype.sorfBind = function (obj) {
        //这个this:执行sorfBind时的调用位置绑定的this
        var fn = this;                
        var arg1 = [].slice.call(arguments, 1);
        var bound = function() {
            //这个this:执行bound时绑定的this
            return fn.apply(
                //arg1.concat.call(arg1, arguments 作用是整合两次执行传入的参数
                //(!this || this === (window || global)) 猜测是为了在严格模式下也试用
                (!this || this === (window || global)) ? obj : this, arg1.concat.call(arg1, arguments)   //arguments是bound函数执行时参数的参数,
                );
        };
        bound.prototype = Object.create(fn.prototype);
        return bound;
    }
}
function f() {
    console.log("name: " + this.name);
}
var obj = {name : "obj"},
    obj1 = {name : "obj1"},
    obj2 = {name : "obj2"};
var fObj = f.sorfBind(obj);
fObj();                      //name : obj
obj1.foo = f.sorfBind(obj);
obj1.foo();                  //name : obj1
fObj.call(obj2);             //name : obj2
setTimeout(obj1.foo, 10);    //name : obj

简单谈谈箭头函数对this的影响

初次绑定this后,箭头函数内部的this就固定了

function f() {
    return (b) => {
        console.log(this.a);
    }
}
var obj = {a : 1};
var obj1 = {a : 2};
var fn = f.call(obj);

fn();                //1
fn.call(obj1);       //1

与第一点类似,初次绑定后内部this固定了也就有了继承

function f() {
    setTimeout(() => {
        console.log(this.a);
    },10);
}
var obj = {a : 1};
f.call(obj);    //1

还有一点,不建议使用的属性也会改变this的指向

arguments.callee 也有改变this指向

function f() {
    console.log(this);
    if(this ==window) {
        arguments.callee();
    }
}
f(); 
//window对象
//arguments对象

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/98163.html

相关文章

  • JavaScript习之Object()new命令

    摘要:命令作用作用是执行构造函数,返回实例对象上面例子是自定义一个构造函数,其最大的特点就是首字母大写,用执行构造函数其中,在的执行下,代表了实例化后的对象,这个也就有属性注意点如果不用执行构造函数,那么指向的是全局有两种方式可以避免内部定义严格 new命令 new作用 作用是执行构造函数,返回实例对象 function F() { this.name = object } var ...

    Salamander 评论0 收藏0
  • JavaScript习之对象原型及继承

    摘要:原型要掌握这三者之间的关系,通过代码例子记录一下自身属性的这里就是通过代码看一下做了什么默认情况下,将的所有属性包括继承的赋值给有什么东西呢自己的原型链,添加一个属性,用来指明对象的谁构造的自身全部属性,这边构建一个空对象原型,所以没有自有 原型 要掌握这三者之间的关系prototype,constructor,__proto__通过代码例子记录一下 function F() { ...

    妤锋シ 评论0 收藏0
  • JavaScript习之对象拷贝

    摘要:对象拷贝可遍历属性浅拷贝简单的浅拷贝可以用,对存取器定义的对象也试用深拷贝属性描述对象的拷贝这是个浅拷贝深拷贝不可遍历属性对象的拷贝例如拷贝获得共同的原型,与是兄弟关系说明也继承了原型,同级别简洁化版 对象拷贝 可遍历属性 浅拷贝 if(typeof Object.prototype.copy != function) { Object.prototype.copy = fun...

    Aklman 评论0 收藏0
  • JavaScript习之Object()相关方法

    摘要:它不区分该属性是对象自身的属性,还是继承的属性。那么我们要遍历对象所有属性,包括继承以及不可遍历的属性,用加原型遍历实现类似的用递归 Object静态方法 Object自身方法,必须由Object调用,实例对象并不能调用 Object.getPrototypeOf() 作用是获取目标对象的原型 function F() {}; var obj = new F(); console.lo...

    amuqiao 评论0 收藏0
  • Javascript习之继承

    摘要:继承是面向对象编程语言中的一个重要的概念,继承可以使得子类具有父类的属性和方法或者重新定义追加属性和方法等。但是在中没有类的概念,是基于原型的语言,所以这就意味着对象可以直接从其他对象继承。 继承是面向对象编程语言中的一个重要的概念,继承可以使得子类具有父类的属性和方法或者重新定义、追加属性和方法等。但是在Javascript中没有类的概念,是基于原型的语言,所以这就意味着对象可以直接...

    CHENGKANG 评论0 收藏0
  • Javascript习之创建对象

    摘要:在中,除了几种原始类型外,其余皆为对象,,既然对象如此重要,那就列举一下在中如何创建对象通过构造函数创建对象实例对象字面量对象字面量是对象定义的一种简写形式,目的在于简化创建包含大量属性的对象的过程。 在Javascript中,除了几种原始类型外,其余皆为对象(Object,Array ...),既然对象如此重要,那就列举一下在Javascript中如何创建对象: 通过Object构造...

    jollywing 评论0 收藏0

发表评论

0条评论

liuyix

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<