阅读 221

微信小程序this的挂载&获取闭包函数的this

挂载在页面和组件的this区别

挂载在page的this

Page({
    something: "挂载在page的this变量",

    onLoad() {
        console.log("page this内容", this);
        this.handleOther();
    },

    handleOther() {},
});

复制代码

image-20210702112146601

something和handleOther都有,并且原型proto也存了一份。

挂载在component的this

Component({
    something: "挂载在component的this变量",
    lifetimes: {
        attached() {
            this.componentTest = "componentTest";
            this.handleOther();
            console.log("component this内容", this);        
        },
    },
    methods: {
        handleOther() {},
    },
});

复制代码

image-20210702111901448

只有componentTest变量,没有something变量。且方法存在了原型_proto_上。

影响:如果认为跟UI渲染相关的数据存储在this.data对象上,而不需要用来渲染的数据,不放在this.data对象的开发者来说(这样某种程度可以加快渲染,减轻this.data的体量)。如果是把数据存储在this对象上,需要区分是page还是compenent:

  1. 如果是page可以直接Page({xx: yy}),这时候可以通过this.xx来获取;
  2. 如果是component,不能直接Component({xx:yy}),需要在页面初始化的时候,手动绑定在this对象上,如attached() {this.xx = yy;}

小程序Component组件在闭包函数内获取不到正确的this

父组件wxml

<testComponent value="{{1}}" />
<testComponent value="{{2}}" />
<testComponent value="{{3}}" />
复制代码

获取不到this的情况

Component({
    properties: {
        value: {
            type: Number,
            value: 0,
        },
    },

    lifetimes: {
        attached() {
            this.handleTest(this.data.value);
        },
    },

    methods: {
        handleTest: (function () {
            let instance = undefined;
            return function (value) {
                if (!instance) {
                    instance = function (value) {
                        console.log("test", value, this);
                    };
                }
                instance(value);
            };
        })(),
    },
});
复制代码

image-20210702162827057

获取到同一个this的情况

Component({
    properties: {
        value: {
            type: Number,
            value: 0,
        },
    },
    lifetimes: {
        attached() {
            this.handleTest(this.data.value);
        },
    },
    methods: {
        handleTest: (function () {
            let instance = undefined;
            return function (value) {
                if (!instance) {
                    instance = (value) => {	// 箭头函数创建时确定this指向
                        console.log("test", value, this, this.data.value);
                    };
                }
                instance(value);
            };
        })(),
    },
});
复制代码

image-20210805113348726

方案1

运行时执行闭包函数,在函数外部传递this值给内部函数。

缺点:通过传值的方式,欠缺优雅。

Component({
    properties: {
        value: {
            type: Number,
            value: 0,
        },
    },

    lifetimes: {
        attached() {
            this.handleTest()(this.data.value);	// 注意点
        },
    },

    methods: {
        handleTest: function () {
            let instance = undefined;
            const that = this;	// 注意点
            return function (value) {
                if (!instance) {
                    instance = function (value) {
                        console.log("test", value, that);	// 注意点
                    };
                }
                instance(value);
            };
        },
    },
});
复制代码

image-20210702161036546

方案2

闭包函数执行时,调用call方法传递this。

缺点:通过传值的方式,欠缺优雅。

Component({
    properties: {
        id: {
            type: Number,
            value: 0,
        },
    },

    lifetimes: {
        attached() {
            this.handleTest(this.id);
        }
    },
    
    methods: {
        handleTest: (function () {
            let instance = undefined;
            return function (id) {
                if (!instance) {
                    instance = function (id) {	// 注意点
                        console.log("test", id, this);
                    };
                }
                instance.call(this, id);	// 注意点
            };
        })(),
    },
});

复制代码

方案3

把instance变量定义成全局的,全局能拿到this。

缺点:instance实际是handleTest函数的业务(内部变量),只会在handleTest函数里面用,没必要定义成全局变量。

Component({
    properties: {
        value: {
            type: Number,
            value: 0,
        },
    },

    lifetimes: {
        attached() {
            this.instance = undefined;	// 注意点
            this.handleTest(this.data.value);
        }
    },

    methods: {
        handleTest: (function () {	// 这里也可以不用再包一层函数
            return function (value) {
                if (!this.instance) {
                    this.instance = function (value) {	// 注意点
                        console.log("test", value, this);
                    };
                }
                this.instance(value);	// 注意点
            };
        })(),
    },
});
复制代码

