ES6 学习教程与demo-case总结

2019-10-18  本文已影响0人  云顶天宫写代码

学习序

1.前端技术

十年前说前端,一定是Flash、Html、Js、Css,这是我工作的时候,前端接触最普遍的技术,Jquery可以说是很新的技术了。

但发展到今天的前端是一个范围很广的技术领域,发展迭代之快,衍生框架之多,可以说其他语言是不能媲美的,这也说明了前端技术是一个活跃的、标准难制定的领域。

这也直接推动了前端技术栈的发展,包括JS的UI框架、模块化的MVVM框架及构建工具、JavaScript 编译器、CSS的预处理器、HTML5技术。

常用的JS相关框架:
Ext.js、Jquery、Jquery MiniUI、Jquery EasyUI (解决浏览器兼容)
React、Angular 2、 Vue(MVVM框架)
RequireJS、 SeaJS、Webpack
Babel、CoffeeScript、 TypeScript
Less、Saas

2.移动开发技术

App(Application 应用程序)一般是指手机端的软件。手机端的操作系统有iOS、Android、Windows Phone、Fuchsia(Google下一代手机操作系统)
各系统的开发语言有:
iOS --- Object-C、Swift
Android ----Java、Kotlin
Fuchsia ---- Dart
上面都可以称为原生开发,移动端技术发展也是提高生产力的过程,出现了跨平台开发的框架,一套代码多端运行,现在常见的开发框架有H5、小程序、uni-app(VUE跨平台框架)、Vue-Native、weex、React-Native、Flutter

3.学习基础的重要性

学习好这些基础是写出合理代码的重要支撑。

实际工作中可能受限于工期和领导的压力,我们在基础的学习上都是蜻蜓点水,急急忙忙的做出效果,导致代码频出bug,这点我是亲身经历过的痛。

去年由原生开发转Native的同事,es6看了一礼拜,告诉我已经学的差不多了,然后让他加入了项目组开始开发,一天的折腾后,临下班前说界面输入的文本,在传参的时候获取不到,找不到问题所在,然后加入log输入参数

我和他一起看打印出的参数,他义正言辞的说没问题,我看了知道是结构赋值的问题。让他重新看了看解构赋值,多想想以前的json取值

结果再过两天又犯了同样的错误,抱怨半天。其实还是基础没理解好,这会很影响开发效率。

废话不多说,祝君学习顺利 !


1. 环境搭建

Node.js是运行在服务端的JavaScript环境,基于Google的V8引擎,下载地址Node.js 安装包及源码下载地址为:https://nodejs.org/en/download/

Node.js 历史版本下载地址:https://nodejs.org/dist/

具体的安装教程:https://www.runoob.com/nodejs/nodejs-install-setup.html
安装完成以后,以windows为例,打开cmd窗口 执行node -v查看,输出版本号说明安装成功。
成功之后就可以使用 npm 命令来管理安装的依赖包了。也可使用 yarn

npm install -g yarn

由于某些安装包有可能安装失败的风险,做React Native的同学应该深有体会,原因当然是天朝网络的问题了,所以我们需要安使用国内源,cmd窗口执行

npm config set registry https://registry.npm.taobao.org --global
npm config set disturl https://npm.taobao.org/dist --global

或

yarn config set registry https://registry.npm.taobao.org --global
yarn config set disturl https://npm.taobao.org/dist --global

