热搜:前端 nest neovim nvim

js-this指向、call、apply、bind

lxf2023-05-30 02:25:49

一、this

概述:

this是 JavaScript 语言的一个关键字。它是函数运行时,在函数体内部自动生成的一个对象,只能在函数体内部使用。

函数的不同使用场合,this有不同的值。总的来说,this就是函数运行时所在的环境对象。下

1. 纯粹的函数调用(默认绑定)

非严格模式下this指向全局对象window,严格模式下函数内的this指向undefined

        // 非严格模式
        var name = 'window';
        var test = function () {
            console.log(this.name);
        }
        test(); // 'window'
        
        let name2 = 'window2';
        let test = function () {
            console.log(this === window);
            console.log(this.name2);
        }
        test() // true, undefined
        
        // 严格模式
        'use strict'
        var name = 'window';
        var test = function () {
            console.log(typeof this === 'undefined');
            console.log(this.name);
        }
        test(); // true,// 报错,因为this是undefined

注意: 如果把 var 改成了 letconst,变量是不会被绑定到window上的,所以此时会打印出三个undefined

2. 作为对象方法的调用(隐式绑定)

函数还可以作为某个对象的方法调用,这时this就指这个上级对象。

        var obj = {
            a: 2,
            foo1: function () {
                console.log(this.a) // 2
            },
            foo2: function () {
                setTimeout(function () {
                    console.log(this) // window
                    console.log(this.a) // 3
                }, 0)
            }
        }
        var a = 3

        obj.foo1()
        obj.foo2() 

对于setTimeout中的函数,这里存在隐式绑定的this丢失,也就是当我们将函数作为参数传递时,会被隐式赋值,回调函数丢失this绑定,因此这时候setTimeout中函数内的this是指向window

3. 构造函数调用

所谓构造函数,就是通过这个函数,可以生成一个新对象。这时,this就指这个新对象。

        function Student(name) {
            this.name = name;
            console.log(this); // {name: '小明'}
            // 相当于返回了
            // return this;
        }
        var result = new Student('小明');

4. 箭头函数调用

箭头函数和普通函数的重要区别:

  1. 没有自己的thissuperargumentsnew.target绑定。
  2. 不能使用new来调用。
  3. 没有原型对象。
  4. 不可以改变this的绑定。
  5. 形参名称不能重复。

箭头函数中没有this绑定,必须通过查找作用域链来决定其值。 如果箭头函数被非箭头函数包含,则this绑定的是最近一层非箭头函数的this,否则this的值则被设置为全局对象。

        var obj = {
            name: 'obj',
            foo1: () => {
                console.log(this.name) // window
            },
            foo2: function () {
                console.log(this.name) // obj
                return () => {
                    console.log(this.name) // obj
                }
            }
        }
        var name = 'window'
        obj.foo1()
        obj.foo2()()

二、 call、apply、bind (显示绑定)

三者的区别:

  1. 三者都可以显式绑定函数的this指向
  2. 三者第一个参数都是this要指向的对象,若该参数为undefined或null,this则默认指向全局window
  3. 传参不同:apply是数组、call是参数列表,而bind可以分为多次传入,实现参数的合并
  4. call、apply是立即执行,bind是返回绑定this之后的函数,如果这个新的函数作为构造函数被调用,那么this不再指向传入给bind的第一个参数,而是指向新生成的对象

2.1 call、apply

语法: fun.call(thisArg, arg1, arg2, ...)

thisArg

fun函数运行时指定的this值。需要注意的是,指定的this值并不一定是该函数执行时真正的this值,如果这个函数处于非严格模式下,则指定为nullundefinedthis值会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象。

arg1, arg2, ...

指定的参数列表
返回值
返回值是你调用的方法的返回值,若该方法没有返回值,则返回undefined

