el's blog

A noder, FEer. use express, vue...etc

ES6标准之逻辑与的规则
研读ES6标准,正好碰到写Java的同事对于 Js 中逻辑与的疑惑。

前言

最近看到了阮一峰的一篇(读懂 ECMAScript 规格](http://www.ruanyifeng.com/blog/2015/11/ecmascript-specification.html),颇受鼓舞,特此收录一些在ES标准中能给我解惑的知识点。

恰逢写RN的原生同事有下面的问题相问:

1
obj.subObj && obj.subObj.name // 如果subObj存在,则表达式会输出name而非输出一个布尔值,为何?

解答

ES6原版文档

ES6标准在线英文文档地址

12.12.3 Runtime Semantics: Evaluation

LogicalANDExpression : LogicalANDExpression && BitwiseORExpression

  1. Let lref be the result of evaluating LogicalANDExpression.
  2. Let lval be GetValue(lref).
  3. Let lbool be ToBoolean(lval).
  4. ReturnIfAbrupt(lbool).
  5. If lbool is false, return lval.
  6. Let rref be the result of evaluating BitwiseORExpression.
  7. Return GetValue(rref).

翻译

  1. 令 lref 为解释执行 LogicalANDExpression 的结果。
  2. 令 lval 为 GetValue(lref)。
  3. 如果 ToBoolean(lval) 为 false,返回 lval。
  4. 令 rref 为解释执行 BitwiseORExpression 的结果。
  5. 返回 GetValue(rref)。

关键在第三部 ,如果 lref 左表达式执行得到的值 lval 不能被当作 false 的时候直接返回 lval,否则就要返回右表达式的执行值 rref 的具体value,而不是 bool 值。

ToBoolean

Argument Type Result
Completion Record If argument is an abrupt completion, return argument. Otherwise return ToBoolean(argument.[[value]])
Undefined Return false.
Null Return false.
Boolean Return argument.
Number Return false if argument is +0, −0, or NaN; otherwise return true.
String Return false if argument is the empty String (its length is zero); otherwise return true.
Symbol Return true.
Object Return true.

可知左表达式如果为 undefeindnullfalse0NaN空字符串 的时候被当作 false。反之为 true,则返回GetValue(rref)

GetValue

  1. ReturnIfAbrupt(V).
  2. If Type(V) is not Reference, return V.
  3. Let base be GetBase(V).
  4. If IsUnresolvableReference(V), throw a ReferenceError exception.
  5. If IsPropertyReference(V), then
    1. If HasPrimitiveBase(V) is true, then
      1. Assert: In this case, base will never be null or undefined.
      2. Let base be ToObject(base).
    2. Return base.[Get], GetThisValue(V)).
  6. Else base must be an Environment Record,
    1. Return base.GetBindingValue(GetReferencedName(V), IsStrictReference(V)) (see 8.1.1).

NOTE: The object that may be created in step 5.a.ii is not accessible outside of the above abstract operation and the ordinary object [[Get]] internal method. An implementation might choose to avoid the actual creation of the object.

第二步谈到如果不是一个引用类型的变量则直接返回这个变量本身,所以 obj.subObj.name 返回的就是 name 属性。

扩展

那么逻辑或也是同理

LogicalORExpression : LogicalORExpression || LogicalANDExpression

  1. Let lref be the result of evaluating LogicalORExpression.
  2. Let lval be GetValue(lref).
  3. Let lbool be ToBoolean(lval).
  4. ReturnIfAbrupt(lbool).
  5. If lbool is true, return lval.
  6. Let rref be the result of evaluating LogicalANDExpression.
  7. Return GetValue(rref).

只是判断 lval 能被当成 true 的时候则返回 lval 本身,否则返回 GetValue(rref).

webpack 把你的 vue 项目编译成了什么
webpack编译你的vue项目生成代码探索

前言

往 main.js 里写入最简单的 vue 项目结构如下

1
2
3
4
5
6
7
8
9
10
import Vue from 'vue';
import App from './App.vue';

new Vue({
el: '#app',
template: '<App/>',
components: {
App
}
})

App.vue 如下

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
<template>
<div id="app">
<h1>{{ msg }}</h1>
<h2>Essential Links</h2>
<ul>
<li>
<a href="https://vuejs.org" target="_blank">Core Docs</a>
</li>
<li>
<a href="https://forum.vuejs.org" target="_blank">Forum</a>
</li>
<li>
<a href="https://chat.vuejs.org" target="_blank">Community Chat</a>
</li>
<li>
<a href="https://twitter.com/vuejs" target="_blank">Twitter</a>
</li>
</ul>
<h2>Ecosystem</h2>
<ul>
<li>
<a href="http://router.vuejs.org/" target="_blank">vue-router</a>
</li>
<li>
<a href="http://vuex.vuejs.org/" target="_blank">vuex</a>
</li>
<li>
<a href="http://vue-loader.vuejs.org/" target="_blank">vue-loader</a>
</li>
<li>
<a href="https://github.com/vuejs/awesome-vue" target="_blank">awesome-vue</a>
</li>
</ul>
</div>
</template>

