工作流程:
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
73class 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
关于模块化
什么是模块化
模块化就是把系统分离成独立功能的方法,从而实现需要什么功能,就加载什么功能
当一个项目开发得越来越复杂时,会出现问题:命名冲突;文件依赖
使用模块化开发,可以避免以上问题,并且提高开发效率:可维护性;可复用性
模块化开发演变
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模块编译时输出接口
加载器结构
模块部分
每个模块创建都先初始化数据,存储在缓存对象中

数据初始化

加载器中设计了一个名为Module的构造函数,【每个模块都是此构造函数的实例对象】
构造函数中给实例对象扩展了’未来’所需要用到的属性(uri:当前模块绝对路径地址,deps:模块的依赖列表….)及方法(load,resolve:获取当前模块绝对路径…)
模块存储
加载器中设计了一个名为cache的缓存对象,每个文件(模块)都会存储在cache对象中
具体存储方式:{‘当前模块绝对路径’: new Module()} 注意:当前模块的绝对路径是通过资源部分,资源定位方法实现的
资源部分
资源定位和依赖管理是加载器涉及的两大核心
资源定位

加载器中设计了一个resolve()方法把模块名解析成绝对路径格式
检测当前模块名称是否有路径短名称配置,是否有模块短名称配置,是否有后缀
在加载器启动方法中会去调用传入数组列表中的模块,获取模块名称
然后根据当前项目绝对路径和模块名称进行拼接
动态加载script文件
动态创建script标签: document.create(‘script’); src指向当前模块绝对路径地址
加载文件同时,模块加载器解析当前模块所依赖的模块,以数组形式存储,更新到deps属性中
依赖管理
已知当前模块在cache中的形态,{‘当前模块绝对路径’: new Module()}
换算成:{‘当前模块绝对路径’: {uri:’当前模块绝对路径’,deps:[]}}

deps存储当前模块的依赖列表,依赖列表通过动态加载script文件正则解析获取
重点:解析依赖—>获取依赖模块绝对路径地址—->动态加载—->提取依赖—->解析依赖
递归方式加载所有模块,直至模块全部加载完毕(模块的deps属性集合中没有依赖的绝对路径,即长度为0)
如何实现一个文件一个作用域?
保证模块拥有独立作用域,采用对象命名空间的思想,即每个模块返回一个接口对象,这个独立作用域就是一个对象
如何拿到依赖模块的接口对象?
参数即define传入的函数,执行函数,返回函数返回的结果即对象
如何让寻找依赖?
拼接地址,缓存,然后递归
图片:依赖加载策略 模块数据初始化 资源定位-动态加载 依赖管理解决方案
实现
1 | (function(global) { |
vue 源码学习一【new vue】
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
36Vue.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属性
}
};
一些关于vue的零散笔记
释放内部对象且能防止外部修改的方式
通过创建中间对象挂载到实例上,通过中间对象的set方法进行拦截1
2
3
4
5
6
7
8
9
10
11
12function 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
3var 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
3Vue.component('my-comp',{
template:'#mycom'
})
组件局部注册
1.调用Vue.extend(),创建名为myCom的组件局部注册(只能在注册该组件的实例中使用,一处注册,一处使用)1
2
3
4
5
6var app = new Vue({
el:'#app',
components:{
'my-com':myCom
}
})
2.template及script构建的组件局部注册1
2
3
4
5
6
7
8var 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
6provide: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赋值给template1
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
13import 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的子模块,为了开发大型项目,方便状态管理而使用的
vue 源码学习三
钩子函数的执行顺序
不使用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
75function _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 |
|
一些关于H5的知识
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'>
一些vueRouter官方文档看到的
复用组件
当使用路由参数时,例如从 /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标签
重定向
重定向三种形式1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
221.
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
5const 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
321.如果 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 | const router = new VueRouter({ |
组件内
1 | const Foo = { |
完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用离开守卫。
- 调用全局的 beforeEach 守卫。
- 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
- 在路由配置里调用 beforeEnter。
- 解析异步路由组件。
- 在被激活的组件里调用 beforeRouteEnter。
- 调用全局的 beforeResolve 守卫 (2.5+)。
- 导航被确认。
- 调用全局的 afterEach 钩子。
- 触发 DOM 更新。
- 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。
数据获取
导航完成后获取数据
1 | created () { |
在导航完成前获取数据
1 | beforeRouteEnter (to, from, next) { |
滚动行为
scrollBehavior 这个功能只在支持 history.pushState 的浏览器中可用。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22const 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)
})
}
})
两个css问题解决
子元素 position:fixed ,但宽度要和父元素一致
背景
父元素宽度随屏幕大小变化,而且margin-left有一个固定值,假设为320px
子元素position:fixed
如果直接设置子元素宽度为100%
则会导致子元素有320px的宽度在可视区域外
知识点
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
221.居中
//居中设置原来
.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;
关于代理proxy
代理是JS es6中引入,可用于控制对象
—代理可以定制对象交互时行动(例如,当读取属性或调用方法)
— 所有交互行为都必须通过代理,指定的行为发生时会调用代理方法
使用代理可以优雅实现以下内容
- 日志记录
- 性能测试
- 数据校验
- 自动填充对象属性
- 数组负索引
使用代理对对象访问添加日志 — report 函数用于添加日志1
2
3
4
5
6
7
8
9
10
11
12function 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 | var personValidator = { |
使用代理自动填充属性1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function 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
20function 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
absolute应用
问题/背景
table 的 td 内容在内容较多时会自动进行省略处理,然后鼠标滑过弹层显示具体内容
td内容还可以配置在线编辑,鼠标滑过,可以显示小笔图标,点击小笔图标或者td块都可以进行在线编辑然后回车或者鼠标点击输入框之外的地方都可以提交发送给后台
table被包裹在一个div1中,只允许出现水平方向滚动条,不出现垂直方向滚动条1
2
3
4.div1{
overflow-x: auto;
overflow-y: hidden;
}
出现的问题就是,如果最后几行的内容过多,弹层显示具体内容时弹层会被遮挡,不能正常飘出来
原理知识
父元素设置position:relative,子元素设置position:absolute,配套使用时,子元素才会会在z轴上相对父元素进行xy面的定位
如果最近父元素没有设置position:relative,那么会往父元素的父元素找,看是否设置,如果设置,则相对爷爷元素进行XY面定位,如果没有继续往上找,以此类推
即会相对父元素及以上元素中靠近自己最近的,设置了position:relative的长辈元素进行相对定位
分析
弹层没有按照预期可以弹到div1以外的地方,被困在div1里面,加上div1不能垂直方向滚动,说明弹层没有相对div1定位,
解除div1垂直方向的滚动限制,弹层把div1在垂直方向上撑起来了,滚动滚动条,可以看完整的弹层内容,说明弹层相对div1的子元素定位了,
即从盛装省略内容的标签到div1的标签中,有标签设置了position:relative,
通过查找发现,在线编辑功能的小笔图标使用了position:absolute相对定位,在它最近的div2标签上设置了position:relative,
而div2标签包含了弹层所在标签,属于div1子元素,所以弹层就会相对div2定位,沿Z轴飘在div2这一层上面,就弹不到div1以外了
解决
将div2上position:relative去掉,给在线编辑功能的小笔图标设置position:absolute的标签div3外加div4,设置position:relative
1 | 解决前: |
普通省略内容鼠标滑过
在线编辑内容省略时,鼠标滑过