实现 call、apply、bind

在之前一篇文章写了这三个参数的区别,但是其实面试更常考察如何实现。其实所有的原生函数的 polyfill 如何实现,只需要考虑 4 点即可:

  1. 基本功能
  2. 原型
  3. this
  4. 返回值

call

  1. call 的基本功能:

    call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

  2. 原型

    不涉及原型链的转移,不用管
  3. this

    本质上,call 就是 this 的转移
  4. 返回值

简单实现:

Function.prototype.myCall = function(context = window, ...args) {
context.fn = this; // 先将fn挂在context上,
var res = context.fn(...args); // 然后通过context调用fn,使得fn中的this指向指到context上
delete context.fn; // 最后删除掉context上的fn
return res; // 返回原函数的返回值
};

上面为了简单,使用了 ES6 的剩余参数和展开语法,基本用这个回答面试官就好了。当然,如果不让使用剩余参数,那就只能使用eval或者new Function的字符串拼接大法了,可以参考这篇模板引擎

再就是 fn 可能会和 context 重名,整一个不会重名的 uniqueID 挂上去,执行完毕后删除。

apply

之前提过 apply 和 call 区别,只有一些入参和性能上的区别。直接上代码:

Function.prototype.myApply = function(context = window, args) {
context.fn = this; // 先将fn挂在context上,
var res = context.fn(...args); // 然后通过context调用fn,使得fn中的this指向指到context上
delete context.fn; // 最后删除掉context上的fn
return res; // 返回原函数的返回值
};

bind

bind 有点不一样,它会返回一个绑定了 this 的函数。

bind()方法创建一个新的函数,在 bind()被调用时,这个新函数的 this 被 bind 的第一个参数指定,其余的参数将作为新函数的参数供调用时使用。

Function.prototype.myBind = function(context, ...args) {
var fn = this; var newFn = function(...restArgs) {
// 使用call去调用fn,因为bind可能会bind一部分参数,所以把restArgs也传进去
return fn.call(context, ...args, ...restArgs);
}; return newFn;
};

上面的函数基本上覆盖了大部分场景,但是不能支持new调用——

绑定函数自动适应于使用 new 操作符去构造一个由目标函数创建的新实例。当一个绑定函数是用来构建一个值的,原来提供的 this 就会被忽略。不过提供的参数列表仍然会插入到构造函数调用时的参数列表之前。

如果直接使用我们上面所写的bind,就会返回

function Person(age, name) {
this.name = name;
this.age = age;
} var Age18Person = Person.myBind(null, 18); var a = {};
var Age20Person = Person.myBind(a, 20); var p18 = new Age18Person("test18"); // newFn {}
var p20 = new Age20Person("test20"); // newFn {}
// a {name: "test20", age: 20}
// window {name: "test18", age: 18}

显然,返回了以newFn生成的对象,并且,因为传入的是null,所以,对context的赋值转移到了window

这里需要判断是否被 new 调用,然后丢弃没用的 context。

Function.prototype.myBind = function(context, ...args) {
var fn = this; var newFn = function(...restArgs) {
// 如果是new构造,则使用new构造的实例
if (new.target) {
return fn.call(this, ...args, ...restArgs);
}
// 使用call去调用fn,因为bind可能会bind一部分参数,所以把restArgs也传进去
return fn.call(context, ...args, ...restArgs);
}; return newFn;
};

再次调用上面的new构造,发现实例的原型不是指向我们希望的 Person

var Age18Person = Person.myBind(null, 18);

var p18 = new Age18Person("test18"); // newFn {}

p instanceof Person; // false
p instanceof Age18Person; // false

记录一下原型链,再来一遍

Function.prototype.myBind = function(context, ...args) {
var fn = this; var newFn = function(...restArgs) {
// 如果是new构造,则使用new构造的实例
if (new.target) {
return fn.call(this, ...args, ...restArgs);
}
// 使用call去调用fn,因为bind可能会bind一部分参数,所以把restArgs也传进去
return fn.call(context, ...args, ...restArgs);
}; // 绑定原型链
newFn.prototype = this.prototype; return newFn;
};

但是这里还有个问题,如果改了Age18Personprototype,也会影响到Personprototype

所以,需要做一个中转——

Function.prototype.myBind = function(context, ...args) {
var fn = this; var newFn = function(...restArgs) {
// 如果是new构造,则使用new构造的实例
if (new.target) {
return fn.call(this, ...args, ...restArgs);
}
// 使用call去调用fn,因为bind可能会bind一部分参数,所以把restArgs也传进去
return fn.call(context, ...args, ...restArgs);
}; var NOOP = function() {}; // 绑定原型链
NOOP.prototype = this.prototype;
newFn.prototype = new NOOP(); return newFn;
};

这样基本上就算完成了,当然更推荐function-bind方案。