<script>
export default {
name: 'app',
data() {
return {
msg: 'Welcome to Your Vue.js App'
}
}
}
</script>

<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}

h1,
h2 {
font-weight: normal;
}

ul {
list-style-type: none;
padding: 0;
}

li {
display: inline-block;
margin: 0 10px;
}

a {
color: #42b983;
}
</style>

编译生成后得到一个316kb的文件,而在316Kb中包含着什么,我很好奇想探索一番。

1
2
3
4
5
6
7
8
9
10
11
npm run build

> learning-in-vue@1.0.0 build /Users/everlose/workspace/github/learningInVue
> cross-env NODE_ENV=production webpack --progress --hide-modules

Hash: 18d868a423b48dc263e9
Version: webpack 3.9.1
Time: 3693ms
Asset Size Chunks Chunk Names
build.js 316 kB 0 [emitted] [big] main
build.js.map 399 kB 0 [emitted] main

代码分解

按顺序往下解读,本篇编译后的代码在这儿,如果只想看结论那么请拉到最后有一张结构梳理图。

webpack 模块机制

前面70行还是熟悉的 webpack 模块机制的基础代码,关于它的细致解读参见上一篇webpack模块机制,编译后的代码格式如下,并且我做了代码美化,并且插上了中文注释

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ // 缓存模块,所有被加载过的模块都会成为installedModules对象的属性,靠函数__webpack_require__做到。
/******/ var installedModules = {};
/******/
/******/ // The require function 核心加载方法
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ // 检查模块是否已在缓存中,是则直接返回缓存中的模块不需要再次加载
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ // 创造一个新模块并放入缓存中,i是模块标识,l意为是否加载此模块完毕,exports是此模块执行后的输出对象
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ // 传入参数并执行模块函数
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded 标为true代表模块执行完成。
/******/ module.l = true;
/******/
/******/ // Return the exports of the module 返回此模块输出的对象
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ // webpack 私有变量,保存传入的modules,即所有的模块组成的数组
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ // 保存缓存中的模块数组
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ // 为 es6 exports 定义 getter
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ // 如果 exports 输出的对象本身不包含 name 属性时,定义一个。
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ // 解决 ES module 和 Common js module 的冲突,ES 则返回 module['default']
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ // 工具方法,判断是否object有property属性。
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ // 大概和 webpack.config.js 的 output 有关吧,webpack 的公共路径
/******/ __webpack_require__.p = "/dist/";
/******/
/******/ // Load entry module and return exports 执行第一个依赖模块并且返回它输出。
/******/ return __webpack_require__(__webpack_require__.s = 0);
/******/ })

0号模块

导出一个全局变量,在web端就是指代window

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
/* 0 */
(function (module, exports) {

var g;

// This works in non-strict mode
g = (function () {
return this;
})();

try {
// This works if eval is allowed (see CSP)
g = g || Function("return this")() || (1, eval)("this");
} catch (e) {
// This works if the window reference is available
if (typeof window === "object")
g = window;
}

// g can still be undefined, but nothing to do about it...
// We return undefined, instead of nothing here, so it's
// easier to handle this case. if(!global) { ...}

module.exports = g;


/***/
}),

1号模块

实际上做的事情很明显,就是导出了 main.js 的代码,一个vue实例对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_vue__ = __webpack_require__(2);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__App_vue__ = __webpack_require__(6);

// 从2号模块导出的一个叫a的变量,就是Vue对象本身
new __WEBPACK_IMPORTED_MODULE_0_vue__["a" /* default */]({
el: '#app',
template: '<App/>',
components: {
App: __WEBPACK_IMPORTED_MODULE_1__App_vue__["a" /* default */]
}
});

/***/ })

2号模块

即是 Vue 源码本身,从114行一直到了10818行,一共是10705行代码,啧啧啧

webpack 有所配置,所以导出的 Vue 实际上是 vue/dist/vue.esm.js 的完整编译版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* 2 */
/***/ (function (module, __webpack_exports__, __webpack_require__) {

"use strict";
/*!
* Vue.js v2.5.9
* (c) 2014-2017 Evan You
* Released under the MIT License.
*/

// 作用域指向__webpack_exports__,并把__webpack_require__(0)作为global,实际上就是window
// __webpack_require__(3).setImmediate)作为setsetImmediate参数传入函数
(function (global, setImmediate) {

// 省略近1w行的代码,关于vue原本本身的解读以后再做......

// 最终 export 出来一个叫 Vue$3的对象
/* harmony default export */
__webpack_exports__["a"] = (Vue$3);

/* WEBPACK VAR INJECTION */
}.call(__webpack_exports__, __webpack_require__(0), __webpack_require__(3).setImmediate))

}),

3,4,5号模块

都和 node_modules/setimmediate 有关,由于 vue 的 DOM 异步更新机制使用到了它,所以被引入。

