2020年已經到來,是否是該爲了更好的2020年再戰一回呢? ‘勝敗兵家事不期,包羞忍恥是男兒。江東子弟多才俊,捲土重來未可知’,那些在秋招失利的人,難道就心甘情願放棄嗎!css
此文總結2019年以來本人經歷以及瀏覽文章中,較熱門的一些面試題,涵蓋從CSS到JS再到Vue再到網絡等前端基礎到進階的一些知識。前端
總結面試題涉及的知識點是對本身的一個提高,也但願能夠幫助到同窗們,在2020年會有一個更好的競爭能力。java
css篇
- juejin.im/post/5e040e…Javavscript篇
- juejin.im/post/5e2122…ECMAScript
是編寫腳本語言的標準,意味着Javascript
遵循ECMAScript
標準中的規範變化,能夠說是Javascript
的藍圖node
ECMAScript
和Javascript
本質上都跟一門語言有關,一個是語言自己,一個是語言的約束條件。c++
ECMAScript 6
是一個新的標準,它包含了許多新的語言特性和庫,是Javascript
最實質的一次升級,好比箭頭函數、字符串模板、generator
(生成器)、async/await
、解構賦值、class
等等,以及引入了module
模塊概念es6
Class
Promise
Generator
生成器Symbol
類型Proxy
代理Set
& Map
rest
與展開運算符...
什麼是塊級做用域? 由一對花括號{}
中的語句集都屬於一個塊,在這個{}
代碼塊中定義的全部變量在這個代碼塊以外都是不可見的,所以稱爲塊級做用域面試
爲何須要塊級做用域?算法
因爲ECMAScript 6
以前只有全局做用域、函數做用域、eval做用域,沒有塊級概念,這會帶來不少不合理的場景:編程
var i = 5
function fun(){
console.log(i)
if(true){
var i = 6
}
}
fun() // undefined
複製代碼
for (var i = 0; i < 10; i++) {
console.log(i);
}
console.log(i); // 10
複製代碼
爲了解決這些問題,ECMAScript 6
新增了let
/ const
實現塊級做用域數組
let
與 const
,與var
區別在哪?let
let
- 用於聲明變量,用法與var
相似,但其聲明的變量,只在let
所在的代碼塊中有效{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
複製代碼
for (let i = 0; i < 10; i++) {
// ...
}
console.log(i) // ReferenceError: i is not defined
複製代碼
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
-------------- var → let ------------------
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
複製代碼
let
不存在變量提高 - var
所定義的變量,存在變量提高現象,便可以在聲明以前使用(值爲undefined
),let
改變了這一語法行爲,它所聲明的變量必定要在聲明以後才能使用,否在報錯console.log(bar); // ReferenceError
let bar = 2;
複製代碼
let
,它所聲明的變量就'綁定'在這個區域,不受外部影響var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
上面代碼中,存在全局變量tmp,可是塊級做用域內let又聲明瞭一個局部變量tmp,致使後者綁定這個塊級做用域,因此在let聲明變量前,對tmp賦值會報錯
複製代碼
❗ 小知識:
ECMAScript 6
明確規定,若是區塊中存在let | const
命令,這個區塊對這些命令聲明的變量,從一開始就造成封閉做用域,凡是在let
聲明變量前,對該變量操做,都會報錯(使用let、const命令聲明變量以前,該變量都是不可用的,這在語法上,成爲'暫時性死區')
let
不容許在相同做用域內,重複聲明同一個變量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
與 let
相同,只在聲明所在的塊級做用域內有效if (true) {
const MAX = 5;
}
MAX // Uncaught ReferenceError: MAX is not defined
複製代碼
const
一樣存在暫時性死區概念,只能在聲明的位置以後使用if (true) {
console.log(MAX); // ReferenceError
const MAX = 5;
}
複製代碼
const
聲明的變量,也不能重複聲明var message = "Hello!";
let age = 25;
// 如下兩行都會報錯
const message = "Goodbye!";
const age = 30;
複製代碼
1 - var【聲明變量】
var 沒有塊的概念,能夠跨塊訪問,沒法跨函數訪問
2 - let【聲明塊中的變量】 - 存在暫時性死區
let 只能在塊做用域裏訪問,不能跨塊訪問,更不能跨函數訪問
3 - const【聲明常量,一旦賦值便不可修改】 - 存在暫時性死區
const 只能在塊級做用域裏訪問,並且不能修改值
Tips: 這裏的不能修改,並非變量的值不能改動,而是變量所指向的那個內存地址保存的指針不能改動
4 - 在全局做用域下使用let和const聲明變量,變量並不會被掛載到window上,這一點與var不一樣
複製代碼
ECMAScript 6
標準新增了一種新的函數:Arrow Function(箭頭函數)
x => x*x
至關於
function(x) {
return x*x
}
複製代碼
this
、arguments
、super
或者new.target
// Es 5
var getDate = function(){
reutrn new Date()
}
// Es 6
var getDate = () => new Date()
在箭頭函數版本中,咱們只須要()括號,不須要 return 語句,由於若是咱們只有一個表達式或值須要返回,箭頭函數就會有一個隱式的返回
複製代碼
arguments
對象,因此調用第一個getArgs()
時會拋出錯誤,咱們能夠經過...rest
來存儲全部參數,並獲取const getArgs = () => arguments
getArgs('1','2','3') // ReferenceError: arguments is not defined
const getArgs2 = (...rest) => rest
getArgs('1','2','3') // ["1", "2", "3"]
複製代碼
this
,它捕獲詞法做用域函數的this
值const data = {
result:0,
nums:[1,2,3,4,5],
computeResult(){
// this → data對象
const addAll = () => {
return this.nums.reduce((total, cur) => total + cur, 0)
}
this.result = addAll()
}
}
複製代碼
這個例子中,addAll
函數將複製computeResult
方法中的this
值,若是咱們在全局做用域聲明箭頭函數,則this
值爲window
對象
this
對象,就是定義時所在的對象,而不是使用時所在的對象new
關鍵字,不然拋出錯誤arguments
對象,該對象在函數體內不存在(可用rest
參數代替)yield
命令,所以箭頭函數不能用做Generator
函數上面四點,第一點尤爲重要,this
對象的指向是可變的,但在箭頭函數中,this
是固定的,不可變的
const obj = {
a: () => {
console.log(this.id)
}
}
var id = '1'
obj.a() // '1'
obj.a.call({
id:'2'
}) // '1'
複製代碼
Javascript
中,生成實例對象的方法是經過構造函數,這種方式與傳統的面嚮對象語言(好比c++,java)差別很大,ECMAScript 6
提供了更接近於傳統面向對象的寫法,引入了Class
類的概念,做爲對象的模板。經過Class
關鍵字來定義類
Class
類是在Js中編寫構造函數的另外一種方式,本質上它就是使用構造函數的語法糖,在底層中使用仍然是原型和基`於原型的繼承
// Es 5
function Person(name, age, job){
this.name = name
this.age = age
this.job = job
}
Person.prototype.getPerson = function(){
return name + '|' + age + '|' + job
}
// Es 6
class Person {
constructor(name, age, job){
this.name = name
this.age = age
this.job = job
}
getPerson(){
return this.name + '|' + this.age + '|' + this.job
}
}
var person = new Person('chicago',22,'student')
person.getPerson() // chicago|22|student
複製代碼
Class
類中繼續存在,實際上,類的全部方法都定義在類的prototype屬性上class Person {
constructor(name, age, job){
this.name = name
this.age = age
this.job = job
}
getPerson(){
return this.name + '|' + this.age + '|' + this.job
}
}
console.log(Person.prototype) // {constructor: ƒ, getPerson: ƒ}
複製代碼
class A{
// ...
}
let a = new A()
console.log(a.constructor === A.prototype.constructor)
// 這其實很好理解,a實例上並無constructor屬性,因此會經過原型鏈向上查找屬性,最後在A類中找到constructor
複製代碼
class Point{
constructor(x,y){
// ...
}
toString(){
// ...
}
}
Object.keys(Point.prototype) // [] 這裏toString方法是Point類內部定義的方法,是不可枚舉的
Object.getOwnPropertyNames(Point.prototype) // ["constructor","toString"]
複製代碼
constructor
方法constructor
方法是類的默認方法,經過new
命令生成對象實例時,自動會調用該方法,一個類必須有constructor
方法,若是沒有顯式定義,一個空的constructor
會被默認添加class A{}
等同於
class A{
constructor(){}
}
複製代碼
this
),咱們徹底能夠指定返回另外一個對象class Foo{
constructor(){
return Object.create(null);
}
}
new Foo() instanceof Foo // false → constructor返回了一個全新的對象,致使實例對象再也不是Foo類的實例
複製代碼
this
上),不然都是定義在原型上class A{
constructor(x, y){
this.x = x
this.y = y
this.getA = function(){
console.log('A')
}
}
toString(){
return '(' + this.x + ', ' + this.y + ')'
}
}
var a = new A(2,3)
point.toString(); // (2,3)
point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('getA') // true
point.hasOwnProperty('toString') // false
point.__proto__.hasOwnProperty("toString") // true
複製代碼
x、y、getA都是實例對象a自身的屬性(由於定義在this變量上),因此hasOwnProperty方法返回true ,而toString是原型對象的屬性(由於定義在A類上),因此hasOwnProperty方法返回false
Es5
一致,類的全部實例也共享一個原型對象var p1 = new Person('chicago')
var p2 = new Person('Amy')
p1.__proto__ === p2.__proto__ // true
- p1 / p2 都是Person的實例,它們的原型都是`Person.prototype`,因此`__proto__`天然是相同的
- 這也意味着能夠經過實例的`__proto__`屬性爲'類'添加方法
p1.__proto__.getName = function(){
return 'i get name'
}
p1.getName() // i get name
p2.getName() // i get name
var p3 = new Person('Jack')
p3.getName() // i get name
複製代碼
Es 5
同樣,在'類'的內部能夠經過使用get
和set
關鍵字,對某個屬性設置存值函數與取值函數class A{
constructor(){
//...
}
get prop(){
return 'getter'
}
set prop(value){
console.log('setter:' + value)
}
}
let a = new A()
a.prop = 123 // setter:123
a.prop // getter
這裏`prop`屬性有對應的存值函數和取值函數。所以賦值和讀取行爲都被自定義了
❗ Ps - 存值函數和取值函數是設置在屬性的Descriptor對象上的
複製代碼
Class
的幾個注意點new
來調用,不然會報錯,這是與普通構造函數的一個主要區別,後者不須要new
也能夠執行class Foo{
constructor(){}
}
Foo() // TypeError: Class constructor Foo cannot be invoked without 'new'
複製代碼
use strict
指定運行模式(只要代碼寫在類之中,就只有嚴格模式可用)new Foo() // ReferenceError: Cannot access 'Foo' before initialization
class Foo{}
❗ Ps - Es6不會把類的聲明提高到代碼頭部
複製代碼
name
屬性 - 本質上,Es6
的類只是Es5
的構造函數的一層包裝,因此函數的許多特性都被class繼承,包括name
class A{}
A.name // A → name屬性老是返回跟在`class`關鍵字後面的類名
複製代碼
this
指向 - 類的方法內部若是含有this,this指向類的實例,經過this.xxx = ...
的方式賦值,都是在實例自己上操做
static
)含有this
關鍵字,則this
指向的是類,而不是實例模板字符串是在Js
中建立字符串的一種新方式,咱們能夠經過使用反引號讓模板字符串化
// Es 5
var greet = 'Hi I\'m Chicago' // Es 6 var greet = `Hi I'm Chicago`
複製代碼
${expr}
來界定// Es 5
var name = 'chicago'
console.log('hello' + name) // hello chicago
// Es 6
var name = 'chicago'
console.log(`hello ${name}`) // hello chicgao
複製代碼
//ES5
var str = '\n' + ' I \n' + ' Am \n' + 'Iron Man \n';
// Es 6
var str = `
I
Am
Iron Man
`
複製代碼
在ES6中當你的對象屬性名和當前做用域中的變量名相同時,ES6的對象會自動的幫你完成鍵到值的賦值
// Es 5
var foo = 'Foo'
var bar = 'Bar'
var A = {
foo:foo,
bar:bar
}
// Es 6
var foo = 'Foo'
var bar = 'Bar'
var A = {
foo,
bar
}
複製代碼
什麼是解構賦值? 從數組和對象中提取值,對變量進行賦值,就稱爲解構賦值
let a = 1,b = 2,c = 3
等同於
let [a, b, c] = [1, 2, 3]
複製代碼
從對象中獲取屬性,Es6
前的作法是建立一個與對象屬性同名的變量,這種方式較繁瑣,由於每個屬性都須要一個新變量,Es6
中解構賦值就完美的解決這一問題
const person = {
name: "Chicago",
age: "22",
job:'student'
}
// Es 5
var name = person.name
var age = person.age
var job = person.job
// Es 6 解構賦值
var {name, age, job} = person
複製代碼
let [foo,[bar],baz] = [1,[2],3]
let [,,c] = ['a','b','c']
let [a,,c] = ['a','b','c']
let [a,b,...c] = ['a'] // a:'a' b:undefined c:[]
複製代碼
let [a] = [] // a:undefined
複製代碼
let [a,b] = [1,2,3]
let [a,[b],d] = [1,[2,3],4] // a:1 b:2 c:4
複製代碼
let [foo] = 1 // TypeError: 1 is not iterable
let [foo] = {} // TypeError: {} is not iterable
...
複製代碼
function* fibs(){
let a = 0
let b = 1
while(true){
yield a;
[a,b] = [b,a + b]
}
}
let [first,second,third,fourth,fifth,sixth] = fibs()
sixth // 5
複製代碼
let [foo = 'Foo'] = [] // foo:'Foo'
let [x,y = 'b'] = ['a'] // x:'a' y:'b'
let [x,y = 'b'] = ['a', undefined] // x:'a' y:'b'
❗ Ps:因爲Es6內部使用嚴格相等運算符(===)來進行判斷是否有值,因此只有一個數組成員嚴格等於undefined時,默認值才生效
let [x = 1] = [null] // x:null
❗ Ps:默認值能夠引用解構賦值的其餘變量,但該變量必須已經聲明
let [x = 1, y = x] = [] // x:1 y:1
let [x = 1, y = x] = [2] // x:2 y:2
let [x = 1, y = x] = [1, 2] // x:1 y:2
let [x = y, y = 1] = [] // ReferenceError: Cannot access 'y' before initialization
複製代碼
let {foo,bar} = { foo:'Foo', bar:'Bar' } // foo:'Foo' bar:'Bar'
let {baz} = { foo:'Foo', bar:'Bar' } // baz:undefined
複製代碼
let { foo:baz } = { foo:'Foo', bar:'Bar' } // baz:'Foo'
{ 屬性名:變量名 } - 真正被賦值的是後者
複製代碼
var { x = 3 } = {} // x:3
var { message: msg = 'Hello Es 6' } = {} // msg:'Hello Es 6'
複製代碼
Promise能夠說是Es6中最重要的一個知識點,想要真正的學好Promise,須要較大篇幅,這裏建議瀏覽阮一峯博客(es6.ruanyifeng.com/#docs/promi…
Promise
的意義 - Promise
是異步編程的一種解決方案,是一個對象,經過它能夠獲取異步操做的消息Promise
對象有如下特色
Promise
對象表明一個異步操做,具備三種狀態:pending(進行中)
、fulfilled(已成功)
、rejected(已失敗)
。只有異步操做的結果能夠決定當前是哪種狀態,任何其餘操做都沒法改變這個狀態❗ 小知識:
Promise
對象的狀態改變,只有兩種可能
pending
變爲fulfilled
pending
變爲rejected
只要這兩種狀況發生,狀態就凝固了(不會再變化),會一直保持這個結果,這時就稱爲resolved(已定型),若是改變已經發生,再對Promise
對象添加回調函數,也會當即獲得這個結果
Event
徹底不一樣,Event
的特色是,若是你錯過了它,再去監聽,是得不到結果的Promise
如何解決回調地獄?回調地獄 - 若是咱們在回調內部存在另一個異步操做,即出現多層嵌套問題,致使變成一段混亂且不可讀的代碼,此代碼就稱爲'回調地獄'
這兩個問題在回調函數中尤其突出,Promise
的誕生就是爲了解決這兩個問題
Promise
解決'回調地獄'
Promise
經過此三個方面解決問題:
let readFilePromise = (fileName) => {
fs.readFile(fileName, (err, data) => {
if(err){
reject(err)
}else{
resolve(data)
}
})
}
readFilePromise('xxx1').then( res => {
return readFilePromise('xxx2')
})
複製代碼
上面代碼中,回調函數經過在then()
中傳入,實現了回調函數延遲綁定
let promise = readFilePromise('xxx1').then( res => {
return readFilePromise('xxx2') // 返回一個Promise對象
})
promise.then( //... )
複製代碼
根據在then()
中回調函數的傳入值建立不一樣的Promise
,而後把返回的Promise
穿透到外層,後續可繼續使用,上面promise
實際上就是內部返回的Promise
,promise
變量能夠繼續調用then()
,這即是返回值穿透
解決多層嵌套,就是結合回調函數延遲綁定
和返回值穿透
,實現鏈式調用
來解決
readFilePromise('xxx1').then( res => {
return readFilePromise('xxx2')
}).then( res => {
return readFilePromise('xxx3')
}).then( res => {
return readFilePromise('xxx4')
})
複製代碼
鏈式調用
解決多層嵌套,那麼每次任務執行結束後成功和失敗的分別處理,又經過什麼解決?
Promise
經過錯誤冒泡
的方式來解決問題readFilePromise('xxx1').then( res => {
return readFilePromise('xxx2')
}).then( res => {
return readFilePromise('xxx3')
}).catch( err => {
// 錯誤處理
})
複製代碼
上面的代碼中,不管是前面的錯誤仍是後面的錯誤,全部產生的錯誤都會一直向後傳遞,被catch()
接收到,這樣一來就沒必要重複就處理每個任務的成功和失敗結果
then()
Promise
具備許多Api
then
- 爲Promise實例添加狀態改變時的回調函數catch
- 用於指定發生錯誤時的回調函數finally
- 用於指定無論Promise對象最後狀態如何,都會執行的操做all
- 用於將多個Promise實例,包裝成一個新的Promise實例race
- 與all同樣,將多個實例包裝成一個新的Promise實例allSettled
- 接受一組 Promise 實例做爲參數,包裝成一個新的 Promise 實例。只有等到全部這些參數實例都返回結果,無論是fulfilled仍是rejected,包裝實例纔會結束any
- 方法接受一組 Promise 實例做爲參數,包裝成一個新的 Promise 實例。只要參數實例有一個變成fulfilled狀態,包裝實例就會變成fulfilled狀態;若是全部參數實例都變成rejected狀態,包裝實例就會變成rejected狀態resolve
- 將現有對象轉爲 Promise 對象reject
- 返回一個新的 Promise 實例,該實例的狀態爲rejectedtry
其中,then()
是Promise
中出現概率最高的,所以then()
返回值是必須理解的
對於一個Promise
來講,當一個Promise
完成(fulfilled
)或者失敗(rejected
),返回函數將被異步調用。具體的返回值依據如下規則返回:
then
中的回調函數返回一個值,那麼then
返回的Promise
就會變成接受狀態(resolved
),而且將返回的值做爲接受狀態的回調函數的參數值promise1().then( () => {
return 'I am return a value'
}).then( res => {
console.log(res) // I am return a value
})
複製代碼
then
中的回調函數沒有返回值,那麼then
返回的Promise將會成爲接受狀態(resolved
),而且該接受狀態的回調函數的參數值爲undefined
promise1().then( () => {
console.log('nothing return')
}).then( res => {
console.log(res) // undefined
})
複製代碼
then
中的回調函數拋出一個錯誤,那麼then
返回的Promise
將會成爲拒絕狀態(rejected
),而且將拋出的錯誤做爲拒絕狀態的回調函數的參數值promise1().then( () => {
throw new Error('I am Error')
}).then( res => {
console.log(res) // 不執行
}).catch( err => {
console.log(err) // I am Error
})
複製代碼
then
中的回調函數返回一個已是接受狀態的Promise
,那麼then
返回的Promise
也會成爲接受狀態,而且將那個Promise
的接受狀態的回調函數的參數值做爲該被返回的Promise
的接受狀態的回調函數的參數值var promise1 = function(){
return new Promise((resolve,reject)=>{
resolve()
})
}
var promise2 = function(){
return new Promise((resolve,reject) => {
resolve('I am p2 and Im resolved')
})
}
promise1().then( () => {
// 返回一個已是接受狀態的Promise
return promise2() // 若是不加return,這個回調將沒有返回值,參考第二點
}).then( res => {
console.log(res) // I am p2 and Im resolved
})
複製代碼
then
中的回調函數返回一個已是拒絕狀態的Promise
,那麼then
返回的Promise也會成爲拒絕狀態,而且將那個Promise
的拒絕狀態的回調函數的參數值做爲該被返回的Promise
的拒絕狀態的回調函數的參數值var promise1 = function(){
return new Promise((resolve,reject)=>{
resolve()
})
}
var promise2 = function(){
return new Promise((resolve,reject) =>{
reject('I am p2 and Im rejected')
})
}
promise1().then( () => {
// 返回一個已是拒絕狀態的Promise
return promise2() // 若是不加return,這個回調將沒有返回值,參考第二點
}).then( res => {
console.log(res) // 不執行
}).catch( err => {
console.log(err) // I am p2 and Im rejected
})
複製代碼
then
中的回調函數返回一個**未定狀態(pending)**的Promise
,那麼then
返回的Promise
也是未定狀態,而且它的最終狀態會與那個Promise
的最終狀態相同,同時,它變爲終態時調用的回調函數的參數值與那個Promise
變爲終態時的回調函數的參數值相同var promise1 = function(){
return new Promise((resolve,reject)=>{
resolve()
})
}
var promise2 = function(){
// 定時器,初始狀態未定,3s後更新狀態
return new Promise((resolve,reject) => {
setTimeout(()=>{
resolve('p2 after 3s resolve')
// reject('p2 after 3s reject')
},3000)
})
}
promise1().then( () => {
return promise2()
}).then( res => {
console.log(res) // p2 resolve → p2 after 3s resolve
}).catch( err => {
console.log(err) // p2 reject → p2 after 3s reject
})
以上 then與catch均在3s後當promise2()狀態改變纔會執行
複製代碼
Generator
函數是什麼,有什麼用?Generator
函數是ECMAScript 6
提供的一種異步編程解決方案,語法行爲與傳統函數徹底不一樣。
Generator
函數能夠理解成狀態機,封裝了多個內部狀態Generator
函數也能夠是一個普通函數,但具備兩個特徵
function
關鍵字與函數名之間有一個星好(*)yield
表達式,定義不一樣的內部狀態執行Generator
函數會返回一個遍歷器對象,每一次Generator
函數裏面的yield
表達式都至關於一次遍歷器對象的next()
方法,而且能夠經過next(value)
的方式傳入自定義的值,來改變Generator
函數的行爲
Generator
的用途Generator
能夠暫停函數執行,返回任意表達式的值。這種特色使得 Generator 有多種應用場景,例如:
Iterator
接口模塊使咱們可以將代碼基礎分割成多個文件,以得到更高的可維護性,而且避免將全部代碼放在一個大文件中
在Es 6
支持模塊以前,有兩個流行的模塊
基本上,Es 6
使用模塊的方式很簡單,import
用於從另外一個文件中獲取功能或值,export
用於從文件中導出功能或值
Es 5 - CommonJs
// 導出
export.xxx = function(args){
// todo
}
// 引入
const xxx = requir('xxx').xxx
Es 6 - CommonJs
// 導出
export function xxx(args){
// todo
}
// 引入
import xxx from 'xxx'
複製代碼
Es 6
模塊與CommonJs
模塊的差別CommonJs
模塊輸出的是一個值的拷貝,Es 6
模塊輸出的是值的引用
CommonJs
模塊輸出的是值的拷貝,也就是說,一旦輸出一個值,模塊內部的變化就影響不到這個值Es 6
模塊的運行機制與CommonJs
不同,JS
引擎對腳本靜態分析的時候,遇到模塊加載命令import
,就會生成一個只讀引用。等到腳本真正執行時,再根據這個只讀引用,到被加載的那個模塊裏面去取值。換句話說,Es 6
的模塊化,原始值變了,import
加載的值也會跟着變。(Es 6
模塊是動態引用,而且不會緩存值,模塊裏面的變量綁定其所在的模塊)CommonJs
模塊是運行時加載,Es 6
模塊是編譯時輸出接口
CommonJs
模塊就是對象,即再輸入時是先加載整個模塊,生成一個對象,而後再從這個對象上面讀取方法Es 6
模塊不是對象,而是經過export
命令顯式指定輸出的代碼,import
時採用靜態命令的形式,即在import
時能夠指定加載某個輸出值,而不是加載整個模塊CommonJs
加載的是一個對象(model.export
屬性),該對象只有在腳本運行完纔會生成
Es 6
模塊不是對象,它的對外接口只是一種靜態定義,在代碼靜態解析階段就會生成
Symbol是Es 6新增的一種數據類型,是一種特殊的、不可變的數據類型,能夠做爲對象屬性的標識符使用,表示獨一無二的值
語法 - Symbol([description])
description
- 可選的字符串,可用於調式但不訪問符號自己的符號的說明,若是不加參數,在控制檯打印的都是Symbol
,不利於區分
用途 - 做爲屬性名的Symbol
let symbolProp = Symbol()
var obj = {}
obj[symbolProp] = 'hello Symbol'
// Or
var obj = {
[symbolProp] : 'hello Symbol';
}
// Or
var obj = {};
Object.defineProperty(obj,symbolProp,{value : 'hello Symbol'});
複製代碼
Proxy用於修改某些操做的默認行爲,也能夠理解爲在目標對象以前架設一層攔截,外部全部的訪問都必須經過這層攔截,所以提供一種機制,能夠對外部的訪問進行過濾和修改
Es 6提供Proxy構造函數,用來生成Proxy實例 - var proxy = new Proxy(target,handler)
Proxy對象的全部用法,都是上面這種形式,不一樣的只是handle參數的寫法,其中new Proxy用來生成Proxy實例,target表示所要攔截的對象,handle用來定製攔截行爲的對象
Js
中未設置的默認值是undefined
,Proxy
能夠改變這種狀況
const withZeroValue = (target, zeroValue) => {
new Proxy(target, {
get:(obj,prop) => (prop in obj) ? obj[prop] : zeroValue
})
}
> (obj, prop) => obj → target prop → target每個屬性
let pos = {
x: 4,
y: 19
}
pos = withZeroValue(pos, 0)
console.log(pos.x, pos.y, pos.z) // 4 19 0
複製代碼
Es 6 提供了新的數據結構Set,它相似於數組,可是成員的值是惟一的,沒有重複的值
Set()
接受一個數組(或者具備iterable接口的其餘數據結構)做爲參數,用來初始化
const arr = new Set([1,2,3,4,4])
console.log([...set]) // Array(4) [1, 2, 3, 4] [...xxx]轉化爲Array
const items = new Set([1,2,3,4,5,5,5,5])
console.log(items) // Set(5) {1, 2, 3, 4, 5}
複製代碼
經典面試考題:一行代碼實現數組去重(字符串去重重複字符)
- 去除數組重複成員的方法
[...new Set(array)]
- 去除字符串的重複字符
[...new Set('abbbbc')].join('') // 'abc' Set → 數組 → 字符串
複製代碼
❗ 小知識:
向Set加入值的時候,不會發生類型轉換,因此5和'5'是兩個不一樣的值。Set內部判斷兩個值是否不一樣,使用的算法成爲
Same-value-zero equality
,它相似於精確相等運算符(===),主要區別是向Set加入值時認爲 NaN 等於自身,而 === 認爲 NaN 不等於自身
let set = new Set()
let a = NaN
let b = NaN
set.add(a)
set.add(b)
console.log(set) // Set(1) { NaN }
複製代碼
同時,Set中兩個對象老是不相同的
let set = new Set()
set.add({})
set.add({})
console.log(set) // Set(2) { {}, {} }
複製代碼
Set.prototype.constructor
- 構造函數,默認就是Set
函數Set.prototype.size
- 返回Set
實例的成員總數Set.prototype.add(value)
- 添加某個值,返回Set結構自己Set.prototype.delete(value)
- 刪除某個值,返回一個布爾值Set.prototype.has(value)
- 返回一個布爾值,表示該值是否爲Set成員Set.prototype.clear()
- 清除全部成員,沒有返回值Set.prototype.keys()
- 返回鍵名的遍歷器Set.prototype.values()
- 返回鍵值的遍歷器Set.prototype.entries()
- 返回鍵值對的遍歷器Set.prototype.forEach()
- 使用回調函數遍歷每一個成員**❗ Ps:**因爲keys()、values()、entries()
返回的都是遍歷器對象,且Set
結構沒有鍵名,只有鍵值,因此keys()
,values()
效果相同,但entries()
會輸出帶有2個相同元素的數組
let set = new Set(['red','green','blue'])
for(let item of set.keys()){
console.log(item)
}
// red
// green
// blue
for(let item of set.values()){
console.log(item)
}
// red
// green
// blue
for(let item of set.entries()){
console.log(item)
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]
複製代碼
Map 是 Es6 提供的一種新的數據結構,它相似於對象,也是鍵值對的集合,可是'鍵'的範圍不限於字符串,各類類型的值(包括對象)均可以看成鍵,即 Map 提供了一種
值 - 值
的對應,是一種更完善的Hash
結構實現
做爲構造函數,Map
能夠接受一個數組做爲參數,該數組的成員是一個個表示鍵值對的數組
const map = new Map([
['name','張三'],
['title','Author']
])
map.size // 2
map.has('name') // true
map.get('name') // '張三'
map.has('title') // true
map.get('title') // 'Author'
複製代碼
❗ 小知識:
事實上,不只僅是數組,任何具備
Iterable
接口,且每一個成員都是一個雙元素數組的數據結構,均可以看成 Map 構造函數的參數,即 Set 和 Map 均可以用來生成新的 Map
Map.prototype.size
- 返回Map
實例的成員總數Map.prototype.set(key,value)
- 設置鍵名Key對應的鍵值Value,而後返回整個Map結構,若是Key已經存在,則鍵值更新Map.prototype.get(key)
- 讀取Key對應的鍵值,若是找不到則返回undefinedMap.prototype.has(key)
- 返回一個布爾值,表示某個鍵是否在當前Map對象中存在Map.prototype.delete(key)
- 刪除某個鍵,返回true,若是刪除失敗,則返回falseMap.prototype.clear()
- 清除全部成員,沒有返回值Map.prototype.keys()
- 返回鍵名的遍歷器Map.prototype.values()
- 返回鍵值的遍歷器Map.prototype.entries()
- 返回鍵值對的遍歷器Map.prototype.forEach()
- 使用回調函數遍歷每一個成員展開運算符(spread) 是三個點(...),能夠將一個數組轉爲用逗號分隔的參數序列
基本用法:拆解字符串與數組
var arr = [1,2,3,4]
console.log(...arr) // 1 2 3 4
var str = 'String'
console.log(...str) // S t r i n g
複製代碼
展開運算符的應用
在使用Math.max()求數組最大值時,Es5能夠經過apply作到(不友好且繁瑣)
var array = [1,2,3,4,3]
var maxItem = Math.max.apply(null,array)
console.log(maxItem)
在Es6中,展開運算符可用於數組的解析,優雅的解決了這個問題
var array = [1,2,3,4,3]
var maxItem = Math.max(...array)
console.log(maxItem) // 4
複製代碼
push
、concat
等方法- 把 arr2 塞進 arr1 中
// Es5
var arr1 = [0,1,2]
var arr2 = [3,4,5]
Array.prototype.push.apply(arr1,arr2)
// arr1 → [0,1,2,3,4,5]
// Es6
var arr1 = [0,1,2]
var arr2 = [3,4,5]
arr1.push(...arr2)
// arr1 → [0,1,2,3,4,5]
複製代碼
var arr = [1,2,3]
var copyArr = [...arr]
console.log(copyArr) // [1,2,3]
let obj = {
a: 1
b:{
foo:'foo',
bar:'bar'
}
}
let objCopy = { ...obj }
console.log(objCopy) // {a:1,b:{foo:'foo',bar:'bar'}}
obj.a = 2
console.log(objCopy.a) // 1
obj.b.foo = 'FOO'
console.log(objCopy.b) // {foo:'FOO',bar:'bar'}
展開運算符...來實現拷貝屬於淺拷貝,若是屬性值是一個對象,拷貝的是地址
複製代碼
var nodeList = document.querySelectorAll('div')
// querySelectorAll 方法返回的是一個 nodeList 對象。它不是數組,而是一個相似數組的對象。
console.log([...nodeList]) // [div,div,div,...]
複製代碼
rest運算符(剩餘運算符) 看起來與展開運算符同樣,可是它是用於解構數組和對象。在某種程度上,剩餘元素和展開元素相反,展開元素會'展開'數組變成多個元素,剩餘元素會收集多個元素和'壓縮'成一個單一的元素
rest能夠看做是擴展運算符的一個逆運算,它是一個數組
rest參數用於獲取函數的多餘參數,這樣就不須要使用arguments
對象了。rest參數搭配的變量是一個數組,該變量將多餘的參數放入數組中
例如實現計算傳入全部參數的和
使用rest參數:
function sumRest(...m) {
var total = 0
for(var i of m){
total += i
}
return total
}
console.log(sumRest(1,2,3)) // 6
複製代碼
rest運算符的應用
rest
參數代替arguments
變量// arguments寫法
function sortNumbers(){
return Array.prototype.slice.call(arguments).sort()
}
// Es6 rest參數寫法
const sortNumbers = (...numbers) => {
numbers.sort()
}
複製代碼
var array = [1,2,3,4,5,6]
var [a,b,...c] = array
console.log(a) // 1
console.log(b) // 2
console.log(c) // [3,4,5,6]
複製代碼
❗ 小知識:
rest參數可理解爲剩餘的參數,因此必須在最後一位定義,若是定義在中間會報錯。
var array = [1,2,3,4,5,6];
var [a,b,...c,d,e] = array;
// Uncaught SyntaxError: Rest element must be last element
複製代碼
const promise = new Promise((resovle,reject) => {
console.log(1)
resolve()
conosole.log(2)
})
promise.then(() => {
console.log(3)
})
console.log(4)
複製代碼
Result:
1
2
4
3
複製代碼
const promise1 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('success')
}, 1000)
})
const promise2 = new Promise((resolve,reject) => {
throw new Error('error!!!')
})
console.log('promise1', promise1)
console.log('promise2', promise2)
setTimeout(() => {
console.log('promise1', promise1)
console.log('promise2', promise2)
}, 2000)
複製代碼
Result:
promise1 Promise { <pending> }
promise2 Promise { <pending> }
Uncaught (in promise) Error: error!!!
promise1 Promise { <resolved>: "success" }
promise2 Promise { <rejected>: Error: error!!! }
複製代碼
解析:
promise有三種狀態:pending,fulfilled或rejected,狀態改變只能是 pending → fulfilled
或 pending → rejected
,狀態一旦改變則不能再變。上面 promise2並非promise1,而是返回的一個新的 Promise 實例
const promise = new Promise((resolve,reject) => {
resolve('success1')
reject('error')
resolve('success2')
})
promise.then(res => {
console.log('then:', res)
}).catch(err => {
console.log('catch:', err)
})
複製代碼
Result:
then:success1
複製代碼
解析:
Promise的resolve
或reject
只有第一次執行有效,屢次調用沒有任何做用(Promise狀態一旦改變則不能再變)
Promise.resolve(1).then( res => {
console.log(res)
return 2
}).catch( err => {
return 3
}).then( res => {
console.log(res)
})
複製代碼
Result:
1
2
複製代碼
解析:
promise每次調用.then
或者.catch
都返回一個新的Promise,從而實現鏈式調用
const promise = new Promise((resolve,reject) => {
setTimeout(() => {
console.log('once')
resolve('success')
},1000)
})
const start = Date.now()
promise.then(res => {
console.log(res, Date.now() - start)
})
promise.then(res => {
console.log(res, Date.now() - start)
})
複製代碼
Result:
once
success 1001
success 1002
複製代碼
解析:
Promise的then
和catch
均可以被屢次調用,這裏promise實例狀態一旦改變,而且有了一個值,那麼後續每次調用promise.then
或者promise.catch
都會拿到這個值
Promise.resolve().then(() => {
return new Error('error!')
}).then( res => {
console.log('then:',res)
}).catch( err => {
console.log('catch:', err)
})
複製代碼
Result:
then: Error: error!
複製代碼
解析:
.then
或者.catch
中return一個error對象並不會拋出錯誤,因此不會被後續的.catch
捕獲,而是進行.then
,須要改爲如下方式纔會被.catch
捕獲
1 - return Promise.reject(new Error('error!'))
2 - throw new Error('error!')
複製代碼
由於返回任意一個非Promise的值都會被包裹成Promise對象,即return new Error('error!')
等於return Promise.resolve(new Error('error!')
const promise = Promise.resolve().then( () => {
return promise
})
promise.catch(console.error)
複製代碼
Result:
TypeError: Chaining cycle detected for promise #<Promise>
複製代碼
解析:
.then
或.catch
返回的值不能是promise自己,不然會形成死循環
Promise.resolve(1).then(2).then(Promise.resolve(3)).then(console.log)
複製代碼
Result:
1
複製代碼
解析:
.then
或.catch
的參數指望是函數,當傳入的是非函數則會發生值穿透
Promise.resolve(1).then(function success(res){
console.log('success',res)
throw new Error('error')
},function fail1(err){
console.log('fail1',err)
}).catch(function fail2(err){
console.log('fail2',err)
})
複製代碼
Result:
success 1
fail2 Error: error
at success (...)
複製代碼
解析:
.then
能夠接受兩個參數,第一個是處理成功的參數,第二個是處理錯誤的函數,.catch
其實是.then
第二個參數的簡便寫法,可是用法上有一點須要注意:
.then
的第二個處理錯誤的函數捕獲不了第一個處理成功的函數拋出的錯誤,然後續的.catch
能夠捕獲以前的錯誤Promise.resolve().then(function success1(res){
throw new Error('error')
},function fail1(err){
console.log('fail1',err)
}).then(function success2 (res) {}, function fail2 (err) {
console.error('fail2: ', err)
})
複製代碼
process.nextTick(() => {
console.log('nextTick')
})
Promise.resolve().then( () => {
console.log('then')
})
setImmediate(() => {
console.log('setImmediate')
})
console.log('end')
複製代碼
Result:
end
nextTick
then
setImmediate
複製代碼
解析:
process.nextTick
、promise.then
均屬於微任務microtask
,而setImmediate
屬於宏任務macrotask
,仔事件循環EventLoop
中,每一個宏任務執行完後都會清空當前全部微任務,再進行下一個宏任務
Es 6
相關的手寫題,會與JavaScript
一塊兒總結,集中成一篇Vue.Js
知識點