阅读 202

js - call apply bind

这是我参与更文挑战的第21天,活动详情查看更文挑战

js语法

call

语法 fun.call(thisArg[, arg1[, arg2[, ...]]])

thisArg

 在外面传入的 thisArg 值会修改并成为 this 值。thisArg 是 undefined 或 null 时它会被替换成全局对象,所有其他值会被应用 ToObject 并将结果作为 this 值,这是第三版引入的更改。

(1)不传,或者传null,undefined, 函数中的this指向window对象
(2)传递另一个函数的函数名,函数中的this指向这个函数的引用,并不一定是该函数执行时真正的this值
(3)值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象,如 String、Number、Boolean
(4)传递一个对象,函数中的this指向这个对象

例子

function a(){
    //输出函数a中的this对象
    console.log(this); 
}
//定义函数b
function b(){} 

var obj = {name:'这是一个屌丝'}; //定义对象obj
a.call(); //window
a.call(null); //window
a.call(undefined);//window
a.call(1); //Number
a.call(''); //String
a.call(true); //Boolean
a.call(b);// function b(){}
a.call(obj); //Object
复制代码

apply

fun.apply(thisArg[, argsArray])

唯一不同的就是参数传入,apply是一个数组

例子

借用内置函数

//里面有最大最小数字值的一个数组对象
var numbers = [5, 6, 2, 3, 7];

/* 使用 Math.min/Math.max 在 apply 中应用 */
var max = Math.max.apply(null, numbers);
// 一般情况是用 Math.max(5, 6, ..) 或者 Math.max(numbers[0], ...) 来找最大值
var min = Math.min.apply(null, numbers);

复制代码

更好做数组的相关处理

function minOfArray(arr) {
  var min = Infinity;
  var QUANTUM = 32768;

  for (var i = 0, len = arr.length; i < len; i += QUANTUM) {
    var submin = Math.min.apply(null, arr.slice(i, Math.min(i + QUANTUM, len)));
    console.log(submin, min)
    min = Math.min(submin, min);
  }

  return min;
}

var min = minOfArray([5, 6, 2, 3, 7]);
复制代码

bind

创建一个新的函数

fun.bind(thisArg[, arg1[, arg2[, ...]]])

  1. bind是ES5新增的一个方法
  2. 传参和call或apply类似
  3. 不会执行对应的函数,call或apply会自动执行对应的函数
  4. 返回对函数的引用

例子

mdn函数

this.x = 9;    // 在浏览器中,this 指向全局的 "window" 对象
var module = {
  x: 81,
  getX: function() { return this.x; }
};

module.getX(); // 81

var retrieveX = module.getX;
retrieveX();   
// 返回 9 - 因为函数是在全局作用域中调用的

// 创建一个新函数,把 'this' 绑定到 module 对象
// 新手可能会将全局变量 x 与 module 的属性 x 混淆
var boundGetX = retrieveX.bind(module);
boundGetX(); // 81
复制代码



js实现

call

规则

  1. 如果 IsCallable(func) 是 false, 则抛出一个 TypeError 异常。
  2. 令 argList 为一个空列表。
  3. 如果调用这个方法的参数大余一个,则从 arg1 开始以从左到右的顺序将每个参数插入为 argList 的最后一个元素。
  4. 提供 thisArg 作为 this 值并以 argList 作为参数列表,调用 func 的 [[Call]] 内部方法,返回结果。

模拟

Function.prototype.customerCall = function call (thisArg) {
  var thisArg;
  var returnValue;
  
  // ES3增加
  if (thisArg === null || thisArg === (void 0)) {
  	thisArg = window;
  } else {
   // thisArg的toObject处理
   thisArg = new Object(thisArg); 
  }
  debugger
  // 通过对象属性的方式进行this绑定
  var __fn = '__fn';
  thisArg[__fn] = this;   // 调用方法绑定
  
  if (typeof this != 'function') {
  	throw new TypeError(`${this} is not a function`);
  }
  
  if (arguments.length <= 1) {
    returnValue = thisArg[__fn]();
    delete thisArg[__fn];
    
    return returnValue;
  }
 
  var argList = [];
  
  for (var i = 1; i < arguments.length; i++) {
  	argList.push(arguments[i])
  }
  debugger
  returnValue = eval("thisArg[__fn](" + argList.reduce(function(x, y) {return x + ',' + JSON.stringify(y);}, '').slice(1) + ")"); // eval 处理参数
  
  // 或者通过new function处理
  // var params = argList.join(',');
  // returnValue = new Function("return arguments[0][arguments[1]](" + argList.reduce(function(x, y) {return x + ',' + JSON.stringify(y);}, '').slice(1) + ")")(thisArg, __fn);
  
  delete thisArg[__fn];
  
  return returnValue;
}
复制代码

