0%

一直使用 webpack 打包项目,但不知道它的原理是什么,给自己有一种很飘的感觉,所以花时间专门学习 webpack

简单的模块打包工具

webpack官网有一个视频,介绍怎么构建自己的 webpackBUILD YOUR OWN WEBPACK by Ronen Amiel示例代码

怎样得到文件的依赖图谱呢?

打包工具有一个 entry 入口文件,通过分析入口文件,得到入口文件的依赖,维护当前文件的依赖列表。然后循环依赖列表,再得到依赖文件的依赖。每个依赖都要自己的模块 id,通过模块 id 可以很方便的找到文件。

BabylonBabel 中使用的 JavaScript 解析器,通过 babylon 得到 AST,在 AST 中,可以得到当前文件的依赖文件列表。
同时可以将得到的 AST 使用其他的 loader 转译代码,比如:通过 babel-corees6 转译成 es5

怎样执行代码

上一步已经得到了依赖图谱,通过依赖图谱,生成以 idkey 的 模块 map,这样可以很快的通过 id 找到对应的模块。

1
2
3
4
5
6
7
8
9
10
11
12
(function(modules) {
function require(id) {
const [fn, mapping] = modules[id];
function localRequire(name) {
return require(mapping[name]);
}
const module = { exports : {} };
fn(localRequire, module, module.exports);
return module.exports;
}
require(0);
})({${modules}})

通过自定义 require 函数,将生成的 code 中根据文件路径 require的文件,替换为对应的模块。
通过传入函数内部的 module对象,通过对象引用的方式,拿到模块导出的值。

有一些文件只在部分页面用到,其他页面用不到,但是不想全局引入,所以需要在组件中动态引入。

vue组件动态引入jscss文件

下面是引入百度的 umeditor 富文本编辑器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function insertJs(src) {
return new Promise((resolve, reject) => {
const tag = document.createElement('script');
tag.type = 'text/javascript';
tag.src = src;
const s = document.getElementsByTagName('head')[0];
s.appendChild(tag);
tag.addEventListener('load', resolve);
});
}

function insertCss(src) {
return new Promise((resolve, reject) => {
const tag = document.createElement('link');
tag.href = src;
tag.type = 'text/css';
tag.rel = 'stylesheet';
const s = document.getElementsByTagName('head')[0];
s.appendChild(tag);
tag.addEventListener('load', resolve);
});
}
const UMEDITOR_HOME_URL = '';
window.UMEDITOR_HOME_URL = UMEDITOR_HOME_URL;
insertCss(`${UMEDITOR_HOME_URL}themes/default/css/umeditor.css`);
insertJs('http://www.kaoyaya.com/source/js/jquery/jquery-1.10.2.min.js');
insertJs(`${UMEDITOR_HOME_URL}umeditor.min.js`);
insertJs(`${UMEDITOR_HOME_URL}umeditor.config.js`).then(() => {
insertJs(`${UMEDITOR_HOME_URL}lang/zh-cn/zh-cn.js`);
});

在文件入口插入js

setTimeoutsetImmediateprocess.nextTick

nodejs 的使用中,为了不阻塞程序的运行,经常会使用到定时器,将任务在当前事件的循环末尾或者下个事件循环的开始执行。

