chang-ke / blog Goto Github PK
View Code? Open in Web Editor NEW:memo:blog
:memo:blog
说实话,这半年来在各大社区看别人分享的面试题中 bind 函数已经出现 n 多次了,这次准备详细探究下
首先让我们看看 mdn 对于 bind 函数的描述是什么
fun.bind(thisArg[, arg1[, arg2[, ...]]])
返回由指定的 this 值和初始化参数改造的原函数拷贝
当代码 new Foo(...) 执行时,会发生以下事情:
1、一个继承自 Foo.prototype 的新对象被创建。
2、使用指定的参数调用构造函数 Foo ,并将 this 绑定到新创建的对象。new Foo 等同于 new Foo(),也就是没有指定参数列表,Foo 不带任何参数调用的情况。
3、由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤 1 创建的对象。(一般情况下,构造函数不返回值,但是用户可以选择主动返回对象,来覆盖正常的对象创建步骤)如果你看不懂这段话,没关系,看完下面这段代码你就清楚了
function Foo(){
}
下面代码就是执行new Foo()时的简单实现
let obj = {};
obj.__proto__ = Foo.prototype
return Foo.call(obj)
对于new的完整实现可以参考这位大神的博客
let obj = {
ll: 'seve'
};
Function.prototype.bind = function(that) {
var self = this;
return function() {
return self.apply(that, arguments);
};
};
let func0 = function(a, b, c) {
console.log(this.ll);
console.log([a, b, c]);
}.bind(obj, 1, 2);
func0(3); // seve
// [ 3, undefined, undefined ] 发现1,2并没有填入
乞丐版也太 low 了对吧,所以我们继续完善
es6 提供了结构运算符,可以很方便的利用其功能实现 bind
Function.prototype.bind = function(that, ...argv) {
if (typeof this !== 'function') {
throw new TypeError(`${this} is not callable`);
}
// 保存原函数
let self = this;
// 获取bind后函数传入的参数
return function(...argu) {
return self.apply(that, [...argv, ...argu]);
};
};
let func1 = function(a, b, c) {
console.log(this.ll);
console.log([a, b, c]);
}.bind(obj, 1, 2);
func1(3); // seve
// [ 1, 2, 3 ]
es6 版实现很简单对吧,但是面试官说我们的运行环境是 es5,这时你心中窃喜,bable 大法好,但是你可千万不要说有 babel,因为面试官的意图不太可能是问你 es6 如何转换成 es5,而是考察你其他知识点,比如下面的类数组如何转换为真正的数组
Function.prototype.bind = function() {
if (typeof this !== 'function') {
throw new TypeError(`${this} is not callable`);
}
var self = this;
var slice = [].slice;
// 模拟es6的解构效果
var that = arguments[0];
var argv = slice.call(arguments, 1);
return function() {
// slice.call(arguments, 0)将类数组转换为数组
return self.apply(that, argv.concat(slice.call(arguments, 0)));
};
};
let func2 = function(a, b, c) {
console.log(this.ll);
console.log([a, b, c]);
}.bind(obj, 1, 2);
func2(3); // seve
// [ 1, 2, 3 ]
当然,写到这里,对于绝大部分面试,这份代码都是一份不错的答案,但是为了给面试官留下更好的印象,我们需要上终极版
实现完整的bind函数,这样还可以跟面试官吹一波
为了当使用new操作符时,bind后的函数不丢失this。我们需要把bind前的函数的原型挂载到bind后函数的原型上
但是为了修改bind后函数的原型而对bind前的原型不产生影响,都是对象惹的祸。。。直接赋值只是赋值对象在堆中的地址
所以需要把原型继承给bind后的函数,而不是直接赋值,我有在一些地方看到说Object.crate可以实现同样的效果,有兴趣的可以了解一下,但是我自己试了下,发现效果并不好,new 操作时this指向错了
通过直接赋值的效果
Function.prototype.bind = function(that, ...argv) {
if (typeof this !== 'function') {
throw new TypeError(`${this} is not callable`);
}
// 保存原函数
let self = this;
let func = function() {};
// 获取bind后函数传入的参数
let bindfunc = function(...arguments) {
return self.apply(this instanceof func ? this : that, [...argv, ...arguments]);
};
// 把this原型上的东西挂载到func原型上面
// func.prototype = self.prototype;
// 为了避免func影响到this,通过new 操作符进行复制原型上面的东西
bindfunc.prototype = self.prototype;
return bindfunc;
};
function bar() {
console.log(this.ll);
console.log([...arguments]);
}
let func3 = bar.bind(null);
func3.prototype.value = 1;
console.log(bar.prototype.value) // 1 可以看到bind后的原型对bind前的原型产生的同样的影响
通过继承赋值的效果
Function.prototype.bind = function(that, ...argv) {
if (typeof this !== 'function') {
throw new TypeError(`${this} is not callable`);
}
// 保存原函数
let self = this;
let func = function() {};
// 获取bind后函数传入的参数
let bindfunc = function(...argu) {
return self.apply(this instanceof func ? this : that, [...argv, ...argu]);
};
// 把this原型上的东西挂载到func原型上面
func.prototype = self.prototype;
// 为了避免func影响到this,通过new 操作符进行复制原型上面的东西
bindfunc.prototype = new func();
return bindfunc;
};
function bar() {
console.log(this.ll);
console.log([...arguments]);
}
let func3 = bar.bind(null);
func3.prototype.value = 1;
console.log(bar.prototype.value) // undefined 可以看到bind后的原型对bind前的原型不产生影响
func3(5); // seve
// [ 5 ]
new func3(5); // undefined
// [ 5 ]
以上代码或者表述如有错误或者不严谨的地方,欢迎指出,或者在评论区讨论,觉得我的文章有用的话,可以订阅或者star支持我
下系列文章我打算写关于koa框架的实现,第一篇我会带大家探究Object.create的效果及实现
反引号应该是执行里面的命令并把输出作为结果给前一个命令
ls命令应该不用介绍了,用过Linux的都会(跟Windows的dir命令一样)
| 是管道符
grep 是正则,-i忽略大小写,别使用-n输出行号就好,不然rm命令会把行号当文件名删除,你还可以用-v去过滤符合要求的文件
setup后面的数字就是nodejs版本号
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
sudo apt-get install -y nodejs
受限于 CORS 策略,会存在跨域问题,虽然可以使用图像(比如 append 到页面上)但是绘制到画布上会污染画布,一旦一个画布被污染,就无法提取画布的数据,比如无法使用使用画布 toBlob(),toDataURL(),或 getImageData()方法;当使用这些方法的时候 会抛出一个安全错误
img.setAttribute("crossOrigin",'Anonymous')
setTimeout(() => {
console.log(111);
}, 0);
const sleep = time => new Promise(resolve => setTimeout(() => resolve(), time));
await sleep(2000); //伪线程挂起
console.log(1)
// => 111,1
这样sleep函数后面的代码需要2秒之后才会执行,值得注意的是,sleep函数并不会阻塞之前已注册的函数
比如setTimeout里面的匿名函数
npm ERR! code ENOSELF
npm ERR! Refusing to install package with name "puppeteer" under a package
npm ERR! also called "puppeteer". Did
you name your project the same
npm ERR! as the dependency you're installing?
npm ERR!
npm ERR! For more information, see:
npm ERR! <https://docs.npmjs.com/cli/install#limitations-of-npms-install-algorithm>
npm ERR! A complete log of this run can be found in:
npm ERR! C:\Users\1\AppData\Roaming\npm-cache\_logs\2018-05-19T07_30_36_044Z-debug.log
IE8中,onload是可以调用的,但是要放在src的赋值之前。
简单来说,就是要先告诉浏览器图片加载完要怎么处理,再让它去加载图片。避免因为加载缓冲区的速度太快,在没有告诉它加载完要怎么办时,它已经加载完了。而其它浏览器则是从缓冲区读取到图片时就执行onload。
undefined
没必要使用typeof undefined
或者是Objec.prototype.toString.call
,可以使用undefined === void 0
stats : 'errors-only' //对于 webpack-dev-server,这个属性要放在 devServer 对象里。
来取消这些信息的输出,更详细的用法见 webpack文档
零宽:只匹配位置,在匹配过程中,不占用字符,所以被称为零宽
先行:正则引擎在扫描字符的时候,从左往右扫描,匹配扫描指针未扫描过的字符,先于指针,故称先行
后行:匹配指针已扫描过的字符,后于指针到达该字符,故称后行,即产生回溯
正向:即匹配括号中的表达式
负向:不匹配括号中的表达式
es5 就支持了先行断言
es2018 才支持后行断言
注意:
.
在正则里面代表匹配除换行符,回车符等少数空白字符之外的任何字符,匹配其时需要转义
(?=pattern):某位置后面紧接着的字符序列要匹配 pattern
例:
`sinM.`.match(/sin(?=M\.)/g); // ["sin"]
`M.sin`.match(/sin(?=M\.)/g); // null
第一个 sin 会匹配,因为他后面有 pattern
(?!pattern):某位置后面紧接着的字符序列不能匹配 pattern
例:
`M.sin`.match(/sin(?!M\.)/g); // ["sin"]
`sinM.`.match(/sin(?!M\.)/g); // null
第一个 sin 会匹配,因为他后面没有 pattern
(?<=pattern):某位置前面紧接着的字符序列要匹配 pattern
例:
'sinM.'.match(/(?<=M\.)sin/g); // null
'M.sin'.match(/(?<=M\.)sin/g); // ["sin"]
第二个 sin 会匹配,因为它前面有 pattern
(?<!pattern):某位置前面紧接着的字符序列不能匹配 pattern
例:
'sinM.'.match(/(?<!M\.)sin/g); // ["sin"]
'M.sin'.match(/(?<!M\.)sin/g); // null
第一个 sin 会匹配,因为它前面没有 pattern
来看个实际的例子,把4+6*sqrt(5)*Math.sqrt(5)
转换成可以通过eval
或者new Function()
获得实际结果的字符串
这个可以使用负向后行断言,即替换前面不紧接 Math.的 sqrt 字符串序列
let s = `4+6*sqrt(5)*Math.sqrt(5)`.replace(/(?<!Math\.)sqrt/g, func => `Math.${func}`);
eval(s); // 34
匹配 url 后面的路径
'https://www.google.com/v3/api/getUser?user=panghu'.match(/(?<=\.\w*(?=\/)).*/);
替换字符串中 img 标签的 width 为 100%
'<img id = "23" style="width:999x;"/><img id = "23" style="width:999x;"/>'.replace(
/(?<=(<img[\s\S]*width:\s*))[^("\/);]*/gm,
'100%'
);
匹配 sin
'M.sin'.match(/(?<=M\.)sin/g); // ["sin"]
`M.sin`.match(/sin(?!M\.)/g); // ["sin"]
这两种方法都可以实现同样的效果,但我个人更喜欢使用第一种方法,它的写法更符合人的直接思维习惯
先看下面两行代码的运行结果
let reg = /js/g;
reg.test('js'); //before: lastIndex:0, after: lastIndex:2
reg.test('js'); //before: lastIndex:2, after: lastIndex:0
reg.test('js'); //before: lastIndex:0, after: lastIndex:2
如果你的答案是三个 true 的话,那就错了
答案其实是 true、false、true,这就是所谓的怪异现象
为什么?答:
RegExp 对象有个 lastIndex 属性,它的初始值是 0,
当不使用 g 修饰符修饰时,每次执行 test 方法之后它都会自动置 0
而使用 g 修饰符时,每次执行 test 方法的时候,它都是从索引值为 lastIndex 的位置开始匹配,lastIndex 为匹配到的字符序列下一个索引值。只有当匹配失败以后才会将 lastIndex 置为 0
例:上述例子中的第一个 test 方法执行之前,lastIndex 值为 0,执行之后 lastIndex 值为 2,于是当第二次执行 test 方法时,从字符串索引值为 2 处开始匹配,显然会匹配失败,所以第三次匹配时又会匹配成功
<div class="root">
这里可以涉及到的知识点有:贪婪/非贪婪匹配,模式匹配,回溯及其消除,分组,反向引用
`<div class="root"><span class="root"></span><i class='root'></i></div>`.match(/<[^>]*class="root".*?>/g);
// ["<div class="root">", "<span class="root">"]
模式匹配[^>]
表示匹配除[^]
里面的所有字符,这里就是匹配除>
外的所有字符
注意前后都需要非贪婪匹配符号?否则只有前面的,它会贪婪的吃掉 div;只有后面的,它会贪婪的吃掉 span
`<div class="root"><span class="root"></span><i class='root'></i></div>`.match(/<[^>]*class=("root"|'root').*?>/g);
// ["<div class="root">", "<span class="root">", "<i class='root'>"]
这里如果不使用[^>]
而使用.*
就会出现下面这种匹配结果,不是我们想要的
["<div class="root">", "<span class="root">", "</span><i class='root'>"]
("root"|'root')
,再消除.*?
回溯`<div class="root"><span class="root"></span><i class='root'></i></div>`.match(/<[^>]*class=("|')root\1[^>]*>/g);
// ["<div class="root">", "<span class="root">", "<i class='root'>"]
\1
表示引用前面的第一个分组结果,即("|')
的匹配结果,这样就能保证单引号配对单引号,双引号匹配双引号
[^>]*
代替.*?
可以消除使用*?
引发的回溯,因为*
是尽可能多的匹配,而?
是尽可能少的匹配
回顾开头,我所说的特殊情况就是标签的属性值不能含有>
,因为为了消除回溯使用的[^>]
含有字符>,这部分其实可以使用其他正则代替,让它在消除回溯的情况下可以匹配特殊情况
参考:
JavaScript 权威指南(第 6 版)
Javascript 正则表达式迷你书
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.