手写源码系列:原生js实现call方法
call语法
func.call(thisArg, arg1, arg2, ...)
参数
-
thiaArg
必选的。在 func 函数运行时使用的 this 值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。
-
arg1, arg2, ...
指定的参数列表。
返回值
- func函数的返回值,如果func没有返回值,则返回undefined
描述
1. call() 允许为不同的对象分配和调用属于一个对象的函数/方法 (func)
2. call() 提供新的 this 值给当前调用的函数/方法 (func)
ECMAScript5.1
15.3.4.4 Function.prototype.call (thisArg [ , arg1 [ , arg2, … ] ] )
当以 thisArg 和可选的 arg1, arg2 等等作为参数在一个 func 对象上调用 call 方法,采用如下步骤:
1. 如果 IsCallable(func) 是 false, 则抛出一个 TypeError 异常。
2. 令 argList 为一个空列表。
3. 如果调用这个方法的参数多余一个,则从 arg1 开始以从左到右的顺序将每个参数
插入为 argList 的最后一个元素。
4. 提供 thisArg 作为 this 值并以 argList 作为参数列表,调用 func 的 [[Call]] 内部方法
,返回结果。
call 方法的 length 属性是 1。
注:在外面传入的 thisArg 值会修改并成为 this 值。
thisArg 是 undefined 或 null 时它会被替换成全局对象,
所有其他值会被应用 ToObject 并将结果作为 this 值,这是第三版引入的更改。
从最简单的例子出发
var tObj = {
name:'tino'
}
function func(){
console.log('this:',this)
console.log('name:',this.name)
}
func.call(tObj);
// this: {name: "tino"}
// name: tino
此时的func绑定函数做了执行操作,并且内部this指向了tObj对象
问题一:怎么通过call函数执行绑定函数func,并且将func内部this指向绑定对象
var tObj = {
name:'tino',
func:function(){
console.log('this:',this)
}
}
tObj.func()
//this: {name: "tino", func: ƒ}
有没有发现,此时的func的this指向的是tObj对象;
也就是说,在绑定对象上创建一个内部函数,这个内部函数的执行会将this指向绑定对象tObj,那也就是我们可以将绑定函数func设置为绑定对象的内部函数,即:
tObj.__func = func
;
那现在我们来用callFunc函数做实现:
Function.prototype.callFunc = function(target) {
target.__func = this; //this为绑定函数
target.__func();
//不能给绑定对象本身添加属性,可以delete删除这个添加的属性
delete target.__func;
}
//测试
var tObj = {
name: 'tino'
}
function func() {
console.log('this:', this)
console.log('name:', this.name)
}
func.callFunc(tObj)
// this: {name: "tino", __func: ƒ}
// name: tino
问题二:怎么通过call传递给定参数给绑定函数并执行
var tObj = {
name: 'tino'
}
function func(arg1, arg2) {
console.log('this:', this)
console.log('name:', this.name)
console.log('arg1:', arg1)
console.log('arg2:', arg2)
}
func.call(tObj, 'arg1', 'arg2');
//this: {name: "tino"}
//name: tino
//arg1: arg1
//arg2: arg2
绑定函数func调用call函数,传入tObj,arg1,arg2三个参数,执行时可在func参数列表中获取到,但是不能确保参数的数量,可以通过函数的Arguments对象获取到所有的参数即Arguments[0]、Arguments[1]、Arguments[2]...;
var argList = [];
for(var i = 0;i<arguments.length;i++>){
argList[i] = 'arguments['+(i + 1)+']';
}
argList参数数组有了,现在我们应该怎么去传入__func中执行呢?
target.__func(...argList);
好像是可以的哦,但是是es6的方法,还是尽量模拟es3的吧,
eval('target.__func(' + argList + ')')
Function.prototype.callFunc = function(target) {
target.__func = this; //this为绑定函数
var argList = [];
for (var i = 0, len = arguments.length; i < len; i++) {
argList[i] = 'arguments[' + (i + 1) + ']';
}
// target.__func(...argList);
eval('target.__func(' + argList + ')')
delete target.__func
}
//测试
var tObj = {
name: 'tino'
}
function func(arg1, arg2) {
console.log('this:', this)
console.log('name:', this.name)
console.log('arg1:', arg1)
console.log('arg2:', arg2)
}
func.callFunc(tObj, "tinolee", 2);
//this: {name: "tino", __func: ƒ}
//name: tino
//arg1: arg1
//arg2: arg2
OK,参数传入测试通过,但是eval似乎不太受欢迎哪,但是还有一个Function构造函数可以来生成执行函数
new Function ([arg1[, arg2[, ...argN]],] functionBody)
// MDN的例子是这样的:
const sum = new Function('a', 'b', 'return a + b');
console.log(sum(2, 6));
那我们应该怎么去设计一个tObj绑定对象执行内部函数__func,并传入参数的执行函数呢?
以上面测试例子为例:我们传入了arg1,arg2两个参数,那么在tObj的__func下怎么传入执行呢?Function构造函数如下:
new Function([[Expression]])(tObj,__func,argList)
构造函数表达式应该是这样
[[Expression]]
{
return tObj.__func(argList[0],argList[1],argList[2]...)
}
通过argments对象来解析不定参数
argment[0] -> tObj
argment[1] -> __func
argment[2] -> argList
那么构造函数表达式应该是这样
[[Expression]]
{
return argment[0][argment[1]](argment[2][0],argment[2][1],argment[2][2]...)
}
封装出一个生成表达式函数
function callExecuteExpression(argList) {
var expression = 'return arguments[0][arguments[1]]('
for (var i = 0, len = argList.length; i < len; i++) {
i > 0 ?
expression += ',arguments[2][' + i + ']' :
expression += 'arguments[2][' + i + ']'
}
expression += ')'
return expression
}
现在构造函数可以这样书写
var expression = callExecuteExpression(argList)
new Function(expression)(tObj,__func,argList)
Function.prototype.callFunc = function(target) {
target.__func = this; //this为绑定函数
var argList = [];
for (var i = 0, len = arguments.length; i < len; i++) {
argList[i] = arguments[i + 1];
}
// target.__func(...argList);
//eval('target.__func(' + argList + ')')
var expression = callExecuteExpression(argList)
new Function(expression)(target, '__func', argList)
delete target.__func
}
//测试
var tObj = {
name: 'tino'
}
function func(arg1, arg2) {
console.log('this:', this)
console.log('name:', this.name)
console.log('arg1:', arg1)
console.log('arg2:', arg2)
}
func.callFunc(tObj, "tinolee", 2);
//this: {name: "tino", __func: ƒ}
//name: tino
//arg1: tinolee
//arg2: 2
但是,还是有问题,也就是创建的内部函数__func可能在tObj中已经存在,所以需要做进一步处理
最简单的就是通过时间戳(new Date().getTime())作为唯一标识来创建内部函数__func,当然Sybmol()的唯一性也是可以的,只是还是上面说的尽量用es3来折磨自己吧。
var __func = '__'+new Date().getTime()
Function.prototype.callFunc = function(target) {
var argList = [];
for (var i = 0, len = arguments.length; i < len; i++) {
argList[i] = arguments[i + 1];
}
var __func = '__' + new Date().getTime();
//还可以进一步做暂存
var ownFunc = target[__func];
var hasOwnFunc = target.hasOwnProperty(__func);
target[__func] = this; //this为绑定函数
// target.__func(...argList);
//eval('target.__func(' + argList + ')')
var expression = callExecuteExpression(argList)
new Function(expression)(target, __func, argList)
delete target[__func]
if (hasOwnFunc) {
target[__func] = ownFunc;
}
}
var tObj = {
name: 'tino'
}
function func(arg1, arg2) {
console.log('this:', this)
console.log('name:', this.name)
console.log('arg1:', arg1)
console.log('arg2:', arg2)
}
func.callFunc(tObj, "tinolee", 2);
//this: {name: "tino", __1585152978563: ƒ}
//name: tino
//arg1: tinolee
//arg2: 2
OK,测试通过; 接下来我们来依照ECMAScript5.1规范来完善下封装的函数
最终代码
function globalThis() {
return this;
}
function callExecuteExpression(argList) {
var expression = 'return arguments[0][arguments[1]]('
for (var i = 0, len = argList.length; i < len; i++) {
i > 0 ?
expression += ',arguments[2][' + i + ']' :
expression += 'arguments[2][' + i + ']'
}
expression += ')'
return expression
}
Function.prototype.callFunc = function(target) {
//this为绑定函数func
//15.3.4.4.1
if (typeof this !== 'function') {
throw new TypeError(this + ' is not a function');
}
//thisArg 是 undefined 或 null 时它会被替换成全局对象
if (typeof target === 'undefined' || target === null) {
target = globalThis();
}
//所有其他值会被应用 ToObject 并将结果作为 this 值
target = new Object(target);
var argList = [];
for (var i = 0, len = arguments.length; i < len; i++) {
argList[i] = arguments[i + 1];
}
var __func = '__' + new Date().getTime();
//还可以进一步做暂存
var ownFunc = target[__func];
var hasOwnFunc = target.hasOwnProperty(__func);
target[__func] = this; //this为绑定函数
// target.__func(...argList);
//eval('target.__func(' + argList + ')')
var expression = callExecuteExpression(argList)
new Function(expression)(target, __func, argList)
delete target[__func]
if (hasOwnFunc) {
target[__func] = ownFunc;
}
}
console.log(Function.prototype.callFunc.length)
// 1
var tObj = {
name: 'tino'
}
function func(arg1, arg2, arg3) {
console.log('this:', this)
console.log('name:', this.name)
console.log('arg1:', arg1)
console.log('arg2:', arg2)
}
func.callFunc(tObj, "tinolee", 2, 4);
//原生call此处的this打印是没有__1585152978563这个属性的,还没思考好这步改怎么解决
//this: {name: "tino", __1585152978563: ƒ}
//name: tino
//arg1: tinolee
//arg2: 2
附apply实现代码
apply的实现其实和call区别不大,主要是call方法的length为1,apply方法的length为2, 所以做了apply的第二个传入数组的处理
Function.prototype.applyFunc = function(target, argsArray) {
//15.3.4.3
// 1、如果 IsCallable(func) 是 false, 则抛出一个 TypeError 异常 .
if (typeof this !== 'function') {
throw new TypeError(this + 'is not a function')
}
//2、如果 argArray 是 null 或 undefined, 则
//返回提供 thisArg 作为 this 值并以空参数列表调用 func 的 [[Call]] 内部方法的结果
if (typeof argsArray === 'undefined' || argsArray === null) {
argsArray = [];
}
// 3、如果 Type(argArray) 不是 Object, 则抛出一个 TypeError 异常 .
if (argsArray !== new Object(argsArray)) {
throw new TypeError('CreateListFromArrayLike called on non-object')
}
// thisArg 是 undefined 或 null 时它会被替换成全局对象,所有其他值会被应用 ToObject 并将结果作为 this 值,这是第三版引入的更改。
if (typeof target === 'undefined' || target === null) {
target = globalThis();
}
target = new Object(target);
var __func = '__' + new Date().getTime();
var ownFunc = target[__func];
var hasOwnFunc = target.hasOwnProperty(__func);
target[__func] = this; //this为绑定函数
var expression = callExecuteExpression(argsArray);
var result = (new Function(expression))(target, __func, argsArray);
delete target[__func];
if (hasOwnFunc) {
target[__func] = ownFunc;
}
return result;
}
学习借鉴了大佬们的思路,记录下自己学习的过程,希望能帮到需要的人, 最后感谢大佬们的分享
参考链接
转载自:https://juejin.cn/post/6844904103114440712