关于前端:jsliang-求职系列-10-手写-callapplybind

43次阅读

共计 8306 个字符,预计需要花费 21 分钟才能阅读完成。

一 目录

不折腾的前端,和咸鱼有什么区别

目录
一 目录
二 前言
三 最终实现
 3.1 手写 call
 3.2 手写 apply
 3.3 手写 bind
四 Arguments 对象
五 call
 5.1 原生 call
 5.2 手写 call
六 apply
 6.1 原生 apply
 6.2 手写 apply
七 bind
 7.1 原生 bind
 7.2 手写 bind
八 题目
 8.1 this 指向问题 1
 8.2 this 指向问题 2
九 参考文献

二 前言

返回目录

面试官:手写一个 call/apply/bind

工欲善其事,必先利其器,咱们先理解一下这 3 者有什么区别:

call 的应用

function Product (name, price) {
  this.name = name;
  this.price = price;
}

function Food (name, price) {Product.call(this, name, price);
  this.category = 'food';
}

const food = new Food('cheese', 5);
console.log(food.name); // 'cheese'
  • call:能够扭转函数指向,第一个参数是要扭转指向的对象,之后的参数模式是 arg1, arg2... 的模式
  • apply:根本同 call,不同点在于第二个参数是一个数组 [arg1, arg2...]
  • bind:扭转 this 作用域会返回一个新的函数,这个函数不会马上执行

三 最终实现

返回目录

上面咱们列一下明天的实现目标:

  1. 手写 call
  2. 手写 apply
  3. 手写 bind

3.1 手写 call

返回目录

Function.prototype.myCall = function(context = globalThis) {
  // 设置 fn 为调用 myCall 的办法
  context.fn = this;

  // 获取残余参数
  const otherArg = Array.from(arguments).slice(1);

  // 调用这个办法,将残余参数传递进去
  context.fn(otherArg);

  // 将这个办法的执行后果传给 result
  let result = context.fn();

  // 删除这个变量
  delete context.fn;

  // 返回 result 后果
  return result;
};

this.a = 1;

const fn = function() {
  this.a = 2;
  console.log(this.a);
}

fn.myCall(fn);

3.2 手写 apply

返回目录

Function.prototype.myApply = function(context = globalThis, arr) {
  // 设置 fn 为调用 myCall 的办法
  context.fn = this;

  let result;

  // 如果存在参数,则传递进去
  // 将后果返回给 result
  if (arr) {result = context.fn(arr);
  } else { // 否则不传
    result = context.fn();}

  // 删除这个变量
  delete context.fn;

  // 返回 result 后果
  return result;
};

this.a = 1;

const fn = function() {
  this.a = 2;
  console.log(this.a);
}

fn.myApply(fn);

3.3 手写 bind

返回目录

Function.prototype.myBind = function(context = globalThis) {
  // 设置 fn 为调用 myCall 的办法
  const fn = this;

  // 获取该办法残余参数
  const otherArg = [...arguments].slice(1);

  // 设置返回的一个新办法
  const result = function() {

    // 获取返回办法体的参数
    const resultArg = [...arguments];

    // 如果是通过 new 调用的,绑定 this 为实例对象
    if (this instanceof result) {fn.apply(this, otherArg.concat(resultArg));
    } else { // 否则一般函数模式绑定 context
      fn.apply(context, otherArg.concat(resultArg));
    }
  }

  // 绑定原型链
  result.prototype = Object.create(fn.prototype);

  // 返回后果
  return result;
};

this.a = 1;

const fn = function() {
  this.a = 2;
  console.log(this.a);
}

fn.myBind(fn);
fn();

OK,懂了么,咱们发车持续深造~

四 Arguments 对象

返回目录

  • MDN – Arguments

arguments 是一个对应于传递给函数的参数的类数组对象。

function fun(a, b, c) {console.log(arguments[0]); // 1
  console.log(arguments[1]); // 2
  console.log(arguments[2]); // 3
}

arguments 对象不是一个 Array

它相似于 Array,但除了 length 属性和索引元素之外没有任何 Array 属性。

arguments 转为数组:

// ES5
var arg1 = Array.prototype.slice.call(arguments);
var arg2 = [].slice.call(arguments);

// ES6
var arg3 = Array.from(arguments);
var arg4 = [...arguments];

在手写 call/bind/apply 过程中,会用到 arguments 来获取办法体的传参,就好比手写 call 过程中,通常咱们通过 Array.from(arguments).slice(1) 来获取第二个及前面的参数。

五 call

返回目录

5.1 原生 call

返回目录

  • MDN – call

call() 办法应用一个指定的 this 值和独自给出的一个或多个参数来调用一个函数。

