My Little World

learn and share


  • 首页

  • 分类

  • 标签

  • 归档

  • 关于
My Little World

Shadow DOM

发表于 2020-01-27

Shadow DOM是HTML的一个规范,它允许浏览器开发者封装自己的HTML标签,css样式和特定js代码
同时也可以让开发者常见类似video这样的自定义一级标签,
创建的这些新标签和相关的api被称为 WEB Component

Shadow host 是shadow dom 的容器元素,就是最终使用的标签元素
Shadow root 是shadow dom 具体内容的根节点,可以使用document.createShadowRoot()创建
shadow tree 是shadow dom 包含的子节点数

在shadow root 上可以任意通过DOM操作添加任意shadow tree,同时指定样式和处理逻辑
并将自己的API暴露出来
完成创建后需要通过document.registerElement()在文档中注册元素
shadow dom 创建就完成了

My Little World

vuerouter源码初步学习

发表于 2020-01-27

工作流程:
URL改变->出发监听事件->改变vue-router里的current变量->
触发current变量的监听者->获取新组件->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
class HistoryRoute{
constructor(){
this.current=null
}
}
class vueRouter{
constructor(options){
this.history= new HistoryRoute;
this.mode = options.mode || 'hash'
this.routes = options.routes || []
this.routesMap = this.createMap(this.routes)
this.init()
}
Init(){
if(this.mode=='hash'){
//如果没有#号,自动加上#
location.hash?"":location.hash='/'
//监听hash改变
window.addEventListener('load',()=>{
//slice去掉#号
this.history.current=location.hash.slice(1)
})
window.addEventListener('hashchange',()=>{
this.history.current=location.hash.slice(1)
})
}else{
location.pathname?"":location.pathname='/'
window.addEventListener('load',()=>{
this.history.current=location.pathname
})
window.addEventListener('hashchange',()=>{
this.history.current=location.pathname
})
}
}
//将路由对象[{path:'xxx',component:a}]转成:{xxx:a}
createMap(routes){
return routes.reduce((memo,current)=>{
memo[current.path] = current.components
return memo
})
}
}
//定义属性标签 get set ===>供VUE.use使用
//VUE.use(xxxx) xxx是一个函数时直接运行xxx函数,xxx是一个对象或者类时,运行其上面的install方法
vueRouter.install = function(Vue){
Vue.mixin({
beforeCreate(){
//this指向当前组件实例,this.$options指向创建VUE实例时传入的配置项
if(this.$options&&this.$option.router){
//把当前实例挂在_root上
this._root = this
this._router = this.$options.router
Vue.util.defineReactive(this,'current',this._router.history) //双向绑定,对路由变化进行监听,
}
Object.defineProperty(this,"$router",{ //能够获取$router但不能修改的原理。防止用户修改_router
get(){
return this._root._router;
}
})
}
})
Vue.components('router-view',{
render(r){
//拿到当前路径
//this指向proxy,当前组件代理对象;this._self当前组件实例化对象;this._self._root当前组件对象
let current = this._self._root._router.history.current
let routeMap = this._self._root._router.routeMap
return r(routeMap[current])
}
})
}
module.exports = vueRouter

My Little World

关于模块化

发表于 2020-01-27

什么是模块化

模块化就是把系统分离成独立功能的方法,从而实现需要什么功能,就加载什么功能
当一个项目开发得越来越复杂时,会出现问题:命名冲突;文件依赖
使用模块化开发,可以避免以上问题,并且提高开发效率:可维护性;可复用性

模块化开发演变
1.使用全局函数 ===>早期开发就是将重复得代码封装到函数中,再将一系列的函数放到一个文件中
存在的问题就是:污染全局变量;看不出相互的直接关系
2.使用对象命名空间 ===>通过对象命名空间的形式,从某种程度上解决了变量命名冲突的问题,但是并不能从根本上解决命名冲突
存在的问题是:内部状态可被外部改写;命名空间越来越来长
3.私有共有成员分离 ===>利用此种方式将函数包装成一个独立的作用域,私有空间的变量和函数不会影响全局作用域
存在问题:解决了变量命名冲突问题,但是没有解决降低开发复杂度的问题

Commonjs

commomJS规范加载模块是【同步】的,也就是说,加载完成才执行后面的操作
Node.js主要用于服务器编程,模块都是存在本地硬盘中加载比较快,所以Node.js采用CommonJS
三个部分:
module变量在每个模块内部,就代表当前模块
exports属性是对外的接口,用于导出当前模块的方法或变量
require()用来加载外部模块,读取并执行js文件,返回该模块的exports对象

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
加载时执行:
a.js
exports.done = ture
var b = require('b.js')
console.log('a.js-done')

b.js
exports.done = false
var a = require('a.js')
console.log(a.done)
console.log('b.js-done')

App.js 执行App.js
var a = require('a.js') // 因为引用b.js,执行b.js代码故打印出true b.js-done,引用完毕后接着执行打印出 a.js-done
var b = require('b.js')// 已经被a引用过,不再执行 直接在缓存中找出 exports {done:false} 返回
console.log(a.done)
console.log(b.done)

如果a.js改成如下
var b = require('b.js')
exports.done = ture
console.log('a.js-done')
执行App.js
打印结果为undefined b.js-done a.js-done true false
因为a依赖b时,执行b,b再依赖a时,exports.done = ture语句还未执行,故a 的 exports 为{} exports.done自然为undefined

只有commonjs因为同步加载,加载时执行可实现循环依赖,其他模式均不可以处理循环依赖的用法

AMD(requirejs)

AMD也就是【异步】模块定义,它采用异步方式加载模块,通过define方法去定义模块,require方法去加载模块
定义:
define([tools],function(){return {}}) //第一个参数为数组时,说明该模块还依赖其他模块,提前声明依赖,没有依赖时,只传入function即可
加载:
require([‘module’],callback) //第一个参数数组内成员即要加载的模块,callback是前面所有某块都加载成功之后的回调函数

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
/** a.js */

define(['b'], function(b) {
console.log(b)
var hello = function(){
console.log('hello work')
}
return {
Hello:Hello
}
});

/** b.js */