这里也不做详解,只给出结构。

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/* 3 */
/***/
(function (module, exports, __webpack_require__) {

// 省略代码...

// setimmediate attaches itself to the global object
__webpack_require__(4);
exports.setImmediate = setImmediate;
exports.clearImmediate = clearImmediate;

/***/
}),

/* 4 */
/***/
(function (module, exports, __webpack_require__) {

/* WEBPACK VAR INJECTION */
(function (global, process) {
// 省略代码...
}.call(exports, __webpack_require__(0), __webpack_require__(5)))

/***/
}),

/* 5 */
/***/
(function (module, exports) {

// shim for using process in browser
var process = module.exports = {};

// 省略代码...

process.cwd = function () {
return '/'
};
process.chdir = function (dir) {
throw new Error('process.chdir is not supported');
};
process.umask = function () {
return 0;
};
/***/
}),

6号模块

和 App.vue 的解析有关,把 App.vue 中的 template 和 script 编译为一个 vue components,并把 style 标签内的样式插入到DOM中。

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/* 6 */
/***/
(function (module, __webpack_exports__, __webpack_require__) {

"use strict";

// 返回具体 App.vue 中 的script 中的代码
var __WEBPACK_IMPORTED_MODULE_0__babel_loader_node_modules_vue_loader_lib_selector_type_script_index_0_App_vue__ = __webpack_require__(13);

// 把App.vue 的 template 解析为一堆 vue render 函数。
var __WEBPACK_IMPORTED_MODULE_1__node_modules_vue_loader_lib_template_compiler_index_id_data_v_66ce2159_hasScoped_false_buble_transforms_node_modules_vue_loader_lib_selector_type_template_index_0_App_vue__ = __webpack_require__(14);

// 注入vue文件里写入的css函数
function injectStyle(ssrContext) {
// 由此可知7号模块是编译并插入vue中的css到DOM上的
__webpack_require__(7)
}
// 12号模块用于输出components渲染函数
var normalizeComponent = __webpack_require__(12)
/* script */

/* template */

/* template functional */
var __vue_template_functional__ = false
/* styles */
var __vue_styles__ = injectStyle
/* scopeId */
var __vue_scopeId__ = null
/* moduleIdentifier (server only) */
var __vue_module_identifier__ = null

// 编译模块,混杂template和script。
var Component = normalizeComponent(
__WEBPACK_IMPORTED_MODULE_0__babel_loader_node_modules_vue_loader_lib_selector_type_script_index_0_App_vue__["a" /* default */ ],
__WEBPACK_IMPORTED_MODULE_1__node_modules_vue_loader_lib_template_compiler_index_id_data_v_66ce2159_hasScoped_false_buble_transforms_node_modules_vue_loader_lib_selector_type_template_index_0_App_vue__["a" /* default */ ],
__vue_template_functional__,
__vue_styles__,
__vue_scopeId__,
__vue_module_identifier__
)

/* harmony default export */
__webpack_exports__["a"] = (Component.exports);


/***/
}),

7、8、9、10、11

都和样式有关,简言之就是7号模块加载8号模块获取css代码作为参数,并作为参数传入10号模块进行插入

太长也只大意上列出结构

  • 7号模块由 style-loader 带入,把所有的css插入到 style 标签里
  • 8号模块加载具体的css代码,
  • 9号模块由css-loader代入,用于做css的sourceMap
  • 10号模块返回具体插入动作函数,供7号模块调用
  • 11号模块把所有的样式组成的数组转为字符串,给10号模块做插入。
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/* 7 */
/***/
(function (module, exports, __webpack_require__) {

// style-loader: Adds some css to the DOM by adding a <style> tag

// load the styles
var content = __webpack_require__(8);
if (typeof content === 'string') content = [
[module.i, content, '']
];
if (content.locals) module.exports = content.locals;
// add the styles to the DOM
var update = __webpack_require__(10)("15459d21", content, true);

/***/
}),
/* 8 */
/***/
(function (module, exports, __webpack_require__) {

// css-loader 用于做css的sourceMap
exports = module.exports = __webpack_require__(9)(undefined);
// imports


// module
// 这就是 App.vue 文件中 style 里的的 css
exports.push([module.i, "#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:#2c3e50;margin-top:60px}h1,h2{font-weight:400}ul{list-style-type:none;padding:0}li{display:inline-block;margin:0 10px}a{color:#42b983}", ""]);

// exports


/***/
}),
/* 9 */
/***/
(function (module, exports) {
// css base code, injected by the css-loader
module.exports = function (useSourceMap) {
// 省略代码...
}
}),
/* 10 */
/***/
(function (module, exports, __webpack_require__) {
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
Modified by Evan You @yyx990803
*/

// ...太长只贴了关键步骤,总之关键的函数就是这些
var hasDocument = typeof document !== 'undefined'
// ...
var listToStyles = __webpack_require__(11)
// ...
var head = hasDocument && (document.head || document.getElementsByTagName('head')[0])


// ...
module.exports = function (parentId, list, _isProduction) {

// ...
var styles = listToStyles(parentId, list)
addStylesToDom(styles)

return function update (newList) {
// ...
}
}

function addStylesToDom (styles /* Array<StyleObject> */) {
for (var i = 0; i < styles.length; i++) {
// ...
domStyle.parts.push(addStyle(item.parts[j]))
// ....
}
}

// 总之先调用了addStylesToDom,接着是addStyle,再是createStyleElement插入样式到head中。
function createStyleElement () {
var styleElement = document.createElement('style')
styleElement.type = 'text/css'
head.appendChild(styleElement)
return styleElement
}

function addStyle (obj /* StyleObjectPart */) {
// ...
styleElement = createStyleElement()
// ...
}

/***/
}),
/* 11 */
/***/
(function (module, exports) {

/**
* Translates the list format produced by css-loader into something
* easier to manipulate.
*/
module.exports = function listToStyles(parentId, list) {
var styles = []
var newStyles = {}
for (var i = 0; i < list.length; i++) {
var item = list[i]
var id = item[0]
var css = item[1]
var media = item[2]
var sourceMap = item[3]
var part = {
id: parentId + ':' + i,
css: css,
media: media,
sourceMap: sourceMap
}
if (!newStyles[id]) {
styles.push(newStyles[id] = {
id: id,
parts: [part]
})
} else {
newStyles[id].parts.push(part)
}
}
return styles
}


/***/
}),

