这是一首 简单的小情歌~~~
青玉案·元夕 东风夜放花千树。更吹落、星如雨 宝马雕车香满路。凤箫声动,玉壶光转,一夜鱼龙舞 蛾儿雪柳黄金缕。笑语盈盈暗香去 众里寻他千百度。蓦然回首,那人却在,灯火阑珊处
原生的方法
无意中发现,有了这个可以直接将 demo 示例的地址放置在 blog 中啦,点开就能看到效果了,舒服啊。
call 与 apply
原生的 call 和 apply 的功能基本相同,只是在使用时,他们的参数不同
// apply
fun.apply(thisArg, [argsArray]);
// call
fun.call(thisArg, arg1, arg2, ...)
// apply 的第二个参数是数组或者类数组对象,作为 fun函数 调用的参数
// call 的 arg1,arg2... 参数列表
call 与 apply 都有 thisArg 参数,参考下 MDN 上的定义
在fun函数运行时指定的this值。需要注意的是,指定的this值并不一定是该函数执行时真正的this值,如果这个函数处于非严格模式下,则指定为null和undefined的this值会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象。
总结如下
call与apply都可以指定 函数执行时的this值,通过第一个参数指定- 非严格模式,第一个参数为
null或undefined,this指向全局对象; - 第一个参数为原始值(数字,字符串,布尔值),
this指向原始值的自动包装对象 apply通过数组的方式传递参数,call通过列举方式传递参数
在 JavaScript-关于this绑定 中,有实现使用 apply 来 实现 bind 的功能,所以我们只需要将 call 和 apply 模拟出来就可以实现 bind 功能啦 ~
鉴于 call 与 apply 功能类似,本文只做了 apply 的模拟,有兴趣的同学可以自行实现 call 的模拟
开工
先来个简单的
//var name = 'global name';
function foo(){
console.log(this.name);
}
var obj1 = {
name: 'xiaodaidai'
};
var obj2 = {
name: 'xiaopangzi'
};
Function.prototype.applyNew = function(thisArg){
thisArg.fn = this;
thisArg.fn();
delete this.fn;
}
foo.applyNew(obj1); // xiaodaidai
foo.applyNew(obj2); // xiaopangzi
这一版是最简单的,我们可以发现他有许多不足,如
- thisArg 为空时,程序出错,应该默认为全局对象
- thisArg 为基本类型时,this未指向该原始值的自动包装对象
- 无法给函数传递参数
- 函数没有返回值
结合以下代码,看下原生的apply这几种情况的输出
function foo(name){
console.log(name);
console.log(this);
console.log(this.toString());
return name;
}
// thisArg 为空
var name1 = foo.apply(); // name1 = undefined
/* console 输出
undefined
Window {stop: ƒ, open: ƒ, alert: ƒ, confirm: ƒ, prompt: ƒ, …}
[object Window] */
// thisArg 为基本类型
var name2 = foo.apply(123); // name2 = undefined
/* console 输出
undefined
Number {[[PrimitiveValue]]: 123}
123 */
// 带参数
var name3 = foo.apply(123,['xiaodaidai']); // name3 = xiaodaidai
/* console 输出
xiaodaidai
Number {[[PrimitiveValue]]: 123}
123 */
thisArg 为 null 或者 undefined
这个比较简单,只要我们加个判断,如果为 null 或者 undefined ,我们只需要直接在全局对象上定义一个函数,然后调用
(function(global){
Function.prototype.applynew = function(thisArg){
thisArg = thisArg || global;
thisArg.fn = this;
thisArg.fn();
delete thisArg[fn];
};
})(window);
thisArg 为 基本类型
为基本类型,我们可以调用基本类型的包装函数,将它装箱(我使用了eval函数)
(function(global){
Function.prototype.applynew = function(thisArg){
// 让字符串首字母大写
const capotalize = ([first,...rest]) =>{
return first.toUpperCase() + rest.join('');
};
// 获取thisArg的类型,并将首字母大写;如 'number' -> 'Number'
let objtype = capotalize(typeof thisArg);
// 如果是基本类型(除去 null 和 undefined),装箱
if(objtype !== 'Object' && objtype !=='Undefined' ){
thisArg = eval('new ' + objtype + '('+ thisArg +')');
}else{
thisArg = thisArg || global;
}
thisArg.fn = this;
thisArg.fn();
delete thisArg[fn];
};
})(window); // 通过立即执行函数将全局对象传入,这里是global
传递参数、并返回值
(function(global){
Function.prototype.applynew = function(thisArg,args){
args = args || [];
const capotalize = ([first,...rest]) =>{
return first.toUpperCase() + rest.join('');
};
let objtype = capotalize(typeof thisArg);
if(objtype !== 'Object' && objtype !=='Undefined' ){
thisArg = eval('new ' + objtype + '('+ thisArg +')');
}else{
thisArg = thisArg || global;
}
var fn = Symbol('callback');
thisArg[fn] = this;
var returnValue = thisArg[fn](...args);
delete thisArg[fn];
return returnValue;
};
})(window);
到这里 apply 的模拟就基本上告一个段落了。
使用 applynew 来 模拟 bind
这里直接借鉴 JavaScript-关于this绑定 中的代码,只需要替换下 apply -> applynew
(function(global){
Function.prototype.applynew = function(thisArg,args){
args = args || [];
const capotalize = ([first,...rest]) =>{
return first.toUpperCase() + rest.join('');
};
let objtype = capotalize(typeof thisArg);
if(objtype !== 'Object' && objtype !=='Undefined' ){
thisArg = eval('new ' + objtype + '('+ thisArg +')');
}else{
thisArg = thisArg || global;
}
var fn = Symbol('callback');
thisArg[fn] = this;
var returnValue = thisArg[fn](...args);
delete thisArg[fn];
return returnValue;
};
Function.prototype.bindnew = function(obj){
var fn = this;
return function(){
return fn.applynew(obj,arguments);
}
};
})(window);
结尾
上面的实现还有许多问题
- 使用了ES6的语法
- 基本类型作为
thisArg输出this属性上会看到函数 - bindNew 应该也还有许多不严谨的地方
能力和时间有限,暂时就先这么处理。
完善
这个是完善版的callNew、apply
function capotalize(str){
var classArry = {};
"Boolean Number String".split(" ").forEach(function(item){
classArry[item.toLowerCase()] = item;
});
return classArry[str];
}
Function.prototype.callNew = Function.prototype.callNew || function(context) {
var contextClass = capotalize(typeof context);
if(contextClass){
context = eval('new ' + contextClass + '(context)');
}else{
context = context || window;
}
var args = [],
result;
context._fn_ = context._fn_ || this;
if (arguments.length > 1) {
for(var i = 1; i < arguments.length; i++){
args.push('arguments[' + i + ']');
}
result = eval('context._fn_(' + args + ')');
}else{
result = context._fn_();
}
delete context._fn_;
return result;
}
var obj = {
val: 'obj1'
};
var person = {
name: 'xiaodaidai',
age: 19
}
function test(person) {
if(person){
console.log(person.name);
console.log(person.age);
}
console.log(this);
return this.val;
}
var value = test.callNew(obj, person);
console.log(value);
console.log(test.callNew(obj));
Function.prototype.applyNew = Function.prototype.applyNew || function(context, arry) {
var contextClass = capotalize(typeof context);
if(contextClass){
context = eval('new ' + contextClass + '(context)');
}else{
context = context || window;
}
var result, args = [];
context._fn_ = context._fn_ || this;
if (arry) {
for(var i = 0; i < arry.length; i++){
args.push('arry[' + i + ']');
}
result = eval('context._fn_(' + args + ')');
} else {
result = context._fn_();
}
delete context._fn_;
return result;
}
var param = [];
param.push(person);
value = test.applyNew(obj, param);
console.log(value);
console.log(test.applyNew(obj));