define(function(){
var name = 'max'
return{
name:name
}
})

/** 使用a.js,b.js */

/*
<script type='text/javascript' src='require.js'></script>
<script type='text/javascript'>
require.config({
paths:{
'a':'./a',
'b':'./b'
}
})
require(['a','b'],function(a,b){ //启用模块加载器
console.log(a)
console.log(b)
})
</script>
*/

CMD(seajs)

与AMD一样,只是定义和模块加载方式上不同
一个模块一个文件
define(function(require,exports,module){
//require 是可以把其他模块导入进来的一个参数
//exports可以把模块内的一些属性和方法导出
//module是一个对象,上面存储了与当前模块相关联的一些属性和方法
})
CMD推崇依赖就近,延迟执行,文件是提前加载好的,只有在require的时候才去执行文件
define(function(require,exports,module){
var math = require(‘./math’)
math.add()
})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/** a.js */

define(function(require,exports,module) {
var b = require('b')
console.log(b)
exports.hello = function(){
console.log('hello work')
}
});

<script type='text/javascript' src='sea.js'></script>
<script type='text/javascript'>
seajs.config({
alias:{
'a':'./a',
'b':'./b'
}
})
seajs.use(['a','b'],function(a,b){ //启用模块加载器
console.log(a)
console.log(b)
})
</script>

ES6模块化

汲取CommonJS和AMD优点,支持异步加载,未来可以成为浏览器和服务器通用的模块化解决方案
export:用于把模块里的内容暴露出来, export default 为模块指定默认输出,一个模块只能有一个默认输出,所以export default 只能使用一次
import: 用于引入模块提供的功能===> import {x1,x2} form ‘./lib’; x1()

ES6模块运行机制:
ES6模块是动态引用,如果使用import从一个模块加载变量,变量不会被缓存,
而是成为一个指向被加载模块的引用。等脚本执行时,根据只读引用到被加载的那个模块中去取值

ES6与CommonJS模块差异
CommonJS模块输出的是一个值的拷贝,ES6模块输出的是值得引用
CommonJS模块运行时加载,ES6模块编译时输出接口

加载器结构

模块部分

每个模块创建都先初始化数据,存储在缓存对象中
module1

数据初始化

module2
加载器中设计了一个名为Module的构造函数,【每个模块都是此构造函数的实例对象】
构造函数中给实例对象扩展了’未来’所需要用到的属性(uri:当前模块绝对路径地址,deps:模块的依赖列表….)及方法(load,resolve:获取当前模块绝对路径…)

模块存储

加载器中设计了一个名为cache的缓存对象,每个文件(模块)都会存储在cache对象中
具体存储方式:{‘当前模块绝对路径’: new Module()} 注意:当前模块的绝对路径是通过资源部分,资源定位方法实现的

资源部分

资源定位和依赖管理是加载器涉及的两大核心

资源定位

module3
加载器中设计了一个resolve()方法把模块名解析成绝对路径格式
检测当前模块名称是否有路径短名称配置,是否有模块短名称配置,是否有后缀
在加载器启动方法中会去调用传入数组列表中的模块,获取模块名称
然后根据当前项目绝对路径和模块名称进行拼接

动态加载script文件

动态创建script标签: document.create(‘script’); src指向当前模块绝对路径地址
加载文件同时,模块加载器解析当前模块所依赖的模块,以数组形式存储,更新到deps属性中

依赖管理

已知当前模块在cache中的形态,{‘当前模块绝对路径’: new Module()}
换算成:{‘当前模块绝对路径’: {uri:’当前模块绝对路径’,deps:[]}}
module4
deps存储当前模块的依赖列表,依赖列表通过动态加载script文件正则解析获取
重点:解析依赖—>获取依赖模块绝对路径地址—->动态加载—->提取依赖—->解析依赖
递归方式加载所有模块,直至模块全部加载完毕(模块的deps属性集合中没有依赖的绝对路径,即长度为0)

如何实现一个文件一个作用域?

保证模块拥有独立作用域,采用对象命名空间的思想,即每个模块返回一个接口对象,这个独立作用域就是一个对象

如何拿到依赖模块的接口对象?

参数即define传入的函数,执行函数,返回函数返回的结果即对象

如何让寻找依赖?

拼接地址,缓存,然后递归
图片:依赖加载策略 模块数据初始化 资源定位-动态加载 依赖管理解决方案