12、13、14号模块

12号做 .vue 文件中的 template 和 script 解析并供6号输出,最终返回了一个 vue components 对象,在浏览器控制台打印如下:

1
2
3
4
5
6
7
8
9
10
Object
beforeCreate: [ƒ]
data: ƒ data()
inject: {}
name: "app"
render: ƒ ()
staticRenderFns: (2) [ƒ, ƒ, cached: Array(2)]
_Ctor: {0: ƒ}
_compiled: true
__proto__: Object

而13号模块返回具体 script 中的代码,而14号模块则是把 template 解析为一堆 vue render 函数。

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
/* 12 */
/***/
(function (module, exports) {

/* globals __VUE_SSR_CONTEXT__ */

// IMPORTANT: Do NOT use ES2015 features in this file.
// This module is a runtime utility for cleaner component module output and will
// be included in the final webpack user bundle.

module.exports = function normalizeComponent(
rawScriptExports,
compiledTemplate,
functionalTemplate,
injectStyles,
scopeId,
moduleIdentifier /* server only */
) {
var esModule
var scriptExports = rawScriptExports = rawScriptExports || {}

// ES6 modules interop
var type = typeof rawScriptExports.default
if (type === 'object' || type === 'function') {
esModule = rawScriptExports
scriptExports = rawScriptExports.default
}

// Vue.extend constructor export interop
var options = typeof scriptExports === 'function' ?
scriptExports.options :
scriptExports

// render functions
if (compiledTemplate) {
options.render = compiledTemplate.render
options.staticRenderFns = compiledTemplate.staticRenderFns
options._compiled = true
}

// functional template
if (functionalTemplate) {
options.functional = true
}

// scopedId
if (scopeId) {
options._scopeId = scopeId
}

var hook
if (moduleIdentifier) { // server build
hook = function (context) {
// 2.3 injection
context =
context || // cached call
(this.$vnode && this.$vnode.ssrContext) || // stateful
(this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext) // functional
// 2.2 with runInNewContext: true
if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {
context = __VUE_SSR_CONTEXT__
}
// inject component styles
if (injectStyles) {
injectStyles.call(this, context)
}
// register component module identifier for async chunk inferrence
if (context && context._registeredComponents) {
context._registeredComponents.add(moduleIdentifier)
}
}
// used by ssr in case component is cached and beforeCreate
// never gets called
options._ssrRegister = hook
} else if (injectStyles) {
hook = injectStyles
}

if (hook) {
var functional = options.functional
var existing = functional ?
options.render :
options.beforeCreate

if (!functional) {
// inject component registration as beforeCreate hook
options.beforeCreate = existing ?
[].concat(existing, hook) :
[hook]
} else {
// for template-only hot-reload because in that case the render fn doesn't
// go through the normalizer
options._injectStyles = hook
// register for functioal component in vue file
options.render = function renderWithStyleInjection(h, context) {
hook.call(context)
return existing(h, context)
}
}
}

return {
esModule: esModule,
exports: scriptExports,
options: options
}
}


/***/
}),
/* 13 */
/***/
(function (module, __webpack_exports__, __webpack_require__) {

"use strict";

/* harmony default export */
// 这就是 App.vue 中 script 的部分
__webpack_exports__["a"] = ({
name: 'app',
data: function data() {
return {
msg: 'Welcome to Your Vue.js App'
};
}
});

/***/
}),
/* 14 */
/***/
(function (module, __webpack_exports__, __webpack_require__) {

"use strict";
// 把template 解析为一堆 render 函数,扔给vue处理最终编译成Vnode节点在渲染成DOM输出到视图
var render = function () {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
return _c('div', {
attrs: {
"id": "app"
}
}, [_c('h1', [_vm._v(_vm._s(_vm.msg))]), _vm._v(" "), _c('h2', [_vm._v("Essential Links")]), _vm._v(" "), _vm._m(0, false, false), _vm._v(" "), _c('h2', [_vm._v("Ecosystem")]), _vm._v(" "), _vm._m(1, false, false)])
}
var staticRenderFns = [function () {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
return _c('ul', [_c('li', [_c('a', {
attrs: {
"href": "https://vuejs.org",
"target": "_blank"
}
}, [_vm._v("Core Docs")])]), _vm._v(" "), _c('li', [_c('a', {
attrs: {
"href": "https://forum.vuejs.org",
"target": "_blank"
}
}, [_vm._v("Forum")])]), _vm._v(" "), _c('li', [_c('a', {
attrs: {
"href": "https://chat.vuejs.org",
"target": "_blank"
}
}, [_vm._v("Community Chat")])]), _vm._v(" "), _c('li', [_c('a', {
attrs: {
"href": "https://twitter.com/vuejs",
"target": "_blank"
}
}, [_vm._v("Twitter")])])])
}, function () {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
return _c('ul', [_c('li', [_c('a', {
attrs: {
"href": "http://router.vuejs.org/",
"target": "_blank"
}
}, [_vm._v("vue-router")])]), _vm._v(" "), _c('li', [_c('a', {
attrs: {
"href": "http://vuex.vuejs.org/",
"target": "_blank"
}
}, [_vm._v("vuex")])]), _vm._v(" "), _c('li', [_c('a', {
attrs: {
"href": "http://vue-loader.vuejs.org/",
"target": "_blank"
}
}, [_vm._v("vue-loader")])]), _vm._v(" "), _c('li', [_c('a', {
attrs: {
"href": "https://github.com/vuejs/awesome-vue",
"target": "_blank"
}
}, [_vm._v("awesome-vue")])])])
}]
var esExports = {
render: render,
staticRenderFns: staticRenderFns
}
/* harmony default export */
__webpack_exports__["a"] = (esExports);

/***/
})

