、`const`關鍵字 在 之前, 中變數預設是全局性的,只存在函數級作用域,聲明函數曾經是創造作用域的唯一方法。這點和其他編程語言存在差異,其他語言大多數都存在塊級作用域。所以在 中,新提出的 和 關鍵字使這個缺陷得到了修複。 同時還引入的概念 ,用來定義一個常量,一旦定義以後不可以修改,如果是引 ...
let
、const
關鍵字
在 ES6
之前,JavaScript
中變數預設是全局性的,只存在函數級作用域,聲明函數曾經是創造作用域的唯一方法。這點和其他編程語言存在差異,其他語言大多數都存在塊級作用域。所以在 ES6
中,新提出的 let
和 const
關鍵字使這個缺陷得到了修複。
if (true) {
let a = 'name';
}
console.log(a); // ReferenceError: a is not defined
同時還引入的概念 const
,用來定義一個常量,一旦定義以後不可以修改,如果是引用類型,那麼可以修改其引用屬性,不可以修改其引用。
const MYNAME = 'liangyin';
MYNAME = 'doke';
// TypeError: Assignment to constant variable.
const MYNAME = {
first: 'liang'
};
MYNAME.last = 'yin';
// {first: "liang", last: "yin"}
有以下幾點需要註意:
- 儘量使用
let
和const
代替var
- 聲明方法儘量使用
const
以防止後期無意覆蓋 const
定義的變數使用大寫形式
函數
箭頭函數
箭頭函數是一種更簡單的函數聲明方式,可以把它看作是一種語法糖,箭頭函數永遠是匿名的。
let add = (a, b) => {
return a + b;
}
// 當後面是表達式(expression)的時候,還可以簡寫成
let add = (a, b) => a + b;
// 等同於
let add = function(a, b) {
return a + b;
}
// 在回調函數中應用
let number = [1, 2, 3];
let doubleNumber = number.map(number => number * 2);
console.log(doubleNumber);
// [2, 4, 6] 看起來很簡便吧
this
在箭頭函數中的使用
在工作中經常會遇到 this
在一個對象方法中嵌套函數作用域的問題。
var age = 2;
var kitty = {
age: 1,
grow: function() {
setTimeout(function() {
console.log(++this.age);
}, 100)
}
};
kitty.grow();
// 3
其實這是因為,在對象方法的嵌套函數中,this
會指向 global
對象,這被看做是 JavaScript
在設計上的一個重大缺陷,一般都會採用一些 hack
來解決它。
let kitty = {
age: 1,
grow: function() {
const self = this;
setTimeout(function() {
console.log(++self.age);
}, 100);
}
}
// 或者
let kitty = {
age: 1,
grow: function() {
setTimeout(function() {
console.log(this.age);
}.bind(this), 100)
}
}
現在有了箭頭函數,可以很輕鬆地解決這個問題。
let kitty = {
age: 1,
grow: function() {
setTimeout(() => {
console.log(this.age);
}, 100)
}
}
但是箭頭函數並不是萬能的,任何事物都具有兩面性,語言的新特性常常被誤解、濫用,比如箭頭函數的使用就存在很多誤區。 大家可以移步☞ES6箭頭函數使用註意點。
函數預設參數
ES6
出現以前,面對預設參數都會讓人感到很痛苦,不得不採用各種 hack
手段,比如說:values = values || []
。現在一切都變得輕鬆很多。
function desc(name = 'liangyin', age = 18) {
return name + '已經' + age + '歲了'
}
desc();
// liangyin已經18歲了
Rest
參數
當一個函數的最後一個參數有...
這樣的首碼,它就會變成一個參數的數組。
function test(...args) {
console.log(args);
}
test(1, 2, 3);
// [1,2,3]
function test2(name, ...args) {
console.log(args);
}
test2('liangyin', 2, 3);
// [2,3]
它和 arguments
參數有如下區別:
Rest
參數只是沒有指定變數名稱的參數數組,而arguments
是所有參數的集合。arguments
對象並不是一個真正的數組,而Rest
參數是一個真正的數組,可以使用各種數組方法,比如sort
,map
等。
展開操作符
剛剛講到了 Rest
操作符來實現函數參數的數組,其實這個操作符的能力不僅如此。它被稱為展開操作符,允許一個表達式在某處展開,在存在多個參數(用於函數調用),多個元素(用於數組字面量)或者多個變數(用於解構賦值)的地方就會出現這種情況。
用於函數調用
之前在 JavaScript
中,想讓函數把一個數組依次作為參數進行調用,一般會採取以下措施:
function test(x, y, z) {};
var args = [1, 2, 3];
test.apply(null, args);
有了 ES6
的展開運算符,可以簡化這個過程:
function test(x, y, z) {};
let args = [1, 2, 3];
test(...args);
用於數組字面量
在之前的版本中,如果想創建含有某些元素的新數組,常常會用到 splice
、concat
、push
等方法:
var arr1 = [1, 2, 3];
var arr2 = [4, 5, 6];
var arr3 = arr1.concat(arr2);
console.log(arr3);
// [1,2,3,4,5,6]
使用展開運算符以後就簡便了很多:
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
let arr3 = [...arr1, ...arr2];
console.log(arr3);
// [1,2,3,4,5,6]
對象的展開運算符(ES7
)
數組的展開運算符簡單易用,那麼對象有沒有這個特性?
let uzi = {
name: 'uzi',
age: 50
};
uzi = {
...uzi,
sex: 'male'
};
console.log(uzi);
// {name: "uzi", age: 50, sex: "male"}
這是 ES7
的提案之一,它可以讓你以更簡潔的形式將一個對象可枚舉屬性複製到另外一個對象上去。
模板字元串
在 ES6
之前的時代,字元串拼接總是一件令人很很很不爽的一件事,但是在ES6
的時代,這個痛點終於被治愈了!!!
// 之前的做法
var name = 'uzi';
var a = 'My name is ' + uzi + '!';
// 多行字元串
var longStory = 'This is a long story,' + 'this is a long story,' + 'this is a long story.'
// 有了 ES6 之後我們可以這麼做
let name = 'uzi';
let a = `My name is ${name} !`;
let longStory = `This is a long story,
this is a long story,
this is a long story.`
解構賦值
解構語法可以快速從數組或者對象中提取變數,可以用一個表達式讀取整個結構。
解構數組
let number = ['one', 'two', 'three'];
let [one, two, three] = number;
console.log(`${one},${two},${three}`);
// one,two,three
解構對象
let uzi = {
name: 'uzi',
age: 20
};
let {
name,
age
} = uzi;
console.log(`${name},${age}`);
// uzi,20
解構賦值可以看做一個語法糖,它受 Python
語言的啟發,提高效率之神器。
類
眾所周知,在 JavaScript
的世界里是沒有傳統類的概念,它使用的是原型鏈的方式來完成繼承,但是聲明方式總是怪怪的(很大一部分人不遵守規則用小寫變數聲明一個類,這就導致類和普通方法難以區分)。
在 ES6
中提供了 class
這個語法糖,讓開發者模仿其他語言類的聲明方式,看起來更加明確清晰。需要註意的是, class
並沒有帶來新的結構,只是原來原型鏈方式的一種語法糖。
class Animal {
// 構造函數
constructor(name, age) {
this.name = name;
this.age = age;
}
shout() {
return `My name is ${this.name}, age is ${this.age}`;
}
// 靜態方法
static foo() {
return 'this is static method';
}
}
const cow = new Animal('uzi', 2);
cow.shout();
// "My name is uzi, age is 2"
Animal.foo();
// "this is static method"
class Dog extends Animal {
constructor(name, age = 2, color = 'black') {
// 在構造函數中直接調用 super 方法
super(name, age);
this.color = color;
}
shout() {
// 非構造函數中不能直接使用 super 方法
// 但是可以採用 super. + 方法名調用父類方法
return super.shout() + `, color is ${this.color}`;
}
}
const uzisDog = new Dog('uzi');
uzisDog.shout();
// "My name is uzi, age is 2, color is black"
對象
Object.assign
方法用來將源對象的所有可枚舉屬性複製到目標對象.
let target = {
a: 1
};
// 後邊的屬性值,覆蓋前面的屬性值
Object.assign(target, {
b: 2,
c: 3
}, {
a: 4
});
console.log(target);
// {a: 4, b: 2, c: 3}
為對象添加屬性
class add {
constructor(obj) {
Object.assign(this, obj);
}
}
let p = new add({
x: 1,
y: 2
});
console.log(p);
// add {x: 1, y: 2}
為對象添加方法
Object.assign(add.prototype, {
getX() {
return this.x;
},
setX(x) {
this.x = x;
}
});
let p = new add(1, 2);
console.log(p.getX()); // 1
克隆對象
function cloneObj(origin) {
return Object.assign({}, origin);
}
Set
、Map
和Array.from
Set
Set
裡面的成員的值都是唯一的,沒有重覆的值,Set加入值時不會發生類型轉換,所以5和"5"是兩個不同的值。
// 數組去重
function dedupe(array) {
return Array.from(new Set(array));
}
console.log(dedupe([1, 2, 2, 3])); // 1, 2, 3
Map
Map
類似於對象,也是鍵值對的集合,但是"鍵"的範圍不限於字元串,各種類型的值(包括對象)都可以當做鍵.
let m = new Map();
let o = {
p: 'Hello World'
};
m.set(o, 'content');
m.get(o); // content
m.has(o); // true
m.delete(o); // true
m.has(o); // false
m.set(o, 'my content').set(true, 7).set('foo', 8);
console.log(m);
// Map(3) {{…} => "my content", true => 7, "foo" => 8}
// Map/數組/對象 三者之間的相互轉換
console.log([...m]);
// (3) [Array(2), Array(2), Array(2)]
Array.from
轉換Map
Map
對象的鍵值對轉換成一個一維數組。
const map1 = new Map();
map1.set('k1', 1);
map1.set('k2', 2);
map1.set('k3', 3);
console.log(Array.from(map1))
// [Array(2), Array(2), Array(2)]
轉換Set
const set1 = new Set();
set1.add(1).add(2).add(3);
console.log(Array.from(set1));
// [1, 2, 3]
轉換字元串
可以把ascii
的字元串拆解成一個數據,也可以準確的將unicode
字元串拆解成數組。
console.log(Array.from('hello world'));
console.log(Array.from('\u767d\u8272\u7684\u6d77'));
// ["h", "e", "l", "l", "o", " ", "w", "o", "r", "l", "d"]
// ["白", "色", "的", "海"]
類數組對象
一個類數組對象必須要有length
,他們的元素屬性名必須是數值或者可以轉換成數值的字元。
註意:屬性名代表了數組的索引號,如果沒有這個索引號,轉出來的數組中對應的元素就為空。
console.log(Array.from({
0: '0',
1: '1',
3: '3',
length:4
}));
// ["0", "1", undefined, "3"]
如果對象不帶length
屬性,那麼轉出來就是空數組。
console.log(Array.from({
0: 0,
1: 1
}));
// []
對象的屬性名不能轉換成索引號時,轉出來也是空數組。
console.log(Array.from({
a: '1',
b: '2',
length: 2
}));
// [undefined, undefined]
Array.from
可以接受三個參數
Array.from(arrayLike[, mapFn[, thisArg]])
arrayLike
:被轉換的的對象。
mapFn
:map函數。
thisArg
:map函數中this指向的對象。
模塊
JavaScript
模塊化是一個很古老的話題,它的發展從側面反映了前端項目越來越複雜、越來越工程化。在 ES6
之前,JavaScript
沒有對模塊做出任何定義,知道 ES6
的出現,模塊這個概念才真正有了語言特性的支持,現在來看看它是如何被定義的。
// hello.js 文件
// 定義一個命名為 hello 的函數
function hello() {
console.log('Hello ES6');
}
// 使用 export 導出模塊
export {hello};
// main.js
// 使用 import 載入這個模塊
import {
hello
} from './hello';
hello();
// Hello ES6
上面的代碼就完成了模塊的一個最簡單的例子,使用 import
和 export
關鍵字完成模塊的導入和導出。當然也可以完成一個模塊的多個導出:
// hello.js
export const PI = 3.14;
export function hello() {
console.log('Hello ES6');
}
export let person = {
name: 'uzi'
};
// main.js
// 使用對象解構賦值載入這3個變數
import {
PI,
hello,
person
} from './hello';
// 也可以將這個模塊全部導出
import * as util from './hello';
console.log(util.PI);
// 3.14
還可以使用 default
關鍵字來實現模塊的預設導出:
// hello.js
export default function() {
console.log('Hello ES6');
}
// main.js
import hello from './hello';
hello();
// Hello ES6