JavaScript從初級往高階走系列————ES6
阿新 • • 發佈:2019-02-19
ES6
現在基本上開發中都在使用ES6,瀏覽器環境支援不好,可以用babel外掛來解決。
採用‘二八定律’,主要涉及ES6常用且重要的部分。
問題:
- ES6模組化如何使用,開發環境如何打包
- Class和普通建構函式有何區別
- Promise的基本使用和原理
- 總結一下ES6其他常用功能
ES6模組化如何使用,開發環境如何打包
模組化的基本語法
// util1.js
export default {
a : 100
}
const str = 'hello';
export default str;
// export default const str = 'hello'; X
// util2.js export function fn1() { console.log('fn1'); } export function fn2() { console.log('fn2'); } export const fn3 = ()=> { console.log('fn3'); }
// index.js
import str from './util1.js';
import {fn1 , fn2} from './util2.js';
import * as fn from './util2.js';
console.log(str);
fn1();
fn2();
fn.fn1();
fn.fn2();
export default
預設輸出這個,然後在import
的時候就會拿到預設輸出的內容。例子中預設輸出的a=100
。export
多個內容,在import
時需要使用{}
進行引用你需要的內容。
export
和export default
與exports
和module.exports
的區別
require
: node 和 es6 都支援的引入export
/import
: 只有es6 支援的匯出引入module.exports
/exports
: 只有 node 支援的匯出
-
module.exports
初始值為一個空物件 {}-
exports
是指向的module.exports
的引用 -
require()
返回的是module.exports
而不是exports
-
Node
裡面的模組系統遵循的是CommonJS規範。
CommonJS定義的模組分為: 模組標識(module
)、模組定義(exports
) 、模組引用(require
)
在nodejs,exports
module.exports
的引用,初始化時,它們都指向同一個{}
物件。
物件在JS中屬於引用型別,意思就是exports
和module.exports
是指向同一個記憶體地址的。
看下面的例子:
exports.fn = function(){
console.log('fn');
}
// 這兩種情況的效果是一樣的,上面說了exports與`module.exports初始化同一個物件,所以兩種定義方就是給這個同對象定義了一個fn的屬性,該屬性值為一個函式。
module.exports.fn = function(){
console.log('fn');
}
exports = function(){
console.log('fn');
}
// 這兩種情況就不一樣了。上面的exports想當於指向了另一個記憶體地址。而下面這種情況是可以正常匯出的。
module.exports = function(){
console.log('fn');
}
exports物件是當前模組的匯出物件,用於匯出模組公有方法和屬性。別的模組通過require函式使用當前模組時得到的就是當前模組的exports物件。
- module.exports 的使用
// sayHello.js
function sayHello() {
console.log('hello');
}
module.exports = sayHello;
// app.js
var sayHello = require('./sayHello');
sayHello();
定義一個sayHello模組,模組裡定義了一個sayHello方法,通過替換當前模組exports物件的方式將sayHello方法匯出。
在app.js中載入這個模組,得到的是一個函式,呼叫該函式,控制檯列印hello。
// sayWorld.js
module.exports = function(){
console.log('world');
}
// app.js
var sayWorld = require('./sayWorld'); // 匿名替換
sayWorld();
- exports 匯出多個變數
當要匯出多個變數怎麼辦呢?這個時候替換當前模組物件的方法就不實用了,我們需要用到exports物件。
// userExports.js
exports.a = function () {
console.log('a exports');
}
exports.b = function () {
console.log('b exports');
}
// app.js
var useExports = require('./userExports');
useExports.a();
useExports.b();
// a exports
// b exports
當然,將useExports.js改成這樣也是可以的:
// userExports.js
module.exports.a = function () {
console.log('a exports');
}
module.exports.b = function () {
console.log('b exports');
}
在實際開發當中可以只使用module.exports
避免造成不必要的問題。
開發環境配置
babel
- nodejs環境 npm init
- npm i babel-core babel-preset-es2015 babel-preset-latest --save-dev
- 建立.babelrc檔案
- npm i --global babel-cli
// .babelrc
{
"presets": ["es2015","latest"],
"plugins": []
}
webpack
rollup.js
npm init
- 安裝
rollup.js
需要的一些外掛
npm i rollup rollup-plugin-node-resolve rollup-plugin-babel babel-core babel-plugin-external-helpers babel-preset-latest --save-dev
- 配置 .babelrc
- 配置 rollup.config.js
rollup 功能單一(打包js模組化), webpack功能強大
工具儘量功能單一,可繼承,可擴充套件
// .babelrc
{
"presets":[
["latest", {
"es2015":{
"modules": false
}
}]
],
"plugins":["external-helpers"]
}
// rollup.config.js
import babel from 'rollup-plugin-babel';
import resolve from 'rollup-plugin-node-resolve';
export default {
entry: 'src/index.js',
format: 'umd',
plugins: [
resolve(),
babel({
exclude: 'node_modules/**'
})
],
dest: 'build/bundle.js'
}
// package.json
...
"scripts":{
"start": "rollup -c rollup.config.js"
}
...
npm run start
關於JS眾多模組化標準
- 沒有模組化
- AMD成為標準,require.js
- 前端打包工具,使得nodejs模組化(CommonJS)可以被使用
- ES6出現,想統一現在所有模組化標準
- nodejs積極支援,瀏覽器尚未統一
- 你可以自造lib,但是不要自造標準!!!
Class和普通建構函式有何區別
JS建構函式
// 建構函式
function MathHandle(x, y){
this.x = x;
this.y = y;
}
// 原型擴充套件
MathHandle.prototype.add = function(){
return this.x + this.y;
}
// 建立例項
var m = new ManthHandle(1,2);
console.log(m.add()); // 3
Class基本語法
class MathHandle { // 直接跟大括號
constructor(x, y) {
this.x = x;
this.y = y;
}
add() {
return this.x + this.y;
}
}
const m = new ManthHandle(1,2);
console.log(m.add()); // 3
語法糖
typeof MathHandle
='function'
MathHandle
其實是個function
,‘建構函式’MathHandle
===MathHandle.prototype.constructor
繼承
原生js繼承
// 動物
function Animal() {
this.eat = function() {
console.log('animal eat');
}
}
// 狗
function Dog() {
this.bark = function(){
console.log('dog bark');
}
}
// 繫結原型,實現繼承
Dog.prototype = new Animal();
// 例項化一隻狗
var hashiqi = new Dog();
// hashiqi就有了eat方法
hashiqi.eat(); // animal eat
廖雪峰老師的原型繼承:點這裡
ES6繼承
class Animal {
constructor(name){
this.name = name;
}
eat() {
console.log(`${this.name} eat`);
}
}
class Dog extends Animal { // extends 繼承
constructor(name){
super(name); // 必須* 記得用super呼叫父類的構造方法!
this.name = name;
}
say() {
console.log(`${this.name} say`);
}
}
const dog = new Dog('hashiqi');
dog.eat(); // hashiqi eat
Promise 的基礎使用
解決回撥地獄(Callback Hell)
詳細點的Promise:點這裡
Promise 基礎語法
new Promise((resolve, reject) => {
// 一段耗時很長的非同步操作
.....
resolve(); // 資料處理完成
reject(); // 資料處理出錯
}).then(function A() {
// 成功,下一步
}, function B(){
// 失敗,做相應處理
})
我最開始接觸到Promise
的時候,一直傻了吧唧的在想resolve()
和reject()
在什麼時候呼叫。resolve()
和reject()
就是為後面then()
中的兩個函式服務的。
resolve和reject
new Promise((resolve, reject) => {
setTimeout(()=>{
resolve('good,我要傳給then裡的一個函式');
},2000);
setTimeout(()=>{
reject('錯了,把我給我then裡的第二個函式');
},2000);
}).then(value => {
console.log(value); // good,我要傳給then裡的一個函式
},value => {
console.log(value); // 錯了,把我給我then裡的第二個函式
});
來個實際的例子
/**
* 基於jquery封裝一個promise ajax請求
* @param {[type]} param [選項]
* @return {[type]} [description]
*/
request(param){
return new Promise((resolve,reject) => {
$.ajax({
type : param.type || 'get',
url : param.url || '',
dataType : param.dataType || 'json',
data : param.data || null,
success:(res)=>{ // 用箭頭函式避免this指向問題
if (0 === res.status) {
typeof resolve === 'function'&&resolve(res.data, res.msg); // 成功就把請求到的資料用resolve返回,這樣就可以在then的第一個函式裡拿到值了
} else {
typeof reject === 'function'&&reject(res.msg || res.data); // 失敗就返回錯誤資訊
}
},
error:(err)=>{ // 這個失敗是請求失敗,上面那個失敗是請求成功傳送了,但是沒有拿到資料失敗了
typeof reject === 'function'&&reject(err.statusText);
}
})
})
}
ES6常用其他功能
let/const
let
const
與var
都是用來定義變數的,不同的是let
自帶作用域,const
不能重複賦值。
let name = 'FinGet'
while (true) {
let name = 'GetFin'
console.log(name) //GetFin
break
}
console.log(name) //FinGet
let
定義的變數只在包含它的程式碼塊內有用
const PI = 3.1415926;
PI = 3.14; // 錯誤
多行字串/模板變數
let name = 'FinGet';
let age = 22;
// js
var str = '我是'+ name+',今年'+age+'歲'; // 很麻煩
let str1 = `我是${name},今年${age}歲`; // 簡單多了
模板字串就是用`(Tab鍵上面那個)包含,變數就是用
${}`表示
解構賦值
let obj = {
name: 'FinGet',
age: 22,
job: '前端',
addr: '成都'
}
let {name,age} = obj;
console.log(name); // FinGet
console.log(age); // 22
還可以反過來:
let name = 'FinGet';
let age = 22;
let job = '前端';
let addr = '成都';
let obj = {name,age,job,addr};
//obj = {name: 'FinGet',age: 22,job: '前端',addr: '成都'}
塊級作用域
另外一個var
帶來的不合理場景就是用來計數的迴圈變數洩露為全域性變數,看下面的例子:
// js
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
let 自帶塊級作用域
// ES6
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
原生js想實現這種效果,需要用到閉包:
var a = [];
for (var i = 0; i < 10; i++) {
(function(j){ // 立即執行函式
a[j] = function() {
console.log(j);
}
}(i))
}
a[6](); // 6
立即執行函式形成了一個塊級作用域,將引數j儲存了下來,並不會被‘汙染’,原生js沒有塊級作用域,var
在for
中定義的變數是個全域性變數,可以在外部訪問,也就可以被改變,所以每次for
迴圈都是重置修改i
的值,導致最後只能輸出10。
函式預設引數與rest
default
很簡單,意思就是預設值。大家可以看下面的例子,呼叫animal()方法時忘了傳引數,傳統的做法就是加上這一句type = type || 'cat'
來指定預設值。
function animal(type){
type = type || 'cat'
console.log(type)
}
animal()
如果用ES6我們而已直接這麼寫:
function animal(type = 'cat'){
console.log(type)
}
animal(); // cat
最後一個rest語法也很簡單,直接看例子:
function animals(...types){
console.log(types)
}
animals('cat', 'dog', 'fish') //["cat", "dog", "fish"]
而如果不用ES6的話,我們則得使用ES5的arguments。
箭頭函式
// js函式
function (a,b){
console.log(a+b);
}
// es6箭頭函式
(a,b) => {
console.log(a+b);
}
把function
去掉,在()
與{}
之間加上=>
當我們使用箭頭函式時,函式體內的this
物件,就是定義時所在的物件,而不是使用時所在的物件。
並不是因為箭頭函式內部有繫結this的機制,實際原因是箭頭函式根本沒有自己的this
,它的this
是繼承外面的,因此內部的this
就是外層程式碼塊的this
。
最後
建立了一個前端學習交流群,感興趣的朋友,一起來嗨呀!