实现

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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
(function(global) {
var startUp = global.startUp = {
version:'1.0.1'
}


var data = {} //配置信息
var cache = {}
var anonymousMeta = {}
//模块生命周期

var status = {
FETCHED: 1,
SAVED:2,
LOADING:3,
LOADED:4,
EXECTING:5,
EXECTED:6
}
var isArray = function (obj) {
return toString.call(obj) === "[object Array]"
}
var isFunction = function (obj) {
return toString.call(obj) === "[object Function]"
}
var isString = function (obj) {
return toString.call(obj) === "[object String]"
}
function scripts(){
return document.getElementsByTagName('script')
}
function getInteractiveScript(){ //支持data-main属性添加默认路径: <script data-main = 'common/js' src='module.js'></script>
var arrS = scripts()
var dataMain,src
var exp = /^.*\.js$/
arrS = [].slice.call(arrS)
arrS.forEach(function(script){
dataMain = script.getAttribute('data-main')
if(dataMain && !data.baseUrl && !(exp.test(dataMain))){
if(dataMain.substring(dataMain.length-1) !== '/'){
dataMain = (dataMain+'/')
}
data.baseUrl = dataMain
}
})
}
getInteractiveScript()
//是否使用了别名
function parseAlias(id){
var alias = data.alias //是否配置别名
return alias && isString(alias[id]) ? alias[id] :id //没有配置别名,返回原来值,有配置别名,返回别名对应值
}
//不能以'/'':'开头,必须是一个'/'后面跟随任意字符至少一个
var PATH_RE = /^([^\/:]+)(\/.+)$/ //([^\/:]+) 路径的短名称配置

//检测是否 书写路径短名称
function parsePaths(id) {
var paths = data.paths; //是否配置短路径
if(paths && (m=id.match(PATH_RE)) && isString(paths[m[1]])){
id = paths[m[1]]+m[2]
}
return id
}

//检测是否添加后缀
function normalize(path){
var last = path.length-1
var lastC = path.charAt(last)
return (lastC === '/' || path.substring(last-2) === '.js') ? path :path+'.js'
}
//添加根目录
function addBase(id,uri){
// var result;
// //相对路径
// if(id.charAt(0) === '.'){
// result = realpath( (uri ? uri.match(/[^?]*\//)[0] : data.cwd ) + id)
// }else{
// result = data.cwd+id
// }
var result = data.baseUrl ?data.cwd + data.baseUrl +id : data.cwd + id //支持data-main属性添加默认路径
return result
}

var DOT_RE = /\/.\//g // /a/b/./c/./d ==>/a/b/c/d /./ ==>/
var DOUBLE_DOT_RE = /\/[^/]+\/\.\.\//; // a/b/c/../../d ===>a/b/../d ==> a/d /xxxxx/../==> /
//规范路径
function realpath(path){
path = path.replace(DOT_RE,'/')
while(path.match(DOUBLE_DOT_RE)){
path= path.replace(DOUBLE_DOT_RE,'/')
}
return path
}
//生成绝对路径 资源路径解析
startUp.resolve = function (child,parent) {
if(!child) return ''
child = parseAlias(child) //检测是否有别名
child = parsePaths(child) //检测是否有路径别名,依赖模块中引包的模块路径地址 require('app/c)
child = normalize(child) //检测是否添加后缀
return addBase(child,parent) //添加根目录
}

startUp.request = function(url,callback){
var node = document.createElement('script')
node.src = url
document.body.appendChild(node)
node.onload = function(){
node.onload = null
document.body.removeChild(node) //加载依赖结束后移除加载时的script标签
callback()
}
}
//模块加载器启用
startUp.use = function(list,callback){
//检阅有没有预先加载的模块
Module.preload(function(){
Module.use(list,callback,data.cwd+"_use_"+cid()) //虚拟的根目录
})
}
//模块加载器配置
/**
*
* startUp.config({
* alias:{
* a:'common/js/a'
* },
* preload:[c.js]
* })
*/
startUp.config = function (options) {
var key,curr
for(key in options){
curr = Option[key]
data[key] = curr
}
}

//构造函数 模块初始化数据
function Module(uri,deps){
this.uri = uri;
this.deps = deps || [] //依赖项
this.exports = null
this.status = 0
this._waitings = {}
this._remain = 0
}

//分析主干(左子树 | 右子树)上的依赖项
Module.prototype.load = function(){
var m = this
m.status = status.LOADING//LOADING == 3 正在加载模块依赖项
var uris = m.resolve()//获取主干上的依赖项
var len = m.remain = uris.length

//加载主干上的依赖项(模块)
var seed
for(var i=0;i<len;i++){
seed = Module.get(uris[i]) //创建缓存信息
if(seed.status <status.LOADED){ //LOADED == 4 准备加载执行当前模块
seed._waitings[m.uri] = seed._waitings[m.uri] || 1 //多少模块依赖于我,_waitings是依赖我的模块的路径集合
}else{
seed._remain--
}
}
//如果依赖列表模块全部加载完毕
if(m._remain == 0){ //递归过程到此结束
//获取模块的接口对象
m.onload()
}

//准备执行根目录下的依赖列表中的模块
var requestCache = {}
for(var i=0;i<len;i++){
seed = Module.get(uris[i])
if(seed.status < status.FETCHED){
seed.fetch(requestCache)
}
}
for(uri in requestCache){
requestCache[uri]()
}
}

Module.prototype.fetch = function(requestCache){
var m =this
m.status = status.FETCHED
var uri = m.uri
requestCache[uri] = sendRequest; //Document.createElement('script)

function sendRequest(){
startUp.request(uri,onRequest) //动态加载script
}

function onRequest(){ //事件函数 script标签加载模块结束后,onload 函数中会被调用
if(anonymousMeta){ //模块的数据更新
m.save(uri,anonymousMeta)
}
m.load() //递归 模块加载策略
}
}

Module.prototype.onload = function(){
var mod = this
mod.status = LOADED //4
if(mod.callback){//获取模块接口对象
mod.callback()
}
var waitings = mod._waitings //依赖加载完,递归回调 被依赖模块(父)的callback
var key,m;
for(key in waitings){
var m = cache[key]
m._remain -= waitings[key]
if(m._remain == 0){ //判断父模块依赖是否全部加载完
m.onload()
}
}
}

//资源定位 解析依赖项生成绝对路径
Module.prototype.resolve = function(){
var mod = this
var ids = mod.deps
var uris = []
for(var i =0;i<ids.length;i++){
uris[i] = startUp.resolve(ids[i],mod.uri) //依赖项(主干 | 子树)
}
return uris;
}

//更改初始化数据
Module.prototype.save = function(uri,meta){
var mod = Module.get(uri)
mod.uri = uri
mod.deps = meta.deps || []
mod.factory = meta.factory
mod.status = status.SAVED
}

//获取接口对象
Module.prototype.exec = function(){
var module = this
//防止重复执行
if(module.status >= status.EXECTING){
return module.exports
}
module.status = status.EXECTING;
var uri = module.uri
function require(id){ //作为参数传递到define参数的函数中
return Module.get(require.resolve(id)).exec() //获取接口对象
}

require.resolve = function(id){
return startUp.resolve(id,uri)
}

var factory = module.factory //define传入的函数
var exports = isFunction(factory) ? factory(require,module.exports= {},module) :factory;
if(exports === undefined) {
exports = module.exports
}
module.exports = exports
module.status = status.EXECTED //6
return exports
}

//定义一个模块
Module.define = function(factory){
var deps
if(isFunction(factory)){
//正则解析依赖项
deps = parseDependencies(factory.toString())
}
//存储当前模块信息
var meta = {
id:'',
uri:'',
deps:deps,
factory:factory
}
anonymousMeta = meta
}

//检测缓存对象上是否有当前模块信息
Module.get = function(uri,deps){
//cache['xxxxx.js'] = {uri:'xxxxx.js',deps:[]} //module 实例对象
return cache[uri] || (cache[uri] = new Module(uri,deps))
}

Module.use = function(deps,callback,uri){
var m = Module.get(uri,isArray(deps)?dep:[deps])
console.log(module)
//所有模块都加载完毕
m.callback= function(){
var exports = [] //所有依赖项模块的接口对象
var uris = m.resolve()
for(var i = 0;i<uris.length;i++){
exports[i] = cache[uris[i]].exec() //获取模块对外定义的接口对象
}
if(callback){
callback.apply(global,exports)
}
}
m.load()
}

var _cid = 0

function cid(){
return _cid++
}
// data.preload = []
//取消当前项目文档的URL
data.cwd = document.URL.match(/[^?]*\//)[0]
Module.preload = function(callback){
var preload = data.preload ||[]
var length = data.preload.length
if(length){ //length !== 0 先加载预先设定模块
Module.use(preload,function () {
preload.splice(0,length)
callback()
},data.cwd+'_use_'+cid())
} else{
callback()
}

}

var REQUIRE_RE = /\brequire\s*\(\s*(["'])(.+?)\1\s*\)/g
function parseDependencies(code){
var ret = []
code.replace(REQUIRE_RE,function(m,m1,m2){
if(m2) ret.push(m2)
})
return ret
}
global.define = module.define
})(this)
My Little World

vue 源码学习一【new vue】

发表于 2020-01-27

new vue时发生了什么

平常练习时使用vue顺序是,将vue.js引入html,
new vue就可以开始使用vue框架写东西

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<body>
<div id = 'app'>
<p>{{a}}</p>
</div>
<script type="text/javascript" src="vue.js"></script>
<script>
let app = new Vue({
el:'#app',
data:{
a:'hello world'
},
method:{},
})
</script>
</body>

代码运行顺序是,当我们在script标签引入文件时,
引入的vuejs文件会先执行一遍,
执行的结果就是帮助我们生成一个挂载在全局的,vue对象的构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = global || self, global.Vue = factory());//将vue挂到全局上下文中
}(this, function () { 'use strict';
function Vue (options) {
if (!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword');
}
this._init(options);
}
initMixin(Vue);
stateMixin(Vue);
eventsMixin(Vue);
lifecycleMixin(Vue);
renderMixin(Vue);
initGlobalAPI(Vue);
Vue.compile = compileToFunctions;

return Vue;

}));

当代码运行到new vue时,代码就会调用function Vue
function Vue 中会【调用】自己的_init()方法,
对即将生成的vue对象做初始化工作,即开始生成vue对象

_init方法定义在initMixin函数中
在调用initMixin生成构造函数流程中被挂到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
Vue.prototype._init = function (options) {
var vm = this;
vm._uid = uid$3++;

var startTag, endTag;
vm._isVue = true;// a flag to avoid this being observed
// 合并选项,将配置项以内置选项进行合并
if (options && options._isComponent) {
//优化内部组件实例化过程
// 由于动态选项合并非常慢,并且内部组件选项均不需要特殊处理。
initInternalComponent(vm, options);
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
);
}
/* istanbul ignore else */
{
initProxy(vm);
}
// expose real self
vm._self = vm;
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm, 'beforeCreate');//运行订阅了beforecreate钩子的相关方法
initInjections(vm); // resolve injections before data/props
initState(vm);
initProvide(vm); // resolve provide after data/props
callHook(vm, 'created');
if (vm.$options.el) {//如果配置了el,就进行挂载处理挂载
vm.$mount(vm.$options.el);//会给给option挂上render,staticRenderFns属性
}
};

