Vue学习一之vue初识
本节目录javascript
vue称为渐进式js框架,这个框架用来作先后端分离的项目,以前咱们学习django,知道django是一个MTV模式的web框架,urls--views--templates,模板渲染经过后端的代码来实现数据的渲染,再加上前端一些简单的dom操做来完成网页的开发,当咱们作一个复杂的大型的网页的时候,你会发现这种模式做起来会比较复杂,扩展起来也比较困难,由于先后端没有分离开,耦合性过高,牵一发而动全身,因此人们就开始想,若是能有专门的人来开发前端,专门的人来开发后端,前端页面就是前端语言来写,后端服务端代码就是后端服务端代码来写,二者以前只有数据的交流,那么之后页面在进行拓展,进行功能的更新的时候就会变得比较简单,所以vue就诞生了,以前咱们前端页面拿到数据都是经过dom操做或者django的模板语言来进行数据的渲染的,有了前端框架vue,就不须要他们了,而且频繁的dom操做,建立标签添加标签对页面的性能是有影响的,那么直接数据驱动视图,将django的MTV中的T交给vue来写,也就是那个templates里面的内容,而且前端的vue拿到了T这部分的工做,MTV前身是MVC,能够将vue拿到的T的工做称为view视图,就是完成MVC的V视图层工做,只不过V称为视图函数,重点在函数,而vue咱们称为视图,接到后端的数据(经过接口url,得到json数据),直接经过vue的视图渲染在前端。css
前端三大框架,Vue、Angular、React,vue是结合了angular和react的优势开发出来的,是中国人尤雨溪开发的,angular不少公司也在用,是谷歌开发的,老项目通常是angular2.0,最新的是6.0的,可是它是基于另一个版本的js,叫作typescript,因此若是未来你工做用的是angular6.0,那么要本身提早学一下typescript,也比较简单,react是facebook开发的,其实越大型的项目react越好用,我的观点昂,react里面用的可能是高阶函数,须要你对js特别熟,对初学者不是很友好,可是你越熟练,用起来越nb,未来若是须要,你们再学习吧,争取哪天给你们整理出来,在githup上react比vue的星还多一些。html
先后端分离项目:分工明确前端
前端作前端的事情:页面+交互+兼容+封装+class+优化 (技术栈:vue+vue-router+vuex+axios+element-ui)vue
后端作后端的事情:接口+表操做+业务逻辑+封装+class+优化 (技术栈:restframework框架+django框架+mysql\redis等)java
画一个django和vue的对比图吧python
因为后面学习vue你会发现有不少es6的语法,因此咱们先学一些es6的基本语法mysql
1 let声明变量
1.1 基本用法
ES6 新增了let
命令,用来声明变量。它的用法相似于var
,可是所声明的变量,只在let
命令所在的代码块内有效,其实在js里面{}大括号括起来的表示一个代码块。 react
{ let a = 10; var b = 1; //至关于将b的声明放在了做用域的外面前面,var b;而后这里只是赋值 } a // ReferenceError: a is not defined. b // 1
上面代码在代码块之中,分别用let
和var
声明了两个变量。而后在代码块以外调用这两个变量,结果let
声明的变量报错,var
声明的变量返回了正确的值。这代表,let
声明的变量只在它所在的代码块有效。ios
再看一个例子:
<script> var l = []; l[1] = 'aa'; console.log(l[0],l[1]); //undefined "aa" 能够给数组经过索引来赋值,若是你给索引1赋值了,那么索引0的值为undefined console.log(a); //undefined //由于var存在变量提高的问题,会在这个打印前面先声明一个var a;而后后面在进行a=1的赋值,因此打印出来不报错,而是打印的undefined,let不存在这个问题,let只在本身的代码块中生效 { //js里面大括号表示一个代码块 var a = 1; let b = 2; } console.log(a); //1 console.log(b); // 报错 </script>
for
循环的计数器,就很合适使用let
命令。
for (let i = 0; i < 10; i++) { // ... } console.log(i); // ReferenceError: i is not defined
上面代码中,计数器i
只在for
循环体内有效,在循环体外引用就会报错。
下面的代码若是使用var
,最后输出的是10
。
var a = []; for (var i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } a[6](); // 10
上面代码中,变量i
是var
命令声明的,在全局范围内都有效,因此全局只有一个变量i
。每一次循环,变量i
的值都会发生改变,而循环内被赋给数组a
的函数内部的console.log(i)
,里面的i
指向的就是全局的i
。也就是说,全部数组a
的成员里面的i
,指向的都是同一个i
,致使运行时输出的是最后一轮的i
的值,也就是 10。
若是使用let
,声明的变量仅在块级做用域内有效,最后输出的是 6。
var a = []; for (let i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } a[6](); // 6
上面代码中,变量i
是let
声明的,当前的i
只在本轮循环有效,因此每一次循环的i
其实都是一个新的变量,因此最后输出的是6
。你可能会问,若是每一轮循环的变量i
都是从新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是由于 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i
时,就在上一轮循环的基础上进行计算。
另外,for
循环还有一个特别之处,就是设置循环变量的那部分是一个父做用域,而循环体内部是一个单独的子做用域。
for (let i = 0; i < 3; i++) { let i = 'abc'; console.log(i); } // abc // abc // abc
上面代码正确运行,输出了 3 次abc
。这代表函数内部的变量i
与循环变量i
不在同一个做用域,有各自单独的做用域。
1.2 不存在变量提高
var
命令会发生“变量提高”现象,即变量能够在声明以前使用,值为undefined
。这种现象多多少少是有些奇怪的,按照通常的逻辑,变量应该在声明语句以后才可使用。
为了纠正这种现象,let
命令改变了语法行为,它所声明的变量必定要在声明后使用,不然报错。
// var 的状况 console.log(foo); // 输出undefined var foo = 2; // let 的状况 console.log(bar); // 报错ReferenceError let bar = 2;
上面代码中,变量foo
用var
命令声明,会发生变量提高,即脚本开始运行时,变量foo
已经存在了,可是没有值,因此会输出undefined
。变量bar
用let
命令声明,不会发生变量提高。这表示在声明它以前,变量bar
是不存在的,这时若是用到它,就会抛出一个错误。
1.3 暂时性死区
只要块级做用域内存在let
命令,它所声明的变量就“绑定”(binding)这个区域,再也不受外部的影响。
var tmp = 123; if (true) { tmp = 'abc'; // ReferenceError let tmp; }
上面代码中,存在全局变量tmp
,可是块级做用域内let
又声明了一个局部变量tmp
,致使后者绑定这个块级做用域,因此在let
声明变量前,对tmp
赋值会报错。
ES6 明确规定,若是区块中存在let
和const
命令,这个区块对这些命令声明的变量,从一开始就造成了封闭做用域。凡是在声明以前就使用这些变量,就会报错。
总之,在代码块内,使用let
命令声明变量以前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。暂时性死区的本质就是,只要一进入当前做用域,所要使用的变量就已经存在了,可是不可获取,只有等到声明变量的那一行代码出现,才能够获取和使用该变量。
1.4 不容许重复声明
let
不容许在相同做用域内,重复声明同一个变量。
// 报错 function func() { let a = 10; var a = 1; } // 报错 function func() { let a = 10; let a = 1; }
所以,不能在函数内部从新声明参数。
function func(arg) { let arg; } func() // 报错 function func(arg) { { let arg; } } func() // 不报错
2. 做用域
ES5 只有全局做用域和函数做用域,没有块级做用域,这带来不少不合理的场景。
第一种场景,内层变量可能会覆盖外层变量。
var tmp = new Date(); function f() { console.log(tmp); if (false) { var tmp = 'hello world'; } } f(); // undefined
上面代码的原意是,if
代码块的外部使用外层的tmp
变量,内部使用内层的tmp
变量。可是,函数f
执行后,输出结果为undefined
,缘由在于变量提高,致使内层的tmp
变量覆盖了外层的tmp
变量。
第二种场景,用来计数的循环变量泄露为全局变量。
var s = 'hello'; for (var i = 0; i < s.length; i++) { console.log(s[i]); } console.log(i); // 5
上面代码中,变量i
只用来控制循环,可是循环结束后,它并无消失,泄露成了全局变量。
ES6中的做用域:
let
实际上为 JavaScript 新增了块级做用域。
function f1() { let n = 5; if (true) { let n = 10; } console.log(n); // 5 }
上面的函数有两个代码块,都声明了变量n
,运行后输出 5。这表示外层代码块不受内层代码块的影响。若是两次都使用var
定义变量n
,最后输出的值才是 10。
ES6 容许块级做用域的任意嵌套。
{{{{{let insane = 'Hello World'}}}}};
上面代码使用了一个五层的块级做用域。外层做用域没法读取内层做用域的变量。
{{{{ {let insane = 'Hello World'} console.log(insane); // 报错 }}}};
内层做用域能够定义外层做用域的同名变量。
{{{{ let insane = 'Hello World'; {let insane = 'Hello World'} }}}};
3.const声明常量
3.1 基本用法
const
声明一个只读的常量。一旦声明,常量的值就不能改变。
const PI = 3.1415; PI // 3.1415 PI = 3; // TypeError: Assignment to constant variable.
上面代码代表改变常量的值会报错。
const
声明的变量不得改变值,这意味着,const
一旦声明变量,就必须当即初始化,不能留到之后赋值。
const foo; // SyntaxError: Missing initializer in const declaration
上面代码表示,对于const
来讲,只声明不赋值,就会报错。
const
的做用域与let
命令相同:只在声明所在的块级做用域内有效。
if (true) { const MAX = 5; } MAX // Uncaught ReferenceError: MAX is not defined
const
命令声明的常量也是不提高,一样存在暂时性死区,只能在声明的位置后面使用。
if (true) { console.log(MAX); // ReferenceError const MAX = 5; }
上面代码在常量MAX
声明以前就调用,结果报错。
const
声明的常量,也与let
同样不可重复声明。
var message = "Hello!"; let age = 25; // 如下两行都会报错 const message = "Goodbye!"; const age = 30;
3.2 本质
const
实际上保证的,并非变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,所以等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const
只能保证这个指针是固定的(即老是指向另外一个固定的地址),至于它指向的数据结构是否是可变的,就彻底不能控制了。所以,将一个对象声明为常量必须很是当心。
const foo = {}; // 为 foo 添加一个属性,能够成功 foo.prop = 123; foo.prop // 123 // 将 foo 指向另外一个对象,就会报错 foo = {}; // TypeError: "foo" is read-only
上面代码中,常量foo
储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把foo
指向另外一个地址,但对象自己是可变的,因此依然能够为其添加新属性。
下面是另外一个例子。
const a = []; a.push('Hello'); // 可执行 a.length = 0; // 可执行 a = ['Dave']; // 报错
上面代码中,常量a
是一个数组,这个数组自己是可写的,可是若是将另外一个数组赋值给a
,就会报错。
ES6 声明变量的六种方法
ES5 只有两种声明变量的方法:var
命令和function
命令。ES6 除了添加let
和const
命令,另外两种声明变量的方法:import
命令和class
命令。因此,ES6 一共有 6 种声明变量的方法。
4.模板字符串
模板字符串就是两个反引号,也就是tab键上面的那个键,括起来的字符串,就是模板字符串,var或者let声明的变量均可以被模板字符串直接经过${变量名}来使用,看例子
var aa = 'chao'; let bb = 'jj'; var ss = `你好${aa}`; ss "你好chao" var ss2 = `你好${bb}`; ss2 "你好jj" let ss3 = `你好${aa}`; ss3 "你好chao"
总结:
let :特色: 1.a是局部做用域的 2.不存在变量提高 3.不能重复声明(var能够重复声明),
const :特色: 1.局部做用域 2.不存在变量提高 3.不能重复声明 4.通常声明不可变的量
模板字符串:tab键上面的反引号,${变量名}来插入值
5.函数
说到函数,咱们来看看es5和es6是怎么声明函数的
//ES5写法 function add(x){ return x } add(5); //匿名函数 var add = function (x) { return x }; //ES6的匿名函数 let add = function (x) { return x }; add(5); //ES6的箭头函数,就是上面方法的简写形式 let add = (x) => { return x }; console.log(add(20)); //更简单的写法,但不是很易阅读 let add = x => x; console.log(add(5));
多个参数的时候必须加括号,函数返回值仍是只能有一个,没有参数的,必须写一个()
let add = (x,y) => x+y;
下面来学一下自定义对象中封装函数的写法,看例子:
//es5对象中封装函数的方法 var person1 = { name:'超', age:18, f1:function () { //在自定义的对象中放函数的方法 console.log(this);//this指向的是当前的对象,{name: "超", age: 18, f1: ƒ} console.log(this.name) // '超' } }; person1.f1(); //经过自定对象来使用函数 //ES6中自定义对象中来封装箭头函数的写法 let person2 = { name:'超', age:18, f1: () => { //在自定义的对象中放函数的方法 console.log(this); //this指向的再也不是当前的对象了,而是指向了person的父级对象(称为上下文),而此时的父级对象是咱们的window对象,Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …} console.log(window);//还记得window对象吗,全局浏览器对象,打印结果和上面同样:Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …} console.log(this.name) //啥也打印不出来 } }; person2.f1(); //经过自定对象来使用函数 //而咱们使用this的时候,但愿this是person对象,而不是window对象,因此还有下面这种写法 let person3 = { name:'超', age:18, f1(){ //至关于f1:function(){},只是一种简写方式,称为对象的单体模式写法,写起来也简单,vue里面会看用到这种方法 console.log(this);//this指向的是当前的对象,{name: "超", age: 18, f1: ƒ} console.log(this.name) //'超' } }; person3.f1()
6.类
咱们看看es5和es6的类写法对比
<script> //es5写类的方式 function Person(name,age) { //封装属性 this.name = name; this.age = age; } //封装方法,原型链 Person.prototype.f1 = function () { console.log(this.name);//this指的是Person对象, 结果:'超' }; //封装方法,箭头函数的形式写匿名函数 Person.prototype.f2 = ()=>{ console.log(this); //Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …} this指向的是window对象 }; var p1 = new Person('超',18); p1.f1(); p1.f2(); //其实在es5咱们将js的基本语法的时候,没有将类的继承,可是也是能够继承的,还记得吗,那么你想,继承以后,咱们是否是能够经过子类实例化的对象调用父类的方法啊,固然是能够的,知道一下就好了,咱们下面来看看es6里面的类怎么写 class Person2{ constructor(name,age){ //对象里面的单体模式,记得上面将函数的时候的单体模式吗,这个方法相似于python的__init__()构造方法,写参数的时候也能够写关键字参数 constructor(name='超2',age=18) //封装属性 this.name = name; this.age = age; } //注意这里不能写逗号 showname(){ //封装方法 console.log(this.name); } //不能写逗号 showage(){ console.log(this.age); } } let p2 = new Person2('超2',18); p2.showname() //调用方法 '超2' //es6的类也是能够继承的,这里我们就不作细讲了,未来你须要的时候,就去学一下吧,哈哈,我记得是用的extends和super </script>
1.下载安装
咱们使用vue就要把人家下载下来安装一下,就像你使用django框架同样,须要下载安装django才能使用,这个vue框架小而精,可是功能很强大,以前咱们的js或者jQuery操做基本均可以经过vue来完成,咱们下面是按照vue2.x学的,若是vue更新为3.0了,那么你们记得去学学里面的新语法。
方式1:
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
方式2:
引用:
<script src="vue.js"></script>
下载的三种方式:cdn,下载js npm下载(后面再说这个),最好的引用方式是先cdn引入而后备选本地js文件引入,减轻本身服务器压力,这些大家先做为了解。
引用了vue以后,咱们直接打开引用了vue的这个html文件,而后在浏览器调试窗口输入Vue,你会发现它就是一个构造函数,也就是我们js里面实例化一个类时的写法:
2 vue的模板语法
个人文件的目录结构是这样的:
简单看一个模板语法的例子:就是上面这个html文件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>test vue</title> </head> <body> <div id="app"> <!-- vue的模板语法,和django的模板语法相似 --> <h2>{{ msg }}</h2> <!-- 放的是变量,就会去下面的Vue对象中的data属性中的键去找对应的数据,注意语法规范,中间的数据先后都有一个空格 --> <h2>{{ 'xxxxx' }}</h2> <!-- 写个字符串就直接显示这个字符串 -->
</div> <div id="content">{{ msg }}</div> <!--不生效--> <!-- 1.引包 --> <script src="vue.js"></script> <script> //2.实例化对象 new Vue({ //实例化的时候要传一个参数,options配置选项,是一个自定义对象,下面就看看这些配置选项都是什么,是咱们必需要知道的东西,el和data是必需要写的 el:'#app', //el是当前咱们实例化对象绑定的根元素(标签),会到html文档中找到这个id属性为app的标签,在html里面写一个id属性为app的div标签,意思就是说,我如今实例化的这个Vue对象和上面这个id为app的div绑定到了一块儿,在这个div里面使用vue的语法才能生效,就像一个地主圈了一块地同样,那么接下来就要种东西了 data:{ //data是数据属性,就是咱们要在地里面种的东西了,是一个自定义对象 msg:'黄瓜', //这些数据之后是经过数据库里面调出来,而后经过后端代码的接口接收到的,如今写死了,先看看效果,咱们在上面的div标签里面写一个其余的标签,而后写上{{ msg }},这样就至关于咱们在id为app的div标签的这个地里面种了一个种子,这个种子产的是黄瓜,那么咱们打开页面就直接看到黄瓜了 } }) </script> </body> </html>
看页面效果:
其余的模板语法的使用:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>test vue</title> </head> <body> <div id="app"> <!-- vue的模板语法,和django的模板语法相似 --> <h2>{{ msg }}</h2> <!-- 放一个变量,会到data属性中去找对应的值 --> <!-- 有人说,咱们直接这样写数据不就行吗,可是你注意,咱们未来的数据都是从后端动态取出来的,不能写死这些数据啊,你说对不对 --> <h2>{{ 'hello beautiful girl!' }}</h2> <!-- 直接放一个字符串 --> <h2>{{ 1+1 }}</h2> <!-- 四则运算 --> <h2>{{ {'name':'chao'} }}</h2> <!-- 直接放一个自定义对象 --> <h2>{{ person.name }}</h2> <!-- 下面data属性里面的person属性中的name属性的值 --> <h2>{{ 1>2?'真的':'假的' }}</h2> <!-- js的三元运算 --> <h2>{{ msg2.split('').reverse().join('') }}</h2> <!-- 字符串反转 --> </div> <!-- 1.引包 --> <script src="vue.js"></script> <script> //2.实例化对象 new Vue({ el:'#app', data:{ msg:'黄瓜', person:{ name:'超', }, msg2:'hello Vue' } }) </script> </body> </html>
看效果:
3 vue的指令系统
vue里面全部的指令系统都是v开头的,v-text和v-html(重点是v-html),使用指令系统就可以立马帮你作dom操做了,不须要我们本身再写dom操做了,因此咱们以后就学习vue的指令系统语法就能够了。
3.1 v-text和v-html
v-text至关于innerText,至关于咱们上面说的模板语法,直接在html中插值了,插的就是文本,若是data里面写了个标签,那么经过模板语法渲染的是文本内容,这个你们不陌生,这个v-text就是辅助咱们使用模板语法的
v-html至关于innerHtml
模板语法data中写个标签的例子:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>test vue</title> </head> <body> <div id="app"> <!-- vue的模板语法 --> <div>{{ text }}</div> </div> <!-- 1.引包 --> <script src="vue.js"></script> <script> //2.实例化对象 new Vue({ el:'#app', data:{ text:'<h2>只要vue学得好</h2>' //这里放个标签 } }) </script> </body> </html>
看效果:
上面咱们使用data属性的时候,都是用的data:{}对应一个自定义对象,可是在咱们后学的学习中,这个data咱们通常都是对应一个函数,而且这个函数里面必须return {}一个对象,看例子:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>test vue</title> </head> <body> <div id="app"> <!-- vue的模板语法 --> <div>{{ msg }}</div> </div> <hr> <div id="content"> {{ msg }} </div> <script src="vue.js"></script> <script> //每个Vue对象均可以绑定一个根元素,好比上面咱们有两个div标签,一个id为app一个id为content,咱们就能够写两个Vue对象,意思是告诉你们是能够绑定多个根元素的,使用多个Vue对象就能够了,可是通常咱们一个就够用了,充当body的角色,那么说在body标签给个id为app行不行啊,固然行,可是通常不这么干 new Vue({ el:'#content', data:function () { return{ msg:'哈哈' } } });
//因此重点看下面这个就好了 new Vue({ el:'#app', // data:function () { //你们记住一点,未来凡是涉及到data数据属性的,牵扯到组件的概念的时候,data都对应一个函数,写法就是这样的,由于后面咱们要学习的组件中明确规定,组件中的data必须对应函数,因此你们就用函数来写data。 // // return{ //函数里面必须return一个对象{} // msg:'超', //这个return的对象里面再玩咱们的数据 // } // } //咱们对于这个函数就能够简写,按照单体模式的写法,你们还记得单体模式吗 data(){ //单体模式 return{ msg:'超', } } }) </script> </body> </html>
下面看一看v-text和v-html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>test vue</title> </head> <body> <div id="app"> <!-- vue的模板语法 --> <div>{{ msg }}</div> <div v-text="msg"></div> <div v-html="msg"></div> </div> <script src="vue.js"></script> <script> new Vue({ el:'#app', data(){ //记着data中是一个函数,函数中return一个对象,能够是一个空对象,但必须return return{ msg:'<h2>超</h2>', //后端返回的是标签,那么咱们就能够直接经过v-html渲染出来标签效果 } } }) </script> </body> </html>
看页面效果:
3.2 v-if和v-show
在模板语法里面{{ 属性或者函数 }},双大括号里面能够是data里面的属性,也能够是一个函数,看写法:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>test vue</title> </head> <body> <div id="content"> <p>{{ msg }}</p> <p>{{ add(5,6) }}</p> <!-- 使用函数,若是函数没有返回值,这里啥也不显示--> </div> <script src="vue.js"></script> <script> new Vue({ el:'#content', data(){ //记着data中是一个函数,函数中return一个对象,能够是一个空对象,但必须return return{ msg:'<h2>超</h2>', //后端返回的是标签,那么咱们就能够直接经过v-html渲染出来标签效果 } }, methods:{ //在模板语法中使用函数的时候,不是在data属性里面写了,而是在这个methods里面写,看写法 add(x,y){ return x+y; } } }) </script> </body> </html>
而后咱们接着来看一下v-if和v-show以及v-on的简单用法
v-show的例子:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>test vue</title> <style> .box{ height: 200px; width: 200px; background-color: red; } </style> </head> <body> <div id="content"> <p>{{ msg }}</p> <p>{{ add(5,6) }}</p> <!-- 使用函数,若是函数没有返回值,这里啥也不显示--> <!-- 注意,使用指令系统的时候,v-xxx=字符串,必须是个字符串,并且这个字符串必须是Vue对象里面声明的属性或者方法,否则在浏览器上会报错,并且使用模板语法{{}}的时候,只能写在标签的里面 --> <div class="box" v-show="isShow"></div> <!-- 根据isShow的值来显示或者隐藏标签 --> </div> <script src="vue.js"></script> <script> new Vue({ el:'#content', data(){ return{ msg:'<h2>超</h2>', // isShow:true, //true显示标签,false隐藏标签,就是给标签加一个css属性为display:'none' // isShow:1===1, isShow:1===2, } }, methods:{ add(x,y){ return x+y; } } }) </script> </body> </html>
v-show结合v-on的例子2:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>test vue</title> <style> .box{ height: 200px; width: 200px; background-color: red; } </style> </head> <body> <div id="content"> <p>{{ msg }}</p> <p>{{ add(5,6) }}</p> <!-- 使用函数,若是函数没有返回值,这里啥也不显示--> <!-- 注意,使用指令系统的时候,v-xxx=字符串,必须是个字符串,并且这个字符串必须是Vue对象里面声明的属性或者方法,否则在浏览器上会报错,并且使用模板语法{{}}的时候,只能写在标签的里面 --> <div class="box" v-show="isShow"></div> <!-- 根据isShow的值来显示或者隐藏标签 --> <div> <!-- 点击隐藏按钮让上面的class值为box的标签隐藏或者显示,以前咱们经过dom操做来完成事件驱动的,这里咱们经过vue的数据驱动来搞 --> <!--<button v-on:click="函数名,找Vue对象中methods里面的函数">隐藏或者显示</button>,经过v-on指令来实现事件的绑定和驱动--> <button v-on:click="handlerClick">隐藏或者显示</button> </div> </div> <script src="vue.js"></script> <script> new Vue({ el:'#content', data(){ return{ msg:'<h2>超</h2>', isShow:true, } }, methods:{ add(x,y){ return x+y; }, handlerClick(){ //数据驱动来控制标签的显示隐藏 // console.log(this) //this是当前Vue对象,当咱们打印这个对象的时候,在浏览器控制台你会发现Vue对象自带的属性(el\data\methods等,会在属性名前面加一个$符号,以便和咱们自定义的属性(msg\isShow\add等等)区分) this.isShow = !this.isShow; //更改isShow的数据,你会发现数据一边,上面的标签就根据数据来显示或者隐藏,这就是vue的思想,数据驱动,彻底不须要咱们本身写dom操做来完成标签的显示或者隐藏了 } } }) </script> </body> </html>
效果:
接下来看一下v-show和v-if的区别
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>test vue</title> <style> .box{ height: 200px; width: 200px; background-color: red; } .box2{ height: 200px; width: 200px; background-color: green; } </style> </head> <body> <div id="content"> <p>{{ msg }}</p> <p>{{ add(5,6) }}</p> <div class="box" v-show="isShow"></div> <!-- v-if也是经过值来判断显示或者隐藏,可是这个隐藏不是加diaplay属性为none了,而是直接将这个标签删除了,经过浏览器控制台你会发现,一隐藏,下面这个标签就没有了,一显示,这个标签又从新添加回来了,这就是v-if和v-show的区别 --> <div class="box2" v-if="isShow"></div> <div> <button v-on:click="handlerClick">隐藏或者显示</button> </div> </div> <script src="vue.js"></script> <script> new Vue({ el:'#content', data(){ return{ msg:'<h2>超</h2>', isShow:true, } }, methods:{ add(x,y){ return x+y; }, handlerClick(){ //数据驱动来控制标签的显示隐藏 // console.log(this) //this是当前Vue对象,当咱们打印这个对象的时候,在浏览器控制台你会发现Vue对象自带的属性(el\data\methods等,会在属性名前面加一个$符号,以便和咱们自定义的属性(msg\isShow\add等等)区分) this.isShow = !this.isShow; //更改isShow的数据,你会发现数据一边,上面的标签就根据数据来显示或者隐藏,这就是vue的思想,数据驱动,彻底不须要咱们本身写dom操做来完成标签的显示或者隐藏了 } } }) </script> </body> </html>
看效果,v-if是标签的添加和删除,v-show是标签的显示和隐藏,v-if的渲染效率开销比较大,v-if叫作条件渲染,还有个v-else,一会咱们测试一下。
v-if和v-show的区别,官网解释:
v-if 是“真正”的条件渲染,由于它会确保在切换过程当中条件块内的事件监听器和子组件适当地被销毁和重建。 v-if 也是惰性的:若是在初始渲染时条件为假,则什么也不作——直到条件第一次变为真时,才会开始渲染条件块。 相比之下,v-show 就简单得多——无论初始条件是什么,元素老是会被渲染,而且只是简单地基于 CSS 进行切换。 通常来讲,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。所以,若是须要很是频繁地切换,则使用 v-show 较好;若是在运行时条件不多改变,则使用 v-if 较好。
接下来咱们简单看一下v-if和v-else
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>test vue</title> <style> .box{ height: 200px; width: 200px; background-color: red; } .box2{ height: 200px; width: 200px; background-color: green; } </style> </head> <body> <div id="content"> <p>{{ msg }}</p> <p>{{ add(5,6) }}</p> <div class="box" v-show="isShow"></div> <div class="box2" v-if="isShow"></div> <div> <button v-on:click="handlerClick">隐藏或者显示</button> </div> <div> <!-- 首先你会发现其实指令系统的值和咱们模板语法同样,也支持各类操做,这里咱们使用了js的一个随机数方法,而且和0.5进行比较,大于0.5的时候结果为true,那么就会显示'有了',若是为false就会显示'没了',那么咱们每次刷新页面的时候,显示的内容可能会发生变化,这就是v-if和v-else的用法 --> <div v-if="Math.random() > 0.5"> 有了 </div> <div v-else> 没了 </div> </div> </div> <script src="vue.js"></script> <script> new Vue({ el:'#content', data(){ return{ msg:'<h2>超</h2>', isShow:true, } }, methods:{ add(x,y){ return x+y; }, handlerClick(){ this.isShow = !this.isShow; } } }) </script> </body> </html>
在vue2.1.0版本以后,又添加了v-else-if,v-else-if
,顾名思义,充当 v-if
的“else-if 块”,能够连续使用:
<div v-if="type === 'A'"> A </div> <div v-else-if="type === 'B'"> B </div> <div v-else-if="type === 'C'"> C </div> <div v-else> Not A/B/C </div>
相似于 v-else
,v-else-if
也必须紧跟在带 v-if
或者 v-else-if
的元素以后。
3.3 v-bind和v-on
直接看例子吧:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>test vue</title> <style> .box{ height: 200px; width: 200px; background-color: red; } .box2{ height: 200px; width: 200px; background-color: green; } .active{ background-color: green; height: 100px; width: 100px; } </style> </head> <body> <div id="app"> <!--1. v-bind 可以绑定标签全部的属性 好比img标签的src alt等,啊标签的href id class title等 --> <!-- 用一个img标签的src和alt属性来试一下,写法,v-bind:属性='字符串(data里面return里面的属性)' --> <!--<img v-bind:src="imgSrc" v-bind:alt="imgAlt">--> <!--2. 在来一个经过控制class属性的值来让标签的css样式动态变化 --> <!-- 注意写法,v-bind:class='{class属性值:Vuew对象的data里面return中的属性}',如今的意思是若是isActive的值为true,那么会将active添加的前面的class属性的值中,变成class='box active' ,若是isActive的值为false则不添加,无论是否已经写了class属性,写了就是添加操做,没写就是建立属性操做--> <!--<div v-bind:class="{active:isActive}"></div>--> <!--<div class="box" v-bind:class="{active:isActive}"></div>--> <!--3. 咱们再来一个button标签来点击让下面的div标签动态的增减class值来变化css样式效果,还要注意,o-on来绑定事件函数,都在Vue对象的methods属性里面声明--> <!--<button v-on:click="handlerChange">点击</button>--> <!--<div class="box" v-bind:class="{active:isActive}"></div>--> <!--4. v-bind和v-on有简单的写法,v-bind:(简写就写一个冒号:) v-on:(简写就写一个@) --> <img :src="imgSrc" :alt="imgAlt"> <!--<button @click="handlerChange">点击</button>--> <!-- 绑定多个事件 --> <button @click="handlerChange" @mouseenter="handlerEnter" @mouseleave="handlerLeave">点击</button> <div class="box" :class="{active:isActive}"></div> </div> <script src="vue.js"></script> <script> // 数据驱动视图,设计模式:MVVM:Model-->View-->ViewModel //vue称为声明式的JavaScript框架,声明了什么属性,HTML里面就是用什么属性,你在页面上一看到@就知道后面绑定了一个方法,一看到:就知道后面绑定了一个属性,这就叫作声明式,以前咱们经过原生的js或者jQuery都是命令式的,让它作什么它就作什么,了解一下就行啦 new Vue({ el:'#app', data(){ return{ //之后经过后端获取到数据,就可以经过更改这些数据来动态的显示图片等信息了 imgSrc:'timg.jpg', //图片路径 // imgSrc:'timg2.jpg', imgAlt:'美女', //图片未加载成功时的描述 isActive:true, } }, methods:{ //鼠标点击时的触发效果 handlerChange(){ this.isActive=!this.isActive; }, //鼠标进入事件的触发效果 handlerEnter(){ this.isActive=false; }, //鼠标离开时的触发效果 handlerLeave(){ this.isActive=true; } } }) </script> </body> </html>
总结:
v-bind能够绑定标签中的任意属性
v-on能够监听js的全部事件
MVVM框架模型图解:(注意:之后工做的时候,先后端应该先商量好后端给什么样子的数据结构,前端再怎么处理)
3.4 v-for
循环,直接看例子:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>test vue</title> </head> <body> <div id="app"> <ul v-if="data.status === 'ok'"> <!-- 遍历数组,item表示下面列表中的每一个字典 --> <!--<li v-for="item in data.users"></li> --> <!-- item和index表示下面列表中的每一个字典中的键和值 --> <!--<li v-for="(item,index) in data.users">--> <!--<h3>{{ item.id }}--{{ item.name }}--{{ item.age }}</h3>--> <!--</li>--> <!-- v-for不只能够遍历数组,还能够遍历对象,这里你们记住v-for里面的一个东西 :key, 就是v-bind:key,这个key是干什么的呢,就是为了给如今已经渲染好的li标签作个标记,之后即使是有数据更新了,也能够在这个li标签里面进行数据的更新,不须要再让Vue作从新生成li标签的dom操做,提升页面渲染的性能,由于咱们知道频繁的添加删除dom操做对性能是有影响的,我如今将数据中的id绑定到这里,若是数据里面有id,通常都用id,若是没有id,就绑定v-for里面那个index(固然你看你给这个索引取的变量名是什么,我这里给索引取的名字是index),这里面它用的是diff算法,回头再说这个算法 --> <!-- <li v-for="(item,index) in data.users" :key="item.id" @click> 还能够绑定事件 --> <li v-for="(item,index) in data.users" :key="item.id"> <!-- v-for的优先级最高,先把v-for遍历完,而后给:key加数据,还有,若是没有bind这个key,有可能你的页面都后期用动态数据渲染的时候,会出现问题,因此之后你们记着,必定写上v-bind:key --> <h3>{{ item.id }}--{{ item.name }}--{{ item.age }}</h3> </li> </ul> <!-- 2. 循环对象,循环对象的时候,注意写法,值在前,key在后,若是只写一个变量,那么这个变量循环出来的是值 --> <!--<div v-for="value in person">--> <!--{{ value }}--> <!--</div>--> <div v-for="(value,key) in person"> {{ value }} -- {{ key }} </div> </div> <script src="vue.js"></script> <script> new Vue({ el:'#app', data(){ return{ //通常后端返回的数据都叫作data data:{ status:'ok', //返回ok表示成功 users:[ {id:1,name:'chao',age:18}, {id:2,name:'jaden',age:38}, {id:3,name:'yue',age:28}, ] }, //变脸这个对象的属性 person:{ name:'超', } } }, methods:{ } }) </script> </body> </html>
看效果:
基本用法
ES6 新增了let
命令,用来声明变量。它的用法相似于var
,可是所声明的变量,只在let
命令所在的代码块内有效。
{ let a = 10; var b = 1; } a // ReferenceError: a is not defined. b // 1
上面代码在代码块之中,分别用let
和var
声明了两个变量。而后在代码块以外调用这两个变量,结果let
声明的变量报错,var
声明的变量返回了正确的值。这代表,let
声明的变量只在它所在的代码块有效。
for
循环的计数器,就很合适使用let
命令。
for (let i = 0; i < 10; i++) { // ... } console.log(i); // ReferenceError: i is not defined
上面代码中,计数器i
只在for
循环体内有效,在循环体外引用就会报错。
下面的代码若是使用var
,最后输出的是10
。
var a = []; for (var i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } a[6](); // 10
上面代码中,变量i
是var
命令声明的,在全局范围内都有效,因此全局只有一个变量i
。每一次循环,变量i
的值都会发生改变,而循环内被赋给数组a
的函数内部的console.log(i)
,里面的i
指向的就是全局的i
。也就是说,全部数组a
的成员里面的i
,指向的都是同一个i
,致使运行时输出的是最后一轮的i
的值,也就是 10。
若是使用let
,声明的变量仅在块级做用域内有效,最后输出的是 6。
var a = []; for (let i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } a[6](); // 6
上面代码中,变量i
是let
声明的,当前的i
只在本轮循环有效,因此每一次循环的i
其实都是一个新的变量,因此最后输出的是6
。你可能会问,若是每一轮循环的变量i
都是从新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是由于 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i
时,就在上一轮循环的基础上进行计算。
另外,for
循环还有一个特别之处,就是设置循环变量的那部分是一个父做用域,而循环体内部是一个单独的子做用域。
for (let i = 0; i < 3; i++) { let i = 'abc'; console.log(i); } // abc // abc // abc
上面代码正确运行,输出了 3 次abc
。这代表函数内部的变量i
与循环变量i
不在同一个做用域,有各自单独的做用域。
不存在变量提高
var
命令会发生“变量提高”现象,即变量能够在声明以前使用,值为undefined
。这种现象多多少少是有些奇怪的,按照通常的逻辑,变量应该在声明语句以后才可使用。
为了纠正这种现象,let
命令改变了语法行为,它所声明的变量必定要在声明后使用,不然报错。
// var 的状况 console.log(foo); // 输出undefined var foo = 2; // let 的状况 console.log(bar); // 报错ReferenceError let bar = 2;
上面代码中,变量foo
用var
命令声明,会发生变量提高,即脚本开始运行时,变量foo
已经存在了,可是没有值,因此会输出undefined
。变量bar
用let
命令声明,不会发生变量提高。这表示在声明它以前,变量bar
是不存在的,这时若是用到它,就会抛出一个错误。
暂时性死区
只要块级做用域内存在let
命令,它所声明的变量就“绑定”(binding)这个区域,再也不受外部的影响。
var tmp = 123; if (true) { tmp = 'abc'; // ReferenceError let tmp; }
上面代码中,存在全局变量tmp
,可是块级做用域内let
又声明了一个局部变量tmp
,致使后者绑定这个块级做用域,因此在let
声明变量前,对tmp
赋值会报错。
ES6 明确规定,若是区块中存在let
和const
命令,这个区块对这些命令声明的变量,从一开始就造成了封闭做用域。凡是在声明以前就使用这些变量,就会报错。
总之,在代码块内,使用let
命令声明变量以前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
if (true) { // TDZ开始 tmp = 'abc'; // ReferenceError console.log(tmp); // ReferenceError let tmp; // TDZ结束 console.log(tmp); // undefined tmp = 123; console.log(tmp); // 123 }
上面代码中,在let
命令声明变量tmp
以前,都属于变量tmp
的“死区”。
“暂时性死区”也意味着typeof
再也不是一个百分之百安全的操做。
typeof x; // ReferenceError let x;
上面代码中,变量x
使用let
命令声明,因此在声明以前,都属于x
的“死区”,只要用到该变量就会报错。所以,typeof
运行时就会抛出一个ReferenceError
。
做为比较,若是一个变量根本没有被声明,使用typeof
反而不会报错。
typeof undeclared_variable // "undefined"
上面代码中,undeclared_variable
是一个不存在的变量名,结果返回“undefined”。因此,在没有let
以前,typeof
运算符是百分之百安全的,永远不会报错。如今这一点不成立了。这样的设计是为了让你们养成良好的编程习惯,变量必定要在声明以后使用,不然就报错。
有些“死区”比较隐蔽,不太容易发现。
function bar(x = y, y = 2) { return [x, y]; } bar(); // 报错
上面代码中,调用bar
函数之因此报错(某些实现可能不报错),是由于参数x
默认值等于另外一个参数y
,而此时y
尚未声明,属于“死区”。若是y
的默认值是x
,就不会报错,由于此时x
已经声明了。
function bar(x = 2, y = x) { return [x, y]; } bar(); // [2, 2]
另外,下面的代码也会报错,与var
的行为不一样。
// 不报错 var x = x; // 报错 let x = x; // ReferenceError: x is not defined
上面代码报错,也是由于暂时性死区。使用let
声明变量时,只要变量在尚未声明完成前使用,就会报错。上面这行就属于这个状况,在变量x
的声明语句尚未执行完成前,就去取x
的值,致使报错”x 未定义“。
ES6 规定暂时性死区和let
、const
语句不出现变量提高,主要是为了减小运行时错误,防止在变量声明前就使用这个变量,从而致使意料以外的行为。这样的错误在 ES5 是很常见的,如今有了这种规定,避免此类错误就很容易了。
总之,暂时性死区的本质就是,只要一进入当前做用域,所要使用的变量就已经存在了,可是不可获取,只有等到声明变量的那一行代码出现,才能够获取和使用该变量。
不容许重复声明
let
不容许在相同做用域内,重复声明同一个变量。
// 报错 function func() { let a = 10; var a = 1; } // 报错 function func() { let a = 10; let a = 1; }
所以,不能在函数内部从新声明参数。
function func(arg) { let arg; } func() // 报错 function func(arg) { { let arg; } } func() // 不报错
块级做用域
为何须要块级做用域?
ES5 只有全局做用域和函数做用域,没有块级做用域,这带来不少不合理的场景。
第一种场景,内层变量可能会覆盖外层变量。
var tmp = new Date(); function f() { console.log(tmp); if (false) { var tmp = 'hello world'; } } f(); // undefined
上面代码的原意是,if
代码块的外部使用外层的tmp
变量,内部使用内层的tmp
变量。可是,函数f
执行后,输出结果为undefined
,缘由在于变量提高,致使内层的tmp
变量覆盖了外层的tmp
变量。
第二种场景,用来计数的循环变量泄露为全局变量。
var s = 'hello'; for (var i = 0; i < s.length; i++) { console.log(s[i]); } console.log(i); // 5
上面代码中,变量i
只用来控制循环,可是循环结束后,它并无消失,泄露成了全局变量。
ES6 的块级做用域
let
实际上为 JavaScript 新增了块级做用域。
function f1() { let n = 5; if (true) { let n = 10; } console.log(n); // 5 }
上面的函数有两个代码块,都声明了变量n
,运行后输出 5。这表示外层代码块不受内层代码块的影响。若是两次都使用var
定义变量n
,最后输出的值才是 10。
ES6 容许块级做用域的任意嵌套。
{{{{{let insane = 'Hello World'}}}}};
上面代码使用了一个五层的块级做用域。外层做用域没法读取内层做用域的变量。
{{{{ {let insane = 'Hello World'} console.log(insane); // 报错 }}}};
内层做用域能够定义外层做用域的同名变量。
{{{{ let insane = 'Hello World'; {let insane = 'Hello World'} }}}};
块级做用域的出现,实际上使得得到普遍应用的当即执行函数表达式(IIFE)再也不必要了。
// IIFE 写法 (function () { var tmp = ...; ... }()); // 块级做用域写法 { let tmp = ...; ... }
块级做用域与函数声明
函数能不能在块级做用域之中声明?这是一个至关使人混淆的问题。
ES5 规定,函数只能在顶层做用域和函数做用域之中声明,不能在块级做用域声明。
// 状况一 if (true) { function f() {} } // 状况二 try { function f() {} } catch(e) { // ... }
上面两种函数声明,根据 ES5 的规定都是非法的。
可是,浏览器没有遵照这个规定,为了兼容之前的旧代码,仍是支持在块级做用域之中声明函数,所以上面两种状况实际都能运行,不会报错。
ES6 引入了块级做用域,明确容许在块级做用域之中声明函数。ES6 规定,块级做用域之中,函数声明语句的行为相似于let
,在块级做用域以外不可引用。
function f() { console.log('I am outside!'); } (function () { if (false) { // 重复声明一次函数f function f() { console.log('I am inside!'); } } f(); }());
上面代码在 ES5 中运行,会获得“I am inside!”,由于在if
内声明的函数f
会被提高到函数头部,实际运行的代码以下。
// ES5 环境 function f() { console.log('I am outside!'); } (function () { function f() { console.log('I am inside!'); } if (false) { } f(); }());
ES6 就彻底不同了,理论上会获得“I am outside!”。由于块级做用域内声明的函数相似于let
,对做用域以外没有影响。可是,若是你真的在 ES6 浏览器中运行一下上面的代码,是会报错的,这是为何呢?
原来,若是改变了块级做用域内声明的函数的处理规则,显然会对老代码产生很大影响。为了减轻所以产生的不兼容问题,ES6 在附录 B里面规定,浏览器的实现能够不遵照上面的规定,有本身的行为方式。
- 容许在块级做用域内声明函数。
- 函数声明相似于
var
,即会提高到全局做用域或函数做用域的头部。 - 同时,函数声明还会提高到所在的块级做用域的头部。
注意,上面三条规则只对 ES6 的浏览器实现有效,其余环境的实现不用遵照,仍是将块级做用域的函数声明看成let
处理。
根据这三条规则,在浏览器的 ES6 环境中,块级做用域内声明的函数,行为相似于var
声明的变量。
// 浏览器的 ES6 环境 function f() { console.log('I am outside!'); } (function () { if (false) { // 重复声明一次函数f function f() { console.log('I am inside!'); } } f(); }()); // Uncaught TypeError: f is not a function
上面的代码在符合 ES6 的浏览器中,都会报错,由于实际运行的是下面的代码。
// 浏览器的 ES6 环境 function f() { console.log('I am outside!'); } (function () { var f = undefined; if (false) { function f() { console.log('I am inside!'); } } f(); }()); // Uncaught TypeError: f is not a function
考虑到环境致使的行为差别太大,应该避免在块级做用域内声明函数。若是确实须要,也应该写成函数表达式,而不是函数声明语句。
// 函数声明语句 { let a = 'secret'; function f() { return a; } } // 函数表达式 { let a = 'secret'; let f = function () { return a; }; }
另外,还有一个须要注意的地方。ES6 的块级做用域容许声明函数的规则,只在使用大括号的状况下成立,若是没有使用大括号,就会报错。
// 不报错 'use strict'; if (true) { function f() {} } // 报错 'use strict'; if (true) function f() {}
const 命令
基本用法
const
声明一个只读的常量。一旦声明,常量的值就不能改变。
const PI = 3.1415; PI // 3.1415 PI = 3; // TypeError: Assignment to constant variable.
上面代码代表改变常量的值会报错。
const
声明的变量不得改变值,这意味着,const
一旦声明变量,就必须当即初始化,不能留到之后赋值。
const foo; // SyntaxError: Missing initializer in const declaration
上面代码表示,对于const
来讲,只声明不赋值,就会报错。
const
的做用域与let
命令相同:只在声明所在的块级做用域内有效。
if (true) { const MAX = 5; } MAX // Uncaught ReferenceError: MAX is not defined
const
命令声明的常量也是不提高,一样存在暂时性死区,只能在声明的位置后面使用。
if (true) { console.log(MAX); // ReferenceError const MAX = 5; }
上面代码在常量MAX
声明以前就调用,结果报错。
const
声明的常量,也与let
同样不可重复声明。
var message = "Hello!"; let age = 25; // 如下两行都会报错 const message = "Goodbye!"; const age = 30;
本质
const
实际上保证的,并非变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,所以等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const
只能保证这个指针是固定的(即老是指向另外一个固定的地址),至于它指向的数据结构是否是可变的,就彻底不能控制了。所以,将一个对象声明为常量必须很是当心。
const foo = {}; // 为 foo 添加一个属性,能够成功 foo.prop = 123; foo.prop // 123 // 将 foo 指向另外一个对象,就会报错 foo = {}; // TypeError: "foo" is read-only
上面代码中,常量foo
储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把foo
指向另外一个地址,但对象自己是可变的,因此依然能够为其添加新属性。
下面是另外一个例子。
const a = []; a.push('Hello'); // 可执行 a.length = 0; // 可执行 a = ['Dave']; // 报错
上面代码中,常量a
是一个数组,这个数组自己是可写的,可是若是将另外一个数组赋值给a
,就会报错。
若是真的想将对象冻结,应该使用Object.freeze
方法。
const foo = Object.freeze({}); // 常规模式时,下面一行不起做用; // 严格模式时,该行会报错 foo.prop = 123;
上面代码中,常量foo
指向一个冻结的对象,因此添加新属性不起做用,严格模式时还会报错。
除了将对象自己冻结,对象的属性也应该冻结。下面是一个将对象完全冻结的函数。
var constantize = (obj) => { Object.freeze(obj); Object.keys(obj).forEach( (key, i) => { if ( typeof obj[key] === 'object' ) { constantize( obj[key] ); } }); };