总结

结构梳理,一图胜千言,并且有关于此图的附注请自行点击参考

上一篇:webpack模块机制

Js 关闭当前页面
因为浏览器限制,window.close() 不能直接关闭当前页面了。

场景

以前见到过一个当你保存某表单成功后,显示倒计时并自动关闭页面的效果,现在终于轮到我来实现了。

不过这有何好说,直接调用下面的代码不就关闭了么?

1
window.close()

然而,在最新的 chrome 浏览器上写上却是显示一个警告并且代码并不生效。

1
Scripts may close only the windows that were opened by it.

翻了好多资料却都于事无补。

close函数的限制

上面出现的警告信息我在 stackoverflow 上看到有人回答

If your script did not initiate opening the window (with something like window.open), then the script in that window is not allowed to close it. Its a security to prevent a website taking control of your browser and closing windows.

简而言之,window.close() 是有限制的,window.open 会返回一个对象,这个对象就是打开的新页面的 window 对象,

在 pageA 中写入

1
2
let pageB = window.open('xxx pageB');
pageB.close();

这就可以看见关闭了新打开的 pageB 页面。

跨页面通信

然而我们在前一个页面却不知道下一个页面的变化,比如上面例子我们不知道新打开的 pageB 页面到底做了什么,pageA 页面就不知道何时才能关闭 pageB。

这时候就需要用上 postMessage 跨页面通信。

在 pageA 上写上监听

1
2
3
window.addEventListener('message', (event) => {
console.log('this is a message');
}, false);

在 pageB 上可以发送 postMessage

1
window.opener.postMessage('close', '*');

在 pageA 上打开 pageB 后就能在控制台上看到 pageA 控制台打印出了 this is a message

完整代码

pageA

1
2
3
4
5
window.addEventListener('message', (event) => {
if (event.data === 'close') {
event.source.top.close();
}
}, false);

pageB

1
window.top.opener.postMessage('close', '*');

当然,如果考虑 iframe 中会比较复杂,所以才用了 window.top。

Vue You are using the runtime-only build
解决 You are using the runtime-only build of Vue where the template compiler is not available

场景

早上起来新建个项目写单测的时候发现报出一个错误

1
2
3
4
console.error node_modules/vue/dist/vue.runtime.common.js:572
[Vue warn]: You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.
(found in <Root>)

runtime-only ? 这是什么情况,我只是在单测代码里构建了一个 vm 对象啊,写入了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import Vue from 'vue';
let vm = new Vue({
template: `
<p>{{ text }}</p>
`,
data () {
return {
text: 'hello world'
}l
}
}).$mount(null);
// ...