好多人推荐使用 yarn,原因是npm的版本管理有个安装不一致的问题,比如 ~1.0.3 指的是 1.0.X的最新版,^1.0.3是指1.X.X的最新版,很容易导致一个月前项目跑起来好好的,来一个新同事,项目跑起来有问题,我相信入坑多年的都有这种经历。yarn的出现也解决了这个痛点(

其实npm 已经用package-lock.json解决了这个问题。用哪个看你喜好
这是常用的操作命令

npm yarn
npm install yarn
npm install react --save yarn add react
npm uninstall react --save yarn remove react
npm install react --save-dev yarn add react --dev
npm update --save yarn upgrade

2. let 与 const

代码块有效

  var PI = "a";
  {
     console.log(PI) // 报错
     let a = 0;
     var b = 1;
 }
 console.log(b)

let 只能声明一次 var 可以声明多次

 let a = 1;
 let a = 2;   //  报错
 var b = 3;
 var b = 4;
 console.log(b)

const 是声明常量,相当于java中final声明

const PI = "3.1415926";
PI = "dadada" // 报错
console.log(PI)

3. 解构赋值

4. Symbol

这是一种新的基础数据类型,和Object、 Number 、 String 、 Boolean同级

5. Map 与 Set

Map 的键值可以是任意值,开发通用是String 字符串

var myMap = new Map();
var keyString = "a string"; 
 
myMap.set(keyString, "和键'a string'关联的值");
  
myMap.get(keyString);    // "和键'a string'关联的值"
myMap.get("a string");   // "和键'a string'关联的值"

6. 字符串

ES6 之前判断字符串是否包含子串,用 indexOf 方法,ES6 新增了子串的识别方法。

includes():返回布尔值,判断是否找到参数字符串。
startsWith():返回布尔值,判断参数字符串是否在原字符串的头部。
endsWith():返回布尔值,判断参数字符串是否在原字符串的尾部。

let string = "apple,banana,orange";
string.includes("banana");   // true
string.startsWith("apple");   // true
string.endsWith("apple");    // false
string.startsWith("banana",6) // true

这三个方法只返回布尔值,如果需要知道子串的位置,还是得用 indexOf 和 lastIndexOf 。

7. 对象

ES6允许对象的属性直接写变量,这时候属性名是变量名,属性值是变量值。

const age = 12;
const name = "Amy";
const person = {age, name};
//等同于
const person = {"age": age, "name": name}

8. 数组

9. 函数

10. Class 类

class (类)作为对象的模板被引入,可以通过 class 关键字定义类。

class 的本质是 function。

它可以看作一个语法糖,使用起来更像面向对象编程的语法。

class Example {
    constructor(a) {
        this.a = a;
    }
}

Example 只能声明一次,名字不能重复声明

class Example {
    constructor(a, b) {
        this.a = a;
        this.b = b;
        console.log('Example');
    }
    sum() {
        return this.a + this.b;
    }
}

let exam1 = new Example(2, 1);
let exam2 = new Example(3, 1);

console.log(exam1.sum(),exam2.sum()); // 3, 4

exam1.__proto__.sub = function() {
    return this.a - this.b;
}

console.log(exam1.sub()); // 1
console.log(exam2.sub()); // 2

使用实例的proto属性改写原型,必须相当谨慎,不推荐使用,因为这会改变“类”的原始定义,影响到所有实例。

class Example{
    constructor(a, b) {
        this.a = a; // 实例化时调用 set 方法
        this.b = b;
    }
    get a(){
        console.log('getter');
        return this.a;
    }
    set a(a){
        console.log('setter');
        this._a = a; // 正确定义
        <!-- this.a = a; // 自身递归调用 -->
    }
}

extends 通过 extends 实现类的继承。getter和setter 必须同时出现

class Father1 {
    constructor(){}
    // 或者都放在子类中
    get a() {
        return this._a;
    }
    set a(a) {
        this._a = a;
    }
}
class Child1 extends Father1 {
    constructor(){
        super(); // 必须放在第一行,后面是相应的代码
        this.child = {name:"child"}
    }
}
let test1 = new Child1();
test1.a = 2;
console.log(test1.a); // 2

11. 模块的导入导出

ES6 的模块化分为导出(export) @与导入(import)两个模块。模块中可以导入和导出各种类型的变量,如函数,对象,字符串,数字,布尔值,类等。

12. Promise 对象

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。


13. Generator 函数

Generator 函数可以通过 yield 关键字,把函数的执行流挂起,为改变执行流程提供了可能,从而为异步编程提供解决方案。

其中 * 用来表示函数为 Generator 函数,yield 用来定义函数内部的状态。

我们来定义一个示例函数

function* test(){
    console.log("one");
    yield '1';
    console.log("two");
    yield '2'; 
    console.log("three");
    return '3';
}

let funResult = test()
f.next();
// one
// {value: "1", done: false}
 
f.next();
// two
// {value: "2", done: false}
 
f.next();
// three
// {value: "3", done: true}
 
f.next();
// {value: undefined, done: true}

这段代码的执行顺序:
第一次调用 next 方法时,从 Generator 函数的头部开始执行,先是打印了 one ,执行到 yield 就停下来,并将yield 后边表达式的值 '1',作为返回对象的 value 属性值,此时函数还没有执行完, 返回对象的 done 属性值是 false。

第二次调用 next 方法时,同上步 。

第三次调用 next 方法时,先是打印了 three ,然后执行了函数的返回操作,并将 return 后面的表达式的值,作为返回对象的 value 属性值,此时函数已经结束,多以 done 属性值为true 。

第四次调用 next 方法时, 此时函数已经执行完了,所以返回 value 属性值是 undefined ,done 属性值是 true 。如果执行第三步时,没有 return 语句的话,就直接返回 {value: undefined, done: true}。

for...of循环可以自动遍历 Generator 函数运行时生成的Iterator对象,且此时不再需要调用next方法。

function print(){
    console.log('执行了打印')
    return "我才是打印"
}
function* sendParameter(){
    console.log("start");
    var x = yield print();
    console.log("one:" + x);
    var y = yield '继续打印';
    console.log("two:" + y);
    console.log("final:" + (x + y));
}

 // 使用 for... of 相当于无参数自动执行该方法
var sendp1 = sendParameter();
for (let v of sendp1) { // v 是迭代对象 也就是yield 后面的数字或字符串或function的返回值
    console.log('for...of',v);
}


<!-- 执行的打印结果为:
start
执行了打印
for...of 我才是打印
one:undefined
for...of 继续打印
two:undefined
final:NaN    // undefined 参与了加法计算,所以值成了NaN 
-->
function getData(){
    console.log('从网络get获取数据')
    setTimeout(() =>{sendp2.next({data:"我是get返回的数据"})}, 1000)
}
function postData(){
    console.log('从网络post获取数据')
    setTimeout(() =>{sendp2.next({data:"我是post返回的数据"})}, 1000)
}
function* requestDataFromServer(){
    console.log("start");
    var x = yield getData();
    console.log("get到的数据:",x);
    var y = yield postData();
    console.log("post到的数据:",y);
}

var sendp2 = requestDataFromServer();
sendp2.next();


<!--打印结果为:
start
从网络get获取数据
get到的数据: { data: '我是get返回的数据' }
从网络post获取数据
post到的数据: { data: '我是post返回的数据' }
-->

结束Generator函数有两种方式,return 和 throw

return 方法返回给定值:

  • 方法提供参数时,返回该参数;
  • 不提供参数时,返回 undefined

throw 是通用关键字,不单纯用在Generator函数,也可以放在普通的方法里做打断或者验证的功能

用法如下:

function* foo(){
    try{
        yield 1;
        yield 2;
        yield 3;
    }catch (e) {
        console.log('catch inner', e);
    }
}
var f = foo();
f.next();
//返回的值是 {value: 1, done: false}

f.return("foo");
//返回的值 {value: "foo", done: true} , done为true,说明迭代完成

try {
  f.throw('a');
  f.throw('b');
} catch (e) {
  console.log('catch outside', e);
}

<!-- 
执行结果为:
catch outside a
 -->

实际是怎么样应用的,考虑以下这样的场景:
打开淘宝登录后,我们看到的了什么信息?

  1. 我的个人信息
  2. 我的订单信息
  3. 我关注的商家自动为我推荐的商品

也就是说我们在登录的时候需要获取到这三种信息,这三种信息被淘宝封装成了三个获取接口,我们以 url1,url2,url3分别代表三个接口地址
登录方式通过Token的方式,方法命名为 login()
获取三个信息的接口分别为 getUserInfo(),getOrderInfo(),getGoods()

function getToken(token){
    console.log('获取Token')
     setTimeout(() =>{loginVal.next("token122222222")}, 500)
}
function getUserInfo(token){
    console.log(`使用${token}获取用户信息`)
    setTimeout(() =>{loginVal.next({userName:'coding'})}, 500)
}

function getOrderInfo(){
    console.log(`使用${token}获取订单信息`)
    setTimeout(() =>{loginVal.next({orderName:'玩具枪'})}, 500)
}

function getGoods(){
    console.log(`使用${token}获取商品信息`)
    setTimeout(() =>{loginVal.next({goodsName:'婴儿玩具'})}, 500)
}

//为了保证登录后信息的完整性,需要将三种数据都获取到,我们会这样写

function* login(){
    try {
        const token = yield getToken();
        console.log(token);
        const userInfo = yield getUserInfo(token);
        const orderInfo = yield getOrderInfo(token);
        const goodsInfo = yield getGoods(token);
        console.log(userInfo,orderInfo,goodsInfo);

    } catch (e) {
        // 处理这个过程遇到的错误
    }
}

var loginVal;
// 点击按钮时执行的方法
function submitClick(){ 
    loginVal = login();
    loginVal.next();
}

14. async 函数

async 是 ES7 才有的与异步操作有关的关键字,和 Promise , Generator 有很大关联的。

// 这是一个没有参数的例子
async function helloAsync(){
    return "helloAsync";
  }
  
console.log(helloAsync())  // Promise {<resolved>: "helloAsync"}
 
helloAsync().then(v=>{
   console.log(v);         // helloAsync
})

await 操作符用于等待一个 Promise 对象, 它只能在异步函数 async function 内部使用。
下面是一个实现红绿灯变换的例子

let time = 8
let intervalId
let newColor ='red 红灯'

function changeColor(lightColor) {
    return new Promise(resolve => {
      if(intervalId)
        clearInterval(intervalId)
      intervalId = setInterval(() => {
          console.log('倒计时:'+time+'秒')
          time -= 1
          if(time<1&&lightColor){
            if(lightColor.includes('red'))
              newColor = "green 绿灯"
            else
              newColor = "red 红灯"
            resolve(newColor);
          }
      }, 1000);
    });
}

async function changeLightAsync() {
    var x = await changeColor(newColor);
    console.log(x); 
}

changeLightAsync()

setInterval(() => {
    time = 8
    changeLightAsync();
},9000)

15. Proxy 与 Reflect

一个 Proxy 对象由两个部分组成: target 、 handler 。

var proxy = new Proxy(target, handler);

Proxy 对象的所有用法,都是上面这种形式,不同的只是handler参数的写法。
其中,new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。
看一个简单的拦截例子:

var proxy = new Proxy({}, {
  get: function(target, property) {
    return 666;
  }
});

proxy.gender // 666
proxy.name // 666
proxy.age // 666

当然y一个拦截器函数可以有多个拦截操作

let target = {name:'张三',age:18}
var handler = {
  get: function(target, name) {
    if (name === 'prototype') {
      return Object.prototype;
    }
    return 'Hello, ' + name;
  },

  apply: function(target, thisBinding, args) {
      console.log('执行了apply',thisBinding)
    return args[0];
  },

  construct: function(target, args) {
    return {value: args[1]};
  }
};

target = function(x, y) {
  return x + y;
}

var fproxy = new Proxy(target, handler);

fproxy(1, 2) // 1
new fproxy(1, 2) // {value: 2}
fproxy.prototype === Object.prototype // true
fproxy.foo === "Hello, foo" // true

下面是所有的拦截操作,一共13项:

上面的基本操作我们可以暂时有一个印象,我们基本上代理的都是对象的get,set,deleteProperty,construct方法等
下面我们看一些适用的场景:

function createValidator(target, validator) {  
    return new Proxy(target, {
        _validator: validator,
        set(target, key, value, proxy) {
            if (target.hasOwnProperty(key)) {
                let validator = this._validator[key];
                if (!!validator(value)) {
                    return Reflect.set(target, key, value, proxy);
                } else {
                    throw Error(`Cannot set ${key} to ${value}. Invalid.`);
                }
            } else {
                throw Error(`${key} is not a valid property`)
            }
        }
    });
}

const personValidators = {  
    name(val) {
        return typeof val === 'string';
    },
    age(val) {
        return typeof age === 'number' && age > 18;
    }
}
class Person {  
    constructor(name, age) {
        this.name = name;
        this.age = age;
        return createValidator(this, personValidators);
    }
}

const bill = new Person('Bill', 25);

// 以下操作都会报错,我们在赋值时可以快速的用try...catch 捕捉错误做出响应
bill.name = 0;  
bill.age = 'Bill';  
bill.age = 15;  
let api = {  
    _apiCase1: 'http://182.190.5.88', // 实例1
    _apiCase2: 'http://182.190.5.89', // 实例2

    /* 测试数据时使用 this._apiCase1 或者 this._apiCase2 */

    getUsers: function(){}, 
    getUser: function(userId){}, 
    setUser: function(userId, config){}
};


// 我们定义私有变量的初衷是为了不修改,但是下面依然可以执行
api._apiCase1 = 'http://182.190.5.90';

很显然,约定俗成是没有束缚力的,使用 ES6 Proxy 我们就可以实现真实的私有变量了

const privateKes = ['_apiCase1','_apiCase2'];
api = new Proxy(api, {  
    get(target, key, proxy) {
        if(privateKes.indexOf(key) > -1) {
            throw Error(`${key} 不存在.`);
        }
        return Reflect.get(target, key, proxy);
    },
    set(target, key, value, proxy) {
        if(privateKes.indexOf(key) > -1) {
            throw Error(`无法为 ${key} 赋值,因为它是私有变量`);
        }
        return Reflect.get(target, key, value, proxy);
    }
});

// 以下操作都会抛出错误
console.log(api._apiCase1);
api._apiCase2 = '987654321'; 
let dataStore = {  
    noDelete: 1235,
    oldMethod: function() {/*...*/ },
    doNotChange: "tried and true"
};

const NODELETE = ['noDelete'];  
const NOCHANGE = ['doNotChange'];
const DEPRECATED = ['oldMethod'];  

dataStore = new Proxy(dataStore, {  
    set(target, key, value, proxy) {
        if (NOCHANGE.includes(key)) {
            throw Error(`Error! ${key} 是只读属性.`);
        }
        return Reflect.set(target, key, value, proxy);
    },
    deleteProperty(target, key) {
        if (NODELETE.includes(key)) {
            throw Error(`Error! ${key} 是不可删除属性`);
        }
        return Reflect.deleteProperty(target, key);

    },
    get(target, key, proxy) {
        if (DEPRECATED.includes(key)) {
            console.warn(`Warning! ${key} 已过时,请谨慎使用.`);
        }
        var val = target[key];

        return typeof val === 'function' ?
            function(...args) {
                Reflect.apply(target[key], target, args);
            } :
            val;
    }
});

// 这三个都会报错
dataStore.doNotChange = "foo";  
delete dataStore.noDelete;  
dataStore.oldMethod();

Proxy.revocable 返回一个可取消的Proxy实例

let target2 = {};
let handler2 = {};

let {proxy:newProxy, revoke} = Proxy.revocable(target2, handler2);

newProxy.foo = 123;
newProxy.foo // 123

revoke();
newProxy.foo // Cannot perform 'get' on a proxy that has been revoked

Proxy.revocable的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。
实际开发中有这样的需求:
比如 登录时候需要判断输入密码次数,如果输入错误密码超过5次,密码输入框变为不可用


了操作对象而提供的新 API*
Reflect对象其实就是为了取代Object对象。取代原因有一下几点:

  1. Object对象的一些内部方法放在了Reflect上面,比如:Object.defineProperty。主要是优化了语言内部的方法。

  2. 修改Object方法的返回,例如:Object.definePropery(obj,name,desc)无法定义属性时报错,而Reflect.definedProperty(obj,name,desc)则会返回false。

  3. 让Object变成函数的行为,以前的:name in obj和delete obj[name],可以让Reflect.has(name)和Reflect.deleteProperty(obj,name)替代。

  4. Reflect方法和Proxy方法一一对应。主要就是为了实现本体和代理的接口一致性,方便用户通过代理操作本体。

下面我们写一个比较复杂的例子,用Proxy和Reflect实现观察者模式

// 定义一个订阅者集合
const queuedObservers = new Set();
// 添加订阅者
const observe = fn => queuedObservers.add(fn);
// 给对象添加代理对象,代理的set方法中进行遍历订阅者列表
const observable = obj => new Proxy(obj,{set});

function set(target,key,value,receiver){
        const result = Reflect.set(target,key,value,receiver);
        //遍历订阅者集合,依次触发订阅者方法
        queuedObservers.forEach(fn => fn());
        //返回订阅者
        return result;
}

//使用Proxy实现观察者模式[发布-订阅者模式]
const person = observable({name:'张三',age:30});

function print(){
    console.log(`${person.name},${person.age}`);
}
function anotherPrint(){
    console.log(`你想的很对`)
}

//订阅者集合里面加入print订阅者
observe(print);
observe(anotherPrint)

person.name = 'miya'

Reflect对象一共有 13 个静态方法 与 Proxy 的方法一一对应
Reflect.apply(target, thisArg, args)
Reflect.construct(target, args)
Reflect.get(target, name, receiver)
Reflect.set(target, name, value, receiver)
Reflect.defineProperty(target, name, desc)
Reflect.deleteProperty(target, name)
Reflect.has(target, name)
Reflect.ownKeys(target)
Reflect.isExtensible(target)
Reflect.preventExtensions(target)
Reflect.getOwnPropertyDescriptor(target, name)
Reflect.getPrototypeOf(target)
Reflect.setPrototypeOf(target, prototype)

上一篇 下一篇

猜你喜欢

热点阅读