JavaScript 函数内部this的各种指向

JavaScript 函数内部this的各种指向

1、引言

this是js中的一个关键字,随着函数使用场合的不同,this的值也会发生变化。当我们在不使用箭头函数call()方法、apply()方法、bind()方法的情况下,在调用函数的时候,这些函数内部this的指向是不确定的。调用函数方式的不同,决定了this指向的不同

​ 但是,总有一个原则,那就是this总是指向调用函数的那个对象

2、code例子

2.1 普通函数

普通模式:

        //this最终指向的是调用它的对象,这里的函数fun实际是被Window对象所点出来的
		function fun() {
            console.log(this);
        };
        fun();//输出window
		//写成Window.fun() 也是一样的!

严格模式:

		// 开启严格模式
        "use strict";

        function fun() {
            console.log(this);
        };
        fun();//输出undefinde
		// 因为从严格意义上来讲它是自己调用的,而不是window

2.2 匿名函数

普通模式:

		//this最终指向的是调用它的对象,这里的函数fun实际是被Window对象所点出来的
		let fun = function() {
            console.log(this);
        };
        fun();//输出window
		//写成Window.fun() 也是一样的!

严格模式:

		 // 开启严格模式
        "use strict";

        let fun = function() {
            console.log(this);
        };
        fun();//输出undefinde
		// 因为从严格意义上来讲它是自己调用的,而不是window

2.3 构造函数

		// 构造函数
        function Person(name) {
            // 实例属性
            this.name = name;
            console.log(this);
        }
        // 原型函数
        Person.prototype.speak = function() {
            console.log(this);
        };
        // 实例对象,调用构造函数
        let son = new Person("儿子");
        // 调用原型方法
        son.speak();
		// 输出Person,即实例对象

2.4 对象方法调用

		// 构造函数
        function Person(name) {
            // 实例属性
            this.name = name;
            // 实例方法
            this.speak = function() {
                console.log(this);
            }
        }
        // 实例对象,调用构造函数
        let son = new Person("儿子");
        // 调用实例方法
        son.speak();
		// 输出Person,即实例对象

2.5 事件绑定函数

<body>
    <div></div>
    <script>
        var box = document.querySelector("div");
        box.addEventListener("click", function() {
            console.log(this);
        })
        // 输出div标签,因为是这个事件绑定在div身上,同时也是div触发的它
    </script>
</body>

2.6 定时器函数

  window.setTimeout(function() {
      		// 输出window,因为不管是炸弹定时器还是循环执行计时器都是由window点出来的,有时候我们只是省略的window而已
            console.log(this);
        }, 1000)

2.7 立即执行函数

普通模式:

(function fun() {
    		// 输出window,因为子调用函数其实与普通函数的原理是一样的,只不过它是直接调用了而已
            console.log(this);
        })()

严格模式:

// 开启严格模式
"use strict";
(function fun() {
    		// 输出undefined,因为严格来讲,它是自己调用的,而并非window
            console.log(this);
        })()

3、总结

调用方式this指向
普通函数严格模式下是undefined,正常模式是Window
匿名函数严格模式下是undefined,正常模式是Window
构造函数实例对象
对象方法调用该方法所属的对象
事件绑定方法当前事件所绑定的对象
定时器函数Window
立即执行函数(自调用函数)严格模式下是undefined,正常模式是Window

注意:

  1. 如果一个函数中有this,但是它没有被上一级的对象所调用,那么this指向的就是window,这里需要说明的是在js的严格版中this指向的不是window。
  2. 如果一个函数中有this,这个函数有被上一级的对象所调用,那么this指向的就是上一级的对象。
  3. 如果一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象。

4、改变函数内部 this 的指向

​ 每个函数或者方法都默认拥有三个非继承而来的方法:call()apply()bind()。这三个方法的作用都是一样的。即:在特定的作用域中调用函数,等于设置函数体内的this的指向,以扩充函数赖以运行的作用域。 一般来说,this总是指向调用某个方法的对象,但是使用这三个方法后,就会改变this的指向。比如在ES5类的继承中,我们总是使用call()方法来借调基类的构造函数来实现借调继承。请看下方code实例:

