Skip to main content

· 3 min read

最近朋友推荐我了解一个开源项目的升级,webpack从1.14.0 升级到目前最新的 v4.14.0

Project-WebCube 开源项目 (github被墙,需要fanqiang)

这个项目中使用了yarn 管理依赖,lerna 进行多项目依赖的统一化管理,解决大项目中依赖不同,版本更新出现的bug.

Project-WebCube/tree/master/packages/webcube > package.json 中yarn安装的webpack是1.14.0,但是在更新webpack当中运行报错 Cannot find module 'webpack/lib/removeAndDo' 然后发现extract-text-webpack-plugin插件最高只能支持webpack3 extract-text-webpack-plugin

Since webpack v4 the extract-text-webpack-plugin should not be used for css. Use mini-css-extract-plugin instead.

需要替换成 mini-css-extract-plugin插件使用 mini-css-extract-plugin

extract-text-webpack-plugin && mini-css-extract-plugin 区别

extract-text-webpack-plugin

Extract text from a bundle, or bundles, into a separate file.

extract-text-webpack-plugin 抽取到同一个文件夹下,名称不变

    var ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
entry: {
"script": "./src/entry.js",
"bundle": "./src/entry2.js",
},
...
module: {
loaders: [
{ test: /\.css$/, loader: ExtractTextPlugin.extract("style-loader", "css-loader") }
]
},
plugins: [
new ExtractTextPlugin("[name].css")
]
}

但是这样配置的话所有分离的文件也会压缩到一个文件上

    plugins: [
new ExtractTextPlugin("[name].css", {allChunks: true})
]
  • mini-css-extract-plugin不能压缩到同一个文件夹,只能压缩到同一个文件中或者是压缩到js文件中,如此一来,优点是减少了请求,缺点也很明显,如果文件很大,打包过后js文件将会很大.
  • extract-text-webpack-plugin 可以实现多个文件抽离到同一个文件夹下,但是目前不支持webpack 4.X.X,官方推荐使用mini-css-extract-plugin
  • mini-css-extract-plugin如下配置打包,会把import './css/index.css' 文件打包压缩到/dist文件夹下的一个js文件中,然后通过script中的src源引入.

mini-css-extract-plugin

mini-css-extract-plugin 配置 抽离到同一个文件夹可以如下:

    module.exports = {
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
devMode ? 'style-loader' : MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'sass-loader',
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: devMode ? '[name].css' : '[name].[hash].css',
chunkFilename: devMode ? '[id].css' : '[id].[hash].css',
})
]
}

· 3 min read

HTTP Cookie(也叫Web Cookie或浏览器Cookie)是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。通常,它用于告知服务端两个请求是否来自同一浏览器,如保持用户的登录状态。Cookie使基于无状态的HTTP协议记录稳定的状态信息成为了可能。

Cookie主要用于以下三个方面:

  • 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
  • 个性化设置(如用户自定义设置、主题等)
  • 浏览器行为跟踪(如跟踪分析用户行为等)

JavaScript通过Document.cookies访问Cookie

    console.log(document.cookie); 

sessionStorage 对象

sessionStorage 属性允许你访问一个 session Storage 对象。它与 localStorage 相似,不同之处在于 localStorage 里面存储的数据没有过期时间设置,而存储在 sessionStorage 里面的数据在页面会话结束时会被清除。页面会话在浏览器打开期间一直保持,并且重新加载或恢复页面仍会保持原来的页面会话。在新标签或窗口打开一个页面会初始化一个新的会话,这点和 session cookies 的运行方式不同。

语法

    // 保存数据到sessionStorage
sessionStorage.setItem('key', 'value');

// 从sessionStorage获取数据
var data = sessionStorage.getItem('key');

// 从sessionStorage删除保存的数据
sessionStorage.removeItem('key');

// 从sessionStorage删除所有保存的数据
sessionStorage.clear();

localStorage 对象

