【前端进阶】call、apply、bind

Function.prototype.call()

💪  作用:

        可实现继承、方法的绑定、this值的绑定等,具体看示例

🤔  区别:

该方法的语法和作用与 apply()方法类似,但就是只有一个区别:
        就是 call() 方法接受的是一个参数列表
        而 apply() 方法接受的是一个包含多个参数的数组

⚠️  注意:当没有传入任何参数作为this值时,this的值将会绑定为全局对象。(严格模式下为undefined)

🏠  返回值:

        调用call方法的函数的返回值,如果没有返回值,则返回 undefined。

⭐️  用法示例:

1、用call调用父构造函数,实现继承

function Father(home, hobby){
    this.home = home
    this.hobby = hobby
}

function Son(home, hobby, weight, dead){
    father.call(this, home, hobby, weight) 
    this.weight = weight
}

console.log(new Son('Shen Zhen','football','60kg','dead'))
// Son {home: 'Shen Zhen', hobby: 'football', weight: '60kg'}
// 可以看到,Son的home和hobby都继承了Father的,
// 而Son也拥有着自己的weight,但是father和Son都没有让他们自己的this.dead = dead,
// 所以Son也没有dead。

2、用call调用匿名函数,可以让数组元素作为this值,这样每个数组元素就都拥有了匿名函数内的方法

const studentsArr = [{a:'小红', b:'小蓝'},{a:'小紫', b:'小绿'}]

for(let i = 0; i < studentsArr.length; i++){
    // 匿名函数,立即调用
    (function(i){
        this.get = function(){
            console.log(`第${i}组的人有${JSON.stringify(studentsArr[i])}`)
        }

        // 每个数组元素调用get方法
        this.get()
    }).call(studentsArr[i],i) 
    // 调用call方法,传入数组的每个元素作为this值,传入索引i作为argument。
    // 在匿名函数内让this绑定get方法,即让每个数组元素都有了get方法。
}
// 第0组的人有{"a":"小红","b":"小蓝"}
// 第1组的人有{"a":"小紫","b":"小绿"}

3、改变函数的this指向

function boyFriend(){
    console.log(this.love)
    return 'xxx'
}

const girlFriend = {
    love: 'each other'
}

boyFriend.call(girlFriend) // 返回值为 'xxx'
// each other
// 当然这里girlFriend.call(boyFriend)是会报girlFriend.call is not a function的(笑)

⭐️  原理及实现思路:

        在Function.prototype上定义myCall方法,保存新对象(context)的function,再给新对象(context)指定一个调用myCall方法的function作为需要调用的函数,结束调用完之后将新对象(context)的function还原。注意入参及返回值,比如call方法的第一个参数是thisArg,第二个及之后的参数则为指定的参数列表。

⭐️  具体代码:

Function.prototype.myCall = function(context){
  // 这里不需要再判断是因为我们在Function.prototype上定义的myCall
  // if(typeof this !== 'function'){
  //     throw new TypeError('Not a Function')
  // }
  ctx = ctx || window
  const tempFunction = ctx.function
  ctx.function = this

  let args = Array.from(arguments).slice(1)
  const result = ctx.function(...args)
  
  ctx.function = tempFunction
  return result
}

⭐️  结合例子理解:

// 结合以下例子进行理解
function boyFriend() {
  console.log(arguments) // 这里arguments即myCall里的args
  return 'returnValue'
}

const girlFriend = {
  love: 'each other',
  function: function() {
    console.log('function')
  }
}

const otherFriends = {
  noLove: 'otherFriends'
}