留神:该办法的语法和作用与 apply() 办法相似,只有一个区别,就是 call() 办法承受的是一个参数列表,而 apply() 办法承受的是一个蕴含多个参数的数组。

  • 语法:function.call(thisArg, arg1, arg2, ...)

    • thisArg:可选的。在 function 函数运行时应用的 this 值。
    • arg1, arg2, ...:指定的参数列表
function Product (name, price) {
  this.name = name;
  this.price = price;
}

function Food (name, price) {Product.call(this, name, price);
  this.category = 'food';
}

const food = new Food('cheese', 5);
console.log(food.name); // 'cheese'

5.2 手写 call

返回目录

首先咱们得搞明确 call 的个性:

  1. 如果 obj.call(null),那么 this 应该指向 window
  2. 如果 obj1.call(obj2),那么谁调用它,this 指向谁(这里就是 obj2 了)
  3. call 能够传入多个参数,所以能够利用 arguments 这个字段来获取所有参数。将 arguments 转换数组后,获取除第一个参数外的其余参数
  4. 设置一个变量,用完能够删掉它

综上:

手写 call 的 JS 代码:

Function.prototype.myCall = function(context = globalThis) {
  // 设置 fn 为调用 myCall 的办法
  context.fn = this;

  // 获取残余参数
  const otherArg = Array.from(arguments).slice(1);

  // 调用这个办法,将残余参数传递进去
  context.fn(otherArg);

  // 将这个办法的执行后果传给 result
  let result = context.fn();

  // 删除这个变量
  delete context.fn;

  // 返回 result 后果
  return result;
};

this.a = 1;

const fn = function() {
  this.a = 2;
  console.log(this.a);
}

fn.myCall(fn);

小伙伴稍稍了解下,搞清楚它外部的流程,而后咱们实际一下:

防抖函数绑定手写 call

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title> 手写 call</title>
</head>
<body>
  <button class="btn">123</button>

  <script>
    (function() {Function.prototype.myCall = function(context) {
        const newContext = context || window;
        newContext.fn = this;
        const otherArg = Array.from(arguments).slice(1);
        newContext.fn(otherArg);
        const result = newContext.fn(otherArg);
        delete newContext;
        return result;
      };

      const debounce = function(fn) {
        let timer = null;
        return function() {clearTimeout(timer);
          timer = setTimeout(() => {fn.myCall(this, arguments);
          }, 1000);
        }
      }

      let time = 0;
      const getNumber = function() {console.log(++time);
      }

      const btn = document.querySelector('.btn');
      btn.addEventListener('click', debounce(getNumber));
    })()
  </script>
</body>
</html>

这样咱们就摸清了手写 call

六 apply

返回目录

6.1 原生 apply

返回目录

  • MDN – apply

apply() 办法调用一个具备给定 this 值的函数,以及以一个数组(或类数组对象)的模式提供的参数。

  • 语法:function.apply(thisArg, [argsArray])

    • thisArg:必选的。在 function 函数运行时应用的 this
    • [argsArray]:可选的。一个数组或者类数组对象,其中的数组元素将作为独自的参数传给 func 函数。
const numbers = [5, 6, 2, 3, 7];

const max = Math.max.apply(null, numbers);
console.log(max); // 7

const min = Math.min.apply(null, numbers);
console.log(min); // 2

6.2 手写 apply

返回目录

上面咱们开始尝试手写 apply,记住这个办法和 call 相似,了解起来也不难:

Function.prototype.myApply = function(context = globalThis, arr) {
  // 设置 fn 为调用 myCall 的办法
  context.fn = this;

  let result;

  // 如果存在参数,则传递进去
  // 将后果返回给 result
  if (arr) {result = context.fn(arr);
  } else { // 否则不传
    result = context.fn();}

  // 删除这个变量
  delete fcontext.fnn;

  // 返回 result 后果
  return result;
};

this.a = 1;

const fn = function() {
  this.a = 2;
  console.log(this.a);
}

fn.myApply(fn);

用自定义 apply + 防抖实际一下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>DOM 操作 </title>
</head>
<body>
  <button class="btn">123</button>

  <script>
    (function() {Function.prototype.myApply = function(context, arr) {
        const newContext = context || window;
        newContext.fn = this;
        console.log(newContext.fn)
        if (!arr) {result = newContext.fn();
        } else {result = newContext.fn(arr);
        }

        delete newContext;
        return result;
      };

      const debounce = function(fn, number) {
        let timer = null;
        return function() {clearTimeout(timer);
          timer = setTimeout(() => {fn.myApply(this, number);
          }, 1000);
        }
      }

      const getNumber = function(time) {console.log(time);
      }

      let number = [1, 2, 3, 4, 5];
      const btn = document.querySelector('.btn');
      btn.addEventListener('click', debounce(getNumber, number));
    })()
  </script>