localStorage 与 sessionStorage 一样,都遵循同源策略,但是它是持续存在的。localStorage 首次出现于 Firefox 3.5。

localStorage 和 globalStorage[location.hostname] 是一样的, 除了作用域是在 HTML5 origin (结构 + 主机名 + 非标准的端口), 并且 localStorage 是一个 Storage 实例 ,而globalStorage[location.hostname] 是一个 StorageObsolete 实例

IndexedDB

IndexedDB 是一个用于在浏览器中储存较大数据结构的 Web API, 并提供索引功能以实现高性能查找. 像其他基于 SQL 的 关系型数据库管理系统 (RDBMS) 一样, IndexedDB 是一个事务型的数据库系统. 然而, 它是使用 JavaScript 对象而非列数固定的表格来储存数据的.

· 4 min read

图ping

使用\<img>标签实现跨域,使用它们的onload和onerror事件处理程序来确定是否收到了相应,通过图像Ping,浏览器得不到任何具体的数据,但通过监听load和error事件,它能知道响应是什么时候接收道的。

demo

    var img = new Image();
img.onload = img.onerror = function() {
alert('Done!');
}
img.src = "http://www.example.com/test?name=Nicholas";

这里创建了一个Image的实例,然后将onload 和 onerror事件处理程序指定为同一个函数。这样无论是什么响应,只要请求完成,就能得到通知.请求从设置src属性的那一刻开始,而这个例子在请求中发送了一个name参数。 图像ping最常用于跟踪用户点击页面或动态广告曝光次数。图像Ping有两个主要的缺点,一是只能发送Get请求,二是无法访问服务器的响应文本。

JSONP

JSONP 是JSON with padding(填充式JSON或参数式JSON)的简写,是应用JSON的一种新方法

demo

    function handleResponse(response) {
alert('You` are at IP' + response.ip + ", which is in" + response.city + ", " + response.response.regin_name);
}
var script = document.createElement("script");
script.src = "http://freegeoip.net/json/?callback=handleResponse"
document.body.insertBefore(script, document.body.firstChild);

优点是能够直接访问响应文本,支持在浏览器与服务器之间双向通信。但是缺点有可能在响应中夹带一些恶意代码。H5给\<script>元素新增了一个onerror事件处理程序,但是目前还没有得到任何浏览器的支持。

Comet

指的是一种更高级的Ajax技术,Ajax是一种从页面向服务器请求数据的技术,而Comet则是一种服务器向页面推送数据的技术。Comet能够让信息近乎实时的被推送到页面上,非常适合处理体育比赛的分数和股票的报价。

短轮询: 浏览器定时向服务器发送请求 长轮询: 页面发起一个请求,然后服务器一直保持连接打开,直到有数据可发送。发送完数据之后,浏览器关闭连接,随即又发起一个到服务器的新请求。 Comet 实现的是HTTP流。流不同于上述两种轮询,因为它在页面的整个生命周期内只使用一个HTTP连接.具体来说就是浏览器向服务器发送一个请求,而服务器保持连接打开,然后周期性的向浏览器发送数据。

· 2 min read

在使用Element 组件库的时候,可以发现点击非弹框对象的时候会触发弹框隐藏事件,但是在对应的封装包中却发现原来自定义了指令,进行管理点击非当前元素的事件触发,如图:

{% asset_img 1.png dropdown.vue %}

接下来看看Clickoutside.js 文件中怎么实现点击事件:

import Vue from 'vue';
import { on } from 'element-ui/src/utils/dom';

const nodeList = [];
const ctx = '@@clickoutsideContext';

let startClick;
let seed = 0;

!Vue.prototype.$isServer && on(document, 'mousedown', e => (startClick = e));

!Vue.prototype.$isServer && on(document, 'mouseup', e => {
nodeList.forEach(node => node[ctx].documentHandler(e, startClick));
});