call 和 apply 不同点: apply只接收两个参数,第二个参数可以是数组也可以是类数组,其实也可以是对象,后续的参数忽略不计。call接收第二个及以后一系列的参数。

        // 例子1:浏览器环境 非严格模式下
        var test = function (a, b) {
            console.log(this);
            console.log([a, b]);
        }
        test.apply(null, [1, 2]); // this是window  // [1, 2]
        test.apply(0, [1, 2]); // this 是 Number(0) // [1, 2]
        test.apply(true); // this 是 Boolean(true) // [undefined, undefined]
        test.call(undefined, 1, 2); // this 是 window // [1, 2]
        test.call('0', 1, { a: 1 }); // this 是 String('0') // [1, {a: 1}]
        
         // 例子2:浏览器环境 严格模式下
        'use strict';
        var test2 = function (a, b) {
            console.log(this);
            console.log([a, b]);
        }
        test2.call(0, 1, 2); // this 是 0 // [1, 2]
        test2.apply('1'); // this 是 '1' // [undefined, undefined]
        test2.apply(null, [1, 2]); // this 是 null // [1, 2]

(1) 模拟实现 call

思路分析:

  1. 执行调用call方法函数
  2. 改变this指向 也就是指向第一个参数
  3. 返回执行函数后的返回值
        Function.prototype.myCall = function (context) {
            // context为undefined或null时,则this默认指向全局window
            if (context === undefined || context === null) {
                context = window;
            }
            // var args = []
            // for (var i = 1; i < arguments.length; i++) {
            //     args.push(`arguments[${i}]`)
            // }
            // var fn = createUUID()

            // es6 写法
            var args = [...arguments].slice(1);
            var fn = Symbol();
                
            context[fn] = this;  // this就是调用者 添加到第一个参数上
            
            // var result = eval(`context[fn](${args})`)
            var result =  context[fn](...args) // 谁调用this指向谁  这样就this指向传入的第一个参数
            delete context[fn] 
            return result // 返回执行后的返回值
        }

        // 测试代码
        var obj = { name: 'obj' }
        function fn(a, b) {
            console.log(this.name);
            return a + b
        }
        fn.myCall(obj, 1, 2)

        // 生成UUID 通用唯一识别码
        function createUUID() {
            return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
                var r = Math.random() * 16 | 0,
                    v = c == 'x' ? r : (r & 0x3 | 0x8);
                return v.toString(16);
            });
        }

(2) 模拟实现 apply

唯一的不同点就是call是传多个参数 apply传入的是数组

         Function.prototype.myApply = function (context, args) {
            if (context === undefined || context === null) {
                context = window;
            }
            var fn = Symbol();
            context[fn] = this;
            var result =  context[fn](...args)
            delete context[fn]
            return result
        }

        // 测试代码
        var obj = { name: 'obj' }
        function fn(a, b) {
            console.log(this.name);
            return a + b
        }
        fn.myApply(obj, [1, 2])

2.2 bind

思路分析:

  1. 函数调用bind()方法返回一个函数
  2. 原函数使用bind()方法绑定函数之后,原函数可以作为构造函数使用的。
     Function.prototype.myBind = function (context, ...args) {
            if (typeof this !== 'function') {
                throw new Error('The bind method is not available');
            }

            if (context === undefined || context === null) {
                context = window;
            }
            // this 原函数(谁调的bind方法)    fn唯一的键
            let self = this
            let fn = Symbol()

            // 要返回的函数  需要调用
            const result = function (...args1) {
                if (this instanceof self) {
                    // 返回函数作为构造函数调用,this指向新new出的实例对象
                    this[fn] = self
                    let res = this[fn](...args, ...args1)
                    delete this[fn]
                    return res
                } else {
                    // 返回函数作为普通函数被调用
                    context[fn] = self;
                    let res = context[fn](...args, ...args1)
                    delete context[fn]
                    return res
                }
            }
            // 继承原函数的原型对象的属性方法
            result.prototype = Object.create(self.prototype);
            return result
        }

        // 测试代码
        var name = '炒米粉';
        var obj = {
            name: '程序员米粉'
        };
        function fn(age, weight) {
            console.log(this.name);
            console.log(age, weight);
            this.phoneNumber = '188888888'
        }

        fn.prototype.haveMoney = 'NO';
        var getFn = fn.myBind(obj, 18);
        // 通过使用bind()方法返回的绑定函数,可以使用new创建实例对象。fn的this不再指向obj,而是新创建的对象newObj
        var newObj = new getFn(150);
        // fn函数
        // this.name => undefined;
        // age, weight => 18, 50
        console.log(newObj.haveMoney); // => 'NO'
        console.log(newObj.phoneNumber); // => '188888888'

this原理 阮一峰老师

深化this理解

本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!