frontend-interview
frontend-interview copied to clipboard
如何实现call和apply、bind?
-
call方法实现 Function.prototype.myCall = function(obj,...args) { let ctx = obj || window;//没有指定指向对象则指向对象设为window ctx.fn = this;//这个this指的是调用myCall的函数,将该函数绑定到ctx的fn属性上 let result = ctx.fn(...args); delete ctx.fn; }
-
apply方法实现 Function.prototype.myApply = function (obj,args) { let ctx = obj || window;//没有指定指向对象则指向对象设为window ctx.fn = this;//这个this指的是调用myApply的函数,将该函数绑定到ctx的fn属性上 let result = args? ctx.fn(...args): ctx.fn(); delete ctx.fn; }
-
bind方法实现 Function.prototype.myBind = function (obj) { if (typeof this !== "function") { throw new Error("Function.prototype.bind - what is trying to be bound is not callable"); }; var args = Array.prototype.slice.call(arguments,1)[0];//取出绑定时的参数 var self = this; var middleFun = function() {};//设置中间函数 var result = function() { var bindArgs = Array.prototype.slice.call(arguments)[0];//取出调用时的总参数 /**
- 当绑定函数作为构造器时,this指向实例,可以让实例获得绑定函数的值
- 当绑定函数为普通函数时,this指向window,将绑定函数的this指向obj */ self.apply(this instanceof middleFun? this: obj,args.concat(bindArgs)); } //中间函数继承调用绑定函数的函数 middleFun.prototype = self.prototype; //生成的函数再继承中间函数,这样当我们改变生成函数的原型中的某个属性时, //就不会影响到调用绑定函数的函数的属性,因为此时生成的函数与调用函数之间多了一层原型, //就是中间函数的原型。 result.prototype = new middleFun();//使用new继承,可以使middleFun的this丢失,重新指向new的新对象 return result; }
-
测试 var name = "window"; let ooo = { name:'haha' } function showname(show,more) { let newName = this.name; console.log(newName) console.log(show,more) } showname.myCall(ooo,"show","more");//haha show more showname.myApply(ooo,["show","more"]);//haha show more var aaa = showname.myBind(ooo,["show"]); aaa(["more"])//haha show more var bbb = new aaa(111);//undefined show 111
fn.call(xx, ...args) call 实现过程: 1. 获取第一个参数,若第一个参数为 null 或 undefined 时,this 指向 window,构建对象 2. 将对应函数传入构建的对象 3. 获取参数并执行相应函数 4. 删除该对象中函数 5. 返回计算结果 Function.prototype.myCall = function (context, ...args) { context = context ? Object(context) : window; context.fn = this; let result = context.fn(...args); delete context.fn; return result; }
fn.apply(xx, [...args]) apply 实现过程:过程call,只是参数处理部分略微不同,call处理的是参数队列,apply处理的是参数数组 Function.prototype.myApply = function (context, arr) { context = context ? Object(context) : window; context.fn = this; let result = arr ? context.fn(...arr) : context.fun(); // 处理的是参数数组 delete context.fn; return result; }
fn.bind(xx,[, arg1[, arg2[, ...]]]) 1. bind 返回的是一个函数 2. 能够接受多个参数,也能够接受柯里化形式的传参 3. bind 绑定 this 只会生效一次 4. 获取到调用bind() 返回值后,若当作构造函数调用的话,bind() 传入的上下文失效 Function.prototype.myBind = function (context, ...args) { if (typeof(this) !== 'function') { throw new TypeError('The bound object needs to be a function'); } const self = this; const fNOP = function() {}; const fBound = function(...fBoundArgs) { return self.apply(this instanceof fNOP ? this : context, [...args, ...fBoundArgs]); } if (this.prototype) { fNOP.prototype = this.prototype; } fBound.prototype = new fNOP(); return fBound; }
实现call apply
的主要思路
- 第一个参数为
undefined
或null
的时候,将this
指向window
- 改变了
this
指向,让新的对象执行该函数。
实现bind
的主要思路
-
bind
方法不会立即执行,需要返回一个待执行的函数 - 实现作用域绑定
- 参数传递
相关代码
- apply
Function.prototype.myApply = function(context){
if(typeof context === 'undefined' || context === null){
context = window
}
context.fn = this
const args = arguments[1]||[]
const result =context.fn(...args)
delete context.fn
return result
}
- call
Function.prototype.myCall = function(context){
if(typeof context === 'undefined' || context === null){
context = window
}
context.fn = this
const args = [...arguments].slice(1)
const result = context.fn(...args)
delete context.fn
return result
}
- bind
Function.prototype.myBind = function () {
let that = this
const args = [...arguments]
return function () {
that.call(...args)
}
}
call
function call(fn, ctx, ...args) {
if (ctx == undefined) {
return fn(...args)
}
let type = typeof ctx
type = type.replace(type[0], p => p.toUpperCase())
let res
const b = Symbol()
window[type].prototype[b] = fn
res = ctx[b](...args)
delete window[type].prototype[b]
return res
}
例子: console.log(call(Array.prototype.slice, '123', 2)) // '3' console.log(call(Object.prototype.toString, Symbol()) // '[object Symbol]' console.log(call(Object.prototype.toString, 1)) // '[object Number]'
apply
function apply(fn, ctx, args) {
return call(fn, ctx, ...args)
}
例子: function ko(a, b) { return this.a + a + b } console.log(apply(ko, { a: '2' }, [1, 2])) // '212'
bind
function bind(fn, ctx) {
return (...args) => apply(fn, ctx, args)
}
例子: console.log(bind(ko, { a: 2 })(1, 2)) // 5
遇到问题
Object.prototype.toString.call(undefined) 无法实现
call、aplly是立即执行,而bind却不是,call方法的第二个参数是展开数组,而apply是数组。
- call
Function.property.call = function call(context, ...params) {
// 1. 判断content是否传递
context == null ? context = window : null
// 2. 如果传递了,则context必须是对象
!/^(object|function)/.test(context) ? context = Object(context) : null
let self = this, result = null,
key = Symbol('key') // 新增的属性名称保持唯一性,防止污染了原始对象中的成员
context[key] = self
result = context[key](...params)
delete context[key] // 用完移除自己新增的属性
return result
}
- apply
Function.property.apply = function apply(context) {
context == null ? context = window : null
!/^(object|function)/.test(context) ? context = Object(context) : null
let self = this, result = null,
key = Symbol('key');
context[key] = self
const args = [...arguments].slice(1)
result = context[key](...args)
delete context[key]
return result
}
- bind
Function.property.bind = function bind(context, ...outerArgs) {
let self = this
return function (...innerArgs) {
self.call(context, ...outerArgs.contact(innerArgs))
}
}
call
Function.prototype.myCall = function (context, ...args) {
// 参数1,将要被指向的this
// apply中的this是调用apply函数函数
// context 表示Man的this,如果为空,将 context 指向全局对象,浏览器下windos,node环境global
if (typeof context === 'undefined' || context === null) {
context = window;
}
//
// 此处使用Symbol,防止context当前命名的属性值key与context已有的属性名冲突
let key = Symbol();
// 在man对象上挂载this,这里的this应为被Person调用,所以此处的this指向的是Person
// 此处用不可枚举属性
Object.defineProperty(context, key, {
value: this,
//是否为枚举属性
enumerable: false,
})
// context[key] = this;
// 执行 Person 函数,因为此处是context调用,context又是Man的this;
// 所以 Person 函数执行的时候,Person中this就指向了context,指向了Man
let fn = context[key](...args);
// 删除对象上的函数
delete context[key];
// Person函数的返回值。若没有返回值,则返回 `undefined`
return fn;
};
function Person(name, price) {
this.name = name;
this.price = price;
}
function Man(name, price) {
// 将Person中属性挂载到Man函数
Person.myCall(this, name, price);
this.category = 'food';
}
const obj = new Man('张三', '男');
console.log('obj', obj);
apply
Function.prototype.myApply = function (context, args) {
// 此处args是数组,已经是数组,不需要使用扩展符展开;此处可以增加args是否为数组的判断
// context 表示Man的this,如果为空,将 context 指向全局对象,浏览器下windos,node环境global
if (typeof context === 'undefined' || context === null) {
context = window;
}
// 此处使用Symbol,防止context为window是,命名的key与window已有的属性名冲突
let key = Symbol();
// 在man对象上挂载this,这里的this应为被Person调用,所以此处的this指向的是Person
context[key] = this;
// 执行 Person 函数,因为此处是context调用,context又是Man的this;
// 所以 Person 函数执行的时候,Person中this就指向了context,指向了Man
let fn = context[key](...args);
// 删除对象上的函数
delete context[key];
// Person函数的返回值。若没有返回值,则返回 `undefined`
return fn;
};
bind
Function.prototype.myBind = function (context) {
// 判空
if (typeof context === 'undefined' || context === null) {
context = window;
}
// 此时 context 是 Man 的this
self = this;
// 此时 self 是 Person 的this
return function (...args) {
// 此处可以理解为 Person 使用 apply 修改 Man的 this 值
return self.apply(context, args);
};
};
function Person(name, price) {
this.name = name;
this.price = price;
}
function Man(name, price) {
Person.call(this, name, price);
console.log(this);
}
function Man2(name, price) {
Person.myBind(this)(name, price);
}
const obj2 = new Man2('张三2', '男2');
console.log(obj2.name);
call和apply的实现就是利用js中this的动态作用域,改变函数的调用者,具体就是使用给传进来的参数添加一个独一无二的属性,这里可以使用symbol,将此函数添加到这个对象上,然后用这个对象调用这个方法,完事之后删除此属性即可 call,和apply实现过程基本相似,只有参数格式不同, bind可以使用call和apply将函数绑定到传入的对象当中,返回一个新的函数,这个函数中就是使用对象来调用函数
使用函数的方法调用模式改变this的指向,将被执行的函数作为传入上下文的属性被调用
// call
Function.prototype.myCall = function(content, ...args) {
// 判断如果执行上下文为空,则置为全局环境变量,浏览器模式是window,node环境是global
if(content === undefined || content === null) {
content = window;
}
// 设置传入上下文的属性key,使用symbol防止重名
const key = Symbol();
// 属性为当前函数的上下文
content[key] = this;
// 使用传入上下文执行当前函数,这里就使用了函数的方法调用模式
const res = content[key](...args);
delete content[key];
return res;
}
//demo
this.c = 333;
var Func = function(a, b) {
console.log(a+b+this.c)
}
var D = function(){
this.c = 222;
}
Func(1,2) // 336
Func.myCall(new D(), 1, 2) // 225
// apply
Function.prototype.myApply = function(content, args) {
// 判断如果执行上下文为空,则置为全局环境变量,浏览器模式是window,node环境是global
if(content === undefined || content === null) {
content = window;
}
// 设置传入上下文的属性key,使用symbol防止重名
const key = Symbol();
// 属性为当前函数的上下文
content[key] = this;
// 使用传入上下文执行当前函数,这里就使用了函数的方法调用模式
const res = content[key](...args);
delete content[key];
return res;
}
//demo
this.c = 333;
var Func = function(a, b) {
console.log(a+b+this.c)
}
var D = function(){
this.c = 222;
}
Func(1,2) // 336
Func.myApply(new D(), [1, 2]) // 225
// bind
Function.prototype.myBind = function(content) {
// 判断如果执行上下文为空,则置为全局环境变量,浏览器模式是window,node环境是global
if(content === undefined || content === null) {
content = window;
}
// this是当前函数的上下文,这里返回使用当前函数去apply的方法
const self = this;
return function(...args) {
self.apply(content, args)
}
}
//demo
this.c = 333;
var Func = function(a, b) {
console.log(a+b+this.c)
}
var D = function(){
this.c = 222;
}
Func(1,2) // 336
Func.myBind(new D())(1,2) // 225
call与apply的实现有些类似,拿apply来说,首先如果没有传this值,默认window; 然后给传入的context对象新填一个属性,让这个属性指向当前调用函数,然后根据传入的参数调用函数,返回结果; bind他是返回一个新函数,这就形成了一个闭包;通过arguments对象可以取得传入的参数,这个参数有两部分,一部分是函数bind是预备转入的参数,一个是这个闭包函数调用时传入的参数,最终接种apply方法处理当前函数想要绑定的this值与参数。
call apply bind利用将函数挂载在第一个参数context上执行来实现函数的内部this指向context, 其实就是利用了对象调用函数,函数默认this指向对象。再说一说三个方法的不同,call和apply一个都是上下文参数,第二个则有所不同,call是多个实参传值,apply则是是一个数组存放所有参数,bind其实与call apply不同的是返回一个被call/apply后函数,并不执行这个函数 模拟实现代码
https://juejin.cn/post/7055308553938010120/
-
模拟call
- 将函数设为对象的属性
- 执行该函数
- 删除该函数
Function.prototype.call2 = function(context) { // 首先要获取调用call的函数,用this可以获取 context.fn = this; context.fn(); delete context.fn; } var foo = { value: 1 }; function bar() { console.log(this.value); }
-
模拟apply
Function.prototype.apply = function (context, arr) { var context = Object(context) || window; context.fn = this; var result; 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;
-
模拟bind
- bind() 方法会创建一个新函数
- 模拟bind
- 返回一个新函数
- 可以传入参数,arguments
Function.prototype.bind2 = function (context) { var self = this; // 获取bind2函数从第二个参数到最后一个参数 var args = Array.prototype.slice.call(arguments, 1); return function () { // 这个时候的arguments是指bind返回的函数传入的参数 var bindArgs = Array.prototype.slice.call(arguments); self.apply(context, args.concat(bindArgs)); } }
- 三种方法都是改变函数this的指向,接收参数的形式不同,bind额外返回一个函数
- 由于函数都可以使用这三种方法,因此在Function的原型上进行修改或者扩充
- 为了不作弊,实现也不使用call,apply,用到es6解构赋值
call
Function.prototype.myCall = function(ctx, ...args) {
ctx = ctx || window
ctx.fn = this
const result = ctx.fn(...args)
delete ctx.fun
return result
}
apply
Function.prototype.myApply = function(ctx, args) {
ctx = ctx || window
ctx.fn = this
const result = ctx.fn(...args)
delete ctx.fun
return result
}
bind
Function.prototype.myBind = function(ctx, ...args) {
ctx = ctx || window
ctx.fn = this
return function(...returnArgs) {
return ctx.fn(...args, ...returnArgs)
}
}
- call
- 改变 this 指向
- 先看看怎么改变 this 的指向问题。call 这个方法是属于函数的,所以我们要实现的类似功能方法的话,需要给在构造函数上的原型上绑定自定义方法
- 处理传入的参数
- 把绑定上下文和arguments结合在一起
- 改变 this 指向
- apply
- call 和 apply 的在使用的时候,有个区别就是 apply 的参数必须是通过数组的形式
- bind
- bind 和 call 差不多,有点不同的就是 bind 终生绑定不立即执行返回一个新函数,传参和 call 是一样的
- 思路和 call 是一样的,唯一的不同是函数不能立即执行,可以返回一个匿名函数,当它执行的时候在去改变 this 指向和处理参数
call实现,使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。 该方法的语法和作用与 apply() 方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组。
Function.prototype.mycall = function (context,...args) {
let context = context ? context : window;
let args = args ? args : [];
let key = Symbol();
context[key] = this;
let res = context[key](...args);
delete context[key];
return res;
}
apply实现
Function.prototype.myapply = function (context,args) {
let context = context ? context : window;
let args = args ? args : [];
let key = Symbol();
context[key] = this;
let res = context[key](...args);
delete context[key];
return res;
}
bind实现,bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。因为返回是一个函数,所以使用new调用的时候应用是返回传入函数的 实例化后的对象
Function.prototype.mybind = function (context,...args) {
if(typeof this !== 'function'){
return new TypeError('must be function');
}
let _this=this;
return function F(...newArgs){
if(this instanceof F){
return new _this(...args,...newArgs);
}
return _this.apply(context,...args,...newArgs);
}
}
call实现
Function.prototype.myCall = function (context) {
if (typeof this !== 'function') {
throw new TypeError('not a function')
}
const symbolFn = Symbol()
const args = [...arguments].slice(1)
context = context || window
context[symbolFn] = this
const result = context[symbolFn](...args)
delete context[symbolFn]
return result
}
const obj = {
name: 'obj'
}
function foo () {
console.log(this.name)
}
foo.myCall(obj) // obj
apply
Function.prototype.myApply = function(context) {
if(typeof this !== 'function') {
throw new TypeError('error');
}
context = context || window;
context.fn = this;
var result = arguments[1] ? context.fn(...arguments[1]) : context.fn();
delete context.fn;
return result;
}
function foo(){
console.log(this.age);
}
var obj = {
age: 101
}
foo.myApply(obj); // 输出101
bind
Function.prototype.myBind = function(context) {
if(typeof this !== 'function') {
throw TypeError('error');
}
const self = this;
const args = [...arguments].slice(1);
return function F() {
if(this instanceof F) {
return new self(...args, ...arguments);
}
return self.apply(context, args.concat(...arguments));
}
}
function foo() {
console.log(this.age);
}
var obj = {
age: 121
}
var newFunc = foo.myBind(obj);
newFunc(); // 输出121
exports.call = function (context, ...args) { context.fn = this; const result = context.fn(...args); delete context.fn; return result; };
exports.apply = function (context, args) { context.fn = this; const result = context.fn(...args); delete context.fn; return result; };
exports.bind = function (context, ...args) { const f = this; return function F() { return f.apply(context, [...args, ...arguments]); }; };
// 此方法为某函数调用,所以this指向调用的函数 // 我们的目标是改变函数内部的this调用,然后调用函数并把参数原样传递 // 由this绑定四条规则: // 默认绑定是绑定为全局,不可行 // 显示绑定就是aply,call我们目前就是要实现这个,不可行 // new绑定是改变this为创建的新对象,不能达到定向绑定为obj的需求 // 所以只剩隐式绑定了, // 隐式绑定就是函数作为对象的属性引用被调用时,this就指向这个对象,那我们想让函数this指向obj,就得让函数变成obj的属性引用并调用
// 实现call,call第二个及以后的参数为一个一个传递
Function.prototype.myCall = function (obj, ...args) {
let context = obj ? Object(obj) : window; //obj不存在就绑定window对象
context.fn = this; //给obj添加fn属性,并指向函数
let result = context.fn(...args); //执行函数
delete context.fn; //删除fn属性
return result; //返回函数执行结果
}
//aply和call相似,只是传参方式不同,只有两个参数,第一个为绑定this的对象,第二个为所有需要传递给函数的参数数组
Function.prototype.myAplay = function (obj, args) {
let context = obj ? Object(obj) : window; //obj不存在就绑定window对象
context.fn = this; //给obj添加fn属性,并指向函数
let result = args ? context.fn(...args) : context.fn(); //执行函数
delete context.fn; //删除fn属性
return result; //返回函数执行结果
}
//bind是硬绑定,bind() 依然用来改变函数 this 指向,但它不会像 call() 与 apply() 方法会立即执行这个函数,而是返回一个 新函数 给外部,外部用一个变量去接收这个新函数并执行。
Function.prototype.myBind = function (context) {
// 判断调用 myBind 方法的是否为函数
if (typeof (this) !== "function") {
throw Error("调用_bind方法的必须为函数")
}
// 截取传给函数的参数
let args = Array.prototype.slice.call(arguments, 1)
// 保存这个函数,以便后续使用
let _fn = this
// 创建一个待会儿返回出去的函数,这个函数会赋到外部变量中
let bindFn = function () {
// 获取_bind方法返回的函数的参数
let newArgs = Array.prototype.slice.call(arguments)
// 通过apply去改变this指向,实现函数柯里化
let _obj = this.constructor === _fn ? this : context
_fn.apply(_obj, newArgs.concat(args))
}
// 创建一个中介函数,以便实现原型继承
let ProtoFn = function () {}
ProtoFn.prototype = _fn.prototype
bindFn.prototype = new ProtoFn()
// 返回bindFn的函数给外部
return bindFn;
}
let obj = {
a: 1
};
function b(b,c) {
console.log(this.a,b,c);
}
b.myCall(obj,2,3);//123
b.myAplay(obj,[2,3]);//123
var bar=b.myBind(obj);
bar(2,3);//123
过程
call 的作用是将函数的this指向第一个参数,并用剩余的参数作为函数的参数执行该函数 所以实现过程首先可以将当前函数绑定到传入 this 的 fn 属性上,然后将后续的参数作为函数的参数执行 this.fn,最后删除对象的这个属性 但是这里没有考虑到 this 对象已经有 fn 属性的情况,所以要判断一下,如果fn 属性存在,可以拼接一个随机数 接下来还没有考虑到有返回值的情况,所以还要处理一下返回值 调用 call 如果没有传入参数或者第一个参数为 null 或者 undefined 的话,会将函数 this 绑定到 window,如果传入参数为基本数据类型,会将其转为对象 所以要处理一下这里的逻辑 有了实现 call 的过程,aplly 和 bind 就大同小异了
call
Function.prototype.call2 = function(obj){
obj = obj?Object(obj):window;
let _fn = "fn",result;
while (obj.hasOwnProperty(_fn)) {
_fn = "fn" + Math.random(); // 循环判断并重新赋值
}
obj[_fn] = this;
if(arguments.length>1){
result = obj[_fn](...([...arguments].slice(1)));
}else{
result = obj[_fn]();
}
delete obj[_fn];
return result;
}
apply
Function.prototype.apply2 = function(obj,arr){
obj = obj?Object(obj):window;
let _fn = "fn",result;
while (obj.hasOwnProperty(_fn)) {
_fn = "fn" + Math.random(); // 循环判断并重新赋值
}
obj[_fn] = this;
if(arr){
result = obj[_fn](...arr);
}else{
result = obj[_fn]();
}
delete obj[_fn];
return result;
}
bind
Function.prototype.bind2 = function(obj){
obj = obj?Object(obj):window;
var myArguments = arguments,self = this;
if(arguments.length>1){
return function(){
self.apply(obj,[...myArguments].slice(1))
};
}else{
return function(){
self.apply(obj);
};
}
}
call call() 方法在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。 call 改变了 this 的指向; 调用函数执行 从 Arguments 对象中取值,取出第二个到最后一个参数,然后放到一个数组里 将函数设为对象的属性 执行该函数 删除该函数 this 参数可以传 null,当为 null 的时候,视为指向 window apply apply 的实现跟 call 类似 apply的第一个参数是普通函数,第二个则为该函数的参数祖成的数组 bind bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。 bind 函数的两个特点: 返回一个函数 可以传入参数
bind、call、apply 异同点 相同点:
调用者都是且必须是函数 改变函数执行时的上下文,即改变this指向
区别: call: fun.call(thisArg, param1, param2, ...)
使用一个指定的this值和单独给出的一个或多个参数来调用一个函数,返回调用结果
apply: fun.apply(thisArg, [param1,param2,...])
使用一个指定的this值和单独给出的 一个或多个参数的数组 来调用一个函数,返回调用结果
bind:fun.bind(thisArg, param1, param2, ...)
改变调用功函数的this指向,返回一个新的函数,不需要调用
实现思路: call、apply
- 设置this指向
- 给ctx设置临时函数,将函数的this隐式绑定指向到ctx上(此处为了防止绑定的对象中本身就存在与临时函数相同的字段,可以使用Symbol规避)
- 执行临时函数并传递单数
- 删除临时函数
- 返回结果
bind:
- 区别于call、apply,bind只需要返回绑定后的函数,不需要执行
- bind接收的参数都需要传递到绑定后的函数
实现call
// 重要思想有以下两点
// 1. 利用this隐式绑定指向调用者的原理,在obj下动态的加上foo方法,执行完之后再删除
// 2. 我们可以将mycall方法挂载到Function的原型对象上,这里每一个函数实例都能够调用mycall方法
// 3. 在mycall方法里可以通过当前的this获取到调用mycall的函数
Function.prototype.mycall = function (obj){
let context = obj || window; // 传入进来的,要将this指向的obj对象
let args = Array.from(arguments).slice(1);// 传进来的参数
let fn = Symbol('fn');// 定义一个方法名,只是用这个名字挂到obj下面而已,名字是什么不重要,所以我们用Symbol定义以免跟obj下的原有属性冲突
context[fn] = this;// 在传进来的对象下面把我们实际要调用的方法挂上
let result = context[fn](...args) // 调用context下的fn方法,这时实际就是执行了真正的函数方法,内部的this已经隐式的指向了context
delete context[fn];// 那到结果后,我们将这个方法删除
return result;
}
function foo(b,c){
console.log(this.a+b+c);
}
obj = {
a:1
}
foo.mycall(obj,2,3)
实现apply,和call相比只是传参不一样而已
Function.prototype.myapply = function (obj,args){
let context = obj || window;
let fn = Symbol('fn');
context[fn] = this;
let result = context[fn](...args);
delete context[fn]
return result;
}
function foo(b,c){
console.log(this.a + b + c)
}
let obj = {a:1}
foo.myapply(obj,[2,3])
实现bind,需要借用apply或者call,如果不让使用,那就先自己实现一个
// 和apply和call相比,bind是返回了一个函数,而且还需要保留传进来的obj和参数,
// 那么我们一定要用到闭包,去将这些内容存在闭包内
// 这里bind方法的实现,需要借用以下apply或者call,如果不准使用apply和call,那我们就自己先实现myapply和mycall
Function.prototype.mybind = function (obj) {
// 这里很多人喜欢加上this判断,判断this如果不是function那么要报错
// 我其实是很有疑问的,如果this不是function,那这个mybind什么情况下会被执行呢?
let context = obj || window;
var _this = this
var args = Array.from(arguments).slice(1)
// 这里返回一个函数
return function F() {
// 由于我们这里返回了一个函数,那么要考虑到外部会把我们这个函数当前做构造函数去执行new操作了
// 如果外部new了我们这个函数,按照new一个函数的步骤,这个this目前应该指向这个新的实例
// 那么这个this一定是F的实例,即满足下列条件
if (this instanceof F) {
// 那我们就用闭包内存储的_this当做构造函数,new一个实例出来返回出去
return new _this(...args, ...arguments)
}
// 如果不是上面那种情况,那就是正常调用了这个函数,那么我们就执行闭包内的存储的_this方法,并改变函数内部this,指向调用mybind传进来的上下文
return _this.apply(context, args.concat(...arguments))
}
}
function foo(b,c){
console.log(this.a+b+c);
}
obj = {
a:1
}
let newFoo = foo.mybind(obj,2,3) // 这一步执行完,其实就是得到了一个闭包F函数,并保存了传进来的obj和参数
newFoo();
let aaa = new newFoo();// 这里是刚才分析的new的那种场景,需要判断instanceof
// newFoo()
call
Function.prototype.mycall = function (context, ...args) {
context.fn = this;
const result = context.fn(...args);
delete context.fn;
return result;
};
apply
Function.prototype.myApply = function (context, args) {
context.fn = this;
const result = context.fn(...args);
delete context.fn;
return result;
};
bind
Function.prototype.myBind = function (context, ...args) {
const f = this;
return function () {
return f.apply(context, [...args, ...arguments]);
};
};
1、实现call、apply、bind这个三个函数我们首先要先实现call,后面两个基于它来实现;
2、要实现他们肯定要知道这三个函数的特点: 1)call/apply是立即执行的函数,直接返回函数执行结果, 2)bind是返回一个调用函数的克隆版并改变了this指向的函数,非立即调用 3)call接受的是多个参数,apply接受的是数组参数
3、开始实现
a. call的实现:
1)我们利用给指定上下文环境创建一个临时对象,添加调用函数作为它的一个唯一key作为键的函数值,这样通过对象调用该函数就实现了用指定的上下文环境的this来指向这个调用函数了
2)用完临时属性再删除返回调用结果
Function.prototype.myCall = function(ctx,...args) { const obj = ctx == undefined ? window : Object(ctx); const key = Symbol(); obj[key] = this; const result = obj[key](...args); delete obj[key]; return result; }
b. apply的实现就简单多了,直接调用call,把数组参数展开传进去就行了:
Function.prototype.myApply = function(ctx,arr=[]) { return this.myCall(ctx,...arr); }
c. bind的实现:我们需要把调用函数克隆一下,首先用个指针保存调用函数的this,创建个闭包函数返回用myCall改变this指向后执行的结果,最后判断下传入的上下文环境有没有prototype,如果有就给新函数加上;
Function.prototype.myBind = function(ctx,...args) { let _self = this; const newFn = function(...rest) { return _self.myCall(ctx,...args,...rest); } if(_self.prototype) { newFn.prototype = Object.create(_self.prototype); } return newFn; }
我们要去实现call apply by这三个函数,就首先是要确定他们的核心职责是什么。他们的核心职责就是要去调用函数,并且是以指定的 this 对象。
那么调用这个函数的时候,肯定会传入一个函数,传入一个指定的对象。那么调用函数是很简单的,实现的难度主要是围绕在怎么去指定 this 上面。那么我们知道指定 this 的话是有三种方式的啊。一种是以构造函数的方式去调用函数,以普通函数的方式去调用函数,还有就是以方法的方式去调用函数,那么,其中只有以方法的方式去调用函数,才可能让我们有机会去让 this 为任意值。
那么我们知道了可以通过以方法调用函数的方式去指定 this 指向。那么具体操作的话,具体实现的话,我们就需要把所调用的函数挂到所指定的对象上,然后以方法的形式去调用,就能实现call apply bind的核心逻辑了。
Function.prototype.mycall = function (context = window,...args) {
let key = Symbol();
context[key] = this;
let res = context[key](...args);
delete context[key];
return res;
}
Function.prototype.myapply = function (context = window,args = []) {
let key = Symbol();
context[key] = this;
let res = context[key](...args);
delete context[key];
return res;
}
Function.prototype.mybind = function (context,...args) {
let _this=this;
return function F(...newArgs){
if(this instanceof F){
return new _this(...args,...newArgs);
}
return _this.apply(context,...args,...newArgs);
}
}