<div @click="divClick">
<span @click="spanClick">123span>
div>
代码中,点击span
标签,不仅会触发span
标签中的spanClick
方法,还会触发div
中的divClick
方法,这个过程叫事件冒泡。事件从目标元素逐渐向上,由内到外的。
<ul class='ul'>
<li>1li>
<li>2li>
<li>3li>
ul>
$('ul').bind('click','li',function() {})
给父元素添加侦听事件,利用事件冒泡,影响每个子节点,叫事件委托。
优点:减少事件注册,提高网页性能;新插入的元素,也能绑定上事件。
typeEmail
为 true 的时候,显示email
标签,否则显示password
标签,当输入email
的时候,切换类型,password
标签显示的数据是之前email
的数据。 <div v-if="typeEmail">
<label>邮箱:label>
<input placeholder="email">
div>
<div v-else>
<label>密码:label>
<input placeholder="password">
div>
<button @click="changeType">修改类型button>
div>
或者如下: li
标签里包含复选框,不加key
时,点击 添加 按钮,从 list 头部插入新数据。当选中 李四 时,点击添加 111,添加成功后,选中项则变成了 111.
-
{{item.name}} {{item.id}}
key
的作用:更高效的更新虚拟dom(在视图层和数据层建立一个一一对应的关系,方便进行局部刷新)EventLoop
由三部分构成:调用栈(call stack)、宏任务(如消息队列(Message Queue))、微任务队列(Microtask Queue)
执行顺序:首先执行调用栈,调用栈里面事件执行完毕后,弹出去,继而把微任务(promise)里的事件推到调用栈里执行、弹出,最后把消息队列(setTimeOut)的事件推到调用栈去执行、弹出。
微任务 执行时机比 宏任务 早
宏任务:setTimeout、setInterval、Dom事件、ajax请求
微任务:promise.then,async/await
process.nextTikc(优先级略高)
微任务 > Dom渲染 > 宏任务
可参考:2分钟了解 JavaScript Event Loop
宏任务与微任务的执行顺序
function MyPromise (fn) {
let that = this;
that.status = "pending";
that.value = "";
function resolve (val) {
if(that.status === "pending") {
that.status = "resolve";
that.value = val;
}
}
function reject (val) {
if(that.status === "pending") {
that.status = "reject";
that.value = val;
}
}
fn(resolve,reject)
}
MyPromise.prototype.then = function (onResolve,onReject) {
let that = this;
if(that.status === "resolve") {
onResolve(that.value);
} else {
onReject(that.value);
}
}
let p1 = new MyPromise((resolve,reject)=>{
resolve('成功了');
// reject("失败了")
}).then((result) => {
console.log(result)
}, (error) => {
console.log(error)
})
Promise.all2 = function (arr) {
return new Promise((resolve, reject) => {
let num = 0,
result = [];
for(let i = 0; i < arr.length; i++){
//将arr的每一项都改为promise类型
Promise.resolve(arr[i]).then((value) => {
num ++;
result.push(value); //arr[i]执行成功后,放入数组
if(num === arr.length) { //当执行完所有的arr以后,状态修改为resolve,将结果数组retrun出去
return resolve(result)
}
},(error) => {
//若arr有执行出错是,状态直接改为reject
reject(error)
})
}
})
}
let p1 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('reject111');
},1000)
});
let p2 = new Promise((resolve) => {
setTimeout(() => {
resolve('reject222');
},5000)
});
Promise.all2([p1,p2]).then((res) => {
console.log(res)
},(error)=> {
console.log(error)
})
//[ 'reject111', '222' ]
学习地址
this.$data
this.$el
;Object.defineProperty
进行数据劫持,把this.$data
中的每个数据添加get
set
方法; — Observe()Object.defineProperty
方法,将this.$data
里的数据,挂载到 this
对象上Observe(obj)
this.$data
中的每个键值对都增加 get 和 set 方法,方便后续进行数据监听Compile()
watch儿
实例,将这个node节点放到订阅列表,回调里面更新数据(this.$data数据修改,实时更新ui)demo:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>手动双向数据绑定title>
head>
<body>
<div id="app">
<div>姓名:{{name}}div>
<div>年龄:{{age}}div>
<div>信息:{{info.a}}div>
<div>
姓名:<input type="text" v-model="name">
div>
<div>
信息:<input type="text" v-model="info.a">
div>
div>
body>
<script src="./vue.js">script>
<script>
let vm = new Vue({
el: "#app",
data: {
name: "张三",
age: "20",
info: {
a: "1",
c: "2"
}
}
})
script>
html>
class Vue {
constructor(options) {
this.$data = options.data;
Observe(this.$data) //数据劫持
//将this.$data[key]挂载到this[key]中
Object.keys(this.$data).forEach(key => {
Object.defineProperty(this, key, {
enumerable: true,
configurable: true,
get() {
return this.$data[key];
},
set(newVal) {
this.$data[key] = newVal;
}
})
})
//调用模板编译方法
Compile(options.el, this)
}
}
//定义一个数据劫持的方法
function Observe (obj) {
if(!obj || typeof obj !== "object") {return} //递归终止条件
console.log(obj)
let dep = new Dep();
Object.keys(obj).forEach(key => { //获取当前obj上的每一个属性
let val = obj[key]; //当前被循环的 key 对应的属性值
Observe(val); //把 val 这个子节点进行递归
Object.defineProperty(obj, key, { //需要为当前的 key 对应的属性添加 getter 和 setter
enumerable: true,
configurable: true,
get() {
Dep.target && dep.addSub(Dep.target);
return val;
},
set(newVal) {
val = newVal;
dep.notify();
Observe(val);
}
})
})
}
//对 HTML 进行模板编译对方法
function Compile (el, vm) {
vm.$el = document.querySelector(el);
//创建文档碎片(通过碎片更新dom比直接修改dom元素性能要好,直接修改dom会使页面进行重绘或重排)
let fragment = document.createDocumentFragment();
while(childNode = vm.$el.firstChild) { //便利el到第一个子元素,存在就从页面中移入碎片中
fragment.appendChild(childNode)
}
replace(fragment)
function replace (node) { //将实际值与{{**}}进行替换
if(node.nodeType === 3) { //当前的 node 节点是文本节点时,利用正则进行替换
let regMustache = /\{\{\s*(\S+)\s*\}\}/,
text = node.textContent, //获取文本节点的字符串内容
execResult = regMustache.exec(text); //进行字符串的正则匹配与提取
if(execResult) {
let nValue = execResult[1].split('.').reduce((newVal,key) => {return newVal[key]}, vm);
node.textContent = text.replace(regMustache,nValue); //文本节点的{{**}}替换成实际值
new Watcher(vm, execResult[1], (newVal) => { //将dom放入消息订阅列表,便于后续数据改变后,告诉dom去更新显示数据
node.textContent = text.replace(regMustache, newVal);
})
}
return //终止递归
}
if(node.nodeType === 1 && node.tagName.toUpperCase() === "INPUT") {
let findResult = Array.from(node.attributes).find((item) => item.name === "v-model");
if(findResult) { //input元素存在v-model属性,才会执行后面的逻辑
let val = findResult.value.split('.').reduce((newVal, key) => newVal[key], vm);
node.value = val; //input元素绑定数据
//将 input 添加到订阅列表
new Watcher(vm, findResult.value, (newVal) => {
node.value = newVal;
})
//对input绑定一个监听事件,确保输入时,更新 vm 数据
node.addEventListener('input',function (e) {
let keyArr = findResult.value.split('.'),
obj = keyArr.slice(0,keyArr.length - 1).reduce((newVal, key) =>newVal[key], vm);
obj[keyArr[keyArr.length-1]] = e.target.value;
})
}
}
node.childNodes.forEach(child => {replace(child)}) //存在子节点时,递归处理文本
}
vm.$el.appendChild(fragment)
}
class Dep {
constructor() {
this.subs = [];
}
addSub(watcher) {
this.subs.push(watcher); //向subs数组中添加订阅者
}
notify() {
this.subs.forEach((watcher) => {watcher.update()}); //数据赋值时,调用该方法,更新dom显示数据
}
}
class Watcher {
constructor(vm, key, cb) {
this.vm = vm;
this.key = key;
this.cb = cb;
Dep.target = this; //这里获取一下 vm 的值,将 watcher 添加到 dep。subs 中
key.split('.').reduce((newVal, key) => newVal[key],vm);
Dep.target = null;
}
update() {
//获取更新后的值
let newVal = this.key.split('.').reduce((newVal, key) => newVal[key], this.vm);
this.cb(newVal);
}
}
//收集订阅者/
class Dep {
constructor() {
this.subs = [] //subs用来存放所有订阅者
}
addSub(watcher) {
this.subs.push(watcher) //向subs数组中添加订阅者
}
notify() {
this.subs.forEach((sub) => {sub.update()}) //发布通知
}
}
//订阅者
class Watcher {
constructor(cb) {
this.cb = cb;
}
update() {
this.cb(); //触发回调到方法
}
}
let w1 = new Watcher(() => {
console.log("第1个消息订阅者")
})
let w2 = new Watcher(() => {
console.log("第2个消息订阅者")
})
// w1.update()
// w2.update()
let dep = new Dep();
dep.addSub(w1)
dep.addSub(w2)
//只要为 vue 中的 data 数据重新赋值了,这个赋值动作会被 vue 监听到
//然后 vue 要把数据的变化,通知到每个订阅者
//接下来订阅者(dom)要根据最新到数据更新自己到文本内容节点
dep.notify()
//结果打印出
//第1个消息订阅者
//第2个消息订阅者
promise先执行,因为peomise是微任务,setTimeout是宏任务。js事件循环机制中,执行栈中的同步任务先执行,全部执行完毕后弹出,之后微任务推入到执行栈,执行完毕后从执行栈中弹出,最后宏任务推入执行栈,执行完毕弹出。
.stop
.prevent
.capture
.self
.once
.passive
<a v-on:click.stop="doThis">a>
<form v-on:submit.prevent="onSubmit">form>
<a v-on:click.stop.prevent="doThat">a>
<form v-on:submit.prevent>form>
<div v-on:click.capture="doThis">...div>
<div v-on:click.self="doThat">...div>
webpack是一个打包模块化js的工具,在webpack里一切文件皆模块,通过loader转换文件,通过plugin注入钩子,最后输出由多个模块组合成的文件,webpack专注构建模块化项目。WebPack可以看做是模块的打包机器:它做的事情是,分析你的项目结构,找到js模块以及其它的一些浏览器不能直接运行的拓展语言,例如:Scss,TS等,并将其打包为合适的格式以供浏览器使用。
三者都是前端构建工具,grunt和gulp在早期比较流行,现在webpack相对来说比较主流,不过一些轻量化的任务还是会用gulp来处理,比如单独打包CSS文件等。grunt和gulp是基于任务和流(Task、Stream)的。类似jQuery,找到一个(或一类)文件,对其做一系列链式操作,更新流上的数据,整条链式操作构成了一个任务,多个任务就构成了整个web的构建流程。webpack是基于入口的。webpack会自动地递归解析入口所需要加载的所有资源文件,然后用不同的Loader来处理不同的文件,用Plugin来扩展webpack功能。所以,从构建思路来说,gulp和grunt需要开发者将整个前端构建过程拆分成多个Task,并合理控制所有Task的调用关系;webpack需要开发者找到入口,并需要清楚对于不同的资源应该使用什么Loader做何种解析和加工对于知识背景来说,gulp更像后端开发者的思路,需要对于整个流程了如指掌webpack更倾向于前端开发者的思路
bundle:是由webpack打包出来的文件;chunk:代码块,一个chunk由多个模块组合而成,用于代码的合并和分割;module:是开发中的单个模块,在webpack的世界,一切皆模块,一个模块对应一个文件,webpack会从配置的entry中递归开始找出所有依赖的模块
Loader是用来告诉webpack如何转化处理某一类型的文件,并且引入到打包出的文件中;
Plugin是用来自定义webpack打包过程的方式,一个插件是含有apply方法的一个对象,通过这个方法可以参与到整个webpack打包的各个流程(生命周期)。
file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件;
url-loader:和 file-loader 类似,但是能在文件很小的情况下以 base64 的方式把文件内容注入到代码中去;
source-map-loader:加载额外的 Source Map 文件,以方便断点调试;
image-loader:加载并且压缩图片文件;
babel-loader:把 ES6 转换成 ES5;
css-loader:加载 CSS,支持模块化、压缩、文件导入等特性;
style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS。
eslint-loader:通过 ESLint 检查 JavaScript 代码
this
指向。this
对象和一个数组。如果要给这个方法传递多个参数,要把参数全部写进数组里面,即使只有一个参数,也要写到数组里。 写法如下:func.call(func1,arg1,arg2)
this
对象和一系列参数。写法如下:func.call(func2,arg2,arg2,arg3)
bind()
方法会创建一个新的函数,称为绑定函数,当调用这个 绑定函数 时,绑定函数会以创建它时传入 baind()
方法的第一个参数作为 this
,传入 baind()
方法的第二个及以后的参数加上绑定函数运行时,本身的参数按照顺序作为原函数的参数来调用原函数。写法如下:var bar = function(){
console.log(this.x);
}
var foo = {
x:3
}
bar(); // undefined
var func = bar.bind(foo);
func(); // 3
ar obj = {
x: 81,
};
var foo = {
getX: function() {
return this.x;
}
}
console.log(foo.getX.bind(obj)()); //81
console.log(foo.getX.call(obj)); //81
console.log(foo.getX.apply(obj)); //81
bind()
方法,后面多了个括号bind()
方法。apply和call会立即执行函数<script src= "http://baidu.com:3000/callback=getData">script>
<script>
function getData(res) {
console.log(res)
}
script>
2、ajax中
dataType: 'jsonp', // 请求方式为jsonp jsonpCallback: "onBack", // 自定义回调函数名
3、vue中
this.$http.jsonp('http://www.demo2.com:8080/login', {
params: {},
jsonp: 'onBack'
}).then((res) => {
console.log(res);
Access-Control-Allow-Origin
属性devServe: {
proxy: {
'/api': {
target: "http://*****:8888", //后台的服务地址
changeOrigin: true, //允许跨域
pathReweite: {
'^/api':"" //重写路径
}
}
}
}
拓展
使用雪碧图、base64、字体图标
使用缓存
css压缩
图片压缩
js压缩(删除无效字符及注释)
css放到header中,优先使用外链
js放到body地步,优先使用外链
模块按需加载、异步加载(webpack)
资源懒加载、按需加载
假设要封装如上的组件:left默认是一条线,颜色可配置,ui也可配置为图标;body标题可以配置;right 按钮也可配置,默认更多,绑定事件
数据可能是同步获取的,也可能是异步获取的,获取到数据以后,去渲染页面,这个过程是异步的,在 mounted 回调中,不能保证子组件的 dom 已经完全渲染到了页面上,$nextTick 会在数据循环渲染到页面之后,去调用里面的回调方法。
1、window.alert/window.confirm
//浏览器弹框、确认框
2、window.open
//打开新窗口
3、window.setTimeout / window.setInterval
//定时
4、window.scrollBy():
//滚动
5、window.navigator
//获取浏览器信息
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
var obj = {};
Object.defineProperty(obj, 'attr',{
value: 12,
writable: false //是否可修改
})
console.log(obj) // {attr: 12}
const b=_.cloneDeep(a)
this.$set(this, 'list', [8,9,0])
<a href="https://www.csdn.net/" class="box">
<button class="btn">按钮</button>
</a>
event.stopPropagation()
这是阻止事件的冒泡方法,不让事件向documen上蔓延,但是默认事件任然会执行,当你掉用这个方法的时候,如果点击一个连接,这个连接仍然会被打开。event.preventDefault()
这是阻止默认事件的方法,调用此方法是,连接不会被打开,但是会发生冒泡,冒泡会传递到上一层的父元素;addEventListener(event,callback,useCapture)
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
//注册局部指令
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
<input v-focus>
参考教程
console.log(typeof "a") //'string'
console.log([1,2,3] instanceof Array) //true
console.log([1,2,3].constructor === Array) //true
console.log(Object.prototype.toString.call({a:1}) === '[object Object]') //true
console.log(Array.isArray(array)) //true
a instanceof b 中, a 是否是 b 实例化构造出来的,a 是否为 b 的实例对象。原型 原型链
Object.keys()
,Object.getOwnPropertyNames()
var obj = {a:1,b:2};
console.log(Object.keys(obj)); //['a', 'b']
console.log(Object.getOwnPropertyNames(obj)); //['a', 'b']
activated
deactivated
//为对象时:
let vm = new Vue({
el: "#app",
data: {
name: "张三",
age: "20",
info: {
a: "1",
c: "2"
}
}
})
//为方法时-----------------------------------------------
export default {
name: "HomeView",
data() {
return {
list: [1,2,3]
}
}
}
当 data 定义为对象时,表示所有的组件实例共用了一份 data 数据,后续无论在哪个组件实例中修改 data,会影响到其他组件实例。
组件中的 data 写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的 data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例各自的数据。(写成函数,会有函数作用域的概念,是私有函数,只作用到当前组件。)
单纯写成对象的话,就时所有的组件实例共用了一份data,会造成一个变了,其他都跟着改变。
js 中,数据分为基本数据类型(String、Number、Bollean、Undefined、Null),和引用数据类型(Array、Object)。其中,基本数据类型都是简单的数据段,存储在栈内存中,引用数据类型存储在堆内存中,然后在栈内存中保存一个对堆内存中实际对象的引用(可以理解为,栈内存中保存了一个地址,这个地址和堆内存中的实际值是相关的)。
参考教程