• javascript 面向对象学习(三)——this,bind、apply 和 call


    this 是 js 里绕不开的话题,也是非常容易混淆的概念,今天试着把它理一理。

    this 在非严格模式下,总是指向一个对象,在严格模式下可以是任意值,本文仅考虑非严格模式。记住它总是指向一个对象对于理解它的意义很重要。this 在实际使用中,大致分为以下几种情况:

    1. 函数作为对象的方法调用时,this 指向调用该函数的对象

    var obj = {
        name: 'jack',
        getName: function() {
            console.log(this === obj) // true
            console.log(this.name)  // jack
        }
    }
    obj.getName()

    这个应该很好理解,不多说了。

    2. 函数作为普通函数被调用时,this 指向全局对象。在浏览器中,全局对象是window。

    var name = 'global'
    function getName() {
        console.log(this === window) // true
        console.log(this.name) // global
    }
    getName()

    我的理解是上面的代码可以改写为

    window.name = 'global'
    window.getName = function() {
        console.log(this === window) // true
        console.log(this.name) // global
    }
    window.getName()

    这样其实与情况1是一样的,相当于函数作为对象的方法调用,只不过这里的对象是全局对象。

    《Javascript 设计模式与开发实践》一书中有个例子如下:

    window.name = 'globalName';
    var myObject = {
        name: 'seven',
        getName: function(){
            return this.name
        } 
    }
    
    var getName = myObject.getName
    console.log(getName())  // globalName

    getName 是定义在myObject 对象中的方法,在调用getName 方法时,打印出的却是全局对象的name,而不是myObject对象的name,这再次证明了 this 并非指向函数被声明时的环境对象,而是指向函数被调用时的环境对象

    3. 函数作为构造函数调用时,指向构造出的新对象

    function Person(name) {
        this.name = name  
    }
    
    var jack = new Person('Jack')
    console.log(jack.name) // Jack
    var rose = new Person('Rose')
    console.log(rose.name) // Rose

    这里创建了两个不同名字的对象,打印出的name也是不一样的,说明构造函数的 this 会根据创建对象的不同而变化。需要注意的是,如果构造函数里返回了一个Object类型的对象,那么this会指向这个对象,而不是利用构造函数创建出的对象。我们在构造函数一章里也提到过,new 操作符所做的最后一步就是返回新对象,而如果我们显式地返回一个对象,就会覆盖这步操作,this也就不再指向新对象。

    4. 函数作为事件处理函数调用时,指向触发事件的元素

    document.getElementById("myBtn").addEventListener("click", function(e){
        console.log(this === e.currentTarget) // true
    });

    5. 箭头函数

    由于箭头函数没有this,它的 this 是继承父执行上下文里面的 this。执行上下文后面再讨论,现在只要知道简单对象(非函数)是没有执行上下文的。

    var obj = {
        name:  'obj',
        getName: function() {
    console.log(this) // 执行上下文里的 this
    return (()=>{ console.log(this.name) }) } } var fn = obj.getName() fn() // obj

    按照情况2来处理的话,this 指向全局对象,应该输出 undefined,结果并不是。与普通函数不同,箭头函数的 this 是在函数被声明时决定的,而不是函数被调用时。在这里,父执行上下文是 getName 函数,也就继承了 getName 的 this,即 obj。

    利用 bind、apply、call 改变 this 指向

    bind、apply、call 都是定义在 Function 原型对象上的方法,所有函数对象都能继承这个方法,三者都能用来改变 this 指向,我们来看看它们的联系与区别。

    function fn() {
        console.log(this.name)
    }
    
    // bind
    var bindfn = fn.bind({name: 'bind'})
    bindfn() // bind
    
    // apply
    fn.apply({name: 'apply'}) // apply
    
    // call
    fn.call({name: 'call'}) // call

    我们定义了一个函数fn,然后分别调用了它的 bind、apply、call 方法,并传入一个对象参数,通过打印出的内容可以看到 this 被绑定到了参数对象上。bind 似乎有些不同,多了一步 bindfn() 调用,这是因为 bind 方法返回的是一个函数,不会立即执行,而调用 apply 和 call 方法会立即执行。

    下面再来看一下 fn 函数存在参数的情况:

    function fn(a, b, c) {
        console.log(a, b, c)
    }
    
    var bindfn = fn.bind(null, 'bind');
    bindfn('A', 'B', 'C');           // bind A B
    
    fn.apply(null, ['apply', 'A']) // apply A undefined
    
    fn.call(null, 'call', 'A');  // bind A undefined

    bindfn 打印出的结果是fn调用bind方法时的传递的参数加上bindfn传递的参数,参数 'C' 被舍弃掉了。调用 apply 和 call 方法打印出的则是传递给它们的参数,不一样的是,apply 的参数是一个数组(或类数组),call 则是把参数依次传入函数。这时候再看它们的定义应该会好理解很多:

    bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

    apply() 方法调用一个具有给定 this 值的函数,以及作为一个数组(或类数组对象)提供的参数。

    call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

    我们可以利用它们来借用其他对象的方法。已知函数的参数列表 arguments 是一个类数组对象,比如上例中函数 fn 的参数 a, b, c,因为它不是一个真正的数组,不能调用数组方法,这时借用 apply/call 方法(bind 也可以,就是用得比较少)将 this 指向 arguments 就能借用数组方法:

    (function(){
        Array.prototype.push.call(arguments, 'c')
        console.log(arguments) // ['a', 'b', 'c']
    })('a','b')

    值得一提的是,push 方法并不是只有数组才能调用,一个对象只要满足1.可读写 length 属性;2.对象本身可存取属性. 就可以利用 call / apply 调用 push 方法。

    参考:

    《Javascript 设计模式与开发实践》

    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/this

    http://www.imooc.com/article/80117

    https://blog.csdn.net/weixin_42519137/article/details/88053339

  • 相关阅读:
    Linux CPU监控指标
    Elasticsearch强大的聚合功能Facet
    业务逻辑层的设计
    数据结构中的棧在C#中的实现
    使用WPF教你一步一步实现连连看
    ASP.NET之旅—再一次与ASP谋面
    每日一帖示例程序(使用TWebBrowser基于HTML做)
    在程序异常中记录堆栈信息(使用ExWatcher)
    获取TBitMap图像缓冲区,提高图像处理速度
    delphi实现穿XP防火墙
  • 原文地址:https://www.cnblogs.com/zdd2017/p/13095713.html
Copyright © 2020-2023  润新知