setTimeout 的使用形式一般是 setTimeout(fn,0),当然我们知道 浏览器端setTimeout 最小时间间隔是不得低于4毫秒,它的执行时机是在下一个事件循环的开始。 setImmediatesetTimeout执行顺序是不确定的。 如果两者都在主模块中调用,那么执行先后取决于进程性能,也就是随机。 如果两者都不在主模块调用(被一个异步操作包裹),那么setImmediate的回调永远先执行。process.nextTickPromise` 都属于微任务,它们会在所属的事件循环最后,并在进入下一个事件循环之前执行。

在具体实现上,process.nextTick()的回调函数保存在一个数组中,setImmediate() 的结果则是保存在链表中。
在行为上,process.nextTick() 在每轮循环中会将数组中的回调函数全部执行完,而 setImmediate() 在每轮循环中执行链表中的一个回调函数。
这样设计的原因是为了保证每轮循环都能较快的执行结束,防止 CPU 占用过多而阻塞后续 I/O 调用的情况。

参考文章:
由setTimeout和setImmediate执行顺序的随机性窥探Node的事件循环机制

假设我们在编写一个飞机大战的网页游戏。某种飞机拥有分身技能,当它使用分身技能的时 候,要在页面中创建一些跟它一模一样的飞机。如果不使用原型模式,那么在创建分身之前,无 疑必须先保存该飞机的当前血量、炮弹等级、防御等级等信息,随后将这些信息设置到新创建的 飞机上面,这样才能得到一架一模一样的新飞机。如果使用原型模式,我们只需要调用负责克隆的方法,便能完成同样的功能。

js中调用 Object.create()即可克隆对象。

1
2
3
4
5
6
7
8
9
10
11
12
function Person(name){
this.name=name
}
Person.prototype.say = function(){
console.log(this.name);
}
var me = new Person('ltinyho')
var myClone = Object.create(me)
myClone.say();
var myClone2 = {}
myClone2.__proto__ = me;
myClone2.say();

原型模式一定有一个根对象,其他的对象一定是从某个已经存在的对象生成的,然后在添加它自己的属性和方法。在这些对象上就会形成一条原型链,当调用某个对象的方法时,如果在它本身没有这个方法,那么就会从原型链一直向上找,直到根对象为止。
js中的根对象为Object.prototype,它是这个空对象。

1
2
var a = {}
Object.getPrototypeOf(a)===Object.prototype // true

构造函数

  • 创建一个对象
  • 将构造函数的作用域赋给新对象。(因此this指向了新对象)
  • 执行构造函数中的代码。(为这个新对象添加属性)
  • 如果不调用return ,默认返回这个对象,显式的return一个对象会影响结果,返回原始值不会。

对象的原型链是如何查找的呢?
要完成查找,每个对象首先至少得记住自己的原型,知道自己从哪里来的。js 给对象提供了一个名为__proto__的隐藏属性,某个对象的 __proto__属性默认指向它的构造器原型对象,即 {Constructor}.prototype ,当在自己找不到属性时,就会到这个对象上查找。但是一个对象的 Constructor 可能会被修改。原因是 js中每个对象都是从 Object.prototype 对象克隆而来的,如果这样的话,只能得到单一的继承关系,即每个对象都是继承于 Object.protoype。但是对象构造器的原型并不仅限于 Object.prototype 上,而是可以动态的指向其他对象。看一下以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
function A(){
}
A.prototype.name= 'A'
A.prototype.say = function(){
console.log(this.name);
}
function B(){
}
var a = new A()
B.prototype = a;
var b= new B()
console.log(b.name);
b.__proto__===B.prototype // true
  • 首先遍历 b 上的所有属性,没有找到 name 。
  • 查找 name 属性的请求被委托给 b 的 b.__proto__b.__proto__记录了构造器原型 B.prototype,而B.prototype指向一个通过 new A() 创建的对象。
  • 然后就到 a 的 属性上遍历,还是没有。然后到a.__proto__记录的 A.prototype 上找的构造器原型上找,找到了。

注意:当在本身查找不到时,是先到 __proto__ 上去找, 如果一个对象已经创建,__proto__ 确定了,这时改变 这个对象的 constructor 并不会改变原型链。

this, call, apply, bind

JavaScript 的 this 总是指向一个对象,而具体指向哪个对象是在运行时基于函数的执行环境动态绑定的,而非函数被声明时的环境。
this的指向大致分为四种

  • 对象的方法调用
  • 普通函数调用
  • 构造函数调用
  • Function.prototype.call和Function.prototype.apply调用

call和apply

call和apply可以修改函数内部的this指向,它们的区别只在于参数的不同。第一个参数都为this指向的对象,第二个参数call接收一个参数,apply则以数组的形式接收。

call和apply的用途

  • 改变this的指向
  • 实现bind方法
  • 借用其他对象的方法

bind的实现

1
2
3
4
5
6
Function.prototype.bind = function(){ 
var self = this, context = [].shift.call( arguments ),args = [].slice.call( arguments );
return function(){
return self.apply( context, [].concat.call( args, [].slice.call( arguments ) ) );
}
};

从上面可以看出,bind 会返回一个新的函数,并且可以传入参数,作为新生成函数的预置的参数。

今天看了一篇有关macro和micro任务的文章,记录一下学习所获。
文章地址 Tasks, microtasks, queues and schedules

首先感受一下代码

6
1
2
3
4
5
6
7
8
9
10
11
console.log('start')
setTimeout(()=>{
console.log('sto');
},0)

new Promise().then(()=>{
console.log('promise 1');
}).then(()=>{
console.log('promise 2');
})
console.log('end')

上面这段代码的打印顺序为

start,end,promise1,promise2,sto

为什么会这样呢?这里需要我们理解事件循环是如何处理macro tasksmicor tasks 的。

每一个线程都有自己的事件循环,这样每个线程能够独立,所有的同源窗口共享同一个事件循环,这样它们就能够同步通信。

一个事件循环存在多个事件源,这样按照不同任务源的优先级执行,确保了任务源的执行顺序。

前面刚复习过 Promise,立即 resolvePromise 在本次事件循环的末尾执行。

macro 和 micro 区别

promise 为什么是 micro