Skip to content

20210906173631

<!-- more -->

apply,call,bind 原理

call、apply 和 bind 是挂在 Function 对象上的三个方法,调用这三个方法的必须是一个函数

都可以改变函数 func 的 this 指向

call 和 apply 的区别在于,传参的写法不同:apply 的第 2 个参数为数组; call 则是从第 2 个至第 N 个都是给 func 的传参

bind 虽然改变了 func 的 this 指向,但不是马上执行,而这两个(call、apply)是在改变了函数的 this 指向之后立马执行

js
let a = {
  name: "jack",
  getName: function(msg) {
    return msg + this.name;
  },
};
let b = {
  name: "lily",
};
console.log(a.getName("hello~")); // hello~jack
console.log(a.getName.call(b, "hi~")); // hi~lily
console.log(a.getName.apply(b, ["hi~"])); // hi~lily
let name = a.getName.bind(b, "hello~");
console.log(name()); // hello~lily

判断数据类型

js
function getType(obj) {
  let type = typeof obj;
  if (type !== "object") {
    return type;
  }
  return Object.prototype.toString.call(obj).replace(/^$/, "$1");
}

类数组借用方法

arguments 这种类数组

js
var arrayLike = {
  0: "java",
  1: "script",
  length: 2,
};
Array.prototype.push.call(arrayLike, "jack", "lily");
console.log(typeof arrayLike); // 'object'
console.log(arrayLike);
// {0: "java", 1: "script", 2: "jack", 3: "lily", length: 4}

获取数组的最大值/最小值

apply 直接传递数组作为调用方法的参数,也可以减少一步展开数组

js
let arr = [13, 6, 10, 11, 16];
const max = Math.max.apply(Math, arr);
const min = Math.min.apply(Math, arr);
console.log(max); // 16
console.log(min); // 6

继承

js
function Parent() {
  this.name = "parent";
}

Parent.prototype.getName = function() {
  return this.name;
};

function Child() {
  Parent.call(this);
  this.type = "child";
}

Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

手写 new

new 被调用后大致做了哪几件事情:

  1. 让实例可以访问到私有属性
  2. 让实例可以访问构造函数原型(constructor.prototype)所在原型链上的属性
  3. 构造函数返回的最后结果是引用数据类型
js
function MyNew(ctor, ...args) {
  if (typeof ctor !== "function") throw " ctor must be a function";
  let obj = Object.create(ctor.prototype);
  // let obj = new Object();
  // obj.prototype = Object.create(ctor.prototype);
  let res = ctor.call(obj, ...args);
  let isObject = typeof res === "object" && res !== null;
  let isFunction = typeof res === "function";
  return isObject || isFunction ? res : obj;
}

手写 call 和 apply

实现 call 和 apply 的关键就在 eval 这行代码。其中显示了用 context 这个临时变量来指定上下文,然后还是通过执行 eval 来执行 context.fn 这个函数,最后返回 result

js
Function.prototype.MyCall = function (context, ...args) {
    context = context || window; // 设置默认上下文
    const fn = Symbol('fn'); // 使用 Symbol 避免属性冲突
    context[fn] = this; // 将当前函数赋值给 context 的 Symbol 属性
    const res = context[fn](...args); // 在 context 上下文中调用函数
    delete context[fn]; // 删除临时属性
    return res; // 返回函数执行结果
}


Function.prototype.apply = function(context, args) {
    context = context || window; // 设置默认上下文
    const fn = Symbol('fn'); // 使用 Symbol 避免属性冲突
    context[fn] = this; // 将当前函数赋值给 context 的 Symbol 属性
    const res = context[fn](...args); // 在 context 上下文中调用函数
    delete context[fn]; // 删除临时属性
    return res; // 返回函数执行结果
};

手写 bind

要注意这两个方法(call 和 apply)和 bind 的区别就在于,这两个方法是直接返回执行结果,而 bind 方法是返回一个函数,因此这里直接用 eval 执行得到结果

实现 bind 的核心在于返回的时候需要返回一个函数,故这里的 fbound 需要返回,但是在返回的过程中原型链对象上的属性不能丢失。因此这里需要用 Object.create 方法,将 this.prototype 上面的属性挂到 fbound 的原型上面,最后再返回 fbound

js
Function.prototype.bind = function(context, ...args) {
  if (typeof this !== "function") {
    throw new Error("Bind must be called on a function");
  }
  var that = this;
  var fbound = function(...args2) {
    return that.apply(this instanceof fBound ? this : context, args.concat(args2));
  };
  if (this.prototype) {
    fbound.prototype = Object.create(this.prototype);
  }
  return fbound;
};

手写 instanceof

js
function myInstanceof(left, right) {
  //基本数据类型直接返回false
  if (typeof left !== "object" || left === null) return false;
  //getProtypeOf是Object对象自带的一个方法,能够拿到参数的原型对象
  let proto = Object.getPrototypeOf(left);
  while (true) {
    //查找到尽头,还没找到
    if (proto == null) return false;
    //找到相同的原型对象
    if (proto == right.prototype) return true;
    proto = Object.getPrototypeOf(proto);
  }
}

手写 debounce

https://blog.shenzjd.com/pages/3c209d1a362c4/

js
function debounce(cb, delay) {
  var timer;
  return function() {
    clearTimeout(timer);
    timer = setTimeout(() => {
      cb(...arguments);
    }, delay);
  };
}

手写 throllte

js
function throttle(cb, delay) {
  var timer;
  return function() {
    if (timer) return;
    timer = setTimeout(() => {
      cb();
      timer = null;
      // clearTimeout(timer)
    }, delay);
  };
}