接着是 npm run unit 启动的

1
2
3
"scripts": {
"unit": "jest test/unit/specs --coverage"
}

解决方案

查了查资料得知 vue2 分为完整构建(包含独立构建和运行时构建) 和 运行时构建两种模式。

简单来说, 完整构建和运行时构建的区别就是:可不可以用template选项。运行时构建少了编译 template 为 render 函数的一个阶段所以代码小一些。

上段代码中由于 vm 使用了 template 所以需要完整构建,我找了找 vue 的 npm package 的 main 入口用的是 vue.runtime.common.js,这个是运行时构建,于是乎报错。

只要修改单测代码中的一句话,引用完整构建的 vue 函数就行了

1
2
3
import Vue from 'vue/dist/vue.min.js';
// ...

思考

那么问题来了,我们平常写项目代码的时候是直接使用 import Vue from 'vue' 的,为何不是使用完整构建呢?

那是因为 webpack 中的 vue-loader 已经提前把你的 vue 文件中的 template 给编译成了 render 函数。

附上 vue2 dist目录下的文件

  • vue.common.js 基于 CommonJS 的完整构建
  • vue.esm.js 基于 ES Module 的完整构建
  • vue.js 基于 UMD 的完整构建,可以用 cdn 直接引入
  • vue.min.js 和 vue.js 一样, 属于压缩后版本
  • vue.runtime.common.js 基于 CommonJS 的运行时构建
  • vue.runtime.esm.js 基于 ES Module 的运行时构建
  • vue.runtime.js 基于 UMD 的运行时构建,可以用 cdn 直接引入
  • vue.runtime.min.js 和 vue.runtime.js 一样, 属于压缩后版本

如果想变动使用的 vue 文件,除了在 import 的时候写上具体路径外,还可以使用 webpack 别名

1
2
3
4
5
6
7
{
resolve: {
alias: {
'vue$': 'vue.esm.js'
}
}
}
POST提交的三种表单类型简介
Content-type 中 application/x-www-form-urlencoded 和 multipart/form-data 和 application/json 的区别

简介

在和小伙伴对接的时候,围绕数组数据传递我们产生了一些争执,于是我耐下心再去找了一下表单格式的资料。

假定要传递的数据格式为以下一个 Js 对象

1
2
3
4
5
{
num: 4,
str: 'test, test',
arr: [1,2,3]
}

application/x-www-form-urlencoded

窗体数据被编码为名称/值对。这是标准的编码格式。在发送前编码所有字符(默认)。

需要设置 request header 中的 Content-Type: application/x-www-form-urlencoded,查看 postman 中发出去的请求头和数据内容变成以下格式

1
2
3
4
5
6
7
POST /py/api/classrooms/1/trainings HTTP/1.1
Host: 112.124.109.3:8080
Content-Type: application/x-www-form-urlencoded
Cache-Control: no-cache
Postman-Token: c7f6dcf3-f78c-b874-c944-6faabdb3b13a
arr=%5B1%2C2%2C3%5D&str=test%2C+test&num=4

提交的数据经由qs这个库序列化后,在chrome下展示的是 Form Data,并且数据将会被显示为

1
2
3
4
5
6
7
8
9
Form Data
{
num: 4,
str: test, test
arr[0]: 1,
arr[1]: 2,
arr[3]: 3,
}

后台接到这种格式 arr,却是不太容易把它当成数组。

multipart/form-data

窗体数据被编码为一条消息,页上的每个控件对应消息
中的一个部分。不对字符编码,这种格式一般用于带上传功能的表单,数据包裹并被 boundary 分割。

查看 postman 中发出去的请求头和数据内容变成以下格式,注意 Content-Type 字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
POST /py/api/classrooms/1/trainings HTTP/1.1
Host: 112.124.109.3:8080
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Cache-Control: no-cache
Postman-Token: a5497708-c791-a09b-aca7-47d9b6b150e2
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="arr"
[1,2,3]
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="str"
test, test
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="num"
4
------WebKitFormBoundary7MA4YWxkTrZu0gW--

application/json

text/plain 类似,提交的是原数据,空格转换为 “+” 加号,但不对特殊字符编码。

查看 postman 中发出去的请求头和数据内容变成以下格式,注意 Content-Type 字段

1
2
3
4
5
6
7
8
9
10
11
POST /test HTTP/1.1
Host: 112.124.109.3:8080
Content-Type: application/json
Cache-Control: no-cache
Postman-Token: 87808356-8aa9-adfd-4e15-e4434ba4509d
{
"arr": [1,2,3],
"str": "test, test",
"num": 4
}

在chrome下展示的是 Request Payload

1
2
3
4
5
6
7
Request Payload
{
"arr": [1,2,3],
"str": "test, test",
"num": 4
}

结论

如果约定传 Form Data,也就是 application/x-www-form-urlencoded头,则数组和对象只能转为字符串后,后端经过一层解析来读取。

