ES6出了有些時間了,看了阮一峰的es6標准入門感覺看到了什么但是感覺什么又沒看到,所以今天來簡單的說下ES6里面的實現原理。
首先是let和const,let聲明一個變量作用於一個塊級作用域上,相當於寫了一個匿名函數保存了let聲明變量(暫存死區),記得一個問題,對象不是一個作用域,const聲明一個常量,此常量不可更改,ES6里面把用let和const聲明的變量開辟了一個內存空間,該內存空間不掛在於window上,而var聲明的變量是掛載到window上的,比如如下代碼可以證實該點。
let a = 1000; // let不會將變量放在window上 let obj = { a: 1, b: () => { this.a = 100; // window } } obj.b(); console.log(obj.a,a);
我們知道現在有些老版本的瀏覽器還是不認ES6語法的,那么我們使用babel可以把ES6的語法轉化為ES5,那么就簡單的說下babel,babel的原理是抽象語法樹,說的多不如寫的多那么我們就寫個demo吧,先初始一個項目,npm init -y初始化之后 npm i babel-core babel-types,開始寫代碼實現一個let轉化成一個var
let babel = require('babel-core'); let code = `let a = 1;let b=2`; // .babelrc let variable = { visitor:{ // 訪問者模式 VariableDeclaration(path){ let node = path.node; node.kind = 'var'; path.replaceWith(node); } } } let r = babel.transform(code,{ plugins:[ variable ] }); console.log(r.code);
再比如我們可以寫es6箭頭函數轉換成es5的普通函數的。
let babel = require('babel-core'); let t = require('babel-types'); let code = `let a = (a,b)=>{return a+b}`; let ArrowFunctions = { visitor:{ ArrowFunctionExpression(path){ let node = path.node; let fns = t.functionExpression(null, node.params,node.body); path.replaceWith(fns); }, VariableDeclaration(path){ let node = path.node node.kind = 'var'; path.replaceWith(node); } } } let r = babel.transform(code,{ plugins:[ ArrowFunctions ] }); console.log(r.code)
如果感覺這個挺好玩的還想接着往下寫,那么可以參考這個鏈接www.exprima.org
解構也是ES6里面比較好的一個地方,那什么是解構呢?就是等號左邊和右邊結構類似,那么我們就可以解構,舉幾個例子
// 既聲明 又賦值 默認值 let [,b,c,d=100] = [1,2,3]; console.log(b,d); // 對象的解構 let obj = {name:'zhangsan',age:15}; let {name:Name,age,address="默認"} = obj; console.log(Name, age, address) // 數組 + 對象 let [{name}] = [{name:'zhangsan'}]; // 傳遞參數,結果解構 Promise.all(['zhangsan','15']).then(([name,age])=>{ console.log(name, age); }); //傳入為空 那么有默認值 function ajax({type='get',url="/",dataType="json"}) { console.log(type, url, dataType) }
ES6里面還有擴展運算符...他可以用來做許多事情比如合並數組什么的,但是這個是淺拷貝。看擴展運算符的例子吧
let arr1 = [1,2,3,[1,2,3]]; let arr2 = [1,2,3]; let arr = [...arr1,...arr2]; console.log(arr); // 對象的合並 如果多層需要深拷貝 (要依次的將對象展開) let school = {name:'zfpx',a:{a:1}}; let my = {age:18}; let newObj = {...school,a:{...school.a},...my}; // 淺拷貝 console.log(newObj) school.a.a = 100; console.log(newObj);
既然擴展運算符是淺拷貝,那么我們實現一個深拷貝
function deepClone(obj) { if(obj == null) return null; if (obj instanceof Date) return new Date(obj); if(obj instanceof RegExp) return new RegExp(obj); if(typeof obj !== 'object') return obj; let t = new obj.constructor for(let key in obj ){ t[key] = deepClone(obj[key]) } return t; } let o = { a: [1, 2, 3] } let r = deepClone(o); o.a[1] = 1000 console.log(r);
說下proxy的攔截原理,這個攔擊的原理就是defineReactive,嗯,你沒有看錯,proxy的原理就是這個,不信你看代碼
let obj = { name: { name: 'zhangsan' }, age: 2 }; function observer(obj) { if (typeof obj != 'object') return obj; for (let key in obj) { // 定義響應式 (通過Object.defineProperty進行重寫); defineReactive(obj, key, obj[key]); } } function defineReactive(obj, key, value) { observer(value); Object.defineProperty(obj, key, { get() { return value; }, set(val) { console.log('更新'); value = val; } }) } observer(obj); obj.name.name = 'lisi';
let obj = { name: { name: 'zhangsan' }, age: 2 }; function observer(obj) { if (typeof obj != 'object') return obj; for (let key in obj) { // 定義響應式 (通過Object.defineProperty進行重寫); defineReactive(obj, key, obj[key]); } } function defineReactive(obj, key, value) { observer(value); Object.defineProperty(obj, key, { get() { return value; }, set(val) { console.log('更新'); value = val; } }) } observer(obj); obj.name.name = 'lisi';
使用proxy可以把對象進行代理 加上特定功能。
類的繼承,可能大家都知道es6里面繼承一個類是用extends,如果想繼承父元素的構造器的私有屬性那么就需要使用super(),我不知道大家注沒注意到一點,子類的實例是無法調用父類的static的方法的,但是子類能調用,這點大家寫東西的時候要注意一下。比如如下面的代碼所示
class Animal { constructor(type){ this.type = type } // 在類中 es6中只定義了靜態方法 static flag(){ return '動物' } eat(){ console.log('吃') } } // Animal.flag = '動物' class Cat extends Animal{ // Object.create constructor(type){ super(type); // Animal.call(this); super中的this指的就是Cat } } let cat = new Cat('哺乳類'); console.log(cat.type); cat.eat() console.log(Cat.flag()) // t.__proto__.eat(); // 只能new這個類才能實現 // 怎么實現兩個類 可以繼承靜態屬性的 function Parent() {} Parent.a = '父親' function Child() {} Object.setPrototypeOf(Child,Parent) console.log(Child.a);
那么我們就用es5去實現一個類的繼承吧,比如我這有一個類,先給他起個名字吧,就叫父類,下面就拿這個父類舉例子。
function Animal(type) { this.type = { t: type}; } Animal.prototype.eat = function () { console.log('eat'); }
比如舉個例子:只繼承父類上的實例上的屬性
let cat = new Cat('哺乳類'); console.log(cat.eat);
比如:繼承父類的私有屬性
function Cat(type) { Animal.call(this,type); // 讓父類在子類中執行,並且this指向子類 }
比如:只繼承父類的公有屬性 有兩種方法 第一種是:Object.setPrototypeOf(Child.prototype,Parent.prototype) 或者也可以這么寫 Child.prototype.__proto__ = Parent.prototype;第二種是Child.prototype = Object.create(parent.prototype,{constructor:{value:Child}});
Cat.prototype = Object.create(Animal.prototype,{constructor:{value:Cat}}); let cat = new Cat('哺乳類') console.log(cat.type); cat.eat(); console.log(cat.constructor);
為什么create里面要傳入一個構造函數?因為本質上create是創建了一個函數作為轉發,他里面沒有構造函數,所以我們需要給他一個構造函數指回子元素的構造函數,這樣我們才能獲取到子元素的構造函數,具體原理見下行。
那么我們說下這個create實現原理吧:第一我們要知道函數原型鏈上的構造函數指向這個函數同理這個函數也指向這個構造函數也就是Parent.prototype.constructor === Parent,類的實例的__proto__會指向函數prototype的構造函數。
function create(parentPrototype,props) { function Fn() {} Fn.prototype = parentPrototype; let fn = new Fn(); for(let key in props){ //直接在一個對象上定義一個新屬性,或者修改一個已經存在的屬性,並返回這個對象 Object.defineProperty(fn,key,{ ...props[key], enumerable:true }); } return fn; }
好,那么我們在對這個進行一下類的拓展吧。
function _classCallCheck(ins, Constructor) { if (!(ins instanceof Constructor)) { throw new Error('without new'); } } function defineProperties(target,properties) { for (let i = 0; i < properties.length;i++){ Object.defineProperty(target,properties[i].key,{ enumerable:true, configurable:true, ... properties[i] } ); } } function _createClass(constructor,properties,staticProperties) { if (properties.length>0){ defineProperties(constructor.prototype, properties); } if (staticProperties.length>0){ defineProperties(constructor, staticProperties); } } var Animal = function () { function Animal(type) { _classCallCheck(this, Animal); this.type = type; // 實例上的屬性 return {} } _createClass(Animal, [ // 數組中放的就是descriptor { key: 'eat', value: function () { console.log('吃') } }, { key: 'drink', value: function () { console.log('喝水') } } ], [ { key: 'flag', value: function () { return '動物' } } ]) // 一種公有方法 eat es6靜態方法 return Animal }() function _inherits(SubClass,ParentClass) { // 繼承原型上的方法 SubClass.prototype = Object.create(ParentClass.prototype,{constructor:{value:SubClass}}); // 繼承靜態方法 Object.setPrototypeOf(SubClass,ParentClass) } let Cat = function(Parent){ _inherits(Cat,Parent); // 1.繼承公有的方法 2.靜態方法 function Cat(type) { _classCallCheck(this,Cat); let returnState = Parent.call(this,type); // 說明父類調用后返回的是一個對象,應該用這個對象作為子類的this let that = this; if (typeof returnState === 'object' || typeof returnState === 'function' ){ that = returnState; } that.a = 100; return that; } return Cat }(Animal); let cat = new Cat('動物'); console.log(cat);
在順手說下es7里面的裝飾器吧,這個裝飾器是@,他可以用來把裝飾的函數來進行拆分,按照英語的語法來說,他把這個函數拆為了過去時、現在時和未來時,在這三個狀態中你可以改變一些東西來達到你的目的。
說下set()這個函數怎么用吧,直接上例子。
let set = new Set([1,2,3,3,2,1]); console.log([...set]); let arr1 = [1,2,3,4]; let arr2 = [4,5,6]; // 求並集 function union(arr1,arr2) { return new Set([...arr1,...arr2]) } union(arr1, arr2); // 交集 function insection(arr1, arr2) { let set1 = new Set(arr1); let set2 = new Set(arr2); return [...set1].filter(item => set2.has(item)); } console.log(insection(arr1, arr2)); // 差級 function difference(arr1, arr2) { let set1 = new Set(arr1); let set2 = new Set(arr2); return [...set2].filter(item => !set1.has(item)); } console.log(difference(arr1, arr2));
ES6里面數組常用的方法filter過濾 forEach 循環 map 映射 reduce 收斂 some every 反義
let arr = [1,2,3] let arr1 = arr.filter(item=>item<=2); console.log(arr1); let arr =[1,2,3]; let arr1 = arr.map(item=>item*2); let arr = [1,2,3]; let flag = arr.every(item=>item==3); console.log(arr.includes(3)); //es7 // findIndex let arr = [1, 2, 3]; let item = arr.find(item => item == 4); console.log(item);
然后有人說ES6有了個新的數據類型叫Symbol,這個數據類型永遠唯一!!!不信你運行下這個
let s = Symbol(); let q = Symbol(); console.log(s === q);
字符串模板看ES6分類里面,這個里面有寫。
Promise我單獨寫了一個,再抽空寫就是async-await了,這個也挺簡單的。