My Little World

vue 源码学习三

发表于 2020-01-27

钩子函数的执行顺序

不使用keep-alive beforeRouteEnter –> created –> mounted –> destroyed

使用keep-alive beforeRouteEnter –> created –> mounted –> activated –> deactivated 再次进入缓存的页面,只会触发beforeRouteEnter –>activated –> deactivated 。 created和mounted不会再执行。

我们可以利用不同的钩子函数,做不同的事。

vue.component

template配置项的处理,与编译时的判断做对比

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 _createElement (context,tag,data,children,normalizationType) {
//这里省略一系列边界判断
var vnode, ns;
if (typeof tag === 'string') {
var Ctor;
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag);
if (config.isReservedTag(tag)) { //标签名是一般的html标签
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
);
} else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) { //标签名是组件标签名
vnode = createComponent(Ctor, data, context, children, tag);
} else {
//未知或未列出的命名空间元素在运行时检查,
//因为当其父项标准化子元素时可能会为其分配一个命名空间
vnode = new VNode(
tag, data, children,
undefined, undefined, context
);
}
} else {
// direct component options / constructor
vnode = createComponent(tag, data, context, children);
}
if (Array.isArray(vnode)) {
return vnode
} else if (isDef(vnode)) {
if (isDef(ns)) { applyNS(vnode, ns); }
if (isDef(data)) { registerDeepBindings(data); }
return vnode
} else {
return createEmptyVNode()
}
}
function createFunctionalComponent (
Ctor,
propsData,
data,
contextVm,
children
) {
var options = Ctor.options;
var props = {};
var propOptions = options.props;
if (isDef(propOptions)) {
for (var key in propOptions) {
props[key] = validateProp(key, propOptions, propsData || emptyObject);
}
} else {
if (isDef(data.attrs)) { mergeProps(props, data.attrs); }
if (isDef(data.props)) { mergeProps(props, data.props); }
}

var renderContext = new FunctionalRenderContext(
data,
props,
children,
contextVm,
Ctor
);

var vnode = options.render.call(null, renderContext._c, renderContext);

if (vnode instanceof VNode) {
return cloneAndMarkFunctionalResult(vnode, data, renderContext.parent, options, renderContext)
} else if (Array.isArray(vnode)) {
var vnodes = normalizeChildren(vnode) || [];
var res = new Array(vnodes.length);
for (var i = 0; i < vnodes.length; i++) {
res[i] = cloneAndMarkFunctionalResult(vnodes[i], data, renderContext.parent, options, renderContext);
}
return res
}
}

生命周期合并策略

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

