Git Product home page Git Product logo

blog's Introduction

Hi there 👋

我的 GitHub 数据

blog's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

blog's Issues

面试常见问题之实现bind函数

前言:

说实话,这半年来在各大社区看别人分享的面试题中 bind 函数已经出现 n 多次了,这次准备详细探究下

首先让我们看看 mdn 对于 bind 函数的描述是什么

语法

fun.bind(thisArg[, arg1[, arg2[, ...]]])

参数

  • thisArg
      当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用 new 操作符调用绑定函数时,该参数无效。
  • 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的完整实现可以参考这位大神的博客


实现

乞丐版, 无法预先填入参数,仅实现执行时改变 this 指向

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 进阶版

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,而是考察你其他知识点,比如下面的类数组如何转换为真正的数组

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的效果及实现

Linux学习

1、使用正则批量删除文件rm `ls | grep -i "^111"`

反引号应该是执行里面的命令并把输出作为结果给前一个命令
ls命令应该不用介绍了,用过Linux的都会(跟Windows的dir命令一样)
| 是管道符
grep 是正则,-i忽略大小写,别使用-n输出行号就好,不然rm命令会把行号当文件名删除,你还可以用-v去过滤符合要求的文件

2、安装nodejs

setup后面的数字就是nodejs版本号

curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
sudo apt-get install -y nodejs

小Tips集合

1、解决 canvas 将图片转为 base64 报错: Uncaught DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported

受限于 CORS 策略,会存在跨域问题,虽然可以使用图像(比如 append 到页面上)但是绘制到画布上会污染画布,一旦一个画布被污染,就无法提取画布的数据,比如无法使用使用画布 toBlob(),toDataURL(),或 getImageData()方法;当使用这些方法的时候 会抛出一个安全错误

img.setAttribute("crossOrigin",'Anonymous')

2、利用Promise和await提供(伪)线程挂起功能

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里面的匿名函数

3、项目中的package名称不能和要安装的包名称一样,否则会报如下错误

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

4、IE8中img.onload怪异行为

IE8中,onload是可以调用的,但是要放在src的赋值之前。
简单来说,就是要先告诉浏览器图片加载完要怎么处理,再让它去加载图片。避免因为加载缓冲区的速度太快,在没有告诉它加载完要怎么办时,它已经加载完了。而其它浏览器则是从缓冲区读取到图片时就执行onload。

5、使用eslint-loader时顺序在babel-loader前面导致webpack在开启热更新模式下无限重新编译

6、严格判断undefined没必要使用typeof undefined或者是Objec.prototype.toString.call,可以使用

undefined === void 0

7、webpack编译源码时会在命令行打印许多日志信息,其实这里面除了警告或者错误信息外,其它的输出都是没用的,我们可以使用

stats : 'errors-only' //对于 webpack-dev-server,这个属性要放在 devServer 对象里。

来取消这些信息的输出,更详细的用法见 webpack文档

8、使用jest + enzyme 对react进行快照测试的时候,如果你的组件结果发生改变,而快照没有更新,那么结果就是测试卡在这里跑不下去。解决办法就是使用 -u进行更新快照

9、使用-webkit-overflow-scrolling: touch,开启阻尼滑动支持,类似better-scroll的效果

关于正则位置匹配(断言)的技巧

正则位置匹配

先了解下以下几个概念

  • 零宽:只匹配位置,在匹配过程中,不占用字符,所以被称为零宽

  • 先行:正则引擎在扫描字符的时候,从左往右扫描,匹配扫描指针未扫描过的字符,先于指针,故称先行

  • 后行:匹配指针已扫描过的字符,后于指针到达该字符,故称后行,即产生回溯

  • 正向:即匹配括号中的表达式

  • 负向:不匹配括号中的表达式

es5 就支持了先行断言
es2018 才支持后行断言

零宽正向先行断言,又称正向向前查找(positive lookhead)

注意: .在正则里面代表匹配除换行符,回车符等少数空白字符之外的任何字符,匹配其时需要转义

(?=pattern):某位置后面紧接着的字符序列要匹配 pattern

例:

`sinM.`.match(/sin(?=M\.)/g); // ["sin"]
`M.sin`.match(/sin(?=M\.)/g); // null

第一个 sin 会匹配,因为他后面有 pattern

零宽负向先行断言,又称负向向前查找(negative lookhead)

(?!pattern):某位置后面紧接着的字符序列不能匹配 pattern

例:

`M.sin`.match(/sin(?!M\.)/g); // ["sin"]
`sinM.`.match(/sin(?!M\.)/g); // null

第一个 sin 会匹配,因为他后面没有 pattern

零宽正向后行断言,又称正向向后查找(positive lookbehind)

(?<=pattern):某位置前面紧接着的字符序列要匹配 pattern

例:

'sinM.'.match(/(?<=M\.)sin/g); // null
'M.sin'.match(/(?<=M\.)sin/g); // ["sin"]

第二个 sin 会匹配,因为它前面有 pattern

零宽负向后行断言,又称负向向后查找(negative lookbehind)

(?<!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"]

这两种方法都可以实现同样的效果,但我个人更喜欢使用第一种方法,它的写法更符合人的直接思维习惯

在全局匹配修饰符 g 作用下正则 test 方法出现的“怪异”结果

先看下面两行代码的运行结果

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 处开始匹配,显然会匹配失败,所以第三次匹配时又会匹配成功

匹配含 class 为 root 的标签(不考虑特殊情况), 如<div class="root">

这里可以涉及到的知识点有:贪婪/非贪婪匹配模式匹配回溯及其消除分组反向引用

基础版:只匹配双引号包裹的 class

`<div class="root"><span class="root"></span><i class='root'></i></div>`.match(/<[^>]*class="root".*?>/g);
// ["<div class="root">", "<span class="root">"]

模式匹配[^>]表示匹配除[^]里面的所有字符,这里就是匹配除>外的所有字符
注意前后都需要非贪婪匹配符号?否则只有前面的,它会贪婪的吃掉 div;只有后面的,它会贪婪的吃掉 span

完整版:单双引号包裹的 class 都可以匹配

`<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 正则表达式迷你书

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.