实现 call、apply、bind
实现 call、apply、bind
在之前一篇文章写了这三个参数的区别,但是其实面试更常考察如何实现。其实所有的原生函数的 polyfill 如何实现,只需要考虑 4 点即可:
- 基本功能
- 原型
- this
- 返回值
call
- call 的基本功能:
call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
- 原型
不涉及原型链的转移,不用管 - this
本质上,call 就是 this 的转移 - 返回值
简单实现:
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;
};
但是这里还有个问题,如果改了Age18Person的prototype,也会影响到Person的prototype。
所以,需要做一个中转——
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的更多相关文章
- call,apply,bind的用法
关于call,apply,bind这三个函数的用法,是学习javascript这门语言无法越过的知识点.下边我就来好好总结一下它们三者各自的用法,及常见的应用场景. 首先看call这个函数,可以理解成 ...
- JavaScript中call,apply,bind方法的总结。
why?call,apply,bind干什么的?为什么要学这个? 一般用来指定this的环境,在没有学之前,通常会有这些问题. var a = { user:"追梦子", fn:f ...
- call(),apply(),bind()与回调
1.call(),apply(),bind()方法 JavaScript 中通过call或者apply用来代替另一个对象调用一个方法,将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定 ...
- JS 的 call apply bind 方法
js的call apply bind 方法都很常见,目的都是为了改变某个方法的执行环境(context) call call([thisObj[,arg1[, arg2[, [,.argN]]]] ...
- javascript-this,call,apply,bind简述2
上节我们一起研究了this这个小兄弟,得出一个结论,this指向调用this所在函数(或作用域)的那个对象或作用域.不太理解的朋友可以看看上节的内容,这次我们主要探讨一下call(),apply(), ...
- javascript-this,call,apply,bind简述1
最近在系统的学习面向对象方面的知识,遇到的最大拦路虎就数this的指向,call,apply,bind函数的使用,单独抽出一天时间把这几个烦人的家伙搞定,去学习更深入的内容. 首先介绍一下this的一 ...
- call,apply,bind方法的总结
why?call,apply,bind干什么的?为什么要学这个? 一般用来指定this的环境,在没有学之前,通常会有这些问题. var a = { user:"追梦子", fn:f ...
- JavaScript中call,apply,bind方法的总结
原文链接:http://www.cnblogs.com/pssp/p/5215621.html why?call,apply,bind干什么的?为什么要学这个? 一般用来指定this的环境,在没有学之 ...
- call, apply,bind 方法解析
call(), apply(),bind() 三者皆为Function的方法 call(),apply()的作用是调用方法,并改变函数运行时的context(作用上下文) bind() 的作用是引用方 ...
- JS中call,apply,bind方法的总结
why?call,apply,bind干什么的?为什么要学这个? 一般用来指定this的环境,在没有学之前,通常会有这些问题. var a = { user: "小马扎", fn: ...
随机推荐
- UVA - 12099 The Bookcase
No wonder the old bookcase caved under the massive piles of books Tom had stacked on it. He had bett ...
- Springboot2.x + ShardingSphere 实现分库分表
之前一篇文章中我们讲了基于Mysql8的读写分离(文末有链接),这次来说说分库分表的实现过程. 概念解析 垂直分片 按照业务拆分的方式称为垂直分片,又称为纵向拆分,它的核心理念是专库专用. 在拆分之前 ...
- 前端Vue知识小白
感觉是已好久没写博文了.今日难得有时间,便写一篇文章.此文章是关于前端知识的,我本身是后端,因工作或其他需要,便学习了前端Vue.此文章是在菜鸟教程上学习的.那么下面进入正文! 首先,Vue.js是一 ...
- DRF框架中csrf异常
一.报错信息 "detail": "CSRF Failed: CSRF cookie not set." 二.解决办法 方法一: 在配置文件中配置 REST_F ...
- win2008加入域控之尝试解析加入域中域控制器的dns名称失败解决办法
记录下今天遇到以前没遇到的问题 加入域的时候提示“尝试解析加入域中控制器的DNS”名称失败 可能的原因: 如果确认dns没问题 dc正常访问,那可能就是因为域控制器无法向dns注册srv记录. SRV ...
- MFC::使用mysql
下载mysql-installer-community-5.7.16.0.msi,安装 mysql server即可. 创建工程包含头文件 #include "winsock.h" ...
- IIS6.0使用冒号上传漏洞利用
利用条件: 1.iis版本为6.0 2.上传文件名不会重命名 利用: 上传一个jpg木马图片 名字为:cs.asp:.jpg 注意是: 默认windows是不允许文件字含:(冒号)的 所以需要抓包后 ...
- C#刷遍Leetcode面试题系列连载(5):No.593 - 有效的正方形
上一篇 LeetCode 面试题中,我们分析了一道难度为 Easy 的数学题 - 自除数,提供了两种方法.今天我们来分析一道难度为 Medium 的面试题. 今天要给大家分析的面试题是 LeetCod ...
- 【Leetcode 做题学算法周刊】第一期
首发于微信公众号<前端成长记>,写于 2019.10.28 背景 本文记录刷题过程中的整个思考过程,以供参考.主要内容涵盖: 题目分析设想 编写代码验证 查阅他人解法 思考总结 目录 1. ...
- Linux下终端字体颜色设置方法
颜色=\033[代码;前景;背景m 如:\033[1;32;40m表示高亮显示字体为绿色,背景色为黑色 颜色=\[\033[代码;前景;背景m\] echo -e "this is a \0 ...