function createDocumentHandler(el, binding, vnode) {
return function(mouseup = {}, mousedown = {}) {
if (!vnode ||
!vnode.context ||
!mouseup.target ||
!mousedown.target ||
el.contains(mouseup.target) ||
el.contains(mousedown.target) ||
el === mouseup.target ||
(vnode.context.popperElm &&
(vnode.context.popperElm.contains(mouseup.target) ||
vnode.context.popperElm.contains(mousedown.target)))) return;

if (binding.expression &&
el[ctx].methodName &&
vnode.context[el[ctx].methodName]) {
vnode.context[el[ctx].methodName]();
} else {
el[ctx].bindingFn && el[ctx].bindingFn();
}
};
}

export default {
bind(el, binding, vnode) {
nodeList.push(el);
const id = seed++;
el[ctx] = {
id,
documentHandler: createDocumentHandler(el, binding, vnode),
methodName: binding.expression,
bindingFn: binding.value
};
},

update(el, binding, vnode) {
el[ctx].documentHandler = createDocumentHandler(el, binding, vnode);
el[ctx].methodName = binding.expression;
el[ctx].bindingFn = binding.value;
},

unbind(el) {
let len = nodeList.length;

for (let i = 0; i < len; i++) {
if (nodeList[i][ctx].id === el[ctx].id) {
nodeList.splice(i, 1);
break;
}
}
delete el[ctx];
}
};

以上就是Clickoutside.js 源码, 使用了nodeList数组管理需要绑定点击非当前节点元素之后关闭的DOM,然后通过监听document文档流的mouseup事件,循环遍历触发DOM的关闭函数,这样处理能够把所有Clickoutside 事件统一化处理,使用方便。

· One min read

引用文章链接

  • Virtual DOM is any kind of representation of a real DOM
  • When we change something in our Virtual DOM Tree, we get a new Virtual Tree. Algorithm compares these two trees (old and new), finds differences and makes only necessary small changes to real DOM so it reflects virtual

Representing our DOM Tree

    <ul class=”list”>
<li>item 1</li>
<li>item 2</li>
</ul>

turn to Js Object

    { type: ‘ul’, props: {class: ‘list’ }, children: [
{ type: ‘li’, props: {}, children: [‘item 1] },
{ type: ‘li’, props: {}, children: [‘item 2] }
] }
  • We represent DOM elements with objects like
    { type: ‘…’, props: {}, children: [] }

· 3 min read

事件流描述的是从页面中接收事件的顺序。IE和Netscape开发团队提出了完全相反的事件流的概念。

IE的事件流是事件冒泡流

Netscape Communicator 的事件流是事件捕获流。

事件冒泡

IE事件流叫做事件冒泡,即事件开始时由最具体的元素接收,然后逐级向上传播到较为不具体的节点.

    <!Doctype html>
<html>
<head>
<title>Event Bubbling Example</title>
</head>
<body>
<div id="myDiv">Click Me</div>
</body>
</html>

冒泡顺序是: Element div > Element body > Element html > Document

事件捕获

Netscape Communicator 团队提出的另一种事件流叫做事件捕获。事件捕获的思想是不太具体的节点应该更早的接收到事件。

捕获的顺序是: Document > Element html > Element body > Element div

DOM事件流

三个阶段: 事件捕获阶段、处于目标阶段 和 事件冒泡阶段

DOM2级事件处理程序

用于处理指定和删除事件处理程序的操作: addEventListener() 和 removeEventListener(). 所有DOM节点中都包含这两个方法,并且它们都接受三个参数: 要处理的事假名, 作为事件处理程序的函数 和 一个布尔值. 布尔值参数如果是 true ,表示在捕获阶段调用事件处理程序; 如果是false, 表示在冒泡阶段调用事件处理程序。

通过addEventListenter() 添加的事件处理程序只能使用removeEventListener() 来移除; 移除时传入的参数与添加处理程序时使用的参数相同。通过addEventListenter()添加的匿名函数将无法移除。

    var btn = document.getElementById("myBtn");
