1.let-const塊級作用域的補充
const names=["abc","cba","nba"];
for(let i=0;i<names.length;i++){ //* 這個數(shù)組的元素有三個篙程,所以會形成3個塊級作用域
console.log(names[i]);
}
以上for遍歷內(nèi)部的實現(xiàn)是以下這樣的:
因為數(shù)組有3個元素,所以會產(chǎn)生3個塊級作用域别厘。
//第一個塊級作用域
{
let i=0;
console.log(names[i])
}
//第二個塊級作用域
{
把上次i++的結(jié)果賦值新定義的i //注意i++的i和let i不是同一個虱饿,js引擎會在內(nèi)部做一些處理,這里為了方便讓我們理解內(nèi)部的實現(xiàn)
let i=結(jié)果
console.log(names[i]);
}
//第三個塊級作用域
{
把上次i++的結(jié)果賦值給新定義的i
let i=結(jié)果
console.log(names[i]);
}
所以每個塊級作用域的i都不是同一個。
那使用const定義是不是可以實現(xiàn)呢?
如果將上面的代碼換一下:
const names=["abc","cba","nba"];
for(const i=0;i<names.length;i++){ //* 這個數(shù)組的元素有三個氮发,所以會形成3個塊級作用域
console.log(names[i]);
}
同樣地渴肉,因為names有三個元素,所以還是會形成三個作用域折柠。內(nèi)部實現(xiàn)是這樣的:
//第一個塊級作用域
{
const i=0;
console.log(names[i]);
}
//第二個塊級作用域 此時在i++的時候會報錯宾娜,因為const本質(zhì)是值不能改變,所以就會拋出錯誤
{
//注意i++的i和const i不是同一個扇售,js引擎會在內(nèi)部做一些處理前塔,這里為了方便讓我們理解內(nèi)部的實現(xiàn)
將i++的結(jié)果賦值給新定義的i //這一行就開始報錯
const i=結(jié)果
}
1.1 for...of 遍歷可迭代的數(shù)組(或?qū)ο?
const names=["abc","cba","nba"];
for(const item of names){ //* 這個數(shù)組的元素有三個,所以會形成3個塊級作用域
console.log(item);
}
for...of是將數(shù)組或?qū)ο竺總€元素取出來
內(nèi)部實現(xiàn):因為數(shù)組有3個元素承冰,所以還是會形成3個塊級作用域
//第一個塊級作用域
{
const item="abc";
console.log(item)
}
//第二個塊級作用域
{
const item="cba";
console.log(item)
}
//第三個塊級作用域
{
const item="nba";
console.log(item)
}
這里每個塊級作用域的item都不是同一個
2.暫時性死區(qū)
在ES6中华弓,我們還有一個概念稱之為暫時性死區(qū):
- 它表達(dá)的意思是在一個代碼塊中,使用let困乒、const聲明的變量寂屏,在聲明之前不可以訪問的
- 我們將這種現(xiàn)象稱之為 temporal dead zone(暫時性死區(qū),TDZ)(社區(qū))
以下兩段代碼都形成了暫時性死區(qū)娜搂,在let迁霎、const聲明之前,不能進行訪問百宇。
var foo="foo";
if(true){
console.log(foo);
let foo="abc"; //* Cannot access 'foo' before initialization
}
var foo="foo";
function bar(){
console.log(foo);
let foo="abc";
}
bar()
3.var考廉、let、const的選擇
對于var的使用:
- var有其自身的特殊性:作用域提升携御、在window對象上添加屬性昌粤、沒有塊級作用域,這些都是歷史遺留問題
- 其實這是javascript設(shè)計之初的一種語言缺陷
- 目前市場上也在利用這種缺陷出一系列的面試題啄刹,來考察大家對JavaScript語言本身以及底層的理解
- 但是在實際工作中涮坐,我們可以使用最新的規(guī)范來編寫,也就是不再使用var來定義變量了誓军。
對于let袱讹、const來說
- 對于let、const昵时,是目前開發(fā)中推薦使用的
- 我們優(yōu)先推薦使用const捷雕,這樣可以保證數(shù)據(jù)的安全性不會被隨意的篡改
- 只有當(dāng)我們明確知道一個變量后續(xù)會需要被重新賦值時,這個時候再使用let
- 這種在很多其他語言里面也都是一種約定俗成的規(guī)范债查,盡量我們也遵守這種規(guī)范非区。
4.字符串模板基本使用
在ES6之前,如果我們想要使用字符串和動態(tài)的變量(標(biāo)識符)拼接在一起盹廷,是非常麻煩和丑陋的(ugly)
ES6允許我們使用字符串模板來嵌入JS的變量或表達(dá)式來進行拼接:
- 首先征绸,我們會使用``符號來編寫字符串,稱之為 模板字符串
- 其次,在模板字符串中管怠,我們可以通過${expresion}來嵌入動態(tài)的內(nèi)容
// * 在ES6之前拼接字符串和動態(tài)的變量在一起淆衷,是非常麻煩的
var name="wjy";
var age=18;
var height=1.6
console.log("我的名字是:"+name+" 年齡是:"+age+" 身高:"+height);
在ES6中提供了模板字符串 ``
如果需要引用動態(tài)變量:${變量名}
// *在ES6當(dāng)中,提供了一個模板字符串 `` 引用動態(tài)變量是:${變量名}
const name="wjy";
const age=18;
const height=1.66
console.log(`我的名字是${name} 年齡是:${age} 身高:${height}`)
5.標(biāo)簽?zāi)0遄址?/h3>
模板字符串還有另外一種用法:標(biāo)簽?zāi)0遄址?(Tagged Template Literals)
普通JavaScript的函數(shù)調(diào)用
function foo(m,n){
console.log(m,n);
}
// * 函數(shù)調(diào)用最普通的方式
foo(20,39)
如果我們使用標(biāo)簽?zāi)0遄址吵冢⑶以谡{(diào)用的時候插入其他的變量:
- 模板字符串被拆分了
- 第一個元素是數(shù)組祝拯,是被模板字符串拆分的字符串組合
- 后面的元素是一個個模板字符串傳入的內(nèi)容
function foo(m,n){
console.log(m,n);
}
// * 函數(shù)調(diào)用最普通的方式
foo(20,39)
// * 另外調(diào)用函數(shù)的方式:標(biāo)簽?zāi)0遄址?
foo``;//[ '' ] undefined
foo`hello World` //[ 'hello World' ] undefined
const name="wjy";
const age=20;
// * 第一個參數(shù)依然是模板字符串的完整字符串,只是被切成了多塊她肯,放到了數(shù)組中 第二個參數(shù)是 模板字符串中佳头,第一個${expression}的expression的值
foo`Hello${age}Wo${name}rld`;//[ 'Hello', 'Wo', 'rld' ] wjy
6.函數(shù)的默認(rèn)參數(shù)
在ES5的時候,可以給參數(shù)設(shè)置值晴氨,使用的是 邏輯或:如果前面為真康嘉,則返回前面的表達(dá)式的值,如果前面為false則將后面的表達(dá)式的值返回籽前。
但是存在缺陷:
- 寫起來比較麻煩亭珍,閱讀性差
- 存在缺陷:如果傳入的值是0或者是"",但還是設(shè)置"aaa"或"bbb"
function foo(m,n){
// * 如果沒有給函數(shù)傳參:那么m,n會是undefined枝哄,如果對m肄梨、n進行操作時,很容易產(chǎn)生錯誤
// * ES5之前是怎么給參數(shù)默認(rèn)值 邏輯或
/**
* 缺點:
* 1.寫起來很麻煩 挠锥,并且代碼的閱讀性比較差
* 2.這種寫起來是有bug 如果傳入的是 0 "" ,但是會被設(shè)置 為"aaa"或"bbb"
*
*/
m=m|| "aaa";
n=n||"bbb"
console.log(m,n);
}
foo();
foo(100);
foo(100,200)
但在ES6中提供了給函數(shù)的參數(shù)設(shè)置默認(rèn)值众羡,直接參數(shù)后面使用="值"
function foo(m="aaa",n="bbb"){
console.log(m,n);
}
foo();//aaa bbb
foo(100);//100 bbb
foo(100,200);//100 200
上面的代碼,轉(zhuǎn)化為ES5是這樣的:
"use strict";
function foo() {
var m =
arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "aaa";
var n =
arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "bbb";
console.log(m, n);
}
foo(); //aaa bbb
foo(100); //100 bbb
foo(100, 200); //100 200
6.1 函數(shù)的默認(rèn)值
// * 可以傳基本數(shù)據(jù)類型
function foo(m="aaa",n="bbb"){
console.log(m,n);
}
foo();//aaa bbb
foo(100);//100 bbb
foo(100,200);//100 200
6.7 函數(shù)默認(rèn)值的補充
-
默認(rèn)值也可以和解構(gòu)一起使用
function printInfo({name,age}={name:"wjy",age:18}){ console.log(name,age); } printInfo() // * 另外一個寫法 function printInfo({name="wjy",age=20}={}){ console.log(name,age); } printInfo()
-
另外參數(shù)的默認(rèn)值我們通常會放到最后(在很多語言中瘪贱,如果不放到最后其實會報錯的)
- 但是Javascript允許不將其放到最后纱控,但是意味著還是會按照順序來匹配
另外默認(rèn)值會改變函數(shù)的length的個數(shù)辆毡,默認(rèn)值以及后面的參數(shù)都不計算在length之內(nèi)的
// * 有默認(rèn)值的函數(shù)的length屬性
console.log(bar.length); //2
function baz(x=20,y,z){
}
console.log(baz.length);//0
7.函數(shù)的剩余參數(shù)
ES6中引用了rest parameter菜秦,可以將不定數(shù)量的參數(shù)放入到一個數(shù)組中:
如果最后一個參數(shù)是以...為前綴,那么它會將剩余的參數(shù)放到該參數(shù)中舶掖,并且作為一個數(shù)組
那么arguments和剩余參數(shù)有什么區(qū)別球昨?
- 剩余參數(shù)只包含沒有對應(yīng)形參的實參,而arguments包含傳遞給函數(shù)的所有實參
- arguments對象不是一個真正的數(shù)組眨攘,而剩余參數(shù)是一個真正的數(shù)組主慰,可以進行數(shù)組的所有操作。
- arguments是早期的ECMAScript中為了方便去獲取所有的參數(shù)提供的一個數(shù)據(jù)結(jié)構(gòu)鲫售,而剩余參數(shù)是ES6提供的共螺,并且希望以此替代arguments
剩余參數(shù)必須是 函數(shù)的參數(shù)的最后一個位置,否則會報錯情竹。
function foo(m,n,...args){
console.log(m,n);//10 20
console.log(args); //[ 30, 40, 50, 60, 70 ]
}
foo(10,20,30,40,50,60,70)
function bar(...args,m,n){ //*Rest parameter must be last formal parameter
console.log(args);
console.log(m);
console.log(n);
}
bar(1,2,3,4)
8.函數(shù)箭頭函數(shù)的補充
- 箭頭函數(shù)是沒有顯式原型的藐不,所以不能作為構(gòu)造函數(shù)
- 箭頭函數(shù)沒有this,箭頭函數(shù)的this會去上層作用域查找,如果還沒有找到雏蛮,會繼續(xù)往上查找涎嚼。直到全局作用域中
- 箭頭函數(shù)沒有arguments
var bar=()=>{
}
console.log(bar.prototype);//undefined
console.log(new bar());//* bar is not a constructor
9.展開語法(Spread syntax)
- 可以在函數(shù)調(diào)用/數(shù)組構(gòu)造時,將數(shù)組表達(dá)式或者string在語法層面展開
- 還可以在構(gòu)造字面量對象時挑秉,將對象表達(dá)式按key-value的方式展開
const names=["abc","wjy","nba"];
// * 1.函數(shù)調(diào)用時
function foo(x,y,z){
console.log(x,y,z);
}
foo.apply(null,names);//這種方式也可以法梯,但是閱讀性非常差
foo(...names)
let str="wjy";
foo(...str)
// * 2.構(gòu)造數(shù)組時
const newNames=[...names]
//* 3. ES2018 (ES9)構(gòu)造 字面量對象
let info={
name:"wjy",
age:20,
height:160
}
let newInfo={...info,...names}; //* 數(shù)組展開在對象中,key是對應(yīng)的索引值
console.log(newInfo);
// * 展開運算符其實是一個淺拷貝
9.1 展開運算符的淺拷貝
const info={
name:"wjy",
friend:{
name:"kobe"
}
}
// * 淺拷貝的是拷貝的對象的第一層屬性的值犀概,如果拷貝的對象的屬性的值引用類型立哑,其實在拷貝的對象中對應(yīng)的屬性的引用的是同一片內(nèi)存空間
const newInfo={...info}
newInfo.friend.name="hyz"
console.log(info);
10.數(shù)值的表示
在ES6中規(guī)范了二進制和八進制的寫法:
- 二進制:以0b開頭
- 八進制:以0O開頭
- 十六進制:以0x開頭
let num1=100;//默認(rèn)是10進制
let num2=0b100;//二進制
let num3=0O100;//八進制
let num4=0x100;//十六進制
console.log(num1,num2,num3,num4);//100 4 64 256
// * 大的數(shù)值(在ES2021 ES12中),允許使用下劃線進行連接
// const num=10000000000;//這種可讀性非常差
const num=10_000_000_000;
console.log(num);
11.Symbol的基本使用
Symbol是ES6新增的一個基本的數(shù)據(jù)類型,翻譯為符號姻灶。
那么為什么需要Symbol呢刁憋?
- 在ES6之前,對象的屬性都是字符串形式木蹬,那么很容易造成屬性名的沖突
- 比如原來有一個對象至耻,我們希望在其中添加一個新的屬性和值,但是在我們不確定它原來內(nèi)部有什么內(nèi)容的情況下镊叁,很容易造成沖突尘颓,從而覆蓋它內(nèi)部的某個屬性
- 比如我們前面實現(xiàn)講apply、call晦譬、bind實現(xiàn)時疤苹,我們給其中添加一個fn屬性,那么如果它內(nèi)部原來有一個fn屬性了呢敛腌?
- 如果在開發(fā)中我們使用了混入卧土,那么混入中出現(xiàn)了同名的屬性,必然有一個會被覆蓋掉像樊。
Symbol就是為了解決上面的問題尤莺,用來生成一個獨一無二的值
- Symbol的值是通過Symbol函數(shù)來生成的,生成后可以作為屬性名
- 也就是在ES6中生棍,對象的屬性名就可以使用字符串颤霎,也可以使用Symbol值
Symbol即使多次創(chuàng)建值,它們也是不同的:Symbol函數(shù)每次創(chuàng)建出來的值都是獨一無二的
我們也可以在創(chuàng)建Symbol值的時候傳入一個描述description:這個是ES2019(ES10)新增的特性
11.1 Symbol作為屬性名
// * 3.Symbol作為key
// * 寫法1
const obj2={
[s1]:"abc",
[s2]:"wjy"
}
console.log(obj2);
// * 新增:ES2019 ES10新增可以向Symbol傳入一個description
obj2[s3]="hyz"
console.log(obj2);
// * 新增
Object.defineProperty(obj2,Symbol("s4"),{
value:"s4",
configurable:true,
enumerable:true,
writable:true
})
// * 獲取:只能通過[]獲取涂滴,不能通過.語法獲取
console.log(obj2[s1]);
console.log(obj2.s1);//* undefined 這樣是獲取不到的友酱,因為通過.獲取的時候,會根據(jù)后面的名字去對象找對象的屬性名為這個的值
// * 4.使用Symbol作為key柔纵,在遍歷/Object.keys是獲取不到Symbol屬性值的
// * 需要通過Object.getOwnPropertySymbols來獲取所有的Symbol的key
console.log(Object.keys(obj2));//[]
console.log(Object.getOwnPropertyNames(obj2));//[]
console.log(Object.getOwnPropertySymbols(obj2));//[ Symbol(), Symbol(), Symbol(hyz), Symbol(s4) ]
let sKeys=Object.getOwnPropertySymbols(obj2)
for(const item of sKeys ){
console.log(obj2[item]);
}
11.2 相同的key生成相同的Symbol
如果想根據(jù)相同的key創(chuàng)建相同的Symbol可以使用Symbol.for(key)
可以通過Symbol.keyFor獲取對應(yīng)的key
// * 5. Symbol.for(key) :如果想要創(chuàng)建一樣的Symbol值:通過相同的key創(chuàng)建相同的Symbol
const sa=Symbol.for("aaa");
const sb=Symbol.for("aaa");
console.log(sa==sb);//true
// * 獲取key
const key=Symbol.keyFor(sa);
console.log(key);