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了,这个也挺简单的。