Function.prototype.myCall = function (context) {
  // 不传参数context(上下文)默认为window
  context = context || window
  // context: { love: 'each other', function: [Function: boyFriend] }

  // 保存ctx.function
  const tempFunction = ctx.function
  
  // 保存this,this指向[Function: boyFriend],即boyFriend这个函数
  context.function = this  

  // 保存参数arguments为args,arguments是一个对应于传递给函数的参数的类数组对象。
  // 同时用Array.from把(伪数组)对象转为数组 
  const args = Array.from(arguments).slice(1)
  // [Arguments] {
  //   '0': { love: 'each other', function: [Function: boyFriend] },
  //   '1': { noLove: 'otherFriends' }
  // }
  // 如果不slice,则boyFriend的console.log(arguments)会输出以上Arguments
  // 和call方法有出入。

  // 使用新对象(context)来调用函数
  const result = context.function(...args)

  // 还原ctx.function
  ctx.function = tempFunction
  
  return result
}

const myCallReturn = boyFriend.myCall(girlFriend, otherFriends) 
console.log(myCallReturn)  // 返回值为 'returnValue' 
console.log(girlFriend.function) // [Function: function]

console.log('compared with call method.')
const callReturn = boyFriend.call(girlFriend, otherFriends) 
console.log(callReturn)  // 返回值为 'returnValue' 
console.log(girlFriend.function) // [Function: function]

// 输出为
// [Arguments] { '0': { noLove: 'otherFriends' } }
// returnValue
// [Function: function]
// compared with call method.
// [Arguments] { '0': { noLove: 'otherFriends' } }
// returnValue
// [Function: function]

 📖  友情链接:

        Function.prototype.call() - JavaScript | MDN

        Arguments 对象 - JavaScript | MDN


Function.prototype.apply()

⚠️  建议先看call方法,再看apply方法

💪  作用:

        可实现节省循环的数组合并、将函数和一个对象构造器链接起来等,具体看示例

🤔  区别:

该方法的语法和作用与 apply()方法类似,但就是只有一个区别:
        就是 call() 方法接受的是一个参数列表
        而 apply() 方法接受的是一个包含多个参数的数组

⚠️  注意:当没有传入任何参数作为this值时,this的值将会绑定为全局对象。(严格模式下为undefined)

🏠  返回值:

        调用apply方法的函数的返回值。比如push返回数组新的length,那么apply也返回数组新的length。

⭐️  用法示例:

1、用 apply 将数组各项添加到另一个数组

const arr_1 = [1,2,3]
const arr_2 = [4,5,6]
arr_1.push.apply(arr_1,arr_2)
// 6
// 返回值是push的返回值——数组长度
console.log(arr_1)
// [1, 2, 3, 4, 5, 6]

// 然鹅一般我都是这样写的
arr_1.push(...arr_2)
console.log(...arr_2)
// 4 5 6
console.log(arr_1)
// [1, 2, 3, 4, 5, 6]

// 对于一些需要写循环以遍历数组各项的需求,我们可以用 `apply` 完成以避免循环。
// 比如求最小最大值,但还是要注意:如果按上面方式调用 `apply`,有超出 JavaScript 引擎参数长度上限的风险。

2、 将函数和一个对象构造器链接起来

// 在Function的prototype原型上定义一个construct(构造)方法
Function.prototype.construct = function (aArgs) {
  // 创建一个对象构造器
  let oNew = Object.create(this.prototype);
  // 使用apply将对象构造器和参数链接起来
  this.apply(oNew, aArgs);
  // 返回构造器
  return oNew;
};

// 定义函数MyConstructor
function MyConstructor() {
  for (let nProp = 0; nProp < arguments.length; nProp++) {
    this['property' + nProp] = arguments[nProp];
  }
}

let myArray = [4, 'Hello world!'];
// 让MyConstructor函数调用函数原型上的construct方法
let myInstance = MyConstructor.construct(myArray);

// 输出第n个属性,myInstance的原型是否为MyConstructor的原型,以及myInstance的构造器
console.log(myInstance.property0);                // logs  4
console.log(myInstance.property1);                // logs 'Hello world!'
console.log(myInstance instanceof MyConstructor); // logs 'true'
console.log(myInstance.constructor);              // logs 'MyConstructor'
// 这里因为MyConstructor.construct()出来的等于来myInstance,所以myInstance的constructor(构造器)是MyConstructor