apply

规则

  1. 如果 IsCallable(func) 是 false, 则抛出一个 TypeError 异常 .
  2. 如果 argArray 是 null 或 undefined, 则
    1. 返回提供 thisArg 作为 this 值并以空参数列表调用 func 的 [[Call]] 内部方法的结果。
  3. 如果 Type(argArray) 不是 Object, 则抛出一个 TypeError 异常 .
  4. 令 len 为以 "length" 作为参数调用 argArray 的 [[Get]] 内部方法的结果。
  5. 令 n 为 ToUint32(len).
  6. 令 argList 为一个空列表 .
  7. 令 index 为 0.
  8. 只要 index < n 就重复
    1. 令 indexName 为 ToString(index).
    2. 令 nextArg 为以 indexName 作为参数调用 argArray 的 [[Get]] 内部方法的结果。
    3. 将 nextArg 作为最后一个元素插入到 argList 里。
    4. 设定 index 为 index + 1.
  9. 提供 thisArg 作为 this 值并以 argList 作为参数列表,调用 func 的 [[Call]] 内部方法,返回结果。

模拟

Function.prototype.customerApply = function apply (thisArg, argArray) {
  var thisArg;
  var returnValue;
  
  // ES3增加
  if (thisArg === null || thisArg === (void 0)) {
  	thisArg = window;
  } else {
   // thisArg的toObject处理
   thisArg = new Object(thisArg); 
  }
  
  // 通过对象属性的方式进行this绑定
  var __fn = '__fn';
  thisArg[__fn] = this;
  
	 // 第一步
  if (typeof this != 'function') {
  	throw new TypeError(`${this} is not a function`);
  }
  
  // 第二步 没有参数的情况下直接调用
  if (argArray === null || argArray === (void 0)) {
    returnValue = thisArg[__fn]();
    delete thisArg[__fn];
    
    return returnValue;
  }
  
  // 第三部 参数类型判断 不是 Object, 则抛出一个 TypeError 异常 .
  if(typeof argArray !== 'object') {
    throw new TypeError('CreateListFromArrayLike called on non-object');
  }
  
  // 第四 / 五步
  var len = argArray.length;
  
  // 第六步
  var argList = [];
  
  // 第七 / 八步
  for(var index = 0; index < len; index++) {
  	var nextArg = argArray[index];
    argList.push(nextArg);
  }
  
  // 第九步
  returnValue = eval("thisArg[__fn](" + argList.reduce(function(x, y) {return x + ',' + JSON.stringify(y);}, '').slice(1) + ")"); // eval 处理参数
  
  // 或者通过new function处理
  // var params = argList.join(',');
  // returnValue = new Function("return arguments[0][arguments[1]](" + argList.reduce(function(x, y) {return x + ',' + JSON.stringify(y);}, '').slice(1) + ")")(thisArg, __fn);
  
  delete thisArg[__fn];
  
  return returnValue;
}
复制代码

bind

Function.prototype.bind (thisArg [, arg1 [, arg2, …]])
复制代码

