## ES6 中類的定義 ****
參考答案:
~~~js
// 1、類的基本定義
class Parent {
constructor(name = "小白") {
this.name = name;
}
}
~~~
~~~js
// 2、生成一個實例
let g_parent = new Parent();
console.log(g_parent); //{name: "小白"}
let v_parent = new Parent("v"); // 'v'就是構造函數name屬性 , 覆蓋構造函數的name屬性值
console.log(v_parent); // {name: "v"}
~~~
~~~js
// 3、繼承
class Parent {
//定義一個類
constructor(name = "小白") {
this.name = name;
}
}
class Child extends Parent {}
console.log("繼承", new Child()); // 繼承 {name: "小白"}
~~~
~~~js
// 4、繼承傳遞參數
class Parent {
//定義一個類
constructor(name = "小白") {
this.name = name;
}
}
class Child extends Parent {
constructor(name = "child") {
// 子類重寫name屬性值
super(name); // 子類向父類修改 super一定放第一行
this.type = "preson";
}
}
console.log("繼承", new Child("hello")); // 帶參數覆蓋默認值 繼承{name: "hello", type: "preson"}
~~~
~~~js
// 5、ES6重新定義的ES5中的訪問器屬性
class Parent {
//定義一個類
constructor(name = "小白") {
this.name = name;
}
get longName() {
// 屬性
return "mk" + this.name;
}
set longName(value) {
this.name = value;
}
}
let v = new Parent();
console.log("getter", v.longName); // getter mk小白
v.longName = "hello";
console.log("setter", v.longName); // setter mkhello
~~~
~~~js
// 6、類的靜態方法
class Parent {
//定義一個類
constructor(name = "小白") {
this.name = name;
}
static tell() {
// 靜態方法:通過類去調用,而不是實例
console.log("tell");
}
}
Parent.tell(); // tell
~~~
~~~js
// 7、類的靜態屬性:
class Parent {
//定義一個類
constructor(name = "小白") {
this.name = name;
}
static tell() {
// 靜態方法:通過類去調用,而不是實例
console.log("tell"); // tell
}
}
Parent.type = "test"; // 定義靜態屬性
console.log("靜態屬性", Parent.type); // 靜態屬性 test
let v_parent = new Parent();
console.log(v_parent); // {name: "小白"}? 沒有tell方法和type屬性
~~~
解析:[參考](https://es6.ruanyifeng.com/#docs/class)
## 談談你對 ES6 的理解 ****
參考答案:es6 是一個新的標準,它包含了許多新的語言特性和庫,是 JS 最實質性的一次升級。 比如'箭頭函數'、'字符串模板'、'generators(生成器)'、'async/await'、'解構賦值'、'class'等等,還有就是引入 module 模塊的概念。
箭頭函數可以讓 this 指向固定化,這種特性很有利于封裝回調函數
* (1)函數體內的 this 對象,就是定義時所在的對象,而不是使用時所在的對象。
* (2)不可以當作構造函數,也就是說,不可以使用 new 命令,否則會拋出一個錯誤。
* (3)不可以使用 arguments 對象,該對象在函數體內不存在。如果要用,可以用 Rest 參數代替。
* (4)不可以使用 yield 命令,因此箭頭函數不能用作 Generator 函數。
* async/await 是寫異步代碼的新方式,以前的方法有回調函數和 Promise。
* async/await 是基于 Promise 實現的,它不能用于普通的回調函數。async/await 與 Promise 一樣,是非阻塞的。
* async/await 使得異步代碼看起來像同步代碼,這正是它的魔力所在。
解析:[參考](https://www.cnblogs.com/heweijain/p/7073553.html)
## 說說你對 promise 的了解****
高級前端會考 自己實現 promise
參考答案:Promise 是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件監聽——更合理和更強大。
所謂 Promise,簡單說就是一個容器,里面保存著某個未來才會結束的事件(通常是一個異步操作)的結果。從語法上說,Promise 是一個對象,從它可以獲取異步操作的消息。Promise 提供統一的 API,各種異步操作都可以用同樣的方法進行處理。
Promise 對象有以下兩個特點:
1. 對象的狀態不受外界影響。Promise 對象代表一個異步操作,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)。只有異步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。這也是Promise這個名字的由來,它的英語意思就是“承諾”,表示其他手段無法改變。
2. 一旦狀態改變,就不會再變,任何時候都可以得到這個結果。Promise對象的狀態改變,只有兩種可能:從pending變為fulfilled和從pending變為rejected。只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱為 resolved(已定型)。如果改變已經發生了,你再對Promise對象添加回調函數,也會立即得到這個結果。這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監聽,是得不到結果的。
解析:[參考](https://es6.ruanyifeng.com/#docs/promise)
## 解構賦值及其原理 ****
參考答案:
解構賦值:其實就是分解出一個對象的解構,分成兩個步驟:
1. 變量的聲明
2. 變量的賦值
原理:ES6 變量的解構賦值本質上是“模式匹配”, 只要等號兩邊的模式相同,左邊的變量就會被賦予匹配的右邊的值,如果匹配不成功變量的值就等于 undefined
解析:
一、 數組的解構賦值
~~~js
// 對于數組的解構賦值,其實就是獲得數組的元素,而我們一般情況下獲取數組元素的方法是通過下標獲取,例如:
let arr = [1, 2, 3];
let a = arr[0];
let b = arr[1];
let c = arr[2];
// 而數組的解構賦值給我們提供了極其方便的獲取方式,如下:
let [a, b, c] = [1, 2, 3];
console.log(a, b, c); //1,2,3
~~~
1. 模式匹配解構賦值
~~~js
let [foo, [
[bar], baz
]] = [1, [
[2], 3
]];
console.log(foo, bar, baz); //1,2,3
~~~
2. 省略解構賦值
~~~js
let [, , a, , b] = [1, 2, 3, 4, 5];
console.log(a, b); //3,5
~~~
3. 含剩余參數的解構賦值
~~~js
let [a, ...reset] = [1, 2, 3, 4, 5];
console.log(a, reset); //1,[2,3,4,5]
~~~
其轉成 ES5 的原理如下:
~~~js
var a = 1,
reset = [2, 3, 4, 5];
console.log(a, reset); //1,[2,3,4,5]
~~~
注意:如果剩余參數是對應的值為 undefined,則賦值為\[\],因為找不到對應值的時候,是通過 slice 截取的,如下:
~~~js
let [a, ...reset] = [1];
console.log(a, reset); //1,[]
~~~
其轉成 ES5 的原理如下:
~~~js
var _ref = [1],
a = _ref[0],
reset = _ref.slice(1);
console.log(a, reset); //1,[]
~~~
4. 非數組解構成數組(重點,難點)
一條原則:要解構成數組的前提:如果等號右邊,不是數組(嚴格地說,不是可遍歷的解構),則直接報錯,例如:
~~~js
let [foo] = 1; //報錯
let [foo1] = false; //報錯
let [foo2] = NaN; //報錯
let [foo3] = undefined; //報錯
let [foo4] = null; //報錯
let [foo5] = {}; //報錯
~~~
為什么?轉成 ES5 看下原理就一清二楚了:
~~~js
var _ = 1,
foo = _[0]; //報錯
var _false = false,
foo1 = _false[0]; //報錯
var _NaN = NaN,
foo2 = _NaN[0]; //報錯
var _undefined = undefined,
foo3 = _undefined[0]; //報錯
var _ref = null;
foo4 = _ref[0]; //報錯
var _ref2 = {},
foo5 = _ref2[0]; //報錯
~~~
5. Set 的解構賦值
先執行 new Set()去重,然后對得到的結果進行解構
~~~js
let [a, b, c] = new Set([1, 2, 2, 3]);
console.log(a, b, c); //1,2,3
~~~
6. 迭代器解構
~~~js
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
~~~
### [](https://github.com/yisainan/web-interview/blob/master/content/js/es6.md#%E6%80%BB%E7%BB%93-1%E5%8F%AA%E8%A6%81%E6%9F%90%E7%A7%8D%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%85%B7%E6%9C%89-iterator-%E6%8E%A5%E5%8F%A3%E9%83%BD%E5%8F%AF%E4%BB%A5%E9%87%87%E7%94%A8%E6%95%B0%E7%BB%84%E5%BD%A2%E5%BC%8F%E7%9A%84%E8%A7%A3%E6%9E%84%E8%B5%8B%E5%80%BC)總結 1:只要某種數據結構具有 Iterator 接口,都可以采用數組形式的解構賦值。
7. 解構賦值的默認值
當變量嚴格等于 undefined 的時候,會讀取默認值,所謂的嚴格等于,就是“===”
~~~js
-- -- -- -- --
let [a, b = 'default'] = [1];
console.log(a, b); //1,'default'
-- -- -- -- --
let [c = 'default'] = [undefined];
console.log(c); //'default'
-- -- -- -- --
function f() {
console.log('aaa');
}
let [x = f()] = [1];
console.log(x); //1
-- -- -- -- --
function f() {
console.log('aaa'); //'aaa'
}
let [a, x = f()] = [1];
console.log(a, x); //1,undefined
~~~
### [](https://github.com/yisainan/web-interview/blob/master/content/js/es6.md#%E6%80%BB%E7%BB%93-2%E5%A6%82%E6%9E%9C%E4%B8%8D%E4%BD%BF%E7%94%A8%E9%BB%98%E8%AE%A4%E5%80%BC%E5%88%99%E4%B8%8D%E4%BC%9A%E6%89%A7%E8%A1%8C%E9%BB%98%E8%AE%A4%E5%80%BC%E7%9A%84%E5%87%BD%E6%95%B0)總結 2:如果不使用默認值,則不會執行默認值的函數
二、對象的解構賦值
1. 解構賦值的舉例:
~~~js
let p1 = {
name: "zhuangzhuang",
age: 25
};
let {
name,
age
} = p1; //注意變量必須為屬性名
console.log(name, age); //"zhuangzhuang",25
~~~
其轉成 es5 的原理則為:
~~~js
var _p1 = p1,
name = _p1.name,
age = _p1.age;
console.log(name, age); //"zhuangzhuang",25
~~~
2. 解構賦值的別名
如果使用別名,則不允許再使用原有的解構出來的屬性名,看以下舉例則會明白:
~~~js
let p1 = {
name: "zhuangzhuang",
age: 25
};
let {
name: aliasName,
age: aliasAge
} = p1; //注意變量必須為屬性名
console.log(aliasName, aliasAge); //"zhuangzhuang",25
console.log(name, age); //Uncaught ReferenceError: age is not defined
~~~
為何打印原有的屬性名則會報錯?讓我們看看轉成 es5 后的原理是如何實現的:
~~~js
var _p1 = p1,
aliasName = _p1.name,
aliasAge = _p1.age;
console.log(aliasName, aliasAge); //"zhuangzhuang",25
console.log(name, age); //所以打印name和age會報錯——“Uncaught ReferenceError: age is not defined”,但是為何只報錯age,不報錯name呢?
~~~
只報錯 age,不報錯 name,這說明其實 name 是存在的,那么根據 js 的解析順序,當在當前作用域 name 無法找到時,會向上找,直到找到 window 下的 name, 而我們打印 window 可以發現,其下面確實有一個 name,值為“”,而其下面并沒有屬性叫做 age,所以在這里 name 不報錯,只報 age 的錯。類似 name 的屬性還有很多,比如 length 等。
3. 解構賦值的默認值
有些情況下,我們解構出來的值并不存在,所以需要設定一個默認值,例如:
~~~js
let obj = {
name: "zhuangzhuang"
};
let {
name,
age
} = obj;
console.log(name, age); //"zhuangzhuang",undefined
~~~
我們可以看到當 age 這個屬性并不存在于 obj 的時候,解構出來的值為 undefined,那么為了避免這種尷尬的情況,我們常常會設置該屬性的默認值,如下:
~~~js
let obj = {
name: "zhuangzhuang"
};
let {
name,
age = 18
} = obj;
console.log(name, age); //"zhuangzhuang",18
~~~
當我們取出來的值不存在,即為 undefined 的時候,則會取默認值(假設存在默認值),ES6 的默認值是使用\*\*“變量=默認值”\*\*的方式。
注意:只有當為 undefined 的時候才會取默認值,null 等均不會取默認值
~~~js
let obj = {
name: "zhuangzhuang",
age: 27,
gender: null, //假設未知使用null
isFat: false
};
let {
name,
age = 18,
gender = "man",
isFat = true,
hobbies = "study"
} = obj;
console.log(name, age, gender, isFat, hobbies); //"zhuangzhuang",27,null,false,"study"
~~~
4. 解構賦值的省略賦值
當我們并不是需要取出所有的值的時候,其實可以省略一些變量,這就是省略賦值,如下
~~~js
let arr = [1, 2, 3];
let [, , c] = arr;
console.log(c); //3
~~~
注意:省略賦值并不存在與對象解構,因為對象解構,明確了需要的屬性
~~~js
let obj = {
name: "zhuangzhuang",
age: 27,
gender: "man"
};
let {
age
} = obj;
console.log(age); //27
~~~
5. 解構賦值的嵌套賦值(易錯點,重點,難點)
~~~js
let obj = {},
arr = [];
({
foo: obj.prop,
bar: arr[0]
} = {
foo: 123,
bar: true
});
console.log(obj, arr); //{prop:123},[true]
~~~
注意當解構出來是 undefined 的時候,如果再給子對象的屬性,則會報錯,如下
~~~js
let {
foo: {
bar
}
} = {
baz: "baz"
};
//報錯,原因很簡單,看下原理即可,如下:
//原理:
let obj = {
baz: "baz"
};
let foo = obj.foo; //foo為undefined
let bar = foo.bar; //undefined的bar,可定報錯
~~~
6. {}是塊還是對象?
當我們寫解構賦值的時候,很容易犯一個錯誤——{}的作用是塊還是對象混淆,舉例如下:
~~~js
//舉例一:
let {
a
} = {
a: "a"
};
console.log(a); //'a',這個很簡單
//很多人覺得,以下這種寫法也是可以的:
let a; {
a
} = {
a: "a"
}; //直接報錯,因為此時a已經聲明過了,在語法解析的時候,會將這一行的{}看做塊結構,而“塊=對象”,顯然是語法錯誤,所以正確的做法是不將大括號寫在開頭,如下:
let a;
({
a
} = {
a: "a"
})
~~~
7. 空解構
按照之前寫的,解構賦值,左邊則為解構出來的屬性名,當然,在這里,我們也可以不寫任何屬性名稱,也不會又任何的語法錯誤,即便這樣沒有任何意義,如下:
~~~js
({} = [true, false]);
({} = "abc");
({} = []);
~~~
8. 解構成對象的原則
如果解構成對象,右側不是 null 或者 undefined 即可! 之前說過,要解構成數組,右側必須是可迭代對象,但是如果解構成對象,右側不是 null 活著 undefined 即可!
三、字符串的解構賦值
字符串也是可以解構賦值的
~~~js
const [a, b, c, d, e] = "hello";
console.log(a, b, c, d, e); //'h','e','l','l','o'
~~~
轉成 es5 的原理如下:
~~~js
var _hello = "hello",
a = _hello[0],
b = _hello[1],
c = _hello[2];
console.log(a, b, c);
~~~
注意:字符串有一個屬性 length,也可以被解構出來,但是要注意,解構屬性一定是對象解構
~~~js
let {
length
} = "hello";
console.log(length); //5
~~~
4. 布爾值和數值的解構
布爾值和數值的解構,其實就是對其包裝對象的解構,取的是包裝對象的屬性
~~~js
{
toString: s
} = 123;
console.log(s); //s === Number.prototype.toString
{
toString: s
} = true;
console.log(s); //s === Boolean.prototype.toString
~~~
### [](https://github.com/yisainan/web-interview/blob/master/content/js/es6.md#%E6%80%BB%E7%BB%93%E8%A7%A3%E6%9E%84%E8%B5%8B%E5%80%BC%E7%9A%84%E8%A7%84%E5%88%99%E6%98%AF)總結:解構賦值的規則是:
> 1. 解構成對象,只要等號右邊的值不是對象或數組,就先將其轉為對象。由于 undefined 和 null 無法轉為對象,所以對它們進行解構賦值,都會報錯。
> 2. 解構成數組,等號右邊必須為可迭代對象
[參考](https://blog.csdn.net/qq_17175013/article/details/81490923)
## var let 在 for 循環中的區別****
參考答案:
~~~js
//使用var聲明,得到3個3
var a = [];
for (var i = 0; i < 3; i++) {
a[i] = function () {
console.log(i);
};
}
a[0](); //3
a[1](); //3
a[2](); //3
//使用let聲明,得到0,1,2
var a = [];
for (let i = 0; i < 3; i++) {
a[i] = function () {
console.log(i);
};
}
a[0](); //0
a[1](); //1
a[2](); //2
~~~
~~~js
for(var i=0;i<5;i++){
setTimeout(()=>{
console.log(i);//5個5
},100)
}
console.log(i);//5
console.log('=============')
for(let j=0;j<5;j++){
setTimeout(()=>{
console.log(j);//0,1,2,3,4
},100)
}
console.log(j);//報錯 j is not defined
~~~
var是全局作用域,有變量提升的作用,所以在for中定義一個變量,全局可以使用,循環中的每一次給變量i賦值都是給全局變量i賦值。
let是塊級作用域,只能在代碼塊中起作用,在js中一個{}中的語句我們也稱為叫一個代碼塊,每次循環會產生一個代碼塊,每個代碼塊中的都是一個新的變量i;
解析:[參考](https://www.cnblogs.com/fanfanZhao/p/12179508.html)
## 模板字符串 *****
參考答案:
* 就是這種形式${varible}, 在以往的時候我們在連接字符串和變量的時候需要使用這種方式'string' + varible + 'string'但是有了模版語言后我們可以使用string${varible}string 這種進行連接。基本用途有如下:
1、基本的字符串格式化,將表達式嵌入字符串中進行拼接,用${}來界定。
~~~js
//es5
var name = "lux";
console.log("hello" + name);
//es6
const name = "lux";
console.log(`hello ${name}`); //hello lux
~~~
2、在 ES5 時我們通過反斜杠()來做多行字符串或者字符串一行行拼接,ES6 反引號(``)直接搞定。
~~~js
//ES5
var template =
"hello \
world";
console.log(template); //hello world
//ES6
const template = `hello
world`;
console.log(template); //hello 空行 world
~~~
## 箭頭函數需要注意的地方****
參考答案:
~~~
箭頭函數有幾個使用注意點。
(1)函數體內的 this 對象,就是定義時所在的對象,而不是使用時所在的對象。
(2)不可以當作構造函數,也就是說,不可以使用 new 命令,否則會拋出一個錯誤。
(3)不可以使用 arguments 對象,該對象在函數體內不存在。如果要用,可以用 rest 參數代替。
(4)不可以使用 yield 命令,因此箭頭函數不能用作 Generator 函數。
~~~
上面四點中,第一點尤其值得注意。this 對象的指向是可變的,但是在箭頭函數中,它是固定的。
~~~js
function foo() {
setTimeout(() => {
console.log("id:", this.id);
}, 100);
}
var id = 21;
foo.call({
id: 42
});
// id: 42
~~~
解析:[參考](https://www.jianshu.com/p/bc28e4f67ef9)
## 箭頭函數和普通函數有什么區別****
參考答案:
* 函數體內的`this`對象,就是定義時所在的對象,而不是使用時所在的對象,用`call``apply``bind`也不能改變`this`指向
* 不可以當作構造函數,也就是說,不可以使用`new`命令,否則會拋出一個錯誤。
* 不可以使用`arguments`對象,該對象在函數體內不存在。如果要用,可以用`rest`參數代替。
* 不可以使用`yield`命令,因此箭頭函數不能用作`Generator`函數。
* 箭頭函數沒有原型對象`prototype`
## Promise 構造函數是同步執行還是異步執行,那么 then 方法呢?****
參考答案:
~~~js
const promise = new Promise((resolve, reject) => {
console.log(1)
resolve()
console.log(2)
})
promise.then(() => {
console.log(3)
})
console.log(4)
~~~
執行結果是:1243
promise構造函數是同步執行的,then方法是異步執行的