</body>
</html>

这样咱们就摸清了手写 apply

七 bind

返回目录

7.1 原生 bind

返回目录

  • MDN – bind

bind() 办法创立一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时应用。

  • 语法:function.bind(thisArg, arg1, arg2, ...)

    • thisArg:调用绑定函数时作为 this 参数传递给指标函数的值。
    • arg1, arg2, ...:当指标函数被调用时,被预置入绑定函数的参数列表中的参数。
  • 返回值:一个原函数的拷贝,并领有指定的 this 值和初始参数
const module = {
  x: 42,
  getX: function() {return this.x;},
};

const unboundGetX = module.getX;
console.log(unboundGetX()); // undefined
// 谁调用指向谁,这里 unboundGetX = module.getX
// 让 getX 外面的 this 指向了 window
// 而 window 外面并没有 x 办法
// 当然,在后面加上 window.x = 43 就有了

const boundGetX = unboundGetX.bind(module);
console.log(boundGetX()); // 42
// 通过 bind,将 this 指向 module

7.2 手写 bind

返回目录

手写 bind 略微有点小简单,然而小伙伴们别慌,多读几遍能够摸清套路:

Function.prototype.myBind = function(context = globalThis) {
  // 设置 fn 为调用 myCall 的办法
  const fn = this;

  // 获取该办法残余参数
  const otherArg = [...arguments].slice(1);

  // 设置返回的一个新办法
  const result = function() {

    // 获取返回办法体的参数
    const resultArg = [...arguments];

    // 如果是通过 new 调用的,绑定 this 为实例对象
    if (this instanceof result) {fn.apply(this, otherArg.concat(resultArg));
    } else { // 否则一般函数模式绑定 context
      fn.apply(context, otherArg.concat(resultArg));
    }
  }

  // 绑定原型链
  result.prototype = Object.create(fn.prototype);

  // 返回后果
  return result;
};

this.a = 1;

const fn = function() {
  this.a = 2;
  console.log(this.a);
}

fn.myBind(fn);
fn();

八 题目

返回目录

通过下面的实际,小伙伴们应该对手写 callbindapply 有本人的了解了,上面看看这些题,试试挑战下。

8.1 this 指向问题 1

返回目录

var color = 'green';

var test4399 = {
  color: 'blue',
  getColor: function() {
    var color = 'red';
    console.log(this.color);
  },
};

var getColor = test4399.getColor;
getColor(); // 输入什么?test4399.getColor(); // 输入什么?

答案:greenblue

8.2 this 指向问题 2

返回目录

var myObject = {
  foo: 'bar',
  func: function() {
    var self = this;
    console.log(this.foo);
    console.log(self.foo);
    (function() {console.log(this.foo);
      console.log(self.foo);
    })()}
}
myObject.func();

程序输入什么?

  • A:bar bar bar bar
  • B:bar bar bar undefined
  • C:bar bar undefined bar
  • D:undefined bar undefined bar

答案:C

  1. 第一个 this.foo 输入 bar,因为以后 this 指向对象 myObject
  2. 第二个 self.foo 输入 bar,因为 selfthis 的正本,同指向 myObject 对象。
  3. 第三个 this.foo 输入 undefined,因为这个 IIFE(立刻执行函数表达式)中的 this 指向 window

4. 第四个 self.foo 输入 bar,因为这个匿名函数所处的上下文中没有 self,所以通过作用域链向上查找,从蕴含它的父函数中找到了指向 myObject 对象的 self

九 参考文献

返回目录

  • [x] MDN – Arguments【浏览倡议:5min】
  • [x] MDN – call【浏览倡议:5min】
  • [x] MDN – apply【浏览倡议:5min】
  • [x] MDN – bind【浏览倡议:5min】
  • [x] 不必 call 和 apply 办法模仿实现 ES5 的 bind 办法【浏览倡议:1h】
  • [x] JavaScript 深刻之 call 和 apply 的模仿实现【浏览倡议:20min】
  • [x] this、apply、call、bind【浏览倡议:30min】
  • [x] 面试官问:是否模仿实现 JS 的 call 和 apply 办法【浏览倡议:10min】
  • [x] JavaScript 根底心法—— call apply bind【浏览倡议:20min】
  • [x] 回味 JS 根底:call apply 与 bind【浏览倡议:10min】

jsliang 的文档库由 梁峻荣 采纳 常识共享 署名 - 非商业性应用 - 雷同形式共享 4.0 国内 许可协定 进行许可。<br/> 基于 https://github.com/LiangJunrong/document-library 上的作品创作。<br/> 本许可协定受权之外的应用权限能够从 https://creativecommons.org/licenses/by-nc-sa/2.5/cn/ 处取得。

正文完
 0