规则

  1. 令 Target 为 this 值 .
  2. 如果 IsCallable(Target) 是 false, 抛出一个 TypeError 异常 .
  3. 令 A 为一个(可能为空的)新内部列表,它包含按顺序的 thisArg 后面的所有参数(arg1, arg2 等等)。
  4. 令 F 为一个新原生 ECMAScript 对象。
  5. 依照 8.12 指定,设定 F 的除了 [[Get]] 之外的所有内部方法。
  6. 依照 15.3.5.4 指定,设定 F 的 [[Get]] 内部属性。
  7. 设定 F 的 [[TargetFunction]] 内部属性为 Target。
  8. 设定 F 的 [[BoundThis]] 内部属性为 thisArg 的值。
  9. 设定 F 的 [[BoundArgs]] 内部属性为 A。
  10. 设定 F 的 [[Class]] 内部属性为 "Function"。
  11. 设定 F 的 [[Prototype]] 内部属性为 15.3.3.1 指定的标准内置 Function 的 prototype 对象。
  12. 依照 15.3.4.5.1 描述,设定 F 的 [[Call]] 内置属性。
  13. 依照 15.3.4.5.2 描述,设定 F 的 [[Construct]] 内置属性。
  14. 依照 15.3.4.5.3 描述,设定 F 的 [[HasInstance]] 内置属性。
  15. 如果 Target 的 [[Class]] 内部属性是 "Function", 则
    1. 令 L 为 Target 的 length 属性减 A 的长度。
    2. 设定 F 的 length 自身属性为 0 和 L 中更大的值。
  16. 否则设定 F 的 length 自身属性为 0.
  17. 设定 F 的 length 自身属性的特性为 15.3.5.1 指定的值。
  18. 设定 F 的 [[Extensible]] 内部属性为 true。
  19. 令 thrower 为 [[ThrowTypeError]] 函数对象 (13.2.3)。
  20. 以 "caller", 属性描述符 {[[Get]]: thrower, [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false}, 和 false 作为参数调用 F 的 [[DefineOwnProperty]] 内部方法。
  21. 以 "arguments", 属性描述符 {[[Get]]: thrower, [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false}, 和 false 作为参数调用 F 的 [[DefineOwnProperty]] 内部方法。
  22. 返回 F.

模拟

上面的规则还是很复杂的,不好实现出来。

第一版:
主要要处理的是两个部分,一个是参数,下方的例子可以看到参数需要进行合并,一个是返回一个函数this指向传入的第一个参数。

function testBindParam() {
  console.log(arguments) 
}

testBindParam.bind({}, 1,2,3,4)(5,6,7)
复制代码

image.png

Function.prototype.customBind = function bind(thisArg) {
	var self = this;
  
  if (typeof self !== 'function') {
     throw new TypeError('Function.prototype.bind called on incompatible ' + self);
  }
  
  var args = Array.prototype.slice.call(arguments, 1);
  
  var bound = function () {
  	var boundArgs = Array.prototype.slice.call(arguments);
    
    return self.apply(thisArg, args.concat(boundArgs))
  }

  return bound;
}
复制代码


第二版:处理new

Function.prototype.customBind = function bind(thisArg) {
	var self = this;
  
  if (typeof self !== 'function') {
     throw new TypeError('Function.prototype.bind called on incompatible ' + self);
  }
  
  var args = Array.prototype.slice.call(arguments, 1);
  
  var bound = function () {
  	var boundArgs = Array.prototype.slice.call(arguments);
    var finalArgs = args.concat(boundArgs);
    debugger
    if (this instanceof bound) {
    	if (self.prototype) {
      	function Empty() {};
        Empty.prototype = self.prototype;
        bound.prototype = new Empty();
      }
      
      var result = self.apply(this, finalArgs);
      
      var isObject = typeof result === 'object' && result !== null;
      var isFunction = typeof result === 'function';
      
      if(isObject || isFunction){
        return result;
      }
      
      return this;
    } else {
    	return self.apply(thisArg, finalArgs)
    }
  }

  return bound;
}
复制代码

后续待跟踪

  • 参数处理,深拷贝
  • new 判断
  • shim实现

github.com/Raynos/func…


总结:

call apply的步骤分解

1. 处理thisArg =》thisArg分成三种情况【unll, undefined | 对象 | 基本数据类型转换为包装对象】
2. 绑定方法thisArg['_fn'] = this;这里的this值的是 调用的方法
2.1 类型判断,this是否他为function

3. 处理参数
3.1 参数个数为1的时候,则直接执行
3.2 参数多个的时候,需要进行eval或者new Functino执行。

4. 执行调用
5. 返回结果
复制代码

bind的步骤分解

基于call方法来支持主要功能,这里是一个高阶函数,缓存bind传进来的参数

1. 处理bind传入参数
2. 返回一个函数
3. 函数内部调用通过apply调用
复制代码

还需要处理new的形式,因为bind返回的函数可能用来做构造函数进行调用。this instanceof会有问题,因为尖头函数表达式没有prototype

1. 处理bind传入参数
2. 返回一个函数
3. 判断当前函数是否是new、调用
3.1 如果是new调用,则构造new操作
3.2 如果不是new调用,函数内部调用通过apply调用
复制代码

文章分类
前端
文章标签