百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 文章教程 > 正文

Js 实现 Bind 的这五层,你在第几层?

xsobi 2024-12-16 17:05 1 浏览

作者: 蓝色的秋风 来源:秋风的笔记

本文转载自微信公众号「秋风的笔记」,作者蓝色的秋风 。转载本文请联系秋风的笔记公众号。

最近在帮女朋友复习 JS 相关的基础知识,遇到不会的问题,她就会来问我。

这不是很简单?三下五除二,分分钟解决。

function bind(fn, obj, ...arr) {

return fn.apply(obj, arr)

}

于是我就将这段代码发了过去

这时候立马被女朋友进行了一连串的灵魂拷问。

这个时候,我马老师就坐不住了,我不服气,我就去复习了一下 bind,发现太久不写基础代码,还是会需要一段时间复习,这一次我得写一个有深度的 bind,深得马老师的真传,给他分成了五层速记法。

第一层 - 绑定在原型上的方法

这一层非常的简单,得益于 JS 原型链的特性。由于 function xxx 的原型链 指向的是 Function.prototype , 因此我们在调用 xxx.bind 的时候,调用的是 Function.prototype 上的方法。

Function.prototype._bind = function() {}

这样,我们就可以在一个构造函数上直接调用我们的bind方法啦~例如像这样。

funciton myfun(){}

myfun._bind();