var strats = config.optionMergeStrategies;
LIFECYCLE_HOOKS.forEach(function (hook) {
strats[hook] = mergeHook;
});
function mergeHook (
parentVal,
childVal
) {
var res = childVal
? parentVal
? parentVal.concat(childVal)
: Array.isArray(childVal)
? childVal
: [childVal]
: parentVal;
return res
? dedupeHooks(res)
: res
}

function mergeOptions (
parent,
child,
vm
) {
{
checkComponents(child);
}

if (typeof child === 'function') {
child = child.options;
}

normalizeProps(child, vm);
normalizeInject(child, vm);
normalizeDirectives(child);

// Apply extends and mixins on the child options,
// but only if it is a raw options object that isn't
// the result of another mergeOptions call.
// Only merged options has the _base property.
if (!child._base) {
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm);
}
if (child.mixins) {
for (var i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm);
}
}
}

var options = {};
var key;
for (key in parent) {
mergeField(key);
}
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key);
}
}
function mergeField (key) {
var strat = strats[key] || defaultStrat;
options[key] = strat(parent[key], child[key], vm, key);
}
return options
}
function initMixin$1 (Vue) {
Vue.mixin = function (mixin) {
this.options = mergeOptions(this.options, mixin);
return this
};
}
My Little World

一些关于vue的零散笔记

发表于 2020-01-27

释放内部对象且能防止外部修改的方式

通过创建中间对象挂载到实例上,通过中间对象的set方法进行拦截

1
2
3
4
5
6
7
8
9
10
11
12
function initGlobalAPI (Vue) {
var configDef = {};
configDef.get = function () { return config; }; //config对象是vue对象构建过程的内部对象,存储全局配置项
{
configDef.set = function () {
warn(
'Do not replace the Vue.config object, set individual fields instead.'
);
};
}
Object.defineProperty(Vue, 'config', configDef);
}

Vue.config.optionMergeStrategies 应用中
Vue.config可以get,得到的即内部的config,但如果进行Vue.config = xxxx set操作就会报错
通过Vue.config可以获得内部config,从而可以修改内部config对象上的属性,从而修改到全局配置

关于3.0的思考

如何更快
Object.defineProperty–>Proxy
Virtual Dom重构
更多编译优化:
Slot默认编译为函数
重构vnode factory,统一输入参数,进一步优化编译处理函数(Monomorphic vnode factory)
编译时增加vnode/children 类型信息标识,从而做进一步优化(Compiler-generated flags for vnode/children types)

Function based API 对比Class API 好处
更好的TS类型推导支持
更灵活的逻辑复用能力
Tree-shaking 友好
代码更容易压缩

现有逻辑复用模式
Mixins
高阶组件
基于scoped slots/作用域插槽封装逻辑的组件
存在问题
数据来源不清晰 (Function based API 数据来源清晰)
命名冲突(Function based API 没有命名冲突)
无谓的额外组件的性能消耗 (Function based API 没有额外组件性能消耗)

对比React Hooks
同样的逻辑组合,复用能力更强
只调用一次
符合JS直觉
没有闭包变量问题
没有内存/GC压力
不存在内联回调导致子组件永远更新问题

2.0
mergeOption 选项策略处理
插件开发:以默认配置优先;以用户配置为覆盖
策略:听过JS 增强配置选项的表达力
{}==>function(){} el===根实例

数据劫持vue3.0改用proxy原因
defineProperty只能监听某个属性,不能对全对象监听,使用proxy可以省去for in提升效率
可以监听数组,不用再去单独的对数组做特异性操作

vue2.0 vdom性能瓶颈
虽然能够保证触发更新的组件最小化,但在单个组件内部依然需要遍历该组件的整个Vdom树
vdom的性能跟模板大小正相关,跟动态节点的数量无关。
在一些组件整个模板内只有少量动态节点的情况下,这些遍历都是性能的浪费

编译3步骤

parse:AST
optimize:标记静态节点
generate 生成目标平台所需的代码 将AST转化成render function字符串

修饰符

v-model.lazy 从input事件中转变为在change事件中同步数据
v-model.number 可以将输入转换为Number类型
v-model.trim 可以自动过滤输入的首尾空格

v-for 循环时,key尽量赋值为不变的值,即静态值,如item.id ,否则列表增删时,会更新每一项

当子组件需要向父组件传递数据时,就要用到自定义事件
子组件用$emit来触发事件,父组件用$on()来监听子组件的事件
自定义组件的v-model :一个组件上的v-model 默认会利用名为value的prop和名为input的事件
将原生事件绑定到组件: 使用v-on的.native修饰符监听原生事件
.sync修饰符:父组件监听自定义事件按需更新数据

组件

动态组件:VUE 提供了一个特殊的元素<component>用来动态的挂载不同的组件,使用is特性来选择要挂载的组件, 还可以使用<keep-alive>标签使组件进行缓存

异步组件:VUE允许将组件定义为一个工厂函数,动态地解析组件。VUE只在组件需要渲染时触发工厂函数,并且把结果缓存起来用于后面地再次渲染

组件创建方式

1.调用Vue.extend(),创建名为xx的组件,template定义模板的标签,模板的内容需要写在该标签下

1
2
3
var xx = Vue.extend({
template:'<div>this is a component</div>'
})

2.使用<template>标签创建,需要加上id属性

1
2
3
<template id = 'mycom'>
<div>this is a component</div>
</template>

3.使用<script>标签创建,需要加id属性,同时还得加type=’text/x-template’ 不执行编译里面的代码

1
2
3
<script type='text/x-template' id = 'mycom'>
<div>this is a component</div>
</script>

组件全局注册方式

1.调用Vue.extend(),创建名为myCom的组件全局注册

1
Vue.component('my-com',myCom)

2.template及script标签构建的组件全局注册

1
2
3
Vue.component('my-comp',{
template:'#mycom'
})

组件局部注册

1.调用Vue.extend(),创建名为myCom的组件局部注册(只能在注册该组件的实例中使用,一处注册,一处使用)

1
2
3
4
5
6
var app = new Vue({
el:'#app',
components:{
'my-com':myCom
}
})