如果约定传 request payload,也就是 application/json头,则数组和对象随便传递,后端接到的也就是未经处理过的原数据。

而文件上传才用 multipart/form-data

input 中文输入法输入优化
用户切换输入法状态为中文时,输入在未选择词组到输入框也会触发事件的bug优化解决

场景描述

当用户当前输入法状态是中文时,在未选择词组到输入框也会触发事件

如图所示输入了input做了事件监听输入事件时强制转化成大写,而引发的中文输入法的情况下输入a,也已经触发了输入动作。

解决方法

需要引入两个事件:compositionstart和compositionend,ie9+ 的兼容。

1
<input type="text" id="plate" />
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var node = document.querySelector('#person');
var cpLock = false;
node.addEventListener('compositionstart', function(){
cpLock = true;
})
node.addEventListener('compositionend', function(){
cpLock = false;
})
node.addEventListener('input', function () {
if (!cpLock) {
console.log(this.value);
this.value = this.value.toUpperCase();
}
});

compositionstart

当浏览器有非直接的文字输入时, compositionstart事件会以同步模式触发.

compositionend

当浏览器是直接的文字输入时, compositionend会以同步模式触发.

参考

input 事件兼容处理以及中文输入法优化

记vue大型表单项目的一个性能问题
不要在一个 vue components 上直接绑定大量数据,如果这么做了,就像本例一般 update 的时候产生卡顿感。

问题场景

身为一个表单表格工程师,自然日复一日的写着表单表格,本以为已经没啥难点的时候转眼间就来了一个有意思的情况,在超大量 数据绑定在 vue 的时候出现了表单操作起来卡顿的情况。

这里先贴上本项目出现的情况演示的 github 上的地址,tag1.0.1

如图所见,当在 input 输入数据的时候,连续输入会感觉明显的延迟。

那么,这到底是怎么回事?

代码

上述的表单数据项修改频繁由后端返回,于是在前端需要渲染从后端返回的 68kb 的一个 JSON 数据串,包括所有配置表单项以及其可能的选项值,数据见这里

核心渲染是有这么一段

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<div class="basic-info ct-form" v-for="(config, configIndex) in formConfig" :key="configIndex">
<h3 class="form__title">{{config.title}}</h3>
<el-form class="form-content" ref="form" label-width="150px">
<el-form-item
class="basic-form-item"
v-for="(item, itemIndex) in config.formItems"
:key="itemIndex"
:prop="item.code"
:label="item.name"
:required="item.required"
:rules="item.rules">
<el-radio-group
v-if="item.type === 'radio'"
v-model="formData[item.code]">
<el-radio
v-for="(option, radioIndex) in formOptions[item.optionCode]"
:key="option.value"
:label="option.value"
:disabled="item.disabled">
{{ option.label }}
</el-radio>
</el-radio-group>
<el-input
v-else-if="item.type === 'input'"
:class="{ longInput: item.isLongInput }"
:placeholder="item.placeholder || '请输入'"
v-model="formData[item.code]"
:label="item.label"
:disabled="item.disabled"
:maxlength="item.maxLength">
</el-input>
<el-select
v-else-if="item.type === 'select'"
v-model="formData[item.code]"
:disabled="item.disabled"
:placeholder="item.placeholder || '请选择'">
<el-option
v-for="(option, optionsIndex) in formOptions[item.optionCode]"
:key="option.value"
:label="option.label"
:value="option.value">
</el-option>
</el-select>
</el-form-item>
</el-form>
</div>

这就是一个简单的双层遍历渲染所有表单配置项的模版代码,其中的 formConfig 正是所有配置表单项,数据量极多。formOptions 挂载了所有表单选项值,也是动辄几千项。

思路

正当我对着这么高的操作延时发愁的时候,组里一个大佬提醒我,可能是 Vue.prototype._update 这个触发的太频繁了。

我急忙找到这一段打了个断点调试

Vue.prototype._update 这函数里触发的是 VNode 虚拟节点的比对更新,打断点调试后发现实际上这是一个循环,在控制台里输出 this.$el 的时候能得到正在深度遍历中的节点,沿着根结点 App(也是 formConfig 数据绑定的作用域) 开始直到具体触发输入的那个表单元素。

在本项目里是使用了遍历输出所有的表单元素,并且当前组件的作用域是直接挂在根结点上的,是否就是这个遍历引发了如此高的延时呢?于是我找到上图右侧的调用堆栈,发现正是 flushSchedulerQueue 函数写着一个 for 循环。

flushSchedulerQueue 函数中的 for 循环里头尾插入代码来获取耗费时间。

结果得知输入时的延迟大概在 300ms 之上。

似乎问题就找到了,flushSchedulerQueue 函数针对 data 中数据的修改把 watcher 推送进队列里在更新,这一循环耗费的时间比较长。

解决

其实早在调试 Vue.prototype._update 函数就初见端倪,循环中的 this.$el 从当前组件的根部开始深度遍历,遍历了太多次,那么只要想办法缩小当前组件所绑定的数据量就解决了。

