1.let-const塊級(jí)作用域的補(bǔ)充
const names=["abc","cba","nba"];
for(let i=0;i<names.length;i++){ //* 這個(gè)數(shù)組的元素有三個(gè),所以會(huì)形成3個(gè)塊級(jí)作用域
console.log(names[i]);
}
以上for遍歷內(nèi)部的實(shí)現(xiàn)是以下這樣的:
因?yàn)閿?shù)組有3個(gè)元素,所以會(huì)產(chǎn)生3個(gè)塊級(jí)作用域。
//第一個(gè)塊級(jí)作用域
{
let i=0;
console.log(names[i])
}
//第二個(gè)塊級(jí)作用域
{
把上次i++的結(jié)果賦值新定義的i //注意i++的i和let i不是同一個(gè),js引擎會(huì)在內(nèi)部做一些處理,這里為了方便讓我們理解內(nèi)部的實(shí)現(xiàn)
let i=結(jié)果
console.log(names[i]);
}
//第三個(gè)塊級(jí)作用域
{
把上次i++的結(jié)果賦值給新定義的i
let i=結(jié)果
console.log(names[i]);
}
所以每個(gè)塊級(jí)作用域的i都不是同一個(gè)。
那使用const定義是不是可以實(shí)現(xiàn)呢?
如果將上面的代碼換一下:
const names=["abc","cba","nba"];
for(const i=0;i<names.length;i++){ //* 這個(gè)數(shù)組的元素有三個(gè),所以會(huì)形成3個(gè)塊級(jí)作用域
console.log(names[i]);
}
同樣地,因?yàn)閚ames有三個(gè)元素,所以還是會(huì)形成三個(gè)作用域。內(nèi)部實(shí)現(xiàn)是這樣的:
//第一個(gè)塊級(jí)作用域
{
const i=0;
console.log(names[i]);
}
//第二個(gè)塊級(jí)作用域 此時(shí)在i++的時(shí)候會(huì)報(bào)錯(cuò),因?yàn)閏onst本質(zhì)是值不能改變,所以就會(huì)拋出錯(cuò)誤
{
//注意i++的i和const i不是同一個(gè),js引擎會(huì)在內(nèi)部做一些處理,這里為了方便讓我們理解內(nèi)部的實(shí)現(xiàn)
將i++的結(jié)果賦值給新定義的i //這一行就開始報(bào)錯(cuò)
const i=結(jié)果
}
1.1 for...of 遍歷可迭代的數(shù)組(或?qū)ο?
const names=["abc","cba","nba"];
for(const item of names){ //* 這個(gè)數(shù)組的元素有三個(gè),所以會(huì)形成3個(gè)塊級(jí)作用域
console.log(item);
}
for...of是將數(shù)組或?qū)ο竺總€(gè)元素取出來
內(nèi)部實(shí)現(xiàn):因?yàn)閿?shù)組有3個(gè)元素,所以還是會(huì)形成3個(gè)塊級(jí)作用域
//第一個(gè)塊級(jí)作用域
{
const item="abc";
console.log(item)
}
//第二個(gè)塊級(jí)作用域
{
const item="cba";
console.log(item)
}
//第三個(gè)塊級(jí)作用域
{
const item="nba";
console.log(item)
}
這里每個(gè)塊級(jí)作用域的item都不是同一個(gè)
2.暫時(shí)性死區(qū)
在ES6中,我們還有一個(gè)概念稱之為暫時(shí)性死區(qū):
- 它表達(dá)的意思是在一個(gè)代碼塊中,使用let、const聲明的變量,在聲明之前不可以訪問的
- 我們將這種現(xiàn)象稱之為 temporal dead zone(暫時(shí)性死區(qū),TDZ)(社區(qū))
以下兩段代碼都形成了暫時(shí)性死區(qū),在let、const聲明之前,不能進(jìn)行訪問。
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的選擇
對(duì)于var的使用:
- var有其自身的特殊性:作用域提升、在window對(duì)象上添加屬性、沒有塊級(jí)作用域,這些都是歷史遺留問題
- 其實(shí)這是javascript設(shè)計(jì)之初的一種語言缺陷
- 目前市場(chǎng)上也在利用這種缺陷出一系列的面試題,來考察大家對(duì)JavaScript語言本身以及底層的理解
- 但是在實(shí)際工作中,我們可以使用最新的規(guī)范來編寫,也就是不再使用var來定義變量了。
對(duì)于let、const來說
- 對(duì)于let、const,是目前開發(fā)中推薦使用的
- 我們優(yōu)先推薦使用const,這樣可以保證數(shù)據(jù)的安全性不會(huì)被隨意的篡改
- 只有當(dāng)我們明確知道一個(gè)變量后續(xù)會(huì)需要被重新賦值時(shí),這個(gè)時(shí)候再使用let
- 這種在很多其他語言里面也都是一種約定俗成的規(guī)范,盡量我們也遵守這種規(guī)范。
4.字符串模板基本使用
在ES6之前,如果我們想要使用字符串和動(dòng)態(tài)的變量(標(biāo)識(shí)符)拼接在一起,是非常麻煩和丑陋的(ugly)
ES6允許我們使用字符串模板來嵌入JS的變量或表達(dá)式來進(jìn)行拼接:
- 首先,我們會(huì)使用``符號(hào)來編寫字符串,稱之為 模板字符串
- 其次,在模板字符串中,我們可以通過${expresion}來嵌入動(dòng)態(tài)的內(nèi)容
// * 在ES6之前拼接字符串和動(dòng)態(tài)的變量在一起,是非常麻煩的
var name="wjy";
var age=18;
var height=1.6
console.log("我的名字是:"+name+" 年齡是:"+age+" 身高:"+height);
在ES6中提供了模板字符串 ``
如果需要引用動(dòng)態(tài)變量:${變量名}
// *在ES6當(dāng)中,提供了一個(gè)模板字符串 `` 引用動(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í)候插入其他的變量:
- 模板字符串被拆分了
- 第一個(gè)元素是數(shù)組,是被模板字符串拆分的字符串組合
- 后面的元素是一個(gè)個(gè)模板字符串傳入的內(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;
// * 第一個(gè)參數(shù)依然是模板字符串的完整字符串,只是被切成了多塊,放到了數(shù)組中 第二個(gè)參數(shù)是 模板字符串中,第一個(gè)${expression}的expression的值
foo`Hello${age}Wo${name}rld`;//[ 'Hello', 'Wo', 'rld' ] wjy
6.函數(shù)的默認(rèn)參數(shù)
在ES5的時(shí)候,可以給參數(shù)設(shè)置值,使用的是 邏輯或:如果前面為真,則返回前面的表達(dá)式的值,如果前面為false則將后面的表達(dá)式的值返回。
但是存在缺陷:
- 寫起來比較麻煩,閱讀性差
- 存在缺陷:如果傳入的值是0或者是"",但還是設(shè)置"aaa"或"bbb"
function foo(m,n){
// * 如果沒有給函數(shù)傳參:那么m,n會(huì)是undefined,如果對(duì)m、n進(jìn)行操作時(shí),很容易產(chǎn)生錯(cuò)誤
// * ES5之前是怎么給參數(shù)默認(rèn)值 邏輯或
/**
* 缺點(diǎn):
* 1.寫起來很麻煩 ,并且代碼的閱讀性比較差
* 2.這種寫起來是有bug 如果傳入的是 0 "" ,但是會(huì)被設(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)值的補(bǔ)充
-
默認(rèn)值也可以和解構(gòu)一起使用
function printInfo({name,age}={name:"wjy",age:18}){ console.log(name,age); } printInfo() // * 另外一個(gè)寫法 function printInfo({name="wjy",age=20}={}){ console.log(name,age); } printInfo() -
另外參數(shù)的默認(rèn)值我們通常會(huì)放到最后(在很多語言中,如果不放到最后其實(shí)會(huì)報(bào)錯(cuò)的)
- 但是Javascript允許不將其放到最后,但是意味著還是會(huì)按照順序來匹配
另外默認(rèn)值會(huì)改變函數(shù)的length的個(gè)數(shù),默認(rèn)值以及后面的參數(shù)都不計(jì)算在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ù)放入到一個(gè)數(shù)組中:
如果最后一個(gè)參數(shù)是以...為前綴,那么它會(huì)將剩余的參數(shù)放到該參數(shù)中,并且作為一個(gè)數(shù)組
那么arguments和剩余參數(shù)有什么區(qū)別?
- 剩余參數(shù)只包含沒有對(duì)應(yīng)形參的實(shí)參,而arguments包含傳遞給函數(shù)的所有實(shí)參
- arguments對(duì)象不是一個(gè)真正的數(shù)組,而剩余參數(shù)是一個(gè)真正的數(shù)組,可以進(jìn)行數(shù)組的所有操作。
- arguments是早期的ECMAScript中為了方便去獲取所有的參數(shù)提供的一個(gè)數(shù)據(jù)結(jié)構(gòu),而剩余參數(shù)是ES6提供的,并且希望以此替代arguments
剩余參數(shù)必須是 函數(shù)的參數(shù)的最后一個(gè)位置,否則會(huì)報(bào)錯(cuò)。
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ù)的補(bǔ)充
- 箭頭函數(shù)是沒有顯式原型的,所以不能作為構(gòu)造函數(shù)
- 箭頭函數(shù)沒有this,箭頭函數(shù)的this會(huì)去上層作用域查找,如果還沒有找到,會(huì)繼續(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í),將數(shù)組表達(dá)式或者string在語法層面展開
- 還可以在構(gòu)造字面量對(duì)象時(shí),將對(duì)象表達(dá)式按key-value的方式展開
const names=["abc","wjy","nba"];
// * 1.函數(shù)調(diào)用時(shí)
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ù)組時(shí)
const newNames=[...names]
//* 3. ES2018 (ES9)構(gòu)造 字面量對(duì)象
let info={
name:"wjy",
age:20,
height:160
}
let newInfo={...info,...names}; //* 數(shù)組展開在對(duì)象中,key是對(duì)應(yīng)的索引值
console.log(newInfo);
// * 展開運(yùn)算符其實(shí)是一個(gè)淺拷貝
9.1 展開運(yùn)算符的淺拷貝
const info={
name:"wjy",
friend:{
name:"kobe"
}
}
// * 淺拷貝的是拷貝的對(duì)象的第一層屬性的值,如果拷貝的對(duì)象的屬性的值引用類型,其實(shí)在拷貝的對(duì)象中對(duì)應(yīng)的屬性的引用的是同一片內(nèi)存空間
const newInfo={...info}
newInfo.friend.name="hyz"
console.log(info);
10.數(shù)值的表示
在ES6中規(guī)范了二進(jìn)制和八進(jìn)制的寫法:
- 二進(jìn)制:以0b開頭
- 八進(jìn)制:以0O開頭
- 十六進(jìn)制:以0x開頭
let num1=100;//默認(rèn)是10進(jìn)制
let num2=0b100;//二進(jìn)制
let num3=0O100;//八進(jìn)制
let num4=0x100;//十六進(jìn)制
console.log(num1,num2,num3,num4);//100 4 64 256
// * 大的數(shù)值(在ES2021 ES12中),允許使用下劃線進(jìn)行連接
// const num=10000000000;//這種可讀性非常差
const num=10_000_000_000;
console.log(num);
11.Symbol的基本使用
Symbol是ES6新增的一個(gè)基本的數(shù)據(jù)類型,翻譯為符號(hào)。
那么為什么需要Symbol呢?
- 在ES6之前,對(duì)象的屬性都是字符串形式,那么很容易造成屬性名的沖突
- 比如原來有一個(gè)對(duì)象,我們希望在其中添加一個(gè)新的屬性和值,但是在我們不確定它原來內(nèi)部有什么內(nèi)容的情況下,很容易造成沖突,從而覆蓋它內(nèi)部的某個(gè)屬性
- 比如我們前面實(shí)現(xiàn)講apply、call、bind實(shí)現(xiàn)時(shí),我們給其中添加一個(gè)fn屬性,那么如果它內(nèi)部原來有一個(gè)fn屬性了呢?
- 如果在開發(fā)中我們使用了混入,那么混入中出現(xiàn)了同名的屬性,必然有一個(gè)會(huì)被覆蓋掉。
Symbol就是為了解決上面的問題,用來生成一個(gè)獨(dú)一無二的值
- Symbol的值是通過Symbol函數(shù)來生成的,生成后可以作為屬性名
- 也就是在ES6中,對(duì)象的屬性名就可以使用字符串,也可以使用Symbol值
Symbol即使多次創(chuàng)建值,它們也是不同的:Symbol函數(shù)每次創(chuàng)建出來的值都是獨(dú)一無二的
我們也可以在創(chuàng)建Symbol值的時(shí)候傳入一個(gè)描述description:這個(gè)是ES2019(ES10)新增的特性
11.1 Symbol作為屬性名
// * 3.Symbol作為key
// * 寫法1
const obj2={
[s1]:"abc",
[s2]:"wjy"
}
console.log(obj2);
// * 新增:ES2019 ES10新增可以向Symbol傳入一個(gè)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 這樣是獲取不到的,因?yàn)橥ㄟ^.獲取的時(shí)候,會(huì)根據(jù)后面的名字去對(duì)象找對(duì)象的屬性名為這個(gè)的值
// * 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獲取對(duì)應(yīng)的key
// * 5. Symbol.for(key) :如果想要?jiǎng)?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);
12.總結(jié)
