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] ); } }); };