call 与 apply
call 和 apply 用法相似,都是用来改变函数的 this 指向
唯一不同点 apply 接受的参数是一个数组,call 是参数列表
// apply语法
func.apply(thisArg, [argsArray])
// call语法
function.call(thisArg, arg1, arg2, ...)
call
thisArg
: 是指在function
运行时指定的this
- 不传或者传 null/undefined 函数的的
this
执向 window - 传递一个对象,函数中的 this 指向这个对象
- 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象,如 String、Number、Boolean
- 传递另一个函数的函数名,函数中的 this 指向这个函数的引用,并不一定是该函数执行时真正的 this 值
// 示例
function a() {
//输出函数a中的this对象
console.log(this);
}
//定义函数b
function b() {}
var obj = { name: "1231" }; //定义对象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
// 模拟实现call
// 第一步 简单实现了this的指向
Function.prototype.call2 = function (context) {
// this是指b这个函数
context.fn = this;
context.fn();
delete context.fn;
};
var f = {
value: 2,
};
function b() {
console.log(this.value);
}
b.call2(b);
// 第二步实现接受参数
// call 接受的参数是一个列表,我们不清楚有多少,这个时候我们可以用arguments去接收参数
Function.prototype.call2 = function (context) {
context.fn = this;
var args = []; // 定义一个数组用来接收传进来的参数
// 0的位置上是函数,所以从1的位置开始循环遍历
for (var i = 1, len = arguments.length; i < len; i++) {
args.push("arguments[" + i + "]"); // argument[i]代表位置上的参数
}
eval("context.fn(" + args + ")"); // eval主要用来执行函数
delete context.fn;
};
// 测试一下
var foo = {
value: 1,
};
function bar(name, age) {
console.log(name);
console.log(age);
console.log(this.value);
}
bar.call2(foo, "kevin", 18);
// 模拟第三步
// this 参数可以传 null 或者 undefined,此时 this 指向 window
// this 参数可以传基本类型数据,原生的 call 会自动用 Object() 转换
// 函数是可以有返回值的
// 第三版
Function.prototype.call2 = function (context) {
context = context ? Object(context) : window; // 增加了对this的判断
context.fn = this;
var args = [];
for (var i = 1, len = arguments.length; i < len; i++) {
args.push("arguments[" + i + "]");
}
var result = eval("context.fn(" + args + ")");
delete context.fn;
return result; // 增加了返回值
};
// 测试一下
var value = 2;
var obj = {
value: 1,
};
function bar(name, age) {
console.log(this.value);
return {
value: this.value,
name: name,
age: age,
};
}
function foo() {
console.log(this);
}
bar.call2(null); // 2
foo.call2(123); // Number {123, fn: ƒ}
bar.call2(obj, "kevin", 18);
// 1
// {
// value: 1,
// name: 'kevin',
// age: 18
// }
强烈建议自己手动敲一遍增加理解,不懂的地方可以打印一下,看看
汇总
//es3
Function.prototype.call = function (context) {
context = context ? Object(context) : window;
context.fn = this;
var args = [];
for (var i = 1, len = arguments.length; i < len; i++) {
args.push("arguments[" + i + "]");
}
var result = eval("context.fn(" + args + ")");
delete context.fn;
return result;
};
//es6
Function.prototype.call = function (context) {
context = context ? Object(context) : window;
context.fn = this;
let args = [...arguments].slice(1);
let result = context.fn(...args);
delete context.fn;
return result;
};
apply
apply 和 call 不同的就是接收参数的问题,所以我们基于之前 call 模拟实现来分析 apply 怎么接收参数就可以了
// es3
Function.prototype.apply = function (context, arr) {
context = context ? Object(context) : window;
context.fn = this;
var result;
// 判断arr参数是否存在,不存在直接执行函数,
if (!arr) {
result = context.fn();
} else {
var args = [];
for (var i = 0, len = arr.length; i < len; i++) {
args.push("arr[" + i + "]");
}
result = eval("context.fn(" + args + ")");
}
delete context.fn;
return result;
};
// es6
Function.prototype.apply = function (context, arr) {
context = context ? Object(context) : window;
context.fn = this;
let result;
if (!arr) {
result = context.fn();
} else {
result = context.fn(...arr);
}
delete context.fn;
return result;
};
以上的模拟实现,我需要注意一个问题我们都是默认 context 没有这个 fn 的属性,所以我们必须保证 fn 的唯一性
// es6
// Symbol 可以完美解决这个问题
var fn = Symbol(); // 添加代码
context[fn] = this; // 添加代码
// es3
//循环遍历判断自身是否存在如果存在给随机数,不存在直接返回
function fnFactory(context) {
var unique_fn = "fn";
while (context.hasOwnProperty(unique_fn)) {
unique_fn = "fn" + Math.random(); // 循环判断并重新赋值
}
return unique_fn;
}
====================================
var fn = fnFactory(context); // 修改代码
context[fn] = this; // 修改代码
bind
概念:bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
demo 主要的作用还是绑定 this
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
bind 方法与 call / apply 最大的不同就是前者返回一个绑定上下文的函数,而后两者是直接执行了函数。
想要实现一个 bind,我们要来看一下 bind 主要的功能
- 接收一个 this
- 返回一个函数
- 接收参数
- 柯里化
模拟实现
// 绑定this 并且返回函数 第一版
Function.prototype.bind1 = function (context) {
// this是指下面demo的bar函数
// context 是指 foo 对象
var self = this;
return function () {
return self.apply(context); // 绑定foo的this
};
};
// 测试案例
var value = 1;
var foo = {
value: 2,
};
function bar() {
return this.value;
}
var ab1 = bar.bind1(foo);
console.log(ab1()); // 2
// 接收参数 柯里化
Function.prototype.bind1 = function (context) {
if (typeof this !== "function") {
throw new Error("调用bind必须是一个函数");
}
var self = this;
// 实现第3点,因为第1个参数是指定的this,所以只截取第1个之后的参数
var args = Array.prototype.slice.call(arguments, 1);
return function () {
// 实现第4点,这时的arguments是指bind返回的函数传入的参数
// 即 return function 的参数
// 这个arguments代表下面测试的20
var bindArgs = Array.prototype.slice.call(arguments);
return self.apply(context, args.concat(bindArgs));
};
};
// 测试用例
var value = 2;
var foo = {
value: 1,
};
function bar(name, age) {
return {
value: this.value,
name: name,
age: age,
};
}
var bindFoo = bar.bind1(foo, "Jack");
console.log(bindFoo(20)); //{ value: 1, name: 'Jack', age: 20 }
使用 bind 绑定的函数,依然可以使用 new 去构造函数,这个时候 this 就无效了,但是参数可以
正常的使用传给函数
看下如何实现
Function.prototype.bind1 = function (context) {
if (typeof this !== "function") {
throw new Error("调用bind必须是一个函数");
}
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fNOP = function () {};
var fBound = function () {
var bindArgs = Array.prototype.slice.call(arguments);
return self.apply(
this instanceof fNOP ? this : context,
args.concat(bindArgs)
);
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
new 的实现
new 具有一下两个特性
- 访问到构造函数里的属性
- 访问到原型里的属性
模拟实现
function new1() {
// 1、获得构造函数,同时删除 arguments 中第一个参数
Con = [].shift.call(arguments);
// 2、创建一个空的对象并链接到原型,obj 可以访问构造函数原型中的属性
var obj = Object.create(Con.prototype);
// 3、绑定 this 实现继承,obj 可以访问到构造函数中的属性
var ret = Con.apply(obj, arguments);
// 4、优先返回构造函数返回的对象
return ret instanceof Object ? ret : obj;
}
参考文献:
网友评论