于是核心代码调整为

1
2
3
4
5
6
7
<div class="basic-info ct-form" v-for="(config, configIndex) in formConfig" :key="configIndex">
<edit-form
:config="config"
:data="formData"
:options="formOptions">
</edit-form>
</div>

只是用一个 edit-form 包裹刚刚所有的 el-form-item 的渲染代码就解决了,再次调试 Vue.prototype._update 得出遍历节点 this.$el 已经变为下图所示的 div.edit-form 了,flushSchedulerQueue 函数 for 循环的延迟也变为 10ms 左右

修复版的代码在2.0.0的tag上,这里贴上链接

后记

本质上这就是一个原则,最好不要在一个vue组件上直接绑定如此多的数据,如果有大量数据请分多个组件绑定。这么浅尝辄止实在让人不够尽兴,于是这里贴上 Vue.prototype._update 前的关键部分调用堆栈以及其函数作用。

找到项目中 node_modules 下的 vue.esm.js

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
# 往input里输入将会触发model data的更新
978 set: function reactiveSetter (newVal)
# 订阅器dep是数据绑定和视图更新的关键,这里触发去通知相关视图的更新
994 dep.notify();
673 Dep.prototype.notify
# notify函数里的subs实际上是Watcher对象的实例,这里触发视图更新操作
677 subs[i].update(); subs实际上是包裹watcher的数组
3093 Watcher.prototype.update
# 把watcher塞进一个队列里,这里是和异步更新视图有关。
3100 queueWatcher(this);
2945 function queueWatcher (watcher) push到队列里
# nextTick是具体做异步更新的部分
2963 nextTick(flushSchedulerQueue);
1778 function nextTick (cb, ctx)
# 异步操作实际上是原生 H5 MessageChannel API 通道通信来推送消息来实现变化。
1738 port.postMessage(1);
# 注意在异步操作中,最终传入的回调函数被执行来进行下面视图的更新。这里是执行一个任务调度队列的调度过程,需要循环遍历。
2856 function flushSchedulerQueue
3108 Watcher.prototype.run
# Evaluate the getter, and re-collect dependencies.
3043 Watcher.prototype.get
# watcher中的getter的name就叫updateComponent,于是被执行
2689 updateComponent
2690 vm._update(vm._render(), hydrating);
# 进入vue的生命周期中的update函数
2548 Vue.prototype._update
# patch做的是vnode的节点比对,最终把新的vnode结构渲染到具体视图,不再多做描述。
2572 vm.$el = vm.__patch__(prevVnode, vnode);
git 远端回滚操作
如果开发过程当中操作不当提交了不该提交的库文件,并且 push 到了远端,那么可以用本篇的办法进行回滚操作

查找提交记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ git log
commit 16577b2c2b8d1f6e92528aa417acc83dcd2f4b64
Author: 陈子云 <ever-lose@foxmail.com>
Date: Wed Mar 8 17:31:26 2017 +0800
commit最近记录1
commit fec5d9f293c4aecad66c9a1414d2e3045be04aad
Author: 陈子云 <ever-lose@foxmail.com>
Date: Mon Feb 6 15:58:29 2017 +0800
commit最近记录2
commit 0279a5684d59afbc0ed568e1c2319395da0a3eeb
Merge: 1e93cdd 64f9f9e
Author: 陈子云 <ever-lose@foxmail.com>
Date: Wed Jan 11 14:06:20 2017 +0800
commit最近记录3

找到最近若干次的commit记录,假如我们需要舍弃记录1和记录2,希望直接把远端的分枝回滚到记录3。

强制回滚

1
2
3
4
5
6
$ git reset --hard 0279a568
HEAD is now at 0279a56 commit最近记录3
# 如果只是要回到之前一个提交记录
# git reset --hard HEAD^1

其中–hard后面的八位字符时上面提交记录里的commit后面的一串的前八位。

1
2
3
4
5
$ git status
On branch master
Your branch is behind 'origin/master' by 2 commits, and can be fast-forwarded.
(use "git pull" to update your local branch)

查看得知本地回滚成功,但是远程的库还没回滚。

远程回滚

1
2
3
4
5
$ git push -f origin master
Total 0 (delta 0), reused 0 (delta 0)
To git.souche.com:fis-projects/dafengche-maintain-query-f2e.git
+ 16577b2...0279a56 master -> master (forced update)

成功的提示如上。但是如果执行后不成功,提示如下的话,则需要上 gitlab/github 上设置 Protected Branches 了,为的是去除本分支的保护后才能强制覆盖提交。

1
2
3
4
5
6
7
$ git push -f origin master
Total 0 (delta 0), reused 0 (delta 0)
remote: GitLab: You are not allowed to force push code to a protected branch on this project.
To git.souche.com:fis-projects/dafengche-maintain-query-f2e.git
! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'git@git.souche.com:fis-projects/dafengche-maintain-query-f2e.git'