這是我參與8月更文挑戰的第1天,活動詳情查看:8月更文挑戰javascript
瞭解 JavaScript 的人比比皆是,理解 JavaScript 的人寥寥無幾。java
本文將帶着你們一塊兒深刻了解 JavaScript 基礎之變量聲明,廢話少說,那就開始吧。es6
能夠看下下面的思惟導圖編程
變量聲明的方式有:瀏覽器
咱們先來看看不使用 var 聲明,可能會出現什麼問題?安全
在控制檯輸入如下代碼markdown
myName = '追夢玩家';
console.log(window.myName); // 請問,這個結果是?
function fn() {
myName = '追夢玩家1';
}
fn();
console.log(window.myName); // 請問,這個結果是?
複製代碼
結果,能夠看下截圖app
有個疑問,爲何我在 fn 函數裏面給 myName 進行賦值 "追夢玩家1",執行完 fn 函數,打印 window.myName,打印出的結果是「追夢玩家1」?編程語言
其實這就是涉及到一個概念,就是不使用 var 聲明的變量,是全局變量,全局變量其實就是在 window 對象中,添加屬性並賦值。函數
最重要的一點,就是若是在函數裏面,執行 myName = '追夢玩家1';
這個代碼,至關於從新設置下 window.myName,這樣的話,有可能覆蓋以前聲明的變量值。
// myName = '追夢玩家1'; // 至關於直接給 window 添加屬性並賦值
window.myName = '追夢玩家1';
複製代碼
相信你們平時聲明變量,都是使用 let 或者 const 聲明,那爲何還要說下 var 聲明變量的優缺點呢?
之前使用 var 聲明變量,多是有它存在的意義。而新特性的出現,每每都是爲了解決某些問題而出現。
若是隻知其然而不知其因此然,只是去背用法,但不知道爲何須要這樣用,不只不能融會貫通,並且也沒有辦法促使本身進步。
所以在下面介紹 let 和 const 以前,咱們要先了解下使用 var 聲明有什麼優缺點,才能理解爲何會有 let 和 const 的出現。
我以爲使用 var 聲明變量,仍是有優勢,只不過很少。好比,上面在函數裏面,不使用 var 聲明的變量,會覆蓋以前聲明的變量值。
使用 var 聲明,能夠解決「函數執行,覆蓋以前的聲明變量」的問題
function fn() {
var myName = '追夢玩家';
}
fn();
console.log(window.myName); // 請問,這個結果是?
複製代碼
相信你們動手嘗試下,均可以知道,這個結果是 "",而不是 「追夢玩家」。可是在全局做用域下,使用 var 聲明變量,仍是至關於給 window 添加屬性,那有辦法解決嗎?請看下面使用 var 聲明的缺點。
在全局做用域下聲明一個變量,也至關於給 window 全局對象設置了一個屬性,變量的值就是屬性值(私有做用域中的聲明的私有變量和 window 沒啥關係)簡單來講,全局變量和 window 中的屬性存在「映射機制」 一個被修改,另一個也跟着變 。
在控制檯輸入如下代碼,看下輸出結果
// 說明會給 window 設置屬性
var myName = '追夢玩家';
console.log(window.myName); // 請問,這個結果是?
// 說明:全局變量和 window 的屬性,會存在「映射機制」
window.myName = '磚家';
console.log(myName); // 請問,這個結果是?
複製代碼
若是你們理解「映射機制」的話,就很好懂了,就是給 myName 或者 window.myName 賦值,window.myName 和 myName 都會發生改變。
結果,能夠看下截圖
有辦法解決這個問題?有的,能夠經過當即執行函數來實現
當即執行函數的做用,就是建立一個獨立的做用域,這個做用域裏面的變量,外面訪問不到,即避免「變量污染」。
// 當即執行函數,其實就是聲明一個函數,而後立刻執行
!function() {
var myName = '追夢玩家';
}();
console.log(window.myName); // 輸出結果?
console.log(myName); // 輸出結果?
複製代碼
下面經過一個例子,來理解
console.log(myName); // 輸出結果?是否會報錯?
var myName = '追夢玩家';
console.log(myName); // 輸出結果?
複製代碼
相信你們,在學習 JavaScript 基礎的時候,都有遇到過相似的代碼。爲何在聲明 myName 變量以前,打印 myName,不會報錯,輸出結果是 undefined,這就是涉及到 JavaScript 一個概念:變量提高。
注:使用 function 聲明的函數,也會有變量提高。
想了解「變量提高」,可看這篇文章:我知道你懂 hoisting,但是你瞭解到多深?
即便在同一個做用域,一樣名稱的變量,也容許重複聲明,不會出現任何錯誤或者警告,很容易忽略本身有聲明過這個變量。
值得注意的是,重複聲明一個變量,並不會從新設置這個變量的值。
舉個例子,好比一段很長的代碼,咱們可能不記得前面有聲明過變量,後面再次使用 var 聲明,當作第一次聲明,容易形成小 Bug。
var myName = '追夢玩家';
// 寫了不少代碼以後
// ...
// ...
// ...
var myName;
while(true){
if( name === undefined ){
console.log('The first time to execute.'); // 這裏會被執行嗎?
}
// ...
}
複製代碼
先看一下這個例子:
function fn() {
{
var myName = '追夢玩家';
}
console.log('fn(): myName=', myName);
}
fn();
複製代碼
執行結果:
fn(): myName= 追夢玩家
複製代碼
使用 var 聲明的變量並不具有塊級做用域的效果。
而塊級做用域特性在其餘編程語言很常見,es6 出現的塊級做用域,其實就是參考其餘編程語言,借鑑優秀經驗。
常量指的是「固定不變的值,沒法從新修改的」。
在程序裏面,有時候,有些值是隻須要聲明一次,不須要也不但願在程序執行的過程當中被修改。
例如,數學的 PI 是 3.14,若是是程序中某個地方修改了這個值,就會致使結果出現錯誤。
var PI = 3.14;
PI = 1024;
複製代碼
可是使用 var 聲明,沒有辦法作到這一點,你想怎麼改變值都是能夠,程序代碼執行,會存在不肯定性,有風險。
許多其餘編程語言,對於常量,都有提供管控機制來提升安全性,一旦誤該,可能編譯階段就能發現,甚至在 IDE 在編寫代碼階段就能提醒,減小 debug 負擔。
let 跟 var 的做用差很少,但有着很是重要的區別。最明顯的區別是,let 聲明的範圍是塊級做用域,而 var 聲明的範圍是函數做用域。let 是在 ES6 版本出現的,其實就是爲了解決使用 var 聲明變量,遇到的問題,或者說是缺點。具體能夠繼續往下看。
能夠看下下面的例子:
var myName = '追夢玩家';
console.log(window.myName); // 輸出結果?
let age = 18;
console.log(window.age); // 輸出結果?
window.age = 30;
console.log(age); // 輸出結果?
複製代碼
具體結果,請看截圖
說明一下,上面代碼爲何給 window.age 進行賦值呢?由於使用 var 聲明的話,會跟 window 的屬性存在「映射機制」,因此要這樣去測試下。
修改了 window.age,打印 age,仍是 18,因此得出結論:使用 let 在全局做用域中聲明的變量不會成爲 window 對象的屬性。
看下下面的例子:
console.log(myName); // 輸出結果?
let myName = '追夢玩家';
複製代碼
若是是使用 var 聲明的話,輸出的結果確定是 undefined,可是使用 let 聲明的話,是輸出 undefined,仍是報錯?
結果固然是報錯啦。由於 let 是沒有變量提高,在聲明以前訪問 myName 變量的話,就出現報錯:
看下下面的例子:
let myName = '追夢玩家';
// ... 寫了好多代碼
let myName;
複製代碼
在控制檯,執行上面的代碼,會出現報錯,瀏覽器說,myName 已經被聲明過了,你爲何還要聲明,給個語法報錯,本身看看吧。
咱們回憶下,若是是使用 var 聲明的變量,是否會出現報錯呢?打印 myName 變量的值是?
不會有報錯,myName 是追夢玩家。
首先先了解下,什麼是塊級做用域?
塊級做用域就是使用一對大括號包裹的一段代碼,好比函數、判斷語句、循環語句,甚至單獨的一個{}均可以被看做是一個塊級做用域。
//if塊
if(1){}
//while塊
while(1){}
//函數塊
function foo(){
//for循環塊
for(let i = 0; i<100; i++){}
//單獨一個塊
{}
複製代碼
簡單來講,塊級做用域裏面的定義的變量,在代碼塊外部是訪問不到的。
理解上面這句話,就知道執行下面的代碼,其實會報錯:Uncaught ReferenceError: myName is not defined
{
let myName = '追夢玩家';
}
console.log(myName); // 輸出結果?
複製代碼
咱們先來了解下,暫時性死區的做用:
ES6 規定暫時性死區和 let 、const 語句不出現變量提高,主要是爲了減小運行時的錯誤,防止在變量聲明前就是用這個變量,從而致使意料以外的行爲。
暫時性死區:只要塊級做用域內存在 let、const 命令,它所聲明的變量就「綁定」(binding)這個區域,再也不受外部的影響。
結合下面的例子來理解下:
// var num = 1;
if (true) {
num = 'a'; // Error: Uncaught ReferenceError: Cannot access 'num' before initialization
let num;
}
複製代碼
由於在 if 中聲明瞭一個局部變量,致使出現暫時性死區,if 裏面的 num 則與這個塊級做用域捆綁在一塊兒,不在受全局變量 num 的影響,同時 let 不存在變量提高,因此在 let 前賦值的 num 是非法的。那個報錯,就好像是瀏覽器說:不要在聲明以前使用 num。const 與之同理。
其本質就是,只要進入當前做用域,所要使用的變量就已經存在,可是不可獲取,只有等到聲明變量的那一行代碼出現,才能夠獲取和使用該變量。
由於 let 的做用域是塊,因此不可能檢查前面是否已經使用 let 聲明過同名變量,同時也就不可能在沒有聲明的狀況下聲明它。
看下下面這個例子:
if (typeof name === 'undefined') {
let name;
}
// name 被限制在 if {} 塊的做用域裏面
name = '追夢玩家';
// 執行上面這行代碼,至關因而給 window 添加一個屬性 name,進行賦值,而不是給變量 name 賦值,
// 由於變量 name 被限制在代碼塊的做用域裏面
複製代碼
const 的行爲與 let 基本相同,惟一一個重要的區別是用它聲明變量時必須同時初始化變量,且嘗試修改 const 聲明的變量會致使運行時錯誤。
上面這個區別,要怎麼理解呢?
能夠看下這個例子:
// 若是使用 const 聲明,後面進行修改,會出現報錯
const myName = '追夢玩家';
myName = '磚家';
複製代碼
這個報錯,就好像是瀏覽器說:不能夠給常量賦值,都說了是常量,懂不。
你們有可能看到過相似的代碼
const person = { name: '追夢玩家' };
person.name = '磚家';
person.age = 18;
console.log(person); // 輸出結果?
複製代碼
並無出現報錯,而是輸出一個對象。
這其實就是涉及到一個問題:const 定義的對象屬性是否能夠改變。
person 對象的 name 屬性確實被修改了,也能夠給 person 對象添加屬性,怎麼理解這個現象呢?
由於對象是引用類型,person 中保存的僅僅是內存地址,也就是 const 聲明的限制只適用於它指向的變量的引用。
簡單來講,怎麼理解內存地址呢?
內存地址,就比如就是倉庫的鑰匙,由於對象,是能夠往裏面添加、修改屬性,就比如如,是一個大倉庫。你有倉庫的鑰匙,就能夠往裏面放東西。
換句話說,使用 const 定義的對象,是能夠修改這個對象內部的屬性的。
function 關鍵字,建立函數,其實函數名也是變量,只不過存儲的值是引用數據類型而已。
在JS中,函數就是一個方法(一個功能體),基於函數通常都是爲了實現某個功能「好比洗衣機就是一個函數」,爲何說洗衣機是一個函數呢?
其實,洗衣機提供入口,給你放衣服和洗衣粉以後,洗衣機將衣服洗乾淨以後,給你的是乾淨的衣服。提供入口,就至關因而函數的參數,給你乾淨的衣服,就至關於函數處理完某些操做,返回一些東西給你。
var total = 10;
total += 10;
total = total /2;
total = total.toFixed(2);
/* 上面這個代碼,就是想作下簡單的數學運算,若是在後續的代碼中, 咱們依然想實現相同的操做(加10除以2),咱們是要複製代碼,粘貼代碼, 這樣的話,就會致使頁面中存在大量冗餘的代碼,也下降了開發效率。 若是咱們能把實現這個功能的代碼進行「封裝」,後期須要這個功能,調用方法便可,方法也就是函數。*/
複製代碼
函數誕生的目的就是爲了實現封裝:把實現一個功能的代碼封裝到一個函數中,後期想要實現這個功能,只須要把函數執行便可,沒必要要再重複編寫重複的代碼,起到了低耦合高內聚(減小頁面中的冗餘代碼,提升代碼的重複使用率)的做用。
function fn(){
var total = 10;
total += 10;
total /= 2;
total = total.toFixed(2);
console.log(tatal);
}
fn();
fn();
...
// 想用多少次,咱們就執行多少次函數便可
複製代碼
ES3 標準
/* // => 建立函數 function 函數名([參數]){ 函數體:實現功能的JS代碼 } // => 把函數執行 函數名(); */
// 建立函數
function fn() {
console.log('hi');
}
fn(); // 函數執行
複製代碼
ES6 標準中建立箭頭函數
/* let 函數名(變量名) = ([參數])=>{ 函數體 } 函數名(); */
let fn = () =>{
let total = 10;
// ...
}
fn();
複製代碼
參數是函數的入口:當咱們在函數中封裝一個功能,發現一些原材料不肯定,須要執行函數的時候用戶傳遞進來才能夠,此時咱們就基於參數的機制,提供出入口。「好比是生產洗衣機的廠家,並不知道用戶是怎麼操做,他們只須要提供一些孔,入水口」
//=>此處的參數叫作形參:入口,形參是變量(n / m 就是變量)
function sum(n,m){
//=> n 和 m 分別對應 要求和的兩個數字
var total = 0;
total = n + m;
console.log(total);
}
// => 此處函數執行傳遞的值是實參:實參是具體的數據值
sum(10,20); // => n=10 m=20
sum(10); //=> n=10 m=undefined
sum(); //=> n 和 m 都是undefined
sum(10,20,30); // => n=10 m=20 30沒有形參變量接收
複製代碼
簡單來講,函數調用的時候,傳遞的是實參。建立的函數的時候,圓括號裏面的參數,是形參。
咱們先來看看這個需求:在函數外面獲取到 total 的值,要怎麼獲取呢?這就要用到 return 啦。
function fn(n,m) {
var total = 0; // => total:私有變量
total = n + m;
return total; // => 並非把 total 變量返回,返回的是變量存儲的值,return 返回的永遠是一個值
}
// 下面先說一下 fn 和 fn() 的區別
fn // => 表明的是函數自己 只建立函數,不去使用,並無意義
fn(10,20); // => 表明的是函數執行(不只如此,它表明的是函數執行後,返回的結果[return 返回的值])
var result = fn(1,2); // 函數執行,返回的結果是有 return 的內容決定的
console.log(result); // => 3
複製代碼
return 的做用
function fn(n,m) {
var total = 0; // => total:私有變量
total = n + m;
return total; // => 並非把 total 變量返回,返回的是變量存儲的值,return 返回的永遠是一個值
console.log('hi'); // 請問,這裏會被執行嗎?
}
fn(1, 2);
複製代碼
上面的 console,是不會被執行的,由於,return 後面的代碼再也不執行的。
不知道你們有沒有看到過這樣的代碼:
function fn() {
console.log('hi');
}
console.log(fn()); // 是否有返回值?返回的結果是?
複製代碼
執行上面的代碼,固然是有返回值,沒有顯示的使用 return,至關因而默認返回了 undefined。因此上面的返回結果是 undefined。
在每個 JavaScript 程序中,函數都會是你須要使用的工具,由於他們能提供的一種獨特的能力,讓你的代碼重複使用。在編程的時候,咱們沒法離開函數,不管是本身構建的函數仍是在使用 JavaScript 語言自己帶有的函數。
上面講的是函數的基本用法。後期也會深刻了解函數的運行機制、一些比較進階的函數特性之類的。
咱們瞭解下 import 的簡單用法,首先要知道什麼是模塊?
一個模塊(module)就是一個文件。一個腳本就是一個模塊。就這麼簡單。
模塊能夠互相加載,並可使用特殊的指令 export 和 import 來交換功能,從另外一個模塊調用一個模塊的函數:
好比,咱們有一個 utils.js 文件導出了一個工具函數:
// utils.js
// 格式化時間
export function formatTime(time) {
if (typeof time !== 'number' || time < 0) {
return time;
}
const hour = parseInt(time / 3600, 10);
time %= 3600;
const minute = parseInt(time / 60, 10);
time = parseInt(time % 60, 10);
const second = time;
return ([hour, minute, second]).map(function(n) {
n = n.toString();
return n[1] ? n : '0' + n;
}).join(':');
}
複製代碼
而後咱們在其餘模塊中須要使用到這個函數,因此使用 import 進行導入使用。
// main.js
import { formatTime } from './utils.js';
formatTime(160); // "00:02:40"
複製代碼
之前生成實例對象的傳統方法,是經過構造函數。ES6 引入了 Class 這個概念,做爲對象的模板。經過 class 關鍵字,能夠定義類。
簡單來講,class 就是語法糖,糖是 sweet,讓你內心甜甜的感受。能讓你少寫一些代碼,可是隻用語法糖,不學語法糖背後的原理,我是反對的。
依然以最簡單的代碼爲例,這就是 class。
class Human{
sayHi(){
console.log('hi')
}
}
複製代碼
後期,我會單獨寫一篇文章,會結合原型相關的知識,深刻了解 class 語法糖背後的原理。
學會了 prototype,再去學 class 就跟吃糖同樣簡單。