1.主文件 babelParse.js
1 | const { getOptions } = require('loader-utils') |
2.校验文件 babelSchema.js
1 | module.exports = { |
3.webpack配置文件 webpack.config.js
1 | ... |
1.主文件 babelParse.js
1 | const { getOptions } = require('loader-utils') |
2.校验文件 babelSchema.js
1 | module.exports = { |
3.webpack配置文件 webpack.config.js
1 | ... |
1 | const express = require('express') |
1 | const express = require('express') |
1 | class Promise { |
babel是现在几乎每个项目中必备的一个东西,但是其工作原理避不开对js的解析在生成的过程,babel有引擎babylon,早期fork了项目acron,了解这个之前我们先来看看这种引擎解析出来是什么东西。不光是babel还有webpack等都是通过javascript parser将代码转化成抽象语法树,这棵树定义了代码本身,通过操作这颗树,可以精准的定位到赋值语句、声明语句和运算语句。
我们可以来看一个简单的例子:
1 | var a = 1; |
我们通过这个网站,他是一个esprima引擎的网站,十分好用.画成流程图如下:
而他的json对象格式是这样的:
1 | { |
通过esprima生成AST
通过estraverse遍历和更新AST
通过escodegen将AST重新生成源码
抽象语法树的作用非常的多,比如编译器、IDE、压缩优化代码等。在JavaScript中,虽然我们并不会常常与AST直接打交道,但却也会经常的涉及到它。例如使用UglifyJS来压缩代码,实际这背后就是在对JavaScript的抽象语法树进行操作。
作用域就是一个独立的地盘,让变量不会外泄、暴露出去。也就是说作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。
ES6 之前 JavaScript 没有块级作用域,只有全局作用域和函数作用域。ES6 的到来,为我们提供了‘块级作用域’,可通过新增命令 let 和 const 来体现。
作用域是分层的,内层作用域可以访问外层作用域的变量,反之则不行。作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。
一般情况下,变量取值到创建这个变量的函数的作用域中取值。但是如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。
JavaScript 采用的是词法作用域,函数的作用域在函数定义的时候就决定了。而与词法作用域相对的是动态作用域,函数的作用域是在函数调用的时候才决定的。
1 | var value = 1; |
假设JavaScript采用静态作用域,让我们分析下执行过程:
执行 foo 函数,先从 foo 函数内部查找是否有局部变量 value,如果没有,就根据书写的位置,查找上面一层的代码,也就是 value 等于 1,所以结果会打印 1。
假设JavaScript采用动态作用域,让我们分析下执行过程:
执行 foo 函数,依然是从 foo 函数内部查找是否有局部变量 value。如果没有,就从调用函数的作用域,也就是 bar 函数内部查找 value 变量,所以结果会打印 2。
前面我们已经说了,JavaScript采用的是静态作用域,所以这个例子的结果是 1。
1 | var scope = "global scope"; |
原因也很简单,因为JavaScript采用的是词法作用域,函数的作用域基于函数创建的位置。
JavaScript 函数的执行用到了作用域链,这个作用域链是在函数定义的时候创建的。嵌套的函数 f() 定义在这个作用域链里,其中的变量 scope 一定是局部变量,不管何时何地执行函数 f(),这种绑定在执行 f() 时依然有效。
每一个javascript对象(null除外)在创建的时候,都会与另外一个对象所关联。而这个与创建对象所关联的对象,就是所创建对象的原型,每一个对象都会从原型中继承属性。
因为一个构造函数可以创建多个实例对象,原型与实例对象是一对多的关系,所以就没法用一个属性指向实例。
当读取实例的属性时,如果找不到就会去查找与对象关联的原型中的属性,如果还找不到,就去找原型的原型,一直找到最顶层为止,这样由原型组成的链状结构就是原型链。
1 | console.log(Object.prototype.__proto__ === null) // true |
Object.prototype.proto 的值为 null 跟 Object.prototype 没有原型,其实表达了一个意思,所以查找属性到了 Object.prototype就可以停止查找了。
图中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。
1 | function Person() {} |
当获取person的constructor属性时,其实 person 中并没有 constructor 属性,当不能读取到constructor 属性时,会从 person 的原型也就是 Person.prototype 中读取,正好原型中有该属性。
1 | person.constructor === Person.prototype.constructor |
绝大多数浏览器都支持这个非标准的方法访问原型,然而它并不存在于Person.prototype中,实际上它是来自于 Object.prototype。与其说是一个属性,不如说是一个getter/setter,当使用 obj.proto 时,可以理解成返回了 Object.getPrototypeOf(obj)。
继承意味着复制操作,然而 JavaScript 默认并不会复制对象的属性。相反,JavaScript 只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些。
typeof和instanceof都是用来判断变量类型的,两者的区别在于:
typeof判断所有变量的类型,返回值有number,boolean,string,function,object,undefined。(这里需要注意一下,js中基本数据类型常用的有六种,其中简单一点的有五个:String、Number、Boolean、Undefined、Null,一个复杂的数据类型:Object。ES6中新增了一个Symbol用于生成唯一标识符,ES10中新增了BigInt可以表示任意大的整数)。
typeof对于丰富的对象实例,只能返回”object”字符串。
instanceof用来判断对象,代码形式为obj1 instanceof obj2(obj1是否是obj2的实例),obj2必须为对象,否则会报错!其返回值为布尔值。
语法: object instanceof constructor
object(要检测的对象),constructor(某个构造函数),instanceof 运算符用来检测 constructor.prototype 是否存在于参数 object 的原型链上。
简而言之,A instanceof B , 是判断对象实例A是否是构造函数B的实例。更准确一点的说法是,构造函数B的原型,是否存在与对象实例A的原型链上。
按照需求来确定是用防抖还是节流:
tips: 在连续频繁操作的时间区域内,要能执行函数的情况用节流。
防抖的原理就是:不管怎么触发事件,但是一定在事件触发 n 秒后才执行,如果一个事件触发的 n 秒内又触发了这个事件,那就以新事件的时间为准,n 秒后才执行,总之就是要等触发完事件 n 秒内不再触发事件才执行。
1 | const debounce = (fn, delay) => { |
节流的原理是:一个函数执行一次后,只有大于设定的执行周期,才会执行第二次。也就是说:在规定的时间内,只让函数触发的第一次生效,后面的不生效。
1 | const throttle = (fn, delay) => { |
1 | function throttle_2(fn, delay) { |
发布订阅模式属于广义上的观察者模式
发布订阅模式是最常用的一种观察者模式的实现,并且从解耦和重用角度来看,更优于典型的观察者模式。
发布订阅模式多了个事件通道
在观察者模式中,观察者需要直接订阅目标事件;在目标发出内容改变的事件后,直接接收事件并作出响应。
在发布订阅模式中,发布者和订阅者之间多了一个发布通道;一方面从发布者接收事件,另一方面向订阅者发布事件;订阅者需要从事件通道订阅事件,以此避免发布者和订阅者之间产生依赖关系。