2.template及script构建的组件局部注册

1
2
3
4
5
6
7
8
var app = new Vue({
el:'#app'
componnets:{
'my-com':{
template:'#myCom'
}
}
})

组件化处理边界情况

1.访问根实例 this.$root.xxx
2.访问父组件实例:this.$parent.xxx
3.访问子组件实例或子元素:<child ref=’xxx’></child > =>this.$refs.xxx
4.依赖注入:

1
2
3
4
5
6
provide:function(){ //父组件
return{
getMap:this.getMap
}
}
inject:[getMap]//子组件

然而,依赖注入还是有负面影响的。它将你应用程序中的组件与它们当前的组织方式耦合起来,使重构变得更加困难。同时所提供的属性是非响应式的。这是出于设计的考虑,因为使用它们来创建一个中心化规模化的数据跟使用 $root做这件事都是不够好的。如果你想要共享的这个属性是你的应用特有的,而不是通用化的,或者如果你想在祖先组件中更新所提供的数据,那么这意味着你可能需要换用一个像 Vuex 这样真正的状态管理方案了

5.统一处理事件侦听器时,在组件卸载前,统一清除监听器
6.组件循环引用,Vue.component注册的组件允许,但是模块化导入的不允许
7.模板定义的替代品:
A:内联模板:当inline-template这个特殊的特性出现在一个子组件上时,这个组件将会使用其里面的内容作为模板
而不是将其作为被分发的内容

1
<myCom inline-template><div><p>these are compiled as the component's own template</p></div></myCom>

B:x-template
在script标签里使用text/x-template,并且指定id,将这个id赋值给template

1
2
3
4
5
6
<script type='text/x-template' id = 'mycom'>
<div>this is a component</div>
</script>
Vue.component('my-comp',{
template:'#mycom'
})

8.强制更新
A:使用this.$forceUpdate
B:使用v-once创建低开销静态组件,组件包含大量静态内容可以在根元素上添加v-once特性以确保这些内容只计算一次然后缓存起来

组件通信类型

父子组件通信
1.使用props和$emit父子组件相互通信
2.父组件用$children或者利用ref操作子组件
3.子组件$parent访问父组件

非父子组件通信
1.使用中央事件总线(eventbus来处理非父子组件间的通信)
2.祖先元素通过provide提供数据,后代通过inject获取该数据
3.使用$attrs和$listeners实现祖孙组件通信
4.$root直接访问根组件

Vue.use

Vue.use(myPlugin)本质上是调用myPlugin.install(VUE)
使用插件必须在new vue()启动应用之前完成,实例化之前就要配置好
使用Vue.use多次注册相同插件,那只会注册成功一次
一个测试案例

1
2
3
4
5
6
7
8
9
10
11
12
13
import Vue from 'vue'
import {expect} from 'chai'
import Counter from '@/counter.vue'

describe('测试Counter.vue',()=>{
const Constructor = Vue.extend(Counter)
const vm = new Constructor().$mount()
const button = vm.$el.querySelector('button')
const clickE = new window.Event('click')
button.dispatchEvent(clickE)
vm._watcher.run()
expect(Number(vm.$el.querySelector('span').textContent)).to.equal(1)
})

vuex

默认的五种基本对象
state:存储状态(对象)
getters:对数据获取之前的再次编译,可以理解为state的计算属性,对state的数据进行筛选,过滤
mutations:同步修改状态,并且是同步的。在组件中使用$store.commit(‘’,params)
actions:异步修改状态,在组件中使用是$store.dispatch(‘’)
modules:store的子模块,为了开发大型项目,方便状态管理而使用的

My Little World

一些关于H5的知识

发表于 2020-01-27

h5 新增语义化布局标签:header,nav,section,aside,article,footer,均表现为块级
canvas,video,audio,sessionStorage,localStorage,
拖放API(源对象:ondragstart,ondrag,ondragend;源对象标签属性要设置为draggable=true
目标对象:ondragenter,ondragover,ondragleave,ondrop;
ondragover事件默认会屏蔽ondrop事件,
如果要触发ondrop事件,需要在ondragover事件函数中阻止默认屏蔽行为
event.preventDefault()
)
//谷歌浏览器v56 之后,window,document,body的touchstart,touchmove事件会被默认为pssive:true
//解决方法一:window.addEventListener(‘touchstart’,func,{passive:false})
//解决方法二,添加样式:*{touch-action:none} 取消所有元素默认事件

1
2
3
4
5
6
7
8
9
10
//实现把A标签拖到B标签
1.A标签开始拖动时
ondragstart(event){
event.dataTransfer.setData('source',event.target.id)
}
2.B标签监听拖拽结束后
ondrop(event){
var source = document.getElementById(event.dataTransfer.getData('source'))
event.target.appendChild(source)
}

History常用场景
单页应用中:实现网页无刷新更新数据的同时,解决浏览器无法前进/后退的问题
pushState:每执行一次都会增加一条历史记录,浏览器在返回时,就不会返回前一个页面,并且不会刷新浏览器
replaceState:用来修改当前历史记录,而不是创建一个新的历史记录,点击返回按钮照样会返回上一个页面
onpopstate:点击后退,前进或者调用history.back(),history.forward(),history.go()方法

跨文档通讯

定义:跨文档消息传送,来自不同域的页面间传递消息
使用场景:内嵌框架和其父文档相互进行数据传输或者通讯

postMessage

window.postMessage(message,origin,transfer)
message:发送到子文档的信息,一般转成字符串,否则可能出现浏览器不兼容
origin:域信息,告诉子文档,消息来源,用于判断来源是否匹配,然后才进行相关操作
transfer:转移消息对象,可选参数,是一串和message同时传递的Transferable对象,这些对象的所有权将被转移给消息的接收方,而发送方将不再保有所有权

onmessage

onmessage事件回调函数参数event事件对象
event.data:postMessage传输过来的第一个参数,通常是字符串类型,(也可以是其他类型,但可能出现浏览器不兼容情况,所以一般转成字符串传递)
event.origin:postMessage传输过来的第二个参数,是用来匹配来源方的域
event.source:来源方目标文档的window引用,通常用作单次握手回应数据(event.source.postMessage())
event.ports:MessageEvent接口特有属性,用于获取消息端口集合对象

