call常见用法
var name = 'cindy'
function fn(age,grade) {
console.log(this.name,age,grade)
}
const obj = {
name:'jack'
}
// 执行
fn.call(obj,18,98) // jack 18 98
call方法的核心是改变函数内部的this的指向,并执行函数。
大概思路:在obj对象添加一个fn方法,执行obj.fn方法,删除该方法。
手写一个call
// ES6 写法
Function.prototype.myCall = function(context,...args){
context = context || window;
/* 这里的this是什么???
** 调用call函数的对象,call内部的this就指向它,因此this指向fn
*/
context.fn = this;
// 被绑定函数有可能是有返回值的,所以我们会在执行函数的时候,使用return。
return context.fn(...args);
delete context.fn;
}
由于...args写法是最早在ES6出现的,我们可以尝试用ES6以下的写法实现吗?
// ES5 写法
Function.prototype.myCall = function(context){
context = context || window;
let args = [];
for(let i=0;i<arguments.length;i++){
args.push('arguments[' + i + ']')
}
context.fn = this;
// 执行context.fn(argumnets[0],argumnets[1]...)
return eval('context.fn(' + args + ')')
delete context.fn;
}
以上写法,eval简单来说,就是用JavaScript的解析引擎来解析这一堆字符串里面的内容,这么说吧,你可以这么理解,你把eval看成是<script>标签。
eval('function Test(a,b,c,d){console.log(a,b,c,d)};Test(1,2,3,4)')
在eval中,args自动调用tostring方法,将数组转换成字符串。
手写一个apply
apply和call的不同之处就是apply可以接受参数数组。
// ES6
Function.prototype.myApply = function(context,arr){
context = context || window;
context.fn = this;
return context.fn(...arr);
delete context.fn;
}
// ES5 写法
Function.prototype.myApply = function(context,arr){
context = context || window;
context.fn = this;
if(!arr){
return context.fn()
} else {
let args = [];
for(let i=0;i<arr.length;i++){
args.push('arr[' + i + ']')
}
// 执行context.fn(arr[0],arr[1]...)
return eval('context.fn(' + args + ')')
}
delete context.fn;
}
bind常见用法
var name = 'cindy'
function fn(age,grade) {
console.log(this.name,age,grade)
}
const obj = {
name:'jack'
}
// 执行
let f = fn.bind(obj)
f(18,98) // jack 18 98
let f = fn.bind(obj,18)
f(98) // jack 18 98
竟然还可以这样传参数!!!
手写一个bind
Function.prototype.myBind = function(context){
let self = this;
context = context || window
let args = Array.prototype.slice.call(arguments,1)
return function(){
return self.apply(context,args.concat(Array.prototype.slice.call(arguments)))
}
}
难点
bind函数该有一个用法,就是当作构造函数。
var value = 2;
var foo = {
value: 1
};
function bar(name, age) {
this.habit = 'shopping';
console.log(this.value);
console.log(name);
console.log(age);
}
bar.prototype.friend = 'kevin';
// 此时bar内部this指向foo
var bindFoo = bar.bind(foo, 'daisy');
// 此时bar函数内部this指向obj,导致this.value无法取值
var obj1 = new bindFoo('18'); // undefined 'daisy' 18
console.log(obj1.habit); // 'shopping'
// 实例对象可以取到bar函数原型对象上的值
console.log(obj1.friend); // 'kevin'
依据以上bind函数的特点:
- bind后返回的函数作构造函数时,此时被绑定的函数bar中的this指向构造函数生成的实例对象obj1
- 实例对象obj1可以取到被绑定函数原型对象上的属性或者方法
我们来改造bind实现:
Function.prototype.myBind = function(context){
let self = this;
context = context || window
let args = Array.prototype.slice.call(arguments,1)
let fbind = function(){
return self.apply(this instanceof fbind ? this: context,args.concat(Array.prototype.slice.call(arguments)))
}
fbind.prototype = Object.create(this.prototype)
return fbind
}
解析:self.apply(this.instanceof fbind ? this: context , params)
如果返回函数(上例中的bindFoo)被用作构造函数,此时被绑定函数(上例中的bar)中的this应该指向实例对象(上例中的obj1)
所以判断this是否是被返回函数的实例。
解析:fbind.prototype = Object.create(this.prototype)
为了可以让实例对象(obj1)可以访问到被绑定函数(上例中的bar)原型对象上的属性和方法。
之所以包一层Object.create,是为了防止修改一个函数原型时,影响了另一个函数原型。
让返回函数原型指向被绑定函数原型。