结合防抖节流函数的几种测试案例

可以获取到变化的this,但无节流效果(每执行一次handleTest函数都打印一次日志,非3秒内只打印一次):

handleTest: (() => {
    let instance = undefined;
    return function (value) {
        if (!this.instance) {
            instance = ThrottleUtil.throttle(3000, (value) => {
                console.log('test', this);
            });
        }
        instance(value);
    };
})(),
复制代码

通过传递this参数,有节流效果,但this获取不到。

handleTest: (() => {
    return ThrottleUtil.throttle(3000, (value, that) => {	// 此时的value是小程序action回调函数的evt参数,并不是this.data.value值
        console.log('test', value, that);
    });
})(),
复制代码

通过传递this参数,有节流效果,但this获取不到。

handleTest: ThrottleUtil.throttle(3000, (value, that) => {	// 此时的value是小程序action回调函数的evt参数,并不是this.data.value值
    console.log('test', value, that);
}),
复制代码

image-20210805171802572

不传递this参数,闭包,有节流效果,this也能正确获取。

handleTest: (function () {
    let instance = undefined;
    if (!instance) {
        instance = ThrottleUtil.throttle(
            3000,
            function (value) {
                console.log('this', value, this, this.data.value);	// 第一个参数value是小程序action回调函数的evt参数,并不是this.data.value值
            }
        );
    }
    return instance;
})(),
复制代码

image-20210813180629917

export default class ThrottleUtil {
    /**
     * 节流
     * 每隔一段时间,只执行一次函数。
     * @param delay 判定时间
     * @param action 实际方法
     * @param options 选项对象:
     * {
     *      toggleLastArg // boolean。在delay时间内多次触发的话,是否选择最后一次触发的参数来执行,默认为false(只触发第一次点击)
     * }
     * @returns {Function} 回调,回调实际的方法
     */
    static throttle = (delay, action = () => {}, options) => {  // 箭头函数
        let last = 0;
        const toggleLastArg = !!(options && options.toggleLastArg);
        let toggleLastArgTimer = null;
        // eslint-disable-next-line func-names
        return function (...args) {  // 匿名函数
            if (toggleLastArgTimer) clearTimeout(toggleLastArgTimer);
            const curr = new Date().getTime();
            if (curr - last > delay) {
                action.call(this, ...args);  // 用call把this绑定在回调函数上
                last = curr;
            } else if (toggleLastArg) {
                toggleLastArgTimer = setTimeout(() => {
                    action.call(this, ...args);
                }, delay);
            }
        };
    }

    /**
     * 防抖
     * 定义:多次触发事件后,事件处理函数只执行一次,并且是在触发操作结束时执行
     * 原理:对处理函数进行延时操作,若设定的延时到来之前,再次触发事件,则清除上一次的延时操作定时器,重新定时。
     * @param delay 判定时间
     * @param action 实际方法
     * @returns {Function} 回调,回调实际的方法
     */
    static debounce = (delay, action = () => {}) => {
        let timer = null;
        // eslint-disable-next-line func-names
        return function (...args) {
            if (timer) {
                clearTimeout(timer);
            }
            timer = setTimeout(() => {
                action.call(this, ...args);
            }, delay);
        };
    }
}

复制代码
参考资料

bind/call/apply区别:

三个函数存在的区别, 用一句话来说的话就是: bind是返回对应函数(每一次返回一个新函数), 便于稍后调用; apply, call则是立即调用. 除此外, 在 ES6 的箭头函数下, call 和 apply 的this绑定失效,即就算手动call\apply绑定this,也不会改变它的指向。

箭头函数的this:

  • 函数体内的 this 对象, 就是定义时所在的对象, 而不是使用时所在的对象;
  • 不可以当作构造函数, 也就是说不可以使用 new 命令, 否则会抛出一个错误;
  • 不可以使用 arguments 对象, 该对象在函数体内不存在. 如果要用, 可以用 Rest 参数代替;
  • 不可以使用 yield 命令, 因此箭头函数不能用作 Generator 函数;
文章分类
前端
文章标签