实现

父文档A通过iframe内嵌框架加载B子文档
通过iframe内嵌框架的onload事件回调父文档的sendMsg函数
再sendMsg函数中通过postMessage()函数向子文档B发送消息

子文档B监听onmessage事件,当文档收到消息后会执行该事件回调函数
回调函数内通过event对象判断域是否安全,然后处理推送过来的消息,
可以再通过调用event.source.postMessage向父文档发送消息

地理位置

navigator.geolocation.getCurrentPosition(success,error,option)
success:成功得到位置时的回调函数,使用Position对象作为唯一的参数
error:失败时回调,使用PositionError对象作为唯一参数
options:可选参数,对请求做一些配置

离线存储优势

离线浏览:当用户网络断开时,可以继续访问页面
访问速度快:将资源缓存到本地,已缓存资源加载更快
减少服务器负载:浏览器将只会从服务器下载更新过或者更改过的资源

视频播放兼容

Flv.js

H5 Flash(FLV)播放器,纯原生js开发,使H5能够支持FLV格式的视频
原理:将FLV文件流转码复用为ISO BMFF(MP4碎片)片段,然后通过Media Source Extension将MP4片段汇进浏览器
使用es6编写,通过Babel Compiler编译成es5能够支持FLV格式的视频

Video.js

几乎兼容所有浏览器,且优先使用H5,在不支持的浏览器中自动使用Flash播放
构建视频播放页面耗时短,界面可定制,开源,纯JS和css实现,文档详细

#Canvas绘制动画步骤
1.清空canvas
除非接下来要画的内容会完全充满canvas(例如背景图),否则需要清空所有,最简单
的做法就是clearRect方法
2.保存canvas状态
如果需要改变一些会改变canvas状态的设置(样式,变形之类的),又要在每画一帧之时,都是原始状态的话,需要先保存一下
3.绘制动画图形
重绘动画帧
4.恢复canvas状态
如果已经保存了canvas的状态,可以先恢复它,然后重绘下一帧

transform

2D功能函数组合使用时,先写的后执行
perspective:100px 可以设置3d旋转时的景深
transform-origin:可以改变中心点

响应式布局

可以通过link标签配置media去使用不同比例的CSS样式

1
2
<link rel='stylesheet' media='screen and (max-device-width:480px)' href='./css1.css'>
<link rel='stylesheet' media='screen and (min-device-width:480px)' href='./css2.css'>

一些触摸事件的实现

My Little World

一些vueRouter官方文档看到的

发表于 2019-11-28

复用组件

当使用路由参数时,例如从 /user/foo 导航到 /user/bar,原来的组件实例会被复用。
因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用。

复用组件时,想对路由参数的变化作出响应的话,可以简单地 watch (监测变化) $route 对象

参数或查询的改变并不会触发进入/离开的导航守卫。
可以通过观察 $route 对象来应对这些变化,或使用 beforeRouteUpdate 的组件内守卫。

参数组合

如果提供了 path,params 会被忽略,query 并不会被忽略。你需要提供路由的 name 或手写完整的带有参数的 path
path—–query name—–params

beforeRouteUpdate

如果目的地和当前路由相同,只有参数发生了改变 (比如从一个用户资料到另一个 /users/1 -> /users/2),你需要使用 beforeRouteUpdate 来响应这个变化 (比如抓取用户信息)

router-view标签

没有设置名字,那么默认为 default;如果设置了name,那么该视图显示路由中components中配置的name值对应的组件

重定向

重定向三种形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1.
const router = new VueRouter({
routes: [
{ path: '/a', redirect: '/b' }
]
})

2.命名的路由
const router = new VueRouter({
routes: [
{ path: '/a', redirect: { name: 'foo' }}
]
})
3.方法,动态返回重定向目标:
const router = new VueRouter({
routes: [
{ path: '/a', redirect: to => {
// 方法接收 目标路由 作为参数
// return 重定向的 字符串路径/路径对象
}}
]
})

导航守卫并没有应用在跳转路由上,而仅仅应用在其目标上,为 /a 路由添加一个 beforeEach 或 beforeLeave 守卫并不会有任何效果

“重定向”的意思是,当用户访问 /a时,URL 将会被替换成 /b,然后匹配路由为 /b,
/a 的别名是 /b,意味着,当用户访问 /b 时,URL 会保持为 /b,但是路由匹配则为 /a,就像用户访问 /a 一样。

1
2
3
4
5
const router = new VueRouter({
routes: [
{ path: '/a', component: A, alias: '/b' }
]
})

props

通过配置props属性实现多路由复用组件

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
1.如果 props 被设置为 true,route.params 将会被设置为组件属性。
const User = {
props: ['id'],
template: '<div>User {{ id }}</div>'
}
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User, props: true },

// 对于包含命名视图的路由,你必须分别为每个命名视图添加 `props` 选项:
{
path: '/user/:id',
components: { default: User, sidebar: Sidebar },
props: { default: true, sidebar: false }
}
]
})

2.如果 props 是一个对象,它会被按原样设置为组件属性。当 props 是静态的时候有用
const router = new VueRouter({
routes: [
{ path: '/promotion/from-newsletter', component: Promotion, props: { newsletterPopup: false } }
]
})

3.创建一个函数返回 props
const router = new VueRouter({
routes: [
{ path: '/search', component: SearchUser, props: (route) => ({ query: route.query.q }) }
]
})
URL /search?q=vue 会将 {query: 'vue'} 作为属性传递给 SearchUser 组件

请尽可能保持 props 函数为无状态的,因为它只会在路由发生变化时起作用。如果你需要状态来定义 props,请使用包装组件,这样 Vue 才可以对状态变化做出反应

路由生命周期

全局配置

router.beforeEach:当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中

1
2
3
4
5
6
7
8
三个参数
to: 即将要进入的目标 路由对象
from: 当前导航正要离开的路由的路由对象
next:一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数
next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。
next(error): 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调

router.beforeResolve:在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用

router.afterEach:不会接受 next 函数也不会改变导航本身

路由配置