⭐️  原理及实现思路:

        在Function.prototype上定义myApply方法,保存新对象(context)的function,再给新对象(context)来根据arguments的区别来给context的function传入不同的参数,结束调用完之后将新对象(context)的function还原。注意入参及返回值。

⭐️  结合例子理解:

// 弄清楚和call的区别,第二个参数是一个数组或者类数组对象,那么就很好实现了
Function.prototype.myApply = function (context) {
  context = context || window
  const tempFunction = context.function
  context.function = this

  let result
  if (arguments[1]) {
    result = context.function(...arguments[1])
  } else {
    result = context.function()
  }
  context.function = tempFunction
  return result
}

const array = ['a', 'b'];
const elements = [0, 1, 2];
array.push.myApply(array, elements);
console.info(array)
// [ 'a', 'b', 0, 1, 2 ]

// [Arguments] {
//   '0': [ 'a', 'b', function: [Function: push] ],
//   '1': [ 0, 1, 2 ]
// }

📖  友情链接:

          Function.prototype.apply() - JavaScript | MDN


Function.prototype.bind()

💪  作用:

        创建一个新的函数,可绑定新函数的this指向、使新函数拥有预设的参数等,具体看示例

⚠️  注意:如果 bind 函数的参数列表为空,或者thisArgnullundefined,执行作用域的 this 将被视为bind创建出来的新函数的 thisArg

🏠  返回值:

        返回一个原函数的拷贝,并拥有指定的 this 值和初始参数。

⭐️  用法示例:

1、创建绑定this指向的函数(我们把MDN里的举例分开来看,会更清晰)

// 在浏览器中,this 指向全局的 "window" 对象
this.x = 9;

// 创建一个作用域存放值为81的x
const modules = {
  x: 81,
  getX: function() { return this.x; }
};

// 错误示范举例:在全局作用域中调用函数
const retrieveX = modules.getX; // this指向了全局的 "window" 对象
retrieveX(); // 9

// 正确示范举例:在modules里调用函数getX
modules.getX(); // 81

使用bind

// 在浏览器中,this 指向全局的 "window" 对象
this.x = 9;

const modules = {
  x: 81,
  getX: function() { return this.x; }
};

const retrieveX = module.getX;

// boundGetX是一个新的函数,他的this指向modules
const boundGetX = retrieveX.bind(modules);
boundGetX(); 
// 81

2、创建偏函数(拥有预设参数的函数,预设参数会在传入参数的前面)

function list() {
  return Array.prototype.slice.call(arguments);
}

// 创建一个函数,它拥有预设参数列表。
const leadingThirtysevenList = list.bind(null, 37);

const list2 = leadingThirtysevenList();
// [37]

const list3 = leadingThirtysevenList(1, 2, 3);
// [37, 1, 2, 3]
function addArguments(arg1, arg2) {
    return arg1 + arg2
}

const result1 = addArguments(1, 2); // 3


// 创建一个函数,它拥有预设的第一个参数
const add_37 = addArguments.bind(null, 37); //这里为了好看命名为add_37了

const result2 = add_37(5);
// 37 + 5 = 42

const result3 = add_37(5, 10);
// 37 + 5 = 42 ,第二个参数被忽略

3、“拯救”setTimeout的this指向

var xxx = 1 // 这里得用var才能把xxx放到window对象里,具体涉及到const和let的原理
// this指向了window对象
setTimeout(()=>{
    const xxx = 2
    console.log(this.xxx)
},1000)
// 1

使用bind

// 创建一个 Son 函数, son 属性设置为'love'
function Son() {
  this.son = 'love';
}

// 在 Son 的原型上定义 father 函数
Son.prototype.father = function() {
  // this指向Son实例
  // 类的方法中this指向类的实例时,把this绑定到回调函数,就不会丢失Son这个类的实例的引用
  window.setTimeout(this.mother.bind(this), 1000);
};

// 在son的原型上定义mother函数
Son.prototype.mother = function() {
  console.log('we have ours ' + this.son + '!');
};

