Jacky's Blog Jacky's Blog
  • 首页
  • 关于
  • 项目
  • 大事记
  • 留言板
  • 友情链接
  • 分类
    • 干货
    • 随笔
    • 项目
    • 公告
    • 纪念
    • 尝鲜
    • 算法
    • 深度学习
首页 › 干货 › JavaScript Function binding

JavaScript Function binding

Jacky
10月 10, 2021干货阅读 2,937
目录
  1. 在 setTimeout 中
    1. 解决方法1:使用匿名函数包装
    2. 解决方法2: bind
  2. 在 Vue 组件中
  3. 参考资料

前几天在优化代码的时候遇到了一个很奇怪的问题,当使用 Function 作为 Vue 组件的 Prop 时传入一个对象内的函数(如 this.$api.user.get 时,这个函数将会丢失 this,换句话来说是函数内的 this 由原来的对象 user 变成了 Vue 实例 (vm),就会造成函数无法调用原本自身对象的属性或函数。经过查阅一些资料后发现,这个问题也出现在 setTimeout 中,并且有解决方案。

在 setTimeout 中

如下写法会造成 sayHi 最终打印出来的值是 Hello, undefined!

let user = {
  firstName: "John",
  sayHi() {
    alert(`Hello, ${this.firstName}!`);
  }
};

setTimeout(user.sayHi, 1000); // Hello, undefined!

如果尝试在控制台中打印 this

会发现 this 指向的是 window(在 Vue 中指向的是 $vm)

而 firstName 在 window 中是 undefined,所以打印的结果是 Hello, undefined!

解决方法1:使用匿名函数包装

当采用下面的写法时,可以正确打印出 Hello, John!

let user = {
  firstName: "John",
  sayHi() {
    alert(`Hello, ${this.firstName}!`);
  }
};

setTimeout(() => user.sayHi(), 1000); // Hello, John!

但这样的写法也存在问题,比如下面这个例子。

let user = {
  firstName: "John",
  sayHi() {
    alert(`Hello, ${this.firstName}!`);
  }
};

setTimeout(() => user.sayHi(), 1000);

// ...the value of user changes within 1 second
user = {
  sayHi() { alert("Another user in setTimeout!"); }
};

// Another user in setTimeout!

如果在 setTimeout 的一秒内,user.sayHi 函数发生了改变,则上面打印的最终结果是 Another user in setTimeout!

下面的解决方法就不会出现这个问题。

解决方法2: bind

Javascript 提供内置的方法 bind 用于固定 this

这是它的基础语法

// more complex syntax will come a little later
let boundFunc = func.bind(context);

比如下面这个例子,我们为 func 手动指定 this,让他作为一个普通函数可以调用对象内的函数和方法。

let user = {
  firstName: "John"
};

function func() {
  alert(this.firstName);
}

let funcUser = func.bind(user);
funcUser(); // John

再来看看刚才的例子

let user = {
  firstName: "John",
  sayHi() {
    alert(`Hello, ${this.firstName}!`);
  }
};

let sayHi = user.sayHi.bind(user); // (*)

// can run it without an object
sayHi(); // Hello, John!

setTimeout(sayHi, 1000); // Hello, John!

// even if the value of user changes within 1 second
// sayHi uses the pre-bound value which is reference to the old user object
user = {
  sayHi() { alert("Another user in setTimeout!"); }
};

使用 bind 函数后,就算1秒内改变了 user 对象,我们也能得到按顺序打印的结果。

在 Vue 组件中

为了复现这个问题,我们构造一个组件 Test

<template>
    <p>
        {{ func() }}

        {{ obj }}
    </p>
</template>

<script>
export default {
    name: 'Test',
    props: {
        func: Function,
        obj: Object,
    },
    created() {}
}
</script>

然后在另一个组件中调用它

<template>
    <test :obj="obj" :func="func"/>
</template>

<script>
import Test from '@/views/other/Test'

class myClass {
    name =  ''
    myFunc = this._myFunc
    constructor(name) {
        this.name = name
    }

    _myFunc() {
        console.log(this)
        console.log(this.name)
        return this.name
    }
}

const obj = new myClass('0xJacky')

export default {
    name: 'About',
    components: {Test},
    data() {
        return {
            obj: obj,
            func: obj.myFunc
        }
    }
}
</script>

在控制台中会出现 _myFunc() 的打印及错误,可以很清楚的看到 this 不再是 obj 而是 $vm,并且因为 $vm 里没有 name 这个属性而出现了错误。

JavaScript Function binding-Jacky's Blog

在页面中可以看到传入 obj 渲染的数据情况,直接传入 Object 时,this 不发生改变。

JavaScript Function binding-Jacky's Blog

下面我们稍微用 bind 改造一下代码

在 myFunc = this._myFunc 后面加上 .bind(this)

<template>
    <test :obj="obj" :func="func"/>
</template>

<script>
import Test from '@/views/other/Test'

class myClass {
    name =  ''
    myFunc = this._myFunc.bind(this)
    constructor(name) {
        this.name = name
    }

    _myFunc() {
        console.log(this)
        console.log(this.name)
        return this.name
    }
}

const obj = new myClass('0xJacky')

export default {
    name: 'About',
    components: {Test},
    data() {
        return {
            obj: obj,
            func: obj.myFunc
        }
    }
}
</script>

然后再回到刚才的页面

控制台打印的 this 就是 myClass

JavaScript Function binding-Jacky's Blog

页面也能渲染出正确的数据了

JavaScript Function binding-Jacky's Blog

参考资料

Function binding: https://javascript.info/bind

赞(1)
本文系作者 @Jacky 原创发布在 Jacky's Blog。未经许可,禁止转载。
air 实时热更新 Go 应用
上一篇
Nginx UI
下一篇
再想想
所有评论(4)
  • AmsChen

    即使是setTimeout(fn, 0)也一样

    1年前 回复
  • AmsChen

    setTimeout那个问题和js事件循环有关,setTimeout是宏任务,其回调是在其它微任务执行完成后才执行的,你原来代码真实的执行顺序应该是下面这个

    let user = {
    firstName: “John”,
    sayHi() {
    alert(`Hello, ${this.firstName}!`);
    }
    };

    user = {
    sayHi() { alert(“Another user in setTimeout!”); }
    };

    user.sayHi();

    1年前 回复
  • CSJerry

    call 跟 apply 也可以哦

    1年前 回复
    • Jacky

      @CSJerry: 好的哥哥

      1年前 回复
近期评论
  • Jacky发表在《Nginx UI》
  • daiwenzh5发表在《Nginx UI》
  • Jacky发表在《Nginx UI》
  • daiwenzh5发表在《Nginx UI》
  • Jacky发表在《Nginx UI》
4 1
  • 1
  • 4
Copyright © 2016-2023 Jacky's Blog. Designed by nicetheme.
粤ICP备16016168号-1
  • 首页
  • 关于
  • 项目
  • 大事记
  • 留言板
  • 友情链接
  • 分类
    • 干货
    • 随笔
    • 项目
    • 公告
    • 纪念
    • 尝鲜
    • 算法
    • 深度学习
# Mac # # Apple # # OS X # # iOS # # macOS #
Jacky
PHP C C++ Python | 舞象之年 | 物联网工程
174
文章
169
评论
267
喜欢