实现 call、apply、bind的更多相关文章

  1. call,apply,bind的用法

    关于call,apply,bind这三个函数的用法,是学习javascript这门语言无法越过的知识点.下边我就来好好总结一下它们三者各自的用法,及常见的应用场景. 首先看call这个函数,可以理解成 ...

  2. JavaScript中call,apply,bind方法的总结。

    why?call,apply,bind干什么的?为什么要学这个? 一般用来指定this的环境,在没有学之前,通常会有这些问题. var a = { user:"追梦子", fn:f ...

  3. call(),apply(),bind()与回调

    1.call(),apply(),bind()方法 JavaScript 中通过call或者apply用来代替另一个对象调用一个方法,将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定 ...

  4. JS 的 call apply bind 方法

    js的call apply bind 方法都很常见,目的都是为了改变某个方法的执行环境(context) call call([thisObj[,arg1[, arg2[,   [,.argN]]]] ...

  5. javascript-this,call,apply,bind简述2

    上节我们一起研究了this这个小兄弟,得出一个结论,this指向调用this所在函数(或作用域)的那个对象或作用域.不太理解的朋友可以看看上节的内容,这次我们主要探讨一下call(),apply(), ...

  6. javascript-this,call,apply,bind简述1

    最近在系统的学习面向对象方面的知识,遇到的最大拦路虎就数this的指向,call,apply,bind函数的使用,单独抽出一天时间把这几个烦人的家伙搞定,去学习更深入的内容. 首先介绍一下this的一 ...

  7. call,apply,bind方法的总结

    why?call,apply,bind干什么的?为什么要学这个? 一般用来指定this的环境,在没有学之前,通常会有这些问题. var a = { user:"追梦子", fn:f ...

  8. JavaScript中call,apply,bind方法的总结

    原文链接:http://www.cnblogs.com/pssp/p/5215621.html why?call,apply,bind干什么的?为什么要学这个? 一般用来指定this的环境,在没有学之 ...

  9. call, apply,bind 方法解析

    call(), apply(),bind() 三者皆为Function的方法 call(),apply()的作用是调用方法,并改变函数运行时的context(作用上下文) bind() 的作用是引用方 ...

  10. JS中call,apply,bind方法的总结

    why?call,apply,bind干什么的?为什么要学这个? 一般用来指定this的环境,在没有学之前,通常会有这些问题. var a = { user: "小马扎", fn: ...

随机推荐

  1. C++学习笔记-预备知识

    1.1 C++简介 C++融合3种不同的编程方式:C语言代表的过程性语言.C++在C语言基础上添加的类代表的面向对象语言.C++模板支持的广泛编程. 1.2 C++简史 1.2.1 C语言 Ritch ...

  2. java IO、NIO、AIO详解

    概述 在我们学习Java的IO流之前,我们都要了解几个关键词 同步与异步(synchronous/asynchronous):同步是一种可靠的有序运行机制,当我们进行同步操作时,后续的任务是等待当前调 ...

  3. Java读源码之LockSupport

    前言 JDK版本: 1.8 作用 LockSupport类主要提供了park和unpark两个native方法,用于阻塞和唤醒线程.注释中有这么一段: 这个类是为拥有更高级别抽象的并发类服务的,开发中 ...

  4. 实验吧之【你真的会PHP吗?】

    你真的会PHP吗? 首先刚进网页就是一个have fun  看了源码没有什么提示,也没有输入框,那就打开F12看看 有提示 6c525af4059b4fe7d8c33a.txt 访问 http://c ...

  5. 线段树区间取max区间查询

    要线段树资瓷区间max和询问区间和. 设要把$[L, R]$对mx取max. 我们可以在线段树上二分出小于mx的区间然后变成区间修改了. 具体实现是,维护区间最小值和区间最大值,我们递归进入一个区间, ...

  6. opencv::基本阈值操作

    图像阈值(threshold) 阈值 是什么?简单点说是把图像分割的标尺,这个标尺是根据什么产生的,阈值产生算法?阈值类型.(Binary segmentation) 阈值类型一阈值二值化(thres ...

  7. .htaccess文件上传利用

    一般.htaccess可以用来留后门和针对黑名单绕过 创建一个txt写入 AddType application/x-httpd-php .png 打开另存为 保存类型为所有文件 上传.htacces ...

  8. jmeter-使用代理服务器录制脚本

    使用代理服务器录制脚本 1.测试计划-添加线程组 2.工作台添加HTTP代理服务器(路径:工作台-右键添加-非测试元件-HTTP代理服务器) 3.端口号一般由8888改为其他的 4.打开chrome的 ...

  9. Mysql高手系列 - 第26篇:聊聊如何使用mysql实现分布式锁

    Mysql系列的目标是:通过这个系列从入门到全面掌握一个高级开发所需要的全部技能. 欢迎大家加我微信itsoku一起交流java.算法.数据库相关技术. 这是Mysql系列第26篇. 本篇我们使用my ...

  10. 在.net core3.0中使用SignalR实现实时通信

    最近用.net core3.0重构网站,老大想做个站内信功能,就是有些耗时的后台任务的结果需要推送给用户.一开始我想简单点,客户端每隔1分钟调用一下我的接口,看看是不是有新消息,有的话就告诉用户有新推 ...