btn.addEventListener("click", function() {
alert(this.id);
}, false);
btn.removeEventListener("click", function() { // 没有用!
alert(this.id);
}, false)
    var btn = document.getElementById("myBtn");
var handler = function() {
alert(this.id);
}
btn.addEventListener("click", handler, false);
btn.removeEventListener("click", handler, false); // 有效

· 6 min read

概述

Node 应用由模块组成,采用 CommonJS 模块规范。

每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。

CommonJS模块的特点如下:

  • 所有代码都运行在模块作用域,不会污染全局作用域。
  • 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
  • 模块加载的顺序,按照其在代码中出现的顺序。

module对象

Node内部提供一个Module构建函数。所有模块都是Module的实例。

function

    function Module(id, parent) {
this.id = id;
this.exports = {};
this.parent = parent;
// ...

每个模块内部,都有一个module对象,代表当前模块。它有以下属性。

  • module.id 模块的识别符,通常是带有绝对路径的模块文件名。
  • module.filename 模块的文件名,带有绝对路径。
  • module.loaded 返回一个布尔值,表示模块是否已经完成加载。
  • module.parent 返回一个对象,表示调用该模块的模块。
  • module.children 返回一个数组,表示该模块要用到的其他模块。
  • module.exports 表示模块对外输出的值。

module.exports属性

module.exports属性表示当前模块对外输出的接口,其他文件加载该模块,实际上就是读取module.exports变量。

demo

    var EventEmitter = require('events').EventEmitter;
module.exports = new EventEmitter();

setTimeout(function() {
module.exports.emit('ready');
}, 1000);

上面模块会在加载后1秒后,发出ready事件。其他文件监听该事件,可以写成下面这样。

    var a = require('./a');
a.on('ready', function() {
console.log('module a is ready');
});

exports变量

为了方便,Node为每个模块提供一个exports变量,指向module.exports。这等同在每个模块头部,有一行这样的命令。

AMD规范与CommonJS规范的兼容性

CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。AMD规范则是非同步加载模块,允许指定回调函数。由于Node.js主要用于服务器编程,模块文件一般都已经存在于本地硬盘,所以加载起来比较快,不用考虑非同步加载的方式,所以CommonJS规范比较适用。但是,如果是浏览器环境,要从服务器端加载模块,这时就必须采用非同步模式,因此浏览器端一般采用AMD规范。

    define(['package/lib'], function(lib){
function foo(){
lib.log('hello world!');
}

return {
foo: foo
};
});

AMD规范允许输出的模块兼容CommonJS规范,这时define方法需要写成下面这样:

    define(function (require, exports, module){
var someModule = require("someModule");
var anotherModule = require("anotherModule");

someModule.doTehAwesome();
anotherModule.doMoarAwesome();

exports.asplode = function (){
someModule.doTehAwesome();
anotherModule.doMoarAwesome();
};
});

require命令

基本用法

Node使用CommonJS模块规范,内置的require命令用于加载模块文件。

require命令的基本功能是,读入并执行一个JavaScript文件,然后返回该模块的exports对象。如果没有发现指定模块,会报错。

模块的缓存

第一次加载某个模块时,Node会缓存该模块。以后再加载该模块,就直接从缓存取出该模块的module.exports属性。

    require('./example.js');
require('./example.js').message = "hello";
require('./example.js').message
// "hello"

所有缓存的模块保存在require.cache之中,如果想删除模块的缓存,可以像下面这样写。

    // 删除指定模块的缓存
delete require.cache[moduleName];

// 删除所有模块的缓存
Object.keys(require.cache).forEach(function(key) {
delete require.cache[key];
})

require.main

require方法有一个main属性,可以用来判断模块是直接执行,还是被调用执行。

直接执行的时候(node module.js),require.main属性指向模块本身。

    require.main === module
// true

模块的加载机制

CommonJS模块的加载机制是,输入的是被输出的值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。请看下面这个例子。

下面是一个模块文件lib.js。

    // lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
counter: counter,
incCounter: incCounter,
};