1
2
3
4
5
6
7
8
9
10
11
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})

组件内

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const Foo = {
template: `...`,
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。
//可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。
next(vm => { //是支持给 next 传递回调的唯一钩子,beforeRouteUpdate,beforeRouteLeave都不支持,因为可以访问的到
// 通过 `vm` 访问组件实例
})
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}

完整的导航解析流程

  1. 导航被触发。
  2. 在失活的组件里调用离开守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
  5. 在路由配置里调用 beforeEnter。
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter。
  8. 调用全局的 beforeResolve 守卫 (2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。

数据获取

导航完成后获取数据

1
2
3
4
5
6
7
8
9
created () {
// 组件创建完后获取数据,
// 此时 data 已经被 observed 了
this.fetchData()
},
watch: {
// 如果路由有变化,会再次执行该方法
'$route': 'fetchData'
},

在导航完成前获取数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
beforeRouteEnter (to, from, next) {
getPost(to.params.id, (err, post) => {
next(vm => vm.setData(err, post))
})
},
// 路由改变前,组件就已经渲染完了
// 逻辑稍稍不同
beforeRouteUpdate (to, from, next) {
this.post = null
getPost(to.params.id, (err, post) => {
this.setData(err, post)
next()
})
},
methods: {
setData (err, post) {
if (err) {
this.error = err.toString()
} else {
this.post = post
}
}
}

滚动行为

scrollBehavior 这个功能只在支持 history.pushState 的浏览器中可用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const router = new VueRouter({
routes: [...],
scrollBehavior (to, from, savedPosition) {

// return 期望滚动到哪个的位置
if (to.hash) {
return {
selector: to.hash //模拟“滚动到锚点”的行为
}
}
if (savedPosition) {
return savedPosition //返回 savedPosition,在按下 后退/前进 按钮时,就会像浏览器的原生表现那样
} else {
return { x: 0, y: 0 } //让页面滚动到顶部
}
return new Promise((resolve, reject) => { //返回一个 Promise 来得出预期的位置描述
setTimeout(() => {
resolve({ x: 0, y: 0 })
}, 500)
})
}
})

My Little World

两个css问题解决

发表于 2019-11-28

子元素 position:fixed ,但宽度要和父元素一致

背景

父元素宽度随屏幕大小变化,而且margin-left有一个固定值,假设为320px
子元素position:fixed
如果直接设置子元素宽度为100%
则会导致子元素有320px的宽度在可视区域外
calc.png

知识点

css3的函数calc() 可以用来计算属性值
特点:
1.浏览器解析calc()结果还是calc(),不会计算参数表达式的值,
意味着浏览器中的值可以更加灵活,能够响应视口的改变,即实际渲染结果始终调用calc计算结果
2.可以使用加减乘除,可以套嵌使用,即calc参数表达式中可以包含calc函数调用
3.可以进行不同单位间的计算,可以混合绝对单位(如百分比与视口单元)与相对单元(比如像素)
应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1.居中
//居中设置原来
.center {
position: absolute
top: 50%;
left: 50%;
marging-top: -150px;
margin-left: -150px;
}

//使用calc
.center {
position: absolute
top: calc(50% - 150px);
left: calc(50% - 150px);
}

2.设置字体,在页面上的任何文本,将会根据视口自动缩放,
相同比例的视口总会显示相同的文本数量,不管视口的真实尺寸是多少。
html {
font-size: calc(100vw / 30);
}

1
width:calc(100%-320px)

input样式兼容

在safari浏览器中给input设置样式不能直接被使用,会依旧使用浏览器默认样式,
此时需要给样式添加

1
2
-webkit-appearance: none;/*去除系统默认appearance的样式*/
line-height: normal;

My Little World

关于代理proxy

发表于 2019-11-08

代理是JS es6中引入,可用于控制对象
—代理可以定制对象交互时行动(例如,当读取属性或调用方法)
— 所有交互行为都必须通过代理,指定的行为发生时会调用代理方法
使用代理可以优雅实现以下内容

  • 日志记录
  • 性能测试
  • 数据校验
  • 自动填充对象属性
  • 数组负索引

使用代理对对象访问添加日志 — report 函数用于添加日志

1
2
3
4
5
6
7
8
9
10
11
12
function makeLoggable(target){
return new Proxy(target,{
get:(target,property)=>{
report('READING:'+property)
return target[property]
},
set:(target,property,value)=>{
report('writing value' +value +'to'+'property')
target[property] = value
}
})
}

proxy 用于数据校验

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
var personValidator = {
name(val){
return typeof val === 'string'
}
age(val){
return typeof val === 'number' && val>18
}
}
class person{
constructor(name,age){
this.name = name;
this.age = age;
return createValidator(this,personValidator)
}
}
function createValidator(target,validator){
return new Proxy(target,{
_validator:validator,
set(target,key,value,proxy){
if(target.hasOwnproperty(key)){
var validator = this._validator[key]
if(validator(value)){
return Reflect.set(target,key,value,proxy)
}else{
throw Error('type error')
}
}
}
})
}

使用代理自动填充属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Folder(){
return new Proxy({},{
get:(target,property)=>{
if(!(property in target)){
target[property] = new Folder()
}
return target[property]
}
})
}

const rootFolder = new Folder()
try {
rootFolder.ninjasDir.firstNinjaDir.ninjaFile = 'yoshi.txt'
}catch(e){
console.log(e)
}

使用代理实现数组负引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function creatNegativeArrayProxy(array){
if(!Array.isArray(array)){
throw new TypeError('expected an array')
}
return new Proxy(array,{
get:(target,index)=>{
index = +index
return target[index<0?target.length+index:index]
},
set:(target,index,val)=>{
index = +index
return target[index<0?target.length+index:index] = val
}
})
}

const ninja = [1,2,3]
const proxi = creatNegativeArrayProxy(ninja)
console.log(proxi[-1]) //==> 3
proxi[-2] = 22 // ninja ==>1,22,3

1…8910…26
YooHannah

YooHannah

260 日志
1 分类
23 标签
RSS
© 2025 YooHannah
由 Hexo 强力驱动
主题 - NexT.Pisces