ECMAScript 6 概览

--摘自 阮一峰老师 ECMAScript 6--

let 与const 命令

  一.块级作用域:注(

    • 允许在块级作用域内声明函数。

    • 函数声明类似于var,即会提升到全局作用域或函数作用域的头部。

    • 同时,函数声明还会提升到所在的块级作用域的头部。

       注意,上面三条规则只对 ES6 的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当作let处理。

  二.不存在变量提升

  三.暂时性死区(如果区块中存在letconst命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错)

  四.let 不允许同一作用域内,重复声明

  const 常量

  一.值不能改变,声明变量后须立即初始化。

  二.与let 相同,只在声明所在的块及作用域内有效。const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。不能重复声明

  三.const ,值不能改变,不能指向其他值

  四.冻结对象 Object.freeze({});

  ES6六种声明变量的方法:var,function,let,const, import,class

  顶层对象的属性与全局变量  

  一.let,const,class 命令声明的全局变量 不属于顶层的属性 let i; window.i //undefined

  二.全局环境中,Node模块和ES6模块中,this返回的是当前模块。

  

变量的解构与赋值    

  ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。

  let [a,b,c] = [1,2,3];  let {bar,foo} = {foo:"aaa",bar:"bbb"};let{foo:bar} = {bar:"bbb"};

 

字符串的操作

 

  一.字符的Unicode表示法

 二 .codePointAt();方法会正确返回32位的UTF-16字符的码点

 三.字符串遍历:优点是可以识别大于0xFFFF的码点,传统的for循环无法识别这样的码点。

for (let codePoint of 'foo') { console.log(codePoint) }
  // f,o,o
  

 四.ar();该方法不能识别码点大于0xFFFF的字符。

 五.用来确定一个字符串是否包含在另一个字符串中。ES6又提供了三种新方法。

    • includes():返回布尔值,表示是否找到了参数字符串。

    • startsWith():返回布尔值,表示参数字符串是否在源字符串的头部。

    • endsWith():返回布尔值,表示参数字符串是否在源字符串的尾部。

六. repeat();返回一个新的字符串,表示将原字符串重复n次。x.repeat(3);//"x x x"

七.padStart(),padEnd();字符串不全长度如果字符串不够指定的长度就会在 头部或者尾部补全

八.模板字符串:用反引号(`)标识
$('#list').html(` <ul> <li>first</li> <li>second</li> </ul> `);


正则的拓展

如果RegExp构造函数第一个参数是一个正则对象,那么可以使用第二个参数指定修饰符。而且,返回的正则表达式会忽略原有的正则表达式的修饰符,只使用新
指定的修饰符。new RegExp(/abc/ig, 'i').flags

   字符串对象共有4个方法,可以使用正则表达式:match()replace()search()split()

   ES6将这4个方法,在语言内部全部调用RegExp的实例方法,从而做到所有与正则相关的方法,全都定义在RegExp对象上。

  • String.prototype.match 调用 RegExp.prototype[Symbol.match]

  • String.prototype.replace 调用 RegExp.prototype[Symbol.replace]

  • String.prototype.search 调用 RegExp.prototype[Symbol.search]

  • String.prototype.split 调用 RegExp.prototype[Symbol.split]

一.修饰符
ES6对正则表达式添加了u修饰符,含义为“Unicode模式”,用来正确处理大于\uFFFF的Unicode字符。也就是说,会正确处理四个字节的UTF-16编码。
ES6还为正则表达式添加了y修饰符,叫做“粘连”(sticky)修饰符。y修饰符的作用与g修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始。不同之处在于,g修饰符只要剩余位置中存在匹配就可,而y修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义ES6还为正则表达式添加了S修饰符正则表达式中,点(.)是一个特殊字符,代表任意的单个字符,但是行终止符(line terminator character)除外。

二.后行断言:V8 引擎4.9版已经支持,Chrome 浏览器49版打开”experimental JavaScript features“开关(地址栏键入about:flags),就可以使用这项功能。
三.Unicode属性类:一种新的类的写法\p{...}\P{...},允许正则表达式匹配符合Unicode某种属性的所有字符

数值的拓展

二进制与八进制表示法
ES6 提供了二进制和八进制数值的新的写法,分别用前缀0b(或0B)和0o(或0O)表示。0o767 === 503

ES6在Number对象上,新提供了Number.isFinite()Number.isNaN()两个方法。Number.isFinite()用来检查一个数值是否为有限的(finite)


Number.parseInt(), Number.parseFloat()

Number.isInteger()

安全整数和Number.isSafeInteger()表示这个范围的上下限

Math对象的扩展

ES6在Math对象上新增了17个与数学相关的方法。所有这些方法都是静态方法,只能在Math对象上调用。Math.trunc(),Math.cbrt(),Math.clz32(),Math.imul(),Math.fround()...

数组的拓展

Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括ES6新增的数据结构Set和Map)

实际应用中,常见的类似数组的对象是DOM操作返回的NodeList集合,以及函数内部的arguments对象。Array.from都可以将它们转为真正的数组。

// NodeList对象 let ps = document.querySelectorAll('p'); Array.from(ps).forEach(function (p) { console.log(p); }); // arguments对象 function foo() { var args = Array.from(arguments); // ... }

Array.of()

Array.of方法用于将一组值,转换为数组。

Array.of(3, 11, 8) // [3,11,8]

数组实例的copyWithin()

数组实例的copyWithin方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。

数组实例的find()和findIndex()

数组实例的find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined

entries(),keys()和values()

includes() 

fill()

 

---函数的拓展---

函数参数可以设置默认值 function Point(x = 10,y = 0){ this.x = x;this.y = y;} var p = new Point(); 但是不能用let const 再次声明 否则会报错。

也不能用重复的参数名;

函数参数的个数,设定默认值的参数不计入length个数(function (a) {}).length // 1(function (a = 5) {}).length // 0(function (a, b, c = 5) {}).length // 2

如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。

作用域:一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域

 

rest参数:(形式为“...变量名”),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。

rest参数中的变量代表了一个数组 数组中特有的方法可以用于这个变量, 函数的length属性不包括 rest参数

拓展运算符。拓展运算符... 将一个数组转换为用逗号分隔的参数序列。该运算符主要用于函数的调用。

拓展运算符可以展开数组所以不需要apply方法也可以.    如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错

--箭头函数--

  var f = v => v;   /  var f = function(v){return v};

如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。

  var f = () =>v; var sum =(num1,num2) => num1+num2;

如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。

  var sum =(num1 ,num2)=>{return num1+num2};

由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号

  var getTempItem = id =>({id:id,name:'temp'});

箭头函数可以与变量解构结合使用。

箭头函数有几个使用注意点。

(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替。

(4)不可以使用yield命令,因此箭头函数不能用作Generator函数。

  箭头函数可以让this指向固定化

 this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正   是因为它没有this,所以也就不能用作构造函数。

  由于箭头函数没有自己的this,所以当然也就不能用call()apply()bind()这些方法去改变this的指向。箭头函数没有自己的this,所以bind方法   无效,内部的this指向外部的this

--尾调用--:某一个函数的最后一步是调用另一个函数:另一个函数不会不在引用包含函数的值(类似于 栈执行现场 的一种 垃圾回收机制吧)

--尾递归--:函数调用自身称为递归,尾函数调用自身称为 尾递归。但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。

      function factorial(n,total){

          if(n === 1) return total;

          return factorial(n -1,n* total);

       }

          factorial(5,1);//120; 复杂度O(1);

---对象的拓展---
var foo = 'bar';
var baz = {foo}; //{foo:'bar'}
ES6允许在对象中直接写变量
function(x,y){return:x,y} ;// function(x,y){ return {x:x,y:y}};
方法也可以简写
var o = {method(){
  return 'hello';
}}
等同于
var o = {
  method:function(){
  return 'hello!';
}
}
示例;
var birth = '2000/01/01';
var Person = {
  name:'张',
  birth,
  hello(){console.log('我的名字是',this.name);}
};
示例;
function getPoint(){
  var x = 1;
  var y =10;
  return {x,y};
}
getPoint();//{x:1,y:10}
--属性名表达式
obj['a'+'b'] = 123; // var obj = {foo:true,abc:123};
let proKey = 'foo';
let obj = {
  [proKey]:true,
  ['a'+'bc']:123
};
定义方法名
let obj = {
  ['h'+'hello'](){
  return 'hi';
}
};
obj.hello();// hi
--属性名表达式 如果是一个对象,默认情况下就会将对象 转换成字符串[object object]
const keyA = {a:1};
const keyB = {b:2};
const myObject = {
  [keyA]:'value1',
  [keyB]:'value2'
};
myObject //object {[object object]:'value2'};
--方法的name属性--
函数的name 属性,返回函数名,对象方法也是函数,因此也有name属性
--Object.is();

ES5比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。JavaScript缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。

 
 

ES6提出“Same-value equality”(同值相等)算法,用来解决这个问题。Object.is就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致

Object.assign();用于对象合并,将源对象source 的所有可枚举属性,赋值到目标对象

Object.assign方法的第一个参数是目标对象,后面的参数都是源对象

如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性

 
 

ES6一共有5种方法可以遍历对象的属性。

 
 

(1)for...in

 
 

for...in循环遍历对象自身的和继承的可枚举属性(不含Symbol属性)。

 
 

(2)Object.keys(obj)

 
 

Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含Symbol属性)。

 
 

(3)Object.getOwnPropertyNames(obj)

 
 

Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含Symbol属性,但是包括不可枚举属性)。

 
 

(4)Object.getOwnPropertySymbols(obj)

 
 

Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有Symbol属性。

 
 

(5)Reflect.ownKeys(obj)

 
 

Reflect.ownKeys返回一个数组,包含对象自身的所有属性,不管是属性名是Symbol或字符串,也不管是否可枚举。

 
 

以上的5种方法遍历对象的属性,都遵守同样的属性遍历的次序规则。

 
 
  • 首先遍历所有属性名为数值的属性,按照数字排序。
  • 其次遍历所有属性名为字符串的属性,按照生成时间排序。
  • 最后遍历所有属性名为Symbol值的属性,按照生成时间排序。
null 传导符
如果读取对象内部的某个属性,一般需要版判断该对像是否存在,为了解决这样的麻烦,因此现在有个提案,引入的null传导符 ?.
const fristName = message?.body?.user?.firstName || 'default';
上面代码有三个?.运算符,只要其中一个返回nullundefined,就不再往下运算,而是返回undefined

“Null 传导运算符”有四种用法。

 
 
  • obj?.prop // 读取对象属性
  • obj?.[expr] // 同上
  • func?.(...args) // 函数或对象方法的调用
  • new C?.(...args) // 构造函数的调用
 
 

传导运算符之所以写成obj?.prop,而不是obj?prop,是为了方便编译器能够区分三元运算符?:(比如obj?prop:123)。

 --Symbol--

ES6引入了一种新的原始数据类型Symbol,表示独一无二的值。它是JavaScript语言的第七种数据类型,前六种是:Undefined、Null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。
let s = Symbol();
  typeof s //'symbol'

--Set和Map数据结构--
Set,类似于数组,但是成员值都是唯一的,没有重复的值。Set 本身就是一个构造函数,用来生成数据结构
console.log([...new Set([1,1,2,3,4,5])]) ;//1 2 3 4 5
Array.from()可以将数Set结构转化为数组结构
var items = new Set([1,2,3,4]);
var array = Array.form(items);
function dedup(array){
  return Array.from(new Set(array));

}
dedupe([1,2,3,3,4,5]); //[1,2,3,4,5];

--WeakSet--

WeakSet结构与Set类似,也是不重复的值的集合。但是,它与Set有两个区别。

首先,WeakSet的成员只能是对象,而不能是其他类型的值。

其次,WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于WeakSet之中。这个特点意味着,无法引用WeakSet的成员,因此WeakSet是不可遍历的。

WeakSet是一个构造函数,可以使用new命令,创建WeakSet数据结构
var ws = new WeakSet();

WeakSet没有size属性,没有办法遍历它的成员。

 
 

 WeakSet不能遍历,是因为成员都是弱引用,随时可能消失,遍历机制无法保证成员的存在,很可能刚刚遍历结束,成员就取不到了。WeakSet的一个用处,是储存DOM节点,而不用担心这些节点从文档移除时,会引发内存泄漏

ES6提供了Map数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object结构提供了“字符串—值”的对应,Map结构提供了“值—值”的对应,是一种更完善的Hash结构实现。如果你需要“键值对”的数据结构,Map比Object更合适。

Map 构造函数接受数组作为参数,
var items = [
  ['name','张三'],
  ['title','Auther']
];
var map = new Map();
items.forEach(([key,value])=>map.set(key,value));
如果对同一个键多次赋值,后面的值覆盖前面的值

let map = new Map();

map.set(1,'aaa').set(1,'bbb');

map.get(1); // bbb

--WeekMap--

WeakMap结构与Map结构基本类似,唯一的区别是它只接受对象作为键名(null除外),不接受其他类型的值作为键名,而且键名所指向的对象,不计入垃圾回收机制。
WeakMap的设计目的在于,键名是对象的弱引用(垃圾回收机制不将该引用考虑在内),所以其所对应的对象可能会被自动回收。当对象被回收后,WeakMap自动移除对应的键值对。典型应用是,一个对应DOM元素的WeakMap结构,当某个DOM元素被清除,其所对应的WeakMap记录就会自动被移除。基本上,WeakMap的专用场合就是,它的键所对应的对象,可能会在将来消失。WeakMap结构有助于防止内存泄漏

WeakMap与Map在API上的区别主要是两个,一是没有遍历操作(即没有key()values()entries()方法),也没有size属性;二是无法清空,即不支持clear方法。这与WeakMap的键不被计入引用、被垃圾回收机制忽略有关。因此,WeakMap只有四个方法可用:get()set()has()delete()。 

 ---Proxy---

Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程

 var proxy = new Proxy(target,handler);

Proxy接受两个参数。第一个参数是所要代理的目标对象,第二个参数是一个配置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作

要使得Proxy起作用,必须针对Proxy实例(上例是proxy对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。

---Reflect---

Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API。Reflect对象的设计目的有这样几个

大部分与Object对象的同名方法的作用都是相同的,而且它与Proxy对象的方法是一一对应的。

---Promise---

 

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6将其写进了语言标准,统一了用法,原生提供了Promise对象。

 

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

 

Promise对象有以下两个特点。

 

(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称 Fulfilled)和Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

 

(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

 

有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。

 

Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

 

如果某些事件不断地反复发生,一般来说,使用 stream 模式是比部署Promise更好的选择。

 

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolvereject。它们是两个函数,由JavaScript引擎提供,不用自己部署。

 

 promise的错误有冒泡的性质,会一直向后传递,知道被捕获位置,也就是说,错误总是会被下一个catch语句捕获。

一般来说,不要在then方法里面定义reject状态的幻术(即then的第二个参数),总是使用catch方法

promise all() ;promise race() ;pormise.prototype.catch();promise.resolve();

---Iterator---(遍历器)的概念

遍历器(Iterator),就是这样一种机制。他是一种接口,为各种不同的数据结构提供统一的接口机制,来处理不同的数据结构。

一,一是为各种数据结构,提供一个统一的简便的接口;二使得结构的成员能够按某种次序排列三,s6,创造了一种新的遍历命令 for ...of ,Iterator接口主要提供for...of消费。

Iteration的遍历过程是这样的

    1.创建一个指针对象,指向当前数据结构的起始位置,也就是说,遍历器最想的本质上,就是一个指针对象

    2.第一次调用指针对象的next方法,就可以将指针指向数组结构的第一个成员。

  3.第二次调用指针对象的 next,指针就指向数据结构的第二个成员。

    4.不断调用指针对象的next方法,知道它指向数据结构的结束为止。

每一次调用next方法,都会返回数据结构的当前成员信息,具体来说,就是返回一个value和done两个属性的对象,其中value属性是当前成员的值,done属性就是个布尔值,表示遍历是否结束。

var it = makeIterator(['a',''b]);

it.next() 

ES6规定,默认的Iterator接口部署在数据结构的 Symbol.iterator属性,或者说,一个数据结构

只要具有symbol.iterator属性,就可以认为是可遍历的(iterable)Symbol.iterator属性本身就是一个函数

就是当前数据结构默认的遍历生成器函数,执行这个函数就会返回一个遍历器。治愈属性名,它是

一个表达式,返回 symbol对象的iterator属性,这是一个预定义好的类型为symbol的特殊值,所以要

放在方括号内

const obj = {

[Symbol.iterator]:function(){

  next:function(){

      return{ value:1,done:true}

    }

}

}可以将任何部署了iterator接口的数据结构转换为数组,也就是说

只要某个数据结构部署了iterator接口,就可以对它使用拓展运算符将其转换为数组
let arr = [...iterable];

字符串的iterator接口

字符串也是一个类数组对象,也具有iterator接口

var someString = 'hi';
typeof someString[Symbol.iterator]
var iterator = someString[Symbol.iterator]();
iterator.next()
iterator.next()
iterator.next() //{value:undefined,done:true}

由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口。下面是一些例子。

  • for...of
  • Array.from()
  • Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]])
  • Promise.all()
  • Promise.race()
for in 循环智能读取键名,for..of 循环 读取键值,如果要通过for..of

 let arr = [3,5,7];
arr.foo = 'hello';
for (let i in arr){
  console.log(i);// '0','1','2','foo'
}
for (let i of arr){
  console.log(i); // '3','5','7'
}
set 和 map 结构也具有 iterator接口

---for of 循环---

并不是所有 类似数组的对象都具有iterator接口,一个简便的解决方法
就是用Array.from 方法将其转换成数组
let arrayLike = {
lenth:2,0:'a',1:'b'
}
for(let x of arrayLike){
  console.log(x);// 报错
}

 //正确

for(let x of Array.form(arrayLike)){

  console.log(x);

}

--Generator-- 函数的语法

Generator 函数 是ES6提供的一种异步编程解决方案,语法行为与传统的函数完全不同

Generator 函数有多重理解的角度,从语法上,可以把它理解成,Generator函数是一个

状态机,封装了多个内部状态

执行Generator 函数返回一个遍历器对象,也就是说,Generator函数除了状态

还是一个遍历器对象生成的函数,返回遍历器对象,可以一次便利Generator函数内部

的每一个状态

形式上Genertor 函数是一个普通的函数,但是有两个特征,一个是,function关键字

与函数名之间有一个星号,

二是,函数体内部使用yield语句,定义不同的内部状态

function* helloWorldGenerator(){

  yield 'hello';

  yield 'world';

  return 'ending';

}

var hw  = helloWorldGenerator();

上面代码定义个一个 Generator函数 ,它内部有两个yield语句,即 改函数有三个状态

结束执行。

Generator函数的调用方法与普通函数一样也是在函数后面加一对圆括号

不同的是调用 函数后 该函数并不执行,返回的也不是函数运行的结果,而是一个

指向内部状态的指针对象,也就是上一张介绍的Iterator Object

下一步必须调用遍历器对象的next方法,是的指针移向下一个状态,也就是说,每次

调用next方法,内部指针就从函数同步或者上一次停下来的地方开始执行

直到遇到下一个yield语句 或者return语句 为止。 Generator函数是分段执行的,

yield 语句是展厅执行的编辑,而next方法可以恢复执行

--yield--

由于Generator函数返回的遍历器对象,只用调用next方法才会遍历下一个内部状态,所以其实

提供了一中国可以暂停的函数,yield语句就是暂停的标志

遍历器对象的next方法的运行逻辑如下

1.遇到yield语句 就暂停执行后面的操作,并紧跟在yield后面那个表达式的值作为返回的

对象的value属性的值。

2.下一次调用next方法时候,就继续往下执行直到遇到下一个yield语句。

3.如果没有遇到新的yield语句,就一直运行到函数结束。直到return语句为止,并将return

语句后面的表达式的值,作为返回对象的value值。

4.如果该函数没有return语句,则返回的对象对象的value属性值为undefined。

yield 语句如果用于表达式中必须放在圆括号里面

function* demo(){

console.log('hello'+ yield);//syntaxError

console.log('hello'+ yield 123);//syntaxError

console.log('hello'+ (yield);//ok

console.log('hello'+ (yield 123));//ok

}

 yield.语句用作函数参数 或者放在赋值表达式的右边,可以不加括号

function* demo(){

  foo(yield 'a' , yield 'b'); //ok

  let input  = yield; //ok

}

与 Iterator接口的关系

任意一个对象的symbol.iterator方法 等于该对象的遍历器生成函数,

调用该函数就会返回对象的一个遍历器对象

Generator函数执行后,返回一个遍历器对象,该对象本身也具有Symbol.iterator属性执行后返回

自身

var function* (){

//  some code

}

var g = gen();

g[Symbol.iteraor]() === g;

//true

---next方法的参数---

yield句本身没有返回值,或者说,总是返回undefined。next方法可以带一个参数

该参数机会被当做上一个yield语句的返回值。

这个功能有个很重要的意义。Generator函数从暂停状态到回复运行,他的上下文状态

是不变的,通过next方法的参数,就有办法在Generator函数开始运行之后,继续想函数

体内注入值。也就是说,可以在Generator函数运行的不同阶段,从外部向内部注入不同的

值,从而调整函数的行为

Generator.prototype.throw();

函数返回的遍历器对象,都有一个throw方法,可以在函数体外抛出错误

然后Generator函数体内捕获

Generator.prototype.return();

函数返回的遍历器对象,还有一个return方法,可以返回给定的值,并且

终结Generator函数

function* gen(){

yield 1;

yield 2;

yield 3;

 

}

var g = gen();

g.next()

g.return('foo'); //{value:'foo',done:ture}

g.next() //{value:undfined}

---yield*--语句

如果Generator函数内部,调用另一个个Generator函数,默认情况下是没有效果的

从语法角度看,如果 yield 命令后面跟的是一个遍历器对象,需要在yield命令后面加上

星号,表明它返回的是一个遍历器对象,这被称作为yield语句

如果yield* 后面跟着一个数组,由于数组原生支持遍历器,因此会遍历数组成员

function* gen(){

 yield* ['a','b','c'];

 

}

gen.next()

 

yield* 命令可以很方便的取出嵌套数组的所有成员

Generator函数总是返回一个遍历器,ES6规定这个遍历器是Generator函数的实例

也继承了Generator函数的prototype对象上的方法

function* g(){}

g.prototype.hello = function(){

return 'hi!';

}

let obj = g();

obj instaceof g //true

obj.hello();

Generator.js 不能跟函数命令一起使用,会报错

function*  F(){

  yield this.y = 2;

  yield this.x = 3;

}

try{

   new F()

}catch (e){

  console.log(`这个错误是 ${e}`);// typeError: F is not constructor

}

Generator 与携程

携程 是一种程序的运行方式,可以理解成协作的线程 或者 协作的函数

协成既可以用单线程实现,也可以用多线程实现,前者是一种特殊的子例程或者是一种

特殊的线程

协成与子例程的差异

传统的子例程 曹勇 堆栈式 “后进后出”的执行方式,只用当调用的子函数完全执行完毕

才会结束执行的父函数,

协程不同,多个线程(单线程的情况下,即是多个函数)可以并行执行,但是只有

一个线程或者函数 处于运行状态 其他线程或者函数 都处于暂停状态,

线程 或者函数 之间可以交换执行权,也就是说,一个线程或者函数,执行到一般,可以

暂停执行,将执行权交给另一个线程或者函数,等到稍后回收执行权的时候,再回复执行,这种可以

这种可以并行的执行交换执行权的线程,或者函数 就是 协成

从现实上看,在内存中,子例程只是用一个栈stack 而协成就是同事存在多个栈但是

只用一个栈是在云zing状态的,也就是说,协成是以占用内存为代价,实现多任务的并行

--协程与普通线程的差异

不难看出 协成适合用于多任务运行的环境,在这个意义上,它与普通的线程很相似

都有自己的执行上下文,可以分享全局变量,他们的不同之处在于,统一时间可以有多个

线程处理运行状态,但是运行的协成只能有一个,其他协成都处于暂停

状态

普通的线程是抢先式的。到底哪个线程优先得到资源必须有运行环境决定,但是协成是

合作式的执行权由协成自己分配

 

Gnerator函数的异步应用

所谓异步 简单说 就是一个任务不是连续完成的,可以理解为被

人为的分成了两段,先执行第一段,然后执行其他任务,等做好了

准备再回头执行第二段。

回调函数本身并没有什么问题,他的问题出现在多个回调函数嵌套,

多个一步操作形成了强耦合,只要有一个操作需要修改,他的上层

回调函数和下层回调函数 都会跟着修改,这样就称为回调地狱

---async 函数

ES2017 标准 引入了 async 函数,使得异步函数操作更加方便

async 函数身什么?一句话,他就是Generator函数的语法糖

var async ReadFile = async function (){

  var f1 = await readFile('/ted/fs');

  var f2 = await readeFile('/etc/shells');

  console.log(f1.tostring());

}

 

async 函数 对Genrator函数的改进 体现在一下四点

1.内置执行器

Generator函数的执行必须考执行器,所以猜啊有了co模块。

而 async 函数自带执行器,也就是说,async函数的执行与普通函数一模一样,只要

一行

var result=asyncReadFile();

上面代码调用了asyncFile函数,然后 他就会自动执行,输出最后的结果,

者完全不行Generator函数需要调用next方法,或者co 模块,才能真正执行,得到最后的

结果

2.更好的语义

async和await 比起星号和 yield 语义更加清楚了,async 表示函数里有异步操作

await 表示紧跟在后面的表达式需要等待结果

3.更广的适应性

co模块的约定,yield命令后面只能是 Thunk 函数或者是promise对象, 而

async函数的await命令后面,可以是promise对象和原始类型的值字符串,但这是

等同于同步操作。

返回值是promise。

async函数的返回值是promise对象,这比generator 函数的返回值是Iterator

对象方便多了。你可以用then方法指定下一步操作

进一步说,async 函数完全可以看做是多个异步操作,包装成的promise对象

从而await命令 就是内部then命令的语法糖。

---await---

正常情况下 await命令后面是一个promise 对象,如果不是,会被转换成一个立即resolve的

promise 对象

async function f(){

return await 123;

}

f().then(v=> console.log(v))

// 123

 

如果 await 语句后面的promise变为reject 那么 整个函数都会中断执行。

---async ---函数的实现原理

async 函数实现的原理,就是将 Generator 函数和自动执行器,包装在一个函数里

async function fn(args){

//...

}

//等同于

function fn(args){

  return apawn(function * (){

  //...

})

}

//所有的asyn函数都可以写成第二种形式,其中的spawn 函数就是自动执行器

下面给出 spawn 函数的实现,基本上就是自动执行器的翻版

 ---calss---

 

/*
javascript 语言的传统方法是通过构造函数,定义并生成新的对象,

*/

// function Point(x,y){
// this.x = x;
// this.y = y;
// }
// Point.prototype.toString = function(){
// return '('+ this.x +','+ this,y + ')';
// };

// var p = new Point(1,2);
/*
上面这种写法,跟传统的面向对象语言(c++ 和 Java)差异很大,很容易让
新学习这门语言的程序员感到困惑

es6 提供了更接近传统语言的写法,引入了calss 类的概念,作为对象的模板
通过class关键字,可以定义类,基本上,ES6 class 可以看做是一个语法糖,
他的绝大部分功能,ES5 都可以做到,新的 class 写法 值是让对象原型的写法
更加清晰,更加像 面向对象编程的语法而已。

*/

//定义类

class Point{
constructor(x,y){
this.x = x;
this.y = y;
}

toString(){
return '('+ this.x +','+this.y+')';
}

}

/*
以上代码定义了一个类,可以看到里面有一个constructor方法,这就是构造方法,
而this关键字,则代表实例对象,也就是说,ES5的构造函数Point,对应ES6的 Point
类的构造方法

Point 类除了构造方法,还定义了一个toString方法,注意,定义类的方法的时候,前面
不要加function关键字,直接把函数定义放进去就可以了,另外,方法之间不需要逗号分隔符
加了就会报错!!!


ES6的类完全可以看做是构造函数的另一种写法。

 

*/


console.log(typeof Point ); //function
console.log(Point === Point.prototype.constructor); //true
// 上面代码表明,类的数据类型就是函数,类本身就指向构造函数,使用的时候也就对类使用new命令,跟构造函数的用法完全一致

class Bar{
doStuff(){
console.log('hello Class ES6 stuff');
}
}

var b = new Bar();
b.doStuff();

// 构造函数的Prototype属性,在ES6的 类上面继续存在,事实上,类的所有方法都定义子在类的
// prototype属性上面

class Point2{
constructor(){
//....
}
toString(){
//..
}
toValue(){

}
}

//等同于
Point2.prototype = {
toString(){},
toValue(){}
};

// prototype 对象的 construct 属性,直接指向类的本身,这与ES5的行为是一一致的
// 另外 ,类的内部所有定义的方法,都是不可枚举的

class Point3 {
constructor(x,y){

}
toString(){

}
}
console.log('所有类 内部定义的方法都是不可枚举的'+ Object.keys(Point3.prototype));
console.log('方法在内部'+ Object.getOwnPropertyNames(Point3.prototype));

// 类的属性名 可以采用表达式

let methodName = 'getArea';

class Square{
constructor(length){
//
}
[methodName](){
//....
}
}


//constructor方法是类的默认值防范 通过new 命令生成对象实例时候,自动调用该方法
/*
一个类必须有constructor方法,如果没有显示的定义,一个空的constructor 方法就会被
默认添加

constructor方法 默认返回实例对象 即this,完全可以指定返回另一个对象。

*/
class Foo{
constructor(){
return Object.create(null);
}
}

console.log(new Foo() instanceof Foo )//false

/*
上面代码中,constructor函数 返回一个全新的对象,结果导致实例对象不是
Foo类的实例

类的构造函数,不使用new 是没有办法调用的,就会报错,他跟普通构造函数的一个主要
区别,后者不用new 也可以执行

*/

// 类的实例对象
/*
生成类的实例对象的写法 ,与ES5完全一样,也就是使用new命令,如果王佳加上new,
像函数那样调用 class ,将会报错

*/
// var point = Point(2,3); 报错

// var point = new Point(2,3); 正确

/*
与ES5一样实例的属性除非定义在其本身,(即定义在this对象上),否则都是定义在原型上
(即定义在class上


*/

class Point4{
constructor(x,y){
this.x = x;
this.y = y;
}

toString(){
return this.x + this.y;
}
}

var point4 = new Point4(2,4);

console.log(point4.toString()); //6
console.log(point4.hasOwnProperty('x'));//true
console.log(point4.hasOwnProperty('y'));// true
console.log(point4.hasOwnProperty('toString'))//false
console.log(point4.__proto__.hasOwnProperty('toString')) //true

//与ES5一样,类的所有实例 共享一个原型对象。

var point4_1 = new Point4(1,2);
var point4_2 = new Point4(2,3);

console.log(point4_1.__proto__ == point4_2.__proto__); //true

//这也就意味着,可以通过__proto__属性 为Class 添加方法

var p1 = new Point4(1,2);
var p2 = new Point4(2,3);

p1.__proto__.printName = function (){ return '宋仁泽'};
var p3 = new Point4(2,2);


console.log(`通过__proto__,属性(prototype的属性) 添加的方法 能被 所有实例对象访问 ${p2.printName() + p3.printName()}`);

/*
上面代码中 p1 的原型上添加了一个printName方法由于p1 的原型对象就是p2 的原型
因此 都会继承相应的方法,必须谨慎,不推荐使用,因为这个会改变class的原始定义
影响到所有实例

*/

// 不存在变量提升,class 不存在变量提升,hoist,这一点与ES5 完全不同。

// new Foo(); 报错
// class Foo{};
/*
上面代码中,Foo 类使用在钱,定义在后面,这样就会报错
*/

// class 表达式

const MyClass = class me{
getClassName(){
// return Me.name;
}
};
//上面代码使用表达式定义了一个类,需要注意的是,这个类的名字是myClass而不是 Me
// Me 只在Class内部代码可用,指定当前类

let inst = new MyClass();
inst.getClassName();
// Me.name ; // 报错
// 上面代码表示,ME只在Class 内部有定义,如果类的内部没有用到的话,可以省略,也就是协成
// 下面的行形式
// const MyClass = class{ /* ssss*/};
// 采用class表达式 可以写出 立即执行的class。
let person = new class {
constructor(name){
this.name = name;
}
sayName() {
console.log(this.name);
}
}('宋仁泽');

person.sayName(); //宋仁泽

// 私有方法-- 私有方法是常见的需求,但是 ES6 不提供 ,只能通过变通的方法模拟实现

// ----第一种方法 是在命名上加以区别。
class Widget{
//共有方法
foo(baz){
this._bar(baz);
}

//私有方法
_(bar){
return this.snaf = baz;
}
//..
}

/*
上面代码中,bar方法前面的下划线,表示这是一个只限于内部使用的私有方法,
凡是,这种命名是不保险的,在类的外部还是可以调用到这个方法

另一种方法:就是索性将私有方法移出模块,因为内部的所有方法 都是对外可见的

*/

class Widget2{
foo(baz){
bar.call(this,baz);
}

}
function bar(baz){
return this.snaf = baz;
}

// 还有一种方法 就是利用symbol 值的唯一性,将私有方法名字命名为一个symbol值
/*
const bar = Symbol('bar');
const sanf = Symbol('sanf');

export default class myClass{
//共有的方法
foo(baz){
this[bar](baz);
}
//私有的方法

[bar](baz){
return this[snaf] = baz;
}

}
*/
// 上面代码中 ,bar 和 snaf 都是Symbol 值,导致第三方无法获取到他们,因此
// 达到了私有的方法和属性的效果

// 类方法内部如果含有this,那它默认指向类的实例,但是,必须非常小心一旦单独使用
// 改方法很有可能报错。


class Logger{
printName( name = 'there'){
this.print(`hello ${name}`);
}
print(text){
console.log(text);
}
}

const logger = new Logger();
// const { printName } = logger;
// printName();// 报错


/*
上面代码中,printName 方法中的 this ,默认指向logger类的实例,但是,如果
将这个方法提出来单独使用,this会指向该方法运行时所在的环境,因为找不到print方法
而导致的错误

一个比较简单的解决方法是,在构造函数中绑定 this,这样,就不会找不到print方法了
*/

class Logger2 {
constructor(){
this.printName = this.printName.bind(this);
}
//...
}

// ---另一种解决方法是,使用箭头函数

class Logger3 {
constructor(){
this.printName = (name = 'there') =>{
this.print(`hello ${name}`);
};
}
}


//还有一种方法 是 使用 proxy ,获取方法的时候自动绑定this

 

// 严格模式
/*
类和模块内部,默认就是严格模式,所以 不需要使用 use strict 指定运行模式
只要你的代码写在类 或者模块之中,就只有严格模式可以用

考虑到未来所有的代码,其实 都是运行在模块之中的,所以 ES6 实际上
把整个语言升级到了严格模式。

由于本质上,ES6 的类只是 ES5 构造函数的一层包装, 所以函数的许多特性 都被Class
继承,包括 name 属性


*/

class Point5{};
console.log(Point5.name); //'point5';

// class 的继承

/*
基本用法 class 之间可以通过 dxtends 关键字继承,这比ES5 的通过修改原型实现的继承
要清晰和方便很多

class colorPoint extends Point{}

通过extends 关键字,继承了point 类的所有属性和方法,但是由于没有部署任何代码
所以这两个类完全一样,等于赋值了一个point类,下面,我们在color
point 内部加上代码
*/

// class ColorPoint extends Point{
// construct(x,y,color){
// super(x,y); //调用父类的constructor(x,y)
// this.color = color;
// }

// toString(){
// return this.color + '' + super.toString(); //调用父类的 toString()

// }
// }

/*
子类必须在constructor 方法中 调用 super 方法,否则 新建实例就会从报错,这是因为
子类没有自己的this对象,继而继承父类的this对象,然后对其加工,如果不调用Super方法
,子类就得不到this对象

 

*/

// class Point{}

// class ColorPoint extends Point{
// constructor(){

// }
// }

// let cp = new ColorPoint(); 会报错, 因为没有this

/*
以上代码中 colorPoint 继承了Point 但是他的构造函数没有调用super 方法,导致
新建的实例报错

ES5 ,实质上是先创造实例对象的this ,然后再将父类的方法 添加到this上面 Parent.apply(this)

ES6 的继承机制完全不同,市值是先创造符类的实例对象 this 然后 任何一个子类
都有constructor 方法

constructor(...args){
super(...args);
}

另一个需要注意的地方是,在子类的构造函数中,只有调用super之后,才可以使用this
关键字,否则会报错,这是因为子类实例的构建 是基于对父类实例的加工,只有super
方法 才能返回父类的实例。

*/

class Point6{
constructor(x,y){
this.x = x;
this.y = y;
}
}

class ColorPoint2 extends Point6{
constructor(x,y,color){
// this.color = color ; //报错
super(x,y);
this.color = color; //正确
}
}

/*
上面代码中,子类的constructor 方法 没有调用super之前,使用this关键字,结果
报错,在房子super方法之后的就是正确的。

*/

let cp = new colorPoint(25,5,'green');
cp instanceof ColorPoint //true;
cp instanceof Point6 //true;

// 实例对象cp 同时是 colorPoint 和 point 两个类的实例,这与ES5的行为完全一致

// ----类的prototype 属性和__proto__属性

/*
大多数浏览器的ES5 实现中,每一个对象都有__proto__属性,指向对应的构造函数的
prototype属性。 Class作为构造函数的语法糖,同时有prototype 属性 和__proto__
属性


1.子类的__proto__属性 ,表示构造函数的继承,总是指向父类

2.子类的prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype
属性
*/

// class A{

// }
// class B extends A {}

// B.__proto__ === A //true
// B.prototype.__proto__ = A.prototype; //true

// 以上代码中 ,子类B 的 __proto__属性指向 父类 A
// 子类B 的 prototype 属性的 __proto__属性指向父类A 的 prototype属性

// extends 关键字 后面可以跟多种类型的值

// class B1 extends A1{

// }

/*
上面的代码A 只要是一个有prototype 属性的函数,就能被B 继承,由于函数都有
prototype属性 除了function.prototype 函数,因此 A可以是任意函数

下面我们讨论三种特殊的情况

第一种情况 子类继承Object类

*/
// class A extends Object{

// }
// A.__proto__ === Object{

// }
// A.__proto__ === Object //true
// A.prototype.__proto__ === Object.prototype //true

// 这种情况下 A 其实就是 构造函数Object的复制,A的实例就是 Object的实例

// 第二种情况,不存在任何继承

// class A2{};
// A2.__proto__ === Function.prototype;
// A2.prototype.__proto__ === undefined; //true;

// 这种情况 与第二种情况 非常像,A也就是一个普通的函数,所以直接
// 继承Function.prototype,但是 A调用后返回的对象不继承任何方法,所以
// 他的__proto__指向Function.prototype.即实质上执行了下面的 代码

// class C ectends null{
// constructor(){
// return object.create(null);
// }
// }

// Object.getPrototypeOf()
// Object.getPrototypeOf(A) === Point ;

// Object.getPrototypeOf方法从子类上获取父类

// 因此 可以判断这 一个类是否继承了另一个类

// -------super-------
// super关键字

// super这个关键字既可以当做函数使用 也可以当做对象使用,这两种情况下
// 他的用法 完全不同,

// 第一种情况

// class A3(){};
// class B3 extends A3{
// construct(){
// super();
// }
// }

// 上面代码中。子类B 的构造函数之中的Super 代理表调用父类构造函数,这是必须的
// 否则JavaScript 引擎会报错。

/*
注意,super 虽然 代表了父类A的构造函数,但是返回的是子类 B 的实例,即super
内部的this指向B 因此super() 在这里相当于 A.peototype.constructor.call(this)

*/

class A3{
constructor(){
console.log(new.target.name);
}
}
class B3 extends A3{
constructor(){
super();
}
}
new A();
new B();


/*
上面代码中,new.target 指向当前正在执行的函数
可以看到 在super()执行时,他指向的是子类B的构造函数 而不是a的构造函数
也就是说,super() 内部的this指向的B

作为函数 是 ,super()只能用在子类的构造函数之中,用在其他地方就会报错

如果 super 用在子类的方法之中 就会报错


第二种情况 ,super 作为 对象时,指向父类的原型对象


*/
class A {
P(){
return 2
}
}

class B extends A {
constructor(){
super();
console.log(super.p());
}
}
let b = new B();

/*

在上面代码中 子类 B 中的super()就是将super()当成一个对象使用,这时
super指向A.prototype,所以Super.P()相当于 A.prototype.p()

仅在这里需要注意的是,由于super指向父类的原型对象,所以定义在(实例方法)方法上的方法
或者属性是无法通过super调用的


*/

class A {
construct(){
this.p = 2;
}
}
class B extends A{
get m(){
return super.p;
}
}

let b = new B()

b.m // undefined ,

// 上面代码中 P是父类A的实例属性 ,子类的实例对象就引用不到它。

// 如果属性定义在父类的原型对象上,super就可以访问取到

class A {}
A.prototype.x = 2;
class B extends A{
constructor(){
super();
console.log(super.x) //2
}
}

let b = new B();

修饰器
1.类的修饰


/*
修饰器(Decorator)是一个函数,用来系应该类的行为,这是ES7的一个提案
目前Babel 转码器已经支持。
修饰器对类的行为的改变,是代码编译时候发生的,而不是在运行时,这意味着,
修饰器能在编译阶段运行代码。


*/


function testable(target){
target.isTestable = true;
}

@testable

class MyTestableClass{}
console.log(MyTestableClass.isTestable);true

基本上,修饰器的行为就是这样
@decorator
class A{}

等同于
class A{}
A = decorator(A) || A;

也就是说,修饰器 本质就是 编译时执行的函数。修饰器函数的第一个参数,就是索要修饰的
目标类

function testable(target){
....
}

上面代码中 teatable函数的参数 target ,就是会被修饰的类。
如果你觉得一个参数不够用,可以在修饰器外面再封装一层函数。
function testable(isTestable){
return function(target){
target.isTestable = isTestable;
}
}
@testable(true)

class MyClass {}
MyClass.isTestable ;true;

@testable(false)

class MyClass_a{}
MyClass_a.isTestable ;false

上面代码中,修饰器的teatable 可以接受参数,这就等于可以修改修饰器的行为。

前面的例子 是为类添加一个静态的属性,如果想添加实例属性,可以通过类的prototype
对象操作。

function testable(target){
target.prototype.isTestable = true;
}
@testable

class myTestableClass{}

let obj = new MyTestableClass();
obj.isTestable; true

上面代码中,修饰器函数 testable是在目标类的prototype对象上添加属性,因此,就可以
在实例上面调用

下面是另一个例子

mixins.js
export function mixins(...list) {
return function(target){
Object.assign(target.prototype,...list)
}
}

mian.js

import {mixins} from './mixins'

const Foo = {
foo(){console.log('foo')}
};

@mixins(Foo)
class MyClass {}

let obj = new MyClass();
obj.foo() foo


上面代码通过修饰器mixins ,把 Foo 类的方法添加到了MyClass的实例上面,可以用
object.assign(),模拟这个功能

const Foo = {
foo(){console.log('foo')}

}
class MyClass{}

Object.assign(MyClass.prototype,Foo);
let obj = new MyClass();
obj.foo(); 'foo';

-----修饰器不仅可以修饰类,还可以修饰类的属性。

class Person{
@readonly
name(){return `${this.first} ${this.last}`}
}

上面代码中,修饰器,readonly 用来修饰类的 name 方法。

/*
此时,修饰器函数一共可以接受三个参数,
第一个参数,就是 所要要修饰的目标对象,
第二个参数是索要修饰的属性名,
第三个参数 是该属性的描述对象。
*/
function readonly(target,name,descriptor){
decorator.writable = false;
return decorator;
}
readonly(Person.prototype,'name',descriptor);

类似于
Object.definePeoperty(Person.prototype,'name',decorator);

/*
上面代码说明,修饰器,会修改属性的描述符对象,然后被修改的描述符对象
再用来定义属性。

下面是另一个例子,修改描述符对象的enumerable属性,使得该属性不可遍历
*/

class Peroson{
@noenumerable
get kidCount(){return this.children.length;}
}

function noenumerable(target,name,decorator){
decorator.enumerable = false;
return decorator;
}


下面是@log 修饰器,可以起到输出日志的作用

class Math{
@log
add(a,b){
return a+b;
}
}

function log(target,name,descriptor){
var oldValue = descriptor.value;

descriptor.value = function(){
console.log(`Calling "${name}" with`,arguments);
return oldValue.apply(null,arguments);
}

return descriptor;
}

const math = new Math();

math.add(2,3);

以上代码中,log修饰器的作用就是在 在执行原始操作之前,执行一次console.log.
从而达到输出日志的目的。
修饰器 有注释的作用

@testable
class Person{
@readonly
@noenumerable
name(){ return `${this.first} ${this.last}`}

}

从上面代码中,我们一眼就能看出,person类是可以测试的,而name方法是只读和
不可枚举的,如果同一个方法有多个修饰器,就会像剥洋葱一样,先从外到内进入,然后
由内向外执行。

function dec(id){
console.log('evaluated',id);
return (target,property,descriptor) => console.log('executed',id);

}

class Example{
@dec(1)
@dec(2)
method(){}
}

evaluated 1
evaluated 2
executed 2
executed 1

/*
上面代码中,外层修饰器@dec(1)先进入,但是内层修饰器@(2)先执行

除了注释,修饰器还能用来类型检查,所以,对于类来说,这项工能相当有用,
长期来看,他是将JavaScript代码静态分析的重要工具。


*/

为什么修饰器不能用于函数?

修饰器智能用于类和方法,不能用于函数,因为存在函数提升。

var counter = 0 ;

var add = function(){
counter ++;
}

@add
function foo(){

}

上面代码意图是执行后 counter 等于1 ,但是实际的结果是counter等于0,因为
存在函数提升,是的实际执行的代码是下面这样。

@dd
function foo(){

}
var counter;
var add;

counter = 0;
add = function(){
counter ++;
};

----重要
总之 由于函数存在函数提升,是的修饰器不能用于函数,类是不会提升的,所以
就没有这方面的问题


/*
4, core-decorators.js

core-decorator.js,是一个 第三方模块,提供了常见的修饰器,通过他,可以更好的
理解修饰器。

*/

1.@autobind

autobind 修饰器使得方法中的this对象,绑定原始对象

import {autobind} from 'core-decorators.js';

class Person{
@autobind
getPerson(){
return this;
}
}

let person = new Person();
let getPerson = person.getPerson;

getPerson() === person; true

autobind 修饰器使得方法中的this 对象,绑定原始对象。


@readonly : 使得 属性或方法不可写

import {readonly} form 'core-decorators.js';

class Meal{
@readonly
entree = 'steak';
}

var dinner = new Meal();
dinner.entree = 'salom';
Connot assign to read only property 'entree' of [object object]


@override :此修饰器 是检查 子类的方法,是否正确覆盖了父类的同名方法,如果
不正确就会报错。

import {override} from 'core-decorators';

class Parent{
speak(first,second){}

}

class Child extends Parent{
@override
speak(){}
SyntaxError,
}

class Child extends Parent{
@override
speaks(){}
SyntaxError,no matching speaks()
}

4, deprecate 或者是 deprecated 修饰器在控制台显示一条警告,表示该方法即将废除

import {deprecate} from 'core-decorators';

class Person{
@deprecate
facepalm(){}
@deprecate(' we stoped facepalming')
facepalmHard(){}
@deprecate('we stoped facepalming',{url:'http:baidu.com'})
facepalHarder(){}
}

let person = new Person();

person.facepalm();
DEPRECATION Person#facepalm: This function will be removed in future versions.

person.facepalmHard();
we stoped facepalming'
person.facepalHarder();
we stoped facepalming:http:baidu.com


5 @supperessWarnings

superessWarnings 修饰器抑制 decorated 修饰器导致的 console.warn()调用
但是异步代码发出的调用除外

import{superessWarnings} from 'core-decorators';

class Person{
@deprecated
facepalm(){}
@superessWarnings
faceWaring(){
this.facepalm();
}
}
let person = new Person();
person.facepalmWithoutWarning();

使用修饰器自动发布事件

我们可以使用修饰器,使得对象的方法被调用时,自动发出一个事件。
import postal from'postal/lib/postal.loadsh';

export default function public(toppic,channel){
return function (target,name,descriptor){
const fn = decorator.value;
decorator.value = function(){
let value = fn.apply(this,arguments);
postal.channel(channel || target.channel || '/').publish(toppic,value);
}
}
}

/*
Mixin

在修饰器的基础上,可以实现Mixin模式,所谓 mixin模式, 就是对象继承的一种替代
方案, 意为 在一个对象之中汇入另一个对象的方法。

请看下面的例子

*/

const Foo = {
foo(){console.log('foo')}
};
class MyClass {}
Object.assign(MyClass.prototype,Foo);

let obj = new MyClass();
obj.foo() foo

/*
上面代码之中,对象Foo有一个foo方法,通过Object.assign 方法,可以将foo
混入MyClass类,导致MyClass的实例obj对象都具有foo方法,这就是 混入模式的
一个简单的实现。
下面,我们部署一个简单的脚本mixins。js j将mixin 写成一个修饰器


*/

export {mixins} form './mixins';

const Foo = {
foo(){console.log('foo')}
};
@mixins(Foo)
class MyClass{}
let obj = new MyClass();
obj.foo() 'foo'

不过 上面的代码 MyClass类的prototype对象,如果你不喜欢这一点,也可以通过国

类的继承来实现mixin。

class MyClass extends MyBaseClass{

}
/*
上面代码中,MyClass继承了baseClass。如果我们想在Myclass里面混入一个 foo
方法,一个办法是在 myClass和MyBaseClass之间插入一个混入类,这个类具有foo
方法,并且继承了 MyBaseClass的所有方法,然后 MyClass再继承这个类。


*/

let MyMixin = (superClass)=>class extends superClass{
foo(){
console.log("foo from MyMixin")
}
}

接着 ,目标类再去继承这个混入类,就达到了 混入 “foo”方法的目的

class MyClass extends MyMixin(MyBaseClass){
/*...*/
}
let c = new MyClass();

c.foo(); foo from myMixin

如果需要混入多个方法,就生成多个混入类。

class MyClass extends Mixin(MyMixin2(myBaseClass)){
....
}

这种写法有一个好处,就是可以调用super,因此,可以避免 在 “混入”过程
中覆盖父类的同名方法。

let Mixin1 = (superClass) => class extends superClass{
foo(){
console.log('foo form Mixin1');
if(super.foo) super.foo();
}
};

let Mixin2 = (superClass) => class extends superclass{
foo(){
console.log('foo from Mixin2');
if(super.foo) super.foo();
}
} ;

class s {
foo(){
console.log('foo from s');
}
}

class c extends Mixin1(Mixin2(s)){
foo(){
console.log('foo from c');
super.foo();
}
}

new c().foo();
foo from c
foo from Mixin1
foo from Mixin2
foo from Mixin s

Trait --------trait 也是一种修饰器,效果与Mixin类似,但是提供更多工功能
比如防止同名方法冲突,排除混入某些方法,为混入的方法起别名等等。
import {traits} from 'traits-decorator';

class TFoo {
foo(){console.log('foo')}
}
const TBar = {
bar(){console.log('bar')}
foo(){console.log('foo')} 有了同名方法 就会报错
}
@traits(TFoo,TBar::excludes('foo'))使用绑定运算符 在Tbar上排除 foo
class MyClass{}
let obj = new Myclass();
obj.foo(); foo
obj.bar(); bar

上面代码中,通过trait是修饰器,在 MyClass类上面 混入了TFoo 类的 foo
和Tbar对象的bar 方法

Trait不允许 混入同名的方法。

有一种解决方法就是排除TBbar的 foo方法。
@traits(TExample::excludes('foo','bar')::alias({baz:'exampleBaz'}))
class MyClass {}
上面代码排除了TExample的foo方法和bar方法,为baz方法起了别名exampleBaz。

as方法则为上面的代码提供了另一种写法。

@traits(TExample::as({excludes:['foo', 'bar'], alias: {baz: 'exampleBaz'}}))
class MyClass {}

 

转载于:https://www.cnblogs.com/songtianen/p/6412190.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值