例1:

  		// window对象下的属性
		window.number = 'one';
		// document对象下的属性
        document.number = 'two';
		// 字面量方式创建对象,并添加一个属性
        var s1 = {number: 'three' };
        // 定义一个普通方法
        function changeColor(){
            //谁调用它,它就打印谁的numbe属性
            console.log(this.number);
        }

        changeColor.apply();         //one (默认传参传递window对象)
        changeColor.apply(window);   //one
        changeColor.apply(document); //two 将documnet文档对象作为参数传递
        changeColor.apply(this);     //one 在全局作用域下this就代表window
        changeColor.apply(s1);       //three

例2:ES5基于原型的组合继承

// 基类构造函数
        function Father(name, age) {
            // 实例属性定义在构造函数内部
            this.name = name;
            this.age = age;
            // 判断原型中的该属性是否是函数类型的,也就是判断原型中是否有该方法
            if ((typeof Father.prototype.speak) !== "function") {
                // 方法定义在原型中
                Father.prototype.speak = function() {
                    console.log(`我叫${this.name},今年${this.age}岁了`)
                }
            }
        }

        // 派生类构造函数
        function Son(name, age, sex) {
            // 调用基类的构造函数,相当于把基类中的属性添加到未来的派生来实例对象中
            // 将Father基类中的this指向改变为son的实例,并将基类所需要的参数传递
            Father.call(this, name, age);
            // 实例对象属性
            this.sex = sex;
        }

        // 修改派生类的原型,这样就可以继承基类原型中的方法
        Son.prototype = new Father();
        // 实例化son,并将基类所需要的参数一并传递
        var son = new Son("小明", 20, "男");
        console.log(son.name);
        console.log(son.age)
        son.speak();

5、异同点

共同的:都可以改变this的指向

不同点:

1.	`call()`和`apply()`都会调用函数,并且改变函数内部的`this`指向
2.	`call()`和`apply()`传递的参数不一样,`call()`传递参数使用逗号隔开,而`apply()`使用数组传递。`bind()`与`call()`参数一致
3.	`bind()`不会立马调用函数,而会创建一个**新的函数(称为绑定函数)**, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,提供一个给定的参数序列。光说相信大家肯定不会明白,那么就看下方code:

例1:

function f(y, z){
    return this.x + y + z;
}
// 使用bind方法将{x:1}对象作为this传递,同时也传递2,没有立马调用,而是生成一个新的函数
var m = f.bind({x : 1}, 2);
// 调用函数并将最后一个参数传递
console.log(m(3));

分析:

1.	`bind()`会把它的第一个实参绑定给f函数内的this,那么这里this的指向为`{x:1}`对象
2.	然后将第二个参数传递进去,对应f函数内部的y参数,这里并没有立马调用,而是生成了一个新的函数m,这就是`bind()`的特别之处
3.	最后调用新生成的m函数,并将最后一个参数3传递到函数f中,对应f函数内的参数z
4.	最后执行的结果为:1+2+3=6
5.	分步处理参数的过程其实是一个典型的函数柯里化的过程(Curry)

例2:

  var a = document.write;
  a('hello'); //Error
  a.bind(document)('hello');//hello
  a.call(document, 'hello');//hello

分析:

  1. var a = document.write;将文档对象的write方法放入变量a中,但是放入变量a中,我们打印一下,看变成了什么

在这里插入图片描述

  1. 那么既然变成了window.write(),那么采用a('hello');这样的方式调用,肯定抛异常,因为window对象下没有这个方法
  2. a.bind(document)('hello')===a.call(document, 'hello')这两种调用方式相同,只是写法不同而已。先将document文档对象作为参数传递给bind(),此时window.write()的指向就变成了document.write(),那么再次传递字符串参数就是作为参数传递到了document.write()方法中,那么就写入到了文档对象中

6、应用场景

  1. call():在ES5中经常做组合继承.
  2. apply():经常跟数组有关系. 比如借助于数学对象实现数组最大值最小值
  3. bind() :不想立马调用函数,并且想隐式创建一个新的函数
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

御弟謌謌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值