想要详细理解这方面的可以看这张图和这篇文章(https://github.com/mqyqingfeng/blog/issues/2)

第二层 - 改变 this 的指向

这可以说是 bind 最核心的特性了,就是改变 this 的指向,并且返回一个函数。而改变 this , 我们可以通过已知的 apply 和 call 来实现,这里我们就暂且使用 apply 来进行模拟。首先通过 self 来保存当前 this,也就是传入的函数。因为我们知道 this 具有 隐式绑定的规则(摘自 《你不知道的JavaScript(上)》2.2.2 ),

function foo() {console.log(this.a)}

var obj = {a: 2, foo};

obj.foo(); // 2

通过以上特性,我们就可以来写我们的 _bind 函数。

Function.prototype._bind = function(thisObj) {

const self = this;

return function () {

self.apply(thisObj);

}

}

var obj = {a:1}

function myname() {console.log(this.a)}

myname._bind(obj)(); // 1

可能很多朋友都止步于此了,因为在一般的面试中,特别是一些校招面试中,可能你只需要知道前面两个就差不多了。但是想要在面试中惊艳所有人,仍然是不够的,接下来我们继续我们的探索与研究。

第三层 - 支持柯里化

函数柯里化是一个老生常谈的话题,在这里再复习一下。

function fn(x) {

return function (y) {

return x + y;

}

}

var fn1 = fn(1);

fn1(2) // 3

不难发现,柯里化使用了闭包,当我们执行 fn1 的时候,函数内使用了外层函数的 x, 从而形成了闭包。

而我们的 bind 函数也是类似,我们通过获取当前外部函数的 arguments ,并且去除了绑定的对象,保存成变量 args,最后 return 的方法,再一次获取当前函数的 arguments, 最终用 finalArgs 进行了一次合并。

Function.prototype._bind = function(thisObj) {

const self = this;

const args = [...arguments].slice(1)

return function () {

const finalArgs = [...args, ...arguments]

self.apply(thisObj, finalArgs);

}

}

通过以上代码,让我们 bind 方法,越来越健壮了。

var obj = { i: 1}

function myFun(a, b, c) {

console.log(this.i + a + b + c);

}

var myFun1 = myFun._bind(obj, 1, 2);

myFun1(3); // 7

一般到了这层,可以说非常棒了,但是再坚持一下下,就变成了完美的答卷。

第四层 - 考虑 new 的调用

要知道,我们的方法,通过 bind 绑定之后,依然是可以通过 new 来进行实例化的, new 的优先级会高于 bind(摘自 《你不知道的JavaScript(上)》2.3 优先级)。

这一点我们通过原生 bind 和我们第四层的 _bind 来进行验证对比。

// 原生

var obj = { i: 1}

function myFun(a, b, c) {

// 此处用new方法,this指向的是当前函数 myFun

console.log(this.i + a + b + c);

}

var myFun1 = myFun.bind(obj, 1, 2);

new myFun1(3); // NAN


// 第四层的 bind

var obj = { i: 1}

function myFun(a, b, c) {

console.log(this.i + a + b + c);

}

var myFun1 = myFun._bind(obj, 1, 2);

new myFun1(3); // 7

注意,这里使用的是 bind方法

因此我们需要在 bind 内部,对 new 进行处理。而 new.target 属性,正好是用来检测构造方法是否是通过 new 运算符来被调用的。

接下来我们还需要自己实现一个 new ,

而根据 MDN,new 关键字会进行如下的操作:

1.创建一个空的简单JavaScript对象(即{});

2.链接该对象(设置该对象的constructor)到另一个对象 ;

3.将步骤1新创建的对象作为this的上下文 ;

4.如果该函数没有返回对象,则返回this。

Function.prototype._bind = function(thisObj) {

const self = this;

const args = [...arguments].slice(1);

return function () {

const finalArgs = [...args, ...arguments];

// new.target 用来检测是否是被 new 调用

if(new.target !== undefined) {

// this 指向的为构造函数本身

var result = self.apply(this, finalArgs);

// 判断改函数是否返回对象

if(result instanceof Object) {

return reuslt;

}

// 没有返回对象就返回 this

return this;

} else {

// 如果不是 new 就原来的逻辑

return self.apply(thisArg, finalArgs);

}

}

}

看到这里,你的造诣已经炉火纯青了,但是最后还有一个小细节。

第五层 - 保留函数原型

以上的方法在大部分的场景下都没有什么问题了,但是,当我们的构造函数有 prototype 属性的时候,就出问题啦。因此我们需要给 prototype 补上,还有就是调用对象必须为函数。

Function.prototype._bind = function (thisObj) {

// 判断是否为函数调用

if (typeof target !== 'function' || Object.prototype.toString.call(target) !== '[object Function]') {

throw new TypeError(this + ' must be a function');

}

const self = this;

const args = [...arguments].slice(1);

var bound = function () {

var finalArgs = [...args, ...arguments];

// new.target 用来检测是否是被 new 调用

if (new.target !== undefined) {

// 说明是用new来调用的

var result = self.apply(this, finalArgs);

if (result instanceof Object) {

return result;

}

return this;

} else {

return self.apply(thisArg, finalArgs);

}

};

if (self.prototype) {

// 为什么使用了 Object.create? 因为我们要防止,bound.prototype 的修改而导致self.prototype 被修改。不要写成 bound.prototype = self.prototype; 这样可能会导致原函数的原型被修改。

bound.prototype = Object.create(self.prototype);

bound.prototype.constructor = self;

}

return bound;

};

以上就是一个比较完整的 bind 实现了,如果你想了解更多细节的实践,可以查看。(也是 MDN 推荐的)

https://github.com/Raynos/function-bind

相关推荐

好用的云函数!后端低代码接口开发,零基础编写API接口

前言在开发项目过程中,经常需要用到API接口,实现对数据库的CURD等操作。不管你是专业的PHP开发工程师,还是客户端开发工程师,或者是不懂编程但懂得数据库SQL查询,又或者是完全不太懂技术的人,通过...

快速上手:Windows 平台上 cURL 命令的使用方法

在工作流程中,为了快速验证API接口有效性,团队成员经常转向直接执行cURL命令的方法。这种做法不仅节省时间,而且促进了团队效率的提升。对于使用Windows系统的用户来说,这里有一套详细...

使用 Golang net/http 包:基础入门与实战

简介Go的net/http包是构建HTTP服务的核心库,功能强大且易于使用。它提供了基本的HTTP客户端和服务端支持,可以快速构建RESTAPI、Web应用等服务。本文将介绍ne...

#小白接口# 使用云函数,人人都能编写和发布自己的API接口

你只需编写简单的云函数,就可以实现自己的业务逻辑,发布后就可以生成自己的接口给客户端调用。果创云支持对云函数进行在线接口编程,进入开放平台我的接口-在线接口编程,设计一个新接口,设计和配置好接口参...

极度精神分裂:我家没有墙面开关,但我虚拟出来了一系列开关

本内容来源于@什么值得买APP,观点仅代表作者本人|作者:iN在之前和大家说过,在iN的家里是没有墙面开关的。...

window使用curl命令的注意事项 curl命令用法

cmd-使用curl命令的注意点前言最近在cmd中使用curl命令来测试restapi,发现有不少问题,这里记录一下。在cmd中使用curl命令的注意事项json不能由单引号包括起来json...

Linux 系统curl命令使用详解 linuxctrl

curl是一个强大的命令行工具,用于在Linux系统中进行数据传输。它支持多种协议,包括HTTP、HTTPS、FTP等,用于下载或上传数据,执行Web请求等。curl命令的常见用法和解...

Tornado 入门:初学者指南 tornados

Tornado是一个功能强大的PythonWeb框架和异步网络库。它最初是为了处理实时Web服务中的数千个同时连接而开发的。它独特的Web服务器和框架功能组合使其成为开发高性能Web...

PHP Curl的简单使用 php curl formdata

本文写给刚入PHP坑不久的新手们,作为工具文档,方便用时查阅。CURL是一个非常强大的开源库,它支持很多种协议,例如,HTTP、HTTPS、FTP、TELENT等。日常开发中,我们经常会需要用到cur...

Rust 服务器、服务和应用程序:7 Rust 中的服务器端 Web 应用简介

本章涵盖使用Actix提供静态网页...

我给 Apache 顶级项目提了个 Bug apache顶级项目有哪些

这篇文章记录了给Apache顶级项目-分库分表中间件ShardingSphere提交Bug的历程。说实话,这是一次比较曲折的Bug跟踪之旅。10月28日,我们在GitHub上提...

linux文件下载、服务器交互(curl)

基础环境curl命令描述...

curl简单使用 curl sh

1.curl--help#查看关键字2.curl-A“(添加user-agent<name>SendUser-Agent<name>toserver)”...

常用linux命令:curl 常用linux命令大全

//获取网页内容//不加任何选项使用curl时,默认会发送GET请求来获取内容到标准输出$curlhttp://www.baidu.com//输出<!DOCTYPEh...

三十七,Web渗透提高班之hack the box在线靶场注册及入门知识

一.注册hacktheboxHackTheBox是一个在线平台,允许测试您的渗透技能和代码,并与其他类似兴趣的成员交流想法和方法。它包含一些不断更新的挑战,并且模拟真实场景,其风格更倾向于CT...