淘宝前端团队
{% asset_img FED.png 淘宝前端团队 %}
AlloyTeam
{% asset_img AlloyTeam.png AlloyTeam %}
奇舞团
{% asset_img qiwutuan.png 奇舞团 %}
凹凸实验室
{% asset_img o2logo.png 凹凸实验室 %}
百度FEX
{% asset_img FEX.png 百度FEX %}
蚂蚁金服AFX
蚂蚁金服AFX
最近朋友推荐我了解一个开源项目的升级,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 from a bundle, or bundles, into a separate file.
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 配置 抽离到同一个文件夹可以如下:
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',
})
]
}
HTTP Cookie(也叫Web Cookie或浏览器Cookie)是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。通常,它用于告知服务端两个请求是否来自同一浏览器,如保持用户的登录状态。Cookie使基于无状态的HTTP协议记录稳定的状态信息成为了可能。
JavaScript通过Document.cookies访问Cookie
console.log(document.cookie);
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 与 sessionStorage 一样,都遵循同源策略,但是它是持续存在的。localStorage 首次出现于 Firefox 3.5。
localStorage 和 globalStorage[location.hostname] 是一样的, 除了作用域是在 HTML5 origin (结构 + 主机名 + 非标准的端口), 并且 localStorage 是一个 Storage 实例 ,而globalStorage[location.hostname] 是一个 StorageObsolete 实例
IndexedDB 是一个用于在浏览器中储存较大数据结构的 Web API, 并提供索引功能以实现高性能查找. 像其他基于 SQL 的 关系型数据库管理系统 (RDBMS) 一样, IndexedDB 是一个事务型的数据库系统. 然而, 它是使用 JavaScript 对象而非列数固定的表格来储存数据的.
使用\<img>标签实现跨域,使用它们的onload和onerror事件处理程序来确定是否收到了相应,通过图像Ping,浏览器得不到任何具体的数据,但通过监听load和error事件,它能知道响应是什么时候接收道的。
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 是JSON with padding(填充式JSON或参数式JSON)的简写,是应用JSON的一种新方法
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事件处理程序,但是目前还没有得到任何浏览器的支持。
指的是一种更高级的Ajax技术,Ajax是一种从页面向服务器请求数据的技术,而Comet则是一种服务器向页面推送数据的技术。Comet能够让信息近乎实时的被推送到页面上,非常适合处理体育比赛的分数和股票的报价。
短轮询: 浏览器定时向服务器发送请求 长轮询: 页面发起一个请求,然后服务器一直保持连接打开,直到有数据可发送。发送完数据之后,浏览器关闭连接,随即又发起一个到服务器的新请求。 Comet 实现的是HTTP流。流不同于上述两种轮询,因为它在页面的整个生命周期内只使用一个HTTP连接.具体来说就是浏览器向服务器发送一个请求,而服务器保持连接打开,然后周期性的向浏览器发送数据。
在使用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 事件统一化处理,使用方便。
<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’] }
] }
{ type: ‘…’, props: { … }, children: [ … ] }
事件流描述的是从页面中接收事件的顺序。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
三个阶段: 事件捕获阶段、处于目标阶段 和 事件冒泡阶段
用于处理指定和删除事件处理程序的操作: 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); // 有效
Node 应用由模块组成,采用 CommonJS 模块规范。
每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。
Node内部提供一个Module构建函数。所有模块都是Module的实例。
function Module(id, parent) {
this.id = id;
this.exports = {};
this.parent = parent;
// ...
每个模块内部,都有一个module对象,代表当前模块。它有以下属性。
module.exports属性表示当前模块对外输出的接口,其他文件加载该模块,实际上就是读取module.exports变量。
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');
});
为了方便,Node为每个模块提供一个exports变量,指向module.exports。这等同在每个模块头部,有一行这样的命令。
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();
};
});
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属性,可以用来判断模块是直接执行,还是被调用执行。
直接执行的时候(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
Symbol.iterator 为每一个对象定义了默认的迭代器。该迭代器可以被 for...of 循环使用。
当需要对一个对象进行迭代时(比如开始用于一个for..of循环中),它的@@iterator方法都会在不传参情况下被调用,返回的迭代器用于获取要迭代的值。
一些内置类型拥有默认的迭代器行为,其他类型(如 Object)则没有。下表中的内置类型拥有默认的@@iterator方法:
const iterable1 = new Object();
iterable1[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
console.log([...iterable1]);
// expected output: Array [1, 2, 3]
订阅者模式涉及三个对象:发布者、主题对象、订阅者,三个对象间的是一对多的关系,每当主题对象状态发生改变时,其相关依赖对象都会得到通知,并被自动更新
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
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