// 执行son原型上的father函数
const son = new Son()
son.father(); // father函数在一秒钟后,调用mother方法
// we have ours love!

4、覆盖new出来的原本的this

function Father(x, y) {
  this.x = x;
  this.y = y;
}

Father.prototype.toString = function() {
  return this.x + ',' + this.y;
};

const father = new Father('age', '30');
father.toString(); // 'age,30'

const emptyObj = {};
const Mother = Father.bind(emptyObj, height/*x*/);

const son = new Mother(163);
son.toString(); // 'height,163'

son instanceof Father; // true
son instanceof Mother; // true

5、快捷调用

// 可以用 Array.prototype.slice 
// 来将一个类似于数组的对象(array-like object)转换成一个真正的数组
const slice = Array.prototype.slice;
slice.apply(arguments); //arguments 是类似于数组的对象

// 用 bind()
// 在下面这段代码里面,slice 是 Function.prototype的 apply() 方法的绑定函数
// 并且将 Array.prototype.slice 方法作为 this 的值。

// 与前一段代码的 "slice" 效果相同
const unbindSlice = Array.prototype.slice;
const bindSlice = Function.prototype.apply.bind(unbindSlice);

bindSlice(arguments);

⭐️  原理及实现思路:

        通过Array.prototype.slice.call(arguments, 1)将arguments转成数组。返回函数,判断函数的调用方式,是否是被new出来的。new出来的话返回空对象,但是实例的_proto_指向this的prototype,完成函数柯里化。

⭐️  具体代码:

Function.prototype.myBind = function (context) {
  const _this = this
  const args = Array.prototype.slice.call(arguments, 1)

  return function Fun() {。
    if (this instanceof Fun) {
      return new _this(...args, ...arguments) 
    } else {
      return _this.apply(context, args.concat(...arguments))
    }
  }
}

⭐️  结合例子理解:

Function.prototype.myBind = function (context) {
  const _this = this
  // const args = arguments.slice(1) 这样写不行,因为arguments是一个伪(类)数组
  const args = Array.prototype.slice.call(arguments, 1)
  // const args = Array.prototype.slice.apply(arguments, [1]) 也可以这样写

  // console.log(this) // [Function: getX]
  // console.log(...args) // 34
  // console.log(...arguments) // { x: 12, getX: [Function: getX] } 34

  return function Fun() {
    // instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
    if (this instanceof Fun) {
      // 如果this是new Fun这样new出来的,也就是说this的prototype在Fun的原型链上
      // 则返回一个实例,且使用new创建出来的实例的_proto_指向_this的prototype,同时完成函数柯里化
      return new _this(...args, ...arguments) // new了一个getX函数,它的arguments是...arguments
    } else {
      // 如果不是new出来的就改变this指向为context,同时完成函数柯里化
      return _this.apply(context, args.concat(...arguments))
    }
  }
}

// 结合例子
this.x = 8    // 在浏览器中,this 指向全局的 "window" 对象
const example = {
  x: 12,
  getX: function() { return this.x }
}

// 创建一个新函数,把 'this' 绑定到 example 对象
const myBoundGetX = example.getX.myBind(example, 34)
console.log(myBoundGetX()) // 12

const boundGetX = example.getX.bind(example, 34)
console.log(boundGetX()) // 12

❓  什么是函数柯里化?

在计算机科学中,柯里化(Currying)是指把(接受多个参数的)函数变换成一个接受单一参数(最初函数的第一个参数)的函数,并且返回(接受余下的参数且返回结果的)新函数的技术。

❓  instanceof 使用的例子?

function Year(year) {
  this.year = year
}
const newYear = new Year(1998)
console.log(newYear instanceof Year)
// expected output: true

📖  友情链接:

        Function.prototype.bind() - JavaScript | MDN

        instanceof - JavaScript | MDN

        new 运算符 - JavaScript | MDN


Function.prototype上的其他方法(延伸):

         Function.prototype.toString() - JavaScript | MDN

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值