阅读 244

this、call、apply、bind的理解及使用

1、this指向的四种情况

1.1、"new" 出实例

function Person(name) {   this.name = name   console.log(this) }  const person = new Person('test-text') // this指向当前person实例对象 复制代码

1.2、默认指向window

function fn() {   console.log(this) }  fn() // 浏览器window,node里global 复制代码

1.3、对象调用方法

const target = {   fn: function () { console.log(this) } } target.fn() // target  const fn = target.fn fn() // 浏览器window,node里global   let obj = {   a: function () {     console.log(this)   }, } obj.a() //打出的是obj对象  const obj = {   // 箭头函数中   a: () => {      console.log(this)   },   b: this } console.log('obj', obj)  // {b: Window, a: ƒ} obj.a()  //打出来的是window 复制代码

1.4、call、apply、bind改变this

const obj1 = {   name: 'test',   sayName: function() {     console.log(this.name)   } } const obj2 = {   name: 'test-text' } // 改变sayName的this指向obj2 obj1.sayName.call(obj2) // test-text  // 改变sayName的this指向obj2 obj1.sayName.apply(obj2) // test-text  // 改变sayName的this指向obj2 const fn = obj1.sayName.bind(obj2) // 还需手动调用 fn() // test-text 复制代码

总结:

1、普通函数中,this指向最终调用者

2、箭头函数中,默认绑定自己当前作用域链的上一层this

2、call/apply/bind的基本介绍

2.1、语法:

fun.call(thisArg, param1, param2, ...) fun.apply(thisArg, [param1,param2,...]) fun.bind(thisArg, param1, param2, ...) 复制代码

2.2、返回值:

call / applyfun执行的结果;

bind:返回fun的拷贝(还需手动调用),并拥有指定的this值和初始参数

2.3、区别:

2.3.1、call与apply的唯一区别,传给fun的参数写法不同:

  • call从第2~n的参数都是传给fun的;

  • apply是第2个参数,这个参数是一个数组:传给fun参数都写在数组中。

2.3.2、call/apply与bind的区别

  • call / apply改变了函数的this上下文后马上执行该函数

  • bind则是返回改变了上下文后的函数,不执行该函数

2.4、核心理念:借用方法

程序中:

A对象有个方法,B对象因为某种原因也需要用到同样的方法,那么这时候我们是单独为 B 对象扩展一个方法呢,还是借用一下 A 对象的方法呢?

当然是借用 A 对象的方法啦,既达到了目的,又节省了内存。

这就是call/apply/bind的核心理念:借用方法

借助已实现的方法,改变方法中数据的this指向,减少重复代码,节省内存。

3、call和apply的应用场景

这些应用场景,多加体会就可以发现它们的理念都是:借用方法

3.1、判断数据类型:

四种常见的判断数据类型的方法,可以看一下我的另外一篇文章:

JS中8种数据类型、4种类型检测方法总结

Object.prototype.toString用来判断类型再合适不过,借用它我们几乎可以判断所有类型的数据:

function isType(data, type) {     const typeObj = {         '[object String]': 'string',         '[object Number]': 'number',         '[object Boolean]': 'boolean',         '[object Null]': 'null',         '[object Undefined]': 'undefined',         '[object Object]': 'object',         '[object Array]': 'array',         '[object Function]': 'function',         '[object Date]': 'date', // Object.prototype.toString.call(new Date())         '[object RegExp]': 'regExp',         '[object Map]': 'map',         '[object Set]': 'set',         '[object HTMLDivElement]': 'dom', // document.querySelector('#app')         '[object WeakMap]': 'weakMap',         '[object Window]': 'window',  // Object.prototype.toString.call(window)         '[object Error]': 'error', // new Error('1')         '[object Arguments]': 'arguments',     }     let name = Object.prototype.toString.call(data)      // 借用Object.prototype.toString()获取数据类型     let typeName = typeObj[name] || '未知类型' // 匹配数据类型     return typeName === type // 判断该数据类型是否为传入的类型 } console.log(     isType({}, 'object'), // true     isType([], 'array'), // true     isType(new Date(), 'object'), // false     isType(new Date(), 'date'), // true ) 复制代码

3.2、类数组借用数组的方法:

类数组因为不是真正的数组所有没有数组类型上自带的种种方法,所以我们需要去借用数组的方法。

比如借用数组的push方法:

var arrayLike = {   0: 'OB',   1: 'Koro1',   length: 2 } Array.prototype.push.call(arrayLike, '添加元素1', '添加元素2'); console.log(arrayLike)  // {"0":"OB","1":"Koro1","2":"添加元素1","3":"添加元素2","length":4} 复制代码

3.3、apply获取数组最大值最小值:

apply直接传递数组做要调用方法的参数,也省一步展开数组,比如使用Math.maxMath.min来获取数组的最大值/最小值:

const arr = [15, 6, 12, 13, 16]; const max = Math.max.apply(Math, arr); // 16 const min = Math.min.apply(Math, arr); // 6 复制代码

3.4、继承:

ES5的继承也都是通过借用父类的构造方法来实现父类方法/属性的继承:

// 父类 function supFather(name) {     this.name = name;     this.colors = ['red', 'blue', 'green']; // 复杂类型 } supFather.prototype.sayName = function (age) {     console.log(this.name, 'age'); };  // 子类 function sub(name, age) {     // 借用父类的方法:修改它的this指向,赋值父类的构造函数里面方法、属性到子类上     supFather.call(this, name);     this.age = age; }  // 重写子类的prototype,修正constructor指向 function inheritPrototype(sonFn, fatherFn) {     sonFn.prototype = Object.create(fatherFn.prototype); // 继承父类的属性以及方法     sonFn.prototype.constructor = sonFn; // 修正constructor指向到继承的那个函数上 } inheritPrototype(sub, supFather); sub.prototype.sayAge = function () {     console.log(this.age, 'foo'); };  // 实例化子类,可以在实例上找到属性、方法 const instance1 = new sub("OBKoro1", 24); const instance2 = new sub("小明", 18); instance1.colors.push('black')  console.log(instance1)  // {"name":"OBKoro1","colors":["red","blue","green","black"],"age":24}  console.log(instance2)  // {"name":"小明","colors":["red","blue","green"],"age":18}  复制代码

类似的应用场景还有很多,就不赘述了,关键在于它们借用方法的理念,不理解的话多看几遍。

3.5、call、apply,该用哪个?

call,apply的效果完全一样,它们的区别也在于

  • 参数数量/顺序确定就用call,参数数量/顺序不确定的话就用apply

  • 考虑可读性:参数数量不多就用call,参数数量比较多的话,把参数整合成数组,使用apply

  • 参数集合已经是一个数组的情况,用apply,比如上文的获取数组最大值/最小值。

参数数量/顺序不确定的话就用apply,比如以下示例:

const obj = {     age: 24,     name: 'OBKoro1', } const obj2 = {     age: 777 } callObj(obj, handle) callObj(obj2, handle)  // 根据某些条件来决定要传递参数的数量、以及顺序 function callObj(thisAge, fn) {     let params = []     if (thisAge.name) {         params.push(thisAge.name)     }     fn.apply(thisAge, params) // 数量和顺序不确定 不能使用call } function handle(...params) {     console.log('params', params) // do some thing } 复制代码

4、bind的应用场景

4.1. 保存函数参数:

首先来看下一道经典的面试题:

for (var i = 1; i <= 5; i++) {    setTimeout(function test() {         console.log(i) // 依次输出:6 6 6 6 6     }, i * 1000); } 复制代码

造成这个现象的原因是等到setTimeout异步执行时,i已经变成6了。

关于js事件循环机制不理解的,可以看这篇博客:

Js 的事件循环(Event Loop)机制以及实例讲解

那么如何使他输出: 1,2,3,4,5呢?

方法有很多:

4.1.1、闭包, 保存变量

for (var i = 1; i <= 5; i++) {     (function (i) {         setTimeout(function () {             console.log('闭包:', i); // 依次输出:1 2 3 4 5         }, i * 1000);     }(i)); } 复制代码

在这里创建了一个闭包(就是能够读取其他函数内部变量的函数),每次循环都会把i的最新值传进去,然后被闭包保存起来。

4.1.2、bind

for (var i = 1; i <= 5; i++) {     // 缓存参数     setTimeout(function (i) {         console.log('bind', i) // 依次输出:1 2 3 4 5     }.bind(null, i), i * 1000); } 复制代码

实际上这里也用了闭包,我们知道bind会返回一个函数,这个函数也是闭包

它保存了函数的this指向、初始参数,每次i的变更都会被bind的闭包存起来,所以输出1-5

4.1.3、let

let声明i也可以输出1-5,因为let块级作用域,所以每次都会创建一个新的变量,所以setTimeout每次读的值都是不同的。


作者:沃斯桢蒂帥
链接:https://juejin.cn/post/7038768983146758180


文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