然后,加载上面的模块。

    // main.js
var counter = require('./lib').counter;
var incCounter = require('./lib').incCounter;

console.log(counter); // 3
incCounter();
console.log(counter); // 3

· One min read

Symbol.iterator 为每一个对象定义了默认的迭代器。该迭代器可以被 for...of 循环使用。

描述

当需要对一个对象进行迭代时(比如开始用于一个for..of循环中),它的@@iterator方法都会在不传参情况下被调用,返回的迭代器用于获取要迭代的值。

一些内置类型拥有默认的迭代器行为,其他类型(如 Object)则没有。下表中的内置类型拥有默认的@@iterator方法:

demo

    const iterable1 = new Object();

iterable1[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};

console.log([...iterable1]);
// expected output: Array [1, 2, 3]

· 3 min read

订阅者模式涉及三个对象:发布者、主题对象、订阅者,三个对象间的是一对多的关系,每当主题对象状态发生改变时,其相关依赖对象都会得到通知,并被自动更新

demo

    function Dep() {//主题对象
this.subs = []; //订阅者列表
}
Dep.prototype.notify = function() { //主题对象通知订阅者
this.subs.forEach(function(sub){ //遍历所有的订阅者,执行订阅者提供的更新方法
sub.update();
});
}
function Sub(x) { //订阅者
this.x = x;
}
Sub.prototype.update = function() { //订阅者更新
this.x = this.x + 1;
console.log(this.x);
}
var pub = { //发布者
publish: function() {
dep.notify();
}
};
var dep = new Dep(); //主题对象实例
Array.prototype.push.call(dep.subs, new Sub(1), new Sub(2), new Sub(4)); //新增 3 个订阅者
pub.publish(); //发布者发布更新
// 2
// 3
// 5

demo2

function Publish() {     // 主题对象
this.subscribers = [];
}

// 定义订阅的函数
Publish.prototype = {
theme: 'default', // 默认主题
sub: function(subPerson) { // 订阅的函数
let btn = true
this.subscribers.forEach(item => { // 不可重复订阅
if(subPerson.name === item.name) {
return btn = false
}
})
if(btn) {
this.subscribers.push(subPerson)
}
},
deliver: function() { // 分发消息函数
if(this.subscribers.length > 0) {
this.subscribers.forEach(item => { // 触发所有订阅者的更新
item.update(this.theme)
});
}else {
console.log(this.theme + 'is no subscribers');
}
}
}

function Sub(name) { // 订阅者名字
this.name = name;
}

Sub.prototype = {
subFn: function(pub) { // 订阅者订阅方法
if(pub && pub.sub && typeof pub.sub === 'function') {
pub.sub(this);
}
},
update: function(theme) { // 订阅者更新方法
console.log(this.name + '收到了' + theme);
}
}


// 创建了三个主题
let publish1 = new Publish()
publish1.theme = '美剧'

let publish2 = new Publish()
publish2.theme = '日剧'

let publish3 = new Publish()
publish3.theme = '韩剧'

// 创建8个订阅者
let sub1 = new Sub('Joan')
let sub2 = new Sub('Joan1')
let sub3 = new Sub('Joan2')
let sub4 = new Sub('Joan3')
let sub5 = new Sub('Joan4')
let sub6 = new Sub('Joan5')
let sub7 = new Sub('Joan6')
let sub8 = new Sub('Joan7')

sub1.subFn(publish1);
sub1.subFn(publish2);
sub2.subFn(publish2);
sub3.subFn(publish2);

publish1.deliver();
publish2.deliver();
publish3.deliver();

运行结果

Joan收到了美剧

Joan收到了日剧

Joan1收到了日剧

Joan2收到了日剧

韩剧is no subscribers