Skip to content
On this page

ES6

ES6版本过渡历史

JavaScript

  • ECMAScript
  • DOM
  • BOM

浏览器的阶段

HTML 1,HTML 2,HTML3 1991 - 1997 IETF(The Internet Enginneering Task Force) 国际互联网工程任务组

1997.1 HTML3.2 版本发布 从这个版本到现在,归属于W3C组织(万维网联盟)。

ECMA  日内瓦  ECMA-262规范 ECMAScript脚本语言规范


1995 LiveScript诞生 但是并不强大  
更名为JavaScript

1996  javascript 1.0 1.1

1997 Jscript IE抄了一份javascript,叫做JScript,网景公司倒闭,并把源码公开

1997.6 ECMAScript 1.0(以当初JavaScript第一个版本为蓝本)

1998.6 ECMAScript 2.0

1999.12 ECMAScript 3.0

2000  ECMAScript 4.0 草案没有通过 改动比较激进
成员:TC39(technical committe 39)

2007  ECMAScript 4.0 准备发布(草案没有通过),TC39绝大数成员不支持发布

2008.7  ECMAScript 3.1版本发布 -> ECMAScript5 (3.1版本直接过渡到5版本,只是修改了名称)
大会项目代号(hamony)针对4.0版本的问题,进行拆分

2009.12 正式发布ECMAScript5
将4.0版本内容分为两部分 
javascript.next(放入草案中)ES6
avascript.next.next(放入草案中)ES7

2011.6  ECMAScript5.1 成为国际标准

2013.3  javascript.next(草案冻结)

2013.12  javascript.next(草案发布)

2015.6  ECMAScript6正式发布,从这开始,每年6月份出来一个新版本升级。

ES6、ECMAScript、ECMAScript2016、ECMAScript2017、ECMAScript2018 有什么联系 ?

任何组织、任何时候都可以向委员会的成员提交提案,只要提案被采用,会把收集的操作,隔一段时间发布一次。

ES6通常指版本更迭。

从2015.6到现在,发布的版本都属于ES6。
ECMAScript2016 ECMAScript2017 ECMAScript2018只是小版本的改动,统称ES6。

学习ES6,是指学习2015年ES6发布以来,所有的内容。

ES7还是草案,并没有完全支持。

babel 配置

babel 编译器 需要 npm 包管理工具进行安装

npm 包管理工具 功能块(代码块) node node是服务器环境,可以运行js代码。

npm init or npm init -y

创建 .babelrc 文件。babel运行之前加载的文件

js
npm i babel-preset-env --save-dev //(env包括所有规则集)

配置 .beablrc

js
{
  "presets": ["babel-preset-env"]
}
shell
npm i babel-cli --save-dev 

配置package.json文件

js
{
  "build": "babel src/app.js -o dist/bundle.js",
	"dev": "babel-node ./src/app.js"
}
  
// 可以使用babel编译或者运行脚本文件
  • babel register:可以运行原文件时同时自动编译
  • babel core:Babel 编译时使用的API方法集合
js
var map = new Map(); // babel不能转译Map。
// 可以配置babel-polyfill进行转译,可以转译成浏览器可以识别的代码。

可以在浏览器中通过 script 的形式引入 babel.min.js.

html
<script type="text/label"></script>

也可以使用Traceur转译ES6语法。谷歌提供的转码工具。

https://node.green 关于node对于ES6支持情况的调查。

块级作用域与嵌套、let、暂行性死区

函数作用域:函数执行前一刻 会产生 [[scope]] 对象 里面存的的是AO、GO

  • AO: 函数执行期上下文
  • GO: 全局执行期上下文

函数声明提升 var

js
var a = 1;
var a = 2;

// 存在变量污染问题,通过立即执行函数来解决一部分问题。

// kiss原则(keep it simple stupid) 保持简单、傻瓜式操作

针对这一问题 推出 let 语法 块级作用域

js
if (1) {
  // 块级作用域
}

for (var i = 0; i < 10; i++) {
  // 块级作用域
}

for (; 1; ) {
  // 块级作用域
  break;
}

{
  // 块级作用域
}

let 语法特征:

js
// 1. 不能在同一作用域下重复声明

// 包括全局作用域、函数作用域等。
// 同一作用域下同时使用let、var,不能重复声明。

function test (a) {
let a = 10;
console.lof(a);
}
test(); // 报错

function test (a) {
{
let a = 10;
}

console.log(a);
}
test(); // undefined

function test (a) {
{
let a = 10;
console.log(a);
}
}
test(); // 10

// 2. let 不会声明提升

// 在声明之前,不能使用该变量,会产生一个暂时性死区。
// 只要在let所对应的作用域范围之内,let都不会提升。

console.log(a); // 报错
let a = 10;

var a = a;
console.log(a); // undefined

let b = b;
console.log(b); // 报错

function test (x = y, y = 2) {
console.log(x, y);
}
test(); // 报错(y is not defined)暂时性死区导致的问题

function test (x = 2, y = x) {
console.log(x, y); // 2 2 
}
test();

typeof(a); // undefined

typeof(a); // 报错 typeof不再安全 暂时性死区
let a;

// 3. let只能在当前的作用域下生效

{
let a = 2;
}
console.log(a); // 报错

for (; 1; ) {
  let a = 1;
}
console.log(a); // 死循环,不会报错

for (; 1; ) {
  let a = 1;
  break;
}
console.log(a); // 报错

for (let i = 0; i < 10; i++) { }
console.log(i); // 报错

var arr = [];
for (var i = 0; i < 10; i++) {
  arr[i] = function () {
    console.log(i);
  }
}
for (var i = 0; i < 10; i++) {
  arr[i](); 
  // 0-9
  // var i 重新赋值 
}

for (var i = 0; i < 10; i++) {
  i = 'a';
  console.log(i); // 1个a
}
for (var i = 0; i < 10; i++) {
  var i = 'a';
  console.log(i); // 1个a
}

for (let i = 0; i < 10; i++) {
  i = 'a';
  console.log(i); // 1个a
}
for (let i = 0; i < 10; i++) {
  var i = 'a';
  console.log(i); // 报错
}
for (let i = 0; i < 10; i++) {
  let i = 'a';
  console.log(i); // 10个a
}

// () 和 {} 不是同一个块级作用域。
// () 是 父级作用域。

// 块级作用域因为有let才有意义,var关键字存在变量的声明提升。
// let本质上就是为了JS增加一个块级作用域。
js
// ES6在块级作用域中允许声明函数,并不推荐使用函数声明的方式来定义函数。
// 主要使用函数表达式的方式声明函数。

if (i) {
  function test () {}
}
try {
  function test () {}
}

if (i) {
  var test = function () {}
}
try {
  var test2 = function () {}
}

// 块级作用域是没有返回值的

var a = for () { return }; // 错误

{
  return; // 错误
}

// 提案中有一个do的方式,可以实现,但目前仅仅是一个草案。

do {
  return xxx;
}

// 块级作用域 == 函数的立即调用 ? 

// 错误。不等于。

// 本质是完全不同的东西。
// 函数有返回值,块级作用域没有返回值。
// 虽然能够使用函数立即执行的方式模拟块级作用域,但是不是相等。

// let 语法特征总结:

// 1. 不能在同一作用域下重复声明
// 2. let不会声明提升
// 3. let只能在当前的作用域下生效

let进阶、const、全部变量与顶层对象

js
for (var i = 0; i < 10; i++) {
  i = 'a';
  console.log(i); // a
}
for (var i = 0; i < 10; i++) {
  var i = 'a';
  console.log(i); // a
}
for (let i = 0; i < 10; i++) {
  var i = 'a';
  console.log(i); // a
}
for (let i = 0; i < 10; i++) {
  var i = 'a';
  console.log(i); // 报错
}
for (let i = 0; i < 10; i++) {
  let i = 'a';
  console.log(i); // 10个a
}


// ES5只能在顶层作用域或者在函数作用域中声明函数。
// ES6允许在块级作用域声明函数。

{
  let a = 1;
  function a () { }
  console.log(a); // 报错 重复声明
}

{
  let a = 1;
  {
    function a () { } // 函数存在作用域提升
  }
  console.log(a); // 1
}

js
// const 定义常量
// 不希望变量被更改 const http = require('http');

// 语法特征:

// 1. 定义常量必须赋值,常量不能更改

const a = 13;

// 2. 产生块级作用域,不能提升,存在暂时性死区

{
  console.log(a); // 报错
  const a = 12;
}

// 3. 与let一致,不能重复声明

const a = 12;
let a = 10; // 报错

// 4. 原始值和引用值都不能更改,但是可以修改或添加引用值的属性

const obj = {};
obj.name = 'yueluo';

// const 只能保证栈内存的引用指针不会被改变。

// 可以使用对象冻结(freeze)解决此类问题。 Object.freeze(obj);
// 对象冻结只能进行一层对象的冻结,可以使用递归对对象内部属性进行再处理。

// 自定义冻结

function freeze (obj) {
  Object.freeze(obj);
  for (const key in obj) {
    if (typeof(obj[key]) === 'object' && obj[key] !== null) {
      Object.freeze(obj[key]);
      freeze(obj[key]);
    }
  }
}                  

// freeze不常用,定义模块时,导出模块一般是实例化对象形式或者函数形式。
// 从根源上解决更改属性值的问题。

顶层对象

浏览器环境中顶层对象是window。顶层对象的属性和全局变量是等效的。  
ES6为了改变现状,提出let、const、class 会生成块级作用域。

node环境中顶层对象是global。

不同环境的顶层对象是不一致的。

es6 提案所有的对象都是global,仅仅是提案,并没有实施。

解构赋值、函数默认值、数组解构、对象解构

js
var x = 1;
{
  let x = x;
  console.log(x); // 暂时性死区 TDZ(Temporal Dead Zone)
  // let关键字声明 取得是当前作用域下x x并没有赋值 所以报错
}

参数默认值问题

js
function foo (x, y) {
  x = x || 1;
  y = y || 2;
  console.log(x + y);
}
foo(); // 3
foo(5, 6); // 11
foo(5); // 7
foo(null, 6); // 7
foo(0, 5); // 6

// falsy(虚值) 通过Boolean转换转化为假的值就是虚值。

// ES5

function foo (x, y) {
  var a = typeof(arguments[0]) !== 'undefined' ? arguments[0] : 1,
      b = typeof(arguments[1]) !== 'undefined' ? arguments[1] : 2;
  console.log(a + b);
}
foo(0, 5); // 5

// ES6

function foo (x = 1, y = 2) {
  console.log(x + y);
}
foo(0, 5); // 5
js
let x = 1;
function foo (y = x) {
  let x = 2;
  console.log(y);
}
foo(); // 1

let x = 1;
function foo (x = 2) {
  let x = 2;
  console.log(x);
}
foo(); // 报错 x已经被定义

function foo (x = 2) {
  let x = 2;
  console.log(x);
}
foo(1); // 报错 x已经被定义

// 定义的函数默认值会影响内部作用域的值,不能重复声明。

let x = 1;
function foo (x = x) {
  let x = 2;
  console.log(x);
}
foo(10); // 报错 x已经被定义

let x = 1;
function foo (x = x) {
  console.log(x);
}
foo(10); // 报错 暂时性死区 x is defined
// 函数执行时,函数的参数会形成单独的参数作用域,函数执行完成后,参数作用域消失。

let x = 1;
function foo (x = x) {
  let x = 2;
  console.log(x);
}
foo(10); // 报错 x已经被定义
// 参数作用域和函数作用域究竟有什么不同?

let x = 1;
function foo (y = x) {
  let x = 2;
  console.log(y);
}
foo(); // 1

function foo (x = 2) {
  let x = 2;
  console.log(x);
}
foo(); // a被重复声明 
// 手册说参数也存在自己的作用域,独立于函数作用域,但是实际情况存在出入。

let x = 1;
function foo(x = x) {
  console.log(x);
}
foo(); // x is not defined 暂时性死区

var w = 1,
    z = 2;
function foo (x = w + 1, y = x + 1, z = z + 1) {
  console.log(x, y, z);
}
foo(); // 报错 暂时性死区 z is not defined

var w = 1,
    y = 6;
function foo (x = w + 1, y = x + 1) {
  console.log(x, y);
}
foo(); // 2 3  

let a = 99;
function foo (b = a + 1) {
  console.log(b);
}
foo(); // 100
a = 100;
foo(); // 101
// 参数传值每次都是重新计算的,不会保留上次的执行结果。
// 函数的参数为表达式的情况下,加载的方式是惰性求值的方式,每一次都需要计算表达式的值。

解构

js
// ES6新增的语法特性:解构赋值

let a = 1,
    b = 2,
    c = 3;

let [a, b, c] = [1, 2, 3];
console.log(a, b, c); // 1 2 3

// 解构依然是一种赋值的过程。
// 解构是通过模式匹配(结构化赋值)的方式进行赋值的,需要保证结构一致。

let [d, [e], [f]] = [1, [2]. [3]];
console.log(d, e, f); // 1 2 3

let [a, [b, c], [d, [e, f, [g]]]] = [1, [4, 3], [5, [1, 3, [2]]]];
console.log(a, b, c, d, e, f, g); // 1 4 3 5 1 3 2

// 解构失败,变量多,匹配不到的值是undefined,以undefined作为填充。

// 不完全解构:值多,并没有完全解构。

// 解构的默认值

let [a = 6] = [1];
console.log(a); // 1

let [a = 6] = [];
console.log(a); // 6

let [a, b = 2] = [1];
console.log(a, b); // 1 2

let [a, b = 2] = [1, null];
console.log(a, b); // 1 null

let [a, b = 2] = [1, false];
console.log(a, b); // 1 false

let [a, b = 2] = [1, NaN];
console.log(a, b); // 1 NaN

function test () {
  console.log(10);
}
let [x = test()] = [1];
console.log(x); // 1

// 默认值可以是函数
function test () {
  console.log(10);
}
let [x = test()] = [];
console.log(x); // undefined

// 默认值可以是变量
let [x = 1, y = x] = [];
console.log(x, y); // 1 1

let x = 5;
let [x = 1, y = x] = [];
console.log(x, y); // x 已经被定义

let [x = 1, y = x] = [2];
console.log(x, y); // 2 2

let [x = 1, y = x] = [1, 2];
console.log(x, y); // 1 2

let [x = y, y = 1] = [];
console.log(x, y); // 报错 y is not defined

// 对象解构

// 对象拓展

let obj = {};        
let obj1 = new Object(); // 不常用
let obj2 = Object.create(null); // 可以指定原型 不常用

obj.name
obj.name = 'yueluo'
obj['name'] = 'yueluo'
delete obj.name

var name = 'yueluo',
    age = 14;

var person = {
  name: name,
  age: age,
  sex: 'male',
  eat: function () {
    console.log('eating');
  }
}
console.log(person);

// ES6 变量和属性名一致时可以简写,
// 定义方法的functoin关键字也可以省略。
var name = 'yueluo',
    age = 14;
var person = {
  name,
  age,
  sex: 'male',
  eat () {
    console.log('eating');
  }
}
console.log(person);
console.log(person.eat());

// ES6支持属性拼接

let firstName = 'ai',
    secondName = 'xiaoye',
    name = 'ai xiaoye';
let person = {
  [firstName + secondName]: name
}

// 解构需要结构完全一致,通过属性名称获取对应的名称的值。
let { a: a, b: b, c: c } = { a: 1, b: 2, c: 3 };
console.log(a, b, c); // 1 2 3

// 变量和属性名一致时,可以简写。
let { a, b, c } = { a: 1, b: 2, c: 3 };
console.log(a, b, c); // 1 2 3

// 解构都存在不完全解构和解构失败的情况。

// 不完全解构
let { a = 2, b } = { a: 1, b: 2, c: 3 };
console.log(a, b); // 1 2 

// 解构失败
let { a = 2, b, c, d, e, f } = { b: 2, c: 3 };
console.log(a, b, c, d, e, f); // 2 2 3 undefined undefined undefined
// 解构失败默认为undefined。

let [d, [e], [f]] = [2, [1], [3]];
console.log(d, e, f); // 2 1 3
// 数组解构存在顺序问题。

let { a, b, c } = { b: 2, a: 1, c: 3 }; 
console.log(a, b, c); // 1 2 3
// 对象结构不存在顺序问题。

var person = {
  name: 'zhangsan',
  son: {
    name: 'lisi',
    age: 30,
    son: {
      name: 'wangwu',
      age: 12
    }
  }
}
let { son: { son: { name } } } = person;
console.log(name); // wangwu

隐式转换、函数参数解构、解构本质、()用法

解构赋值

js
let { a } = { a: 1 };
console.log(a); // 1

let a;
{ a } = { a: 1 };
console.log(a); // 报错 语法错误 认为{a}作用域,需要转为表达式

let a;
({ a } = { a: 1 });
console.log(a); // 1
// 通过括号的方式转为表达式。

// 模式匹配本质是声明一个变量,通过模式匹配的方式进行变量的赋值。

let  a = [1, 2, 3],
    obj = {};
[obj.a, obj.b, obj.c] = a;
console.log(obj); // { a: 1, b: 2, c: 3 }

let [a] = [1];
console.log(a); // 1

let { a: b } = {};
console.log(b); // undefined

let [(a)] = [1]; // 语法错误
let { a: (b) } = {}; // 语法错误
let { (a): b } = {}; // 语法错误
let ({a: b}) = {}; // let is not defined
// 当用let或者var声明时,只要使用()就报错。

function ((z)) {} // 语法错误

function foo ([z]) {
  return z;
}
console.log(foo([2])); // 2
// 函数传参也相对于变量的声明。
// 通过函数定义参数时,不能使用(),报错。

let arr = [1, 2, 3];
let { 0: first, [arr.length - 1]: last } = arr;
console.log(first, last); // 1 3
// 数组本身也是一种对象,可以使用对象进行解构,只要规则属性相同,就可以获取对应的值。

[(b)] = [3];
console.log(b); // 3

([b]) = [3];
console.log(b); // 报错

({a: (b) = {}});
console.log(b); // {} 作为默认值处理,本身并没有进行匹配

var a = {};
[(a.b)] = [3];
console.log(a); // {b: 3}

let a = 'x',
    b = 'y',
    obj = {};
({ a: obj[a + b] } = { a: 2 });
console.log(obj); // { xy: 2 }

let obj = { a: 1, b: 2, c: 3 },
    obj2 = {};
({ a: obj2.x, b: obj2.y, c: obj2.z } = obj)
console.log(obj2); // {x: 1, y: 2, z: 3}

// 数值交换
let a = 10,
    b = 20;
[a, b] = [b, a];
console.log(a, b); // 20 10

// 解构赋值是指变量的解构,变量的解构。
// 变量的解构本质就是变量的赋值。
// 解构使用模式匹配的方式。

let { a: x, a: y } = { a: 1 };
console.log(x, y); // 1 1
// 模式匹配允许匹配同源属性(同一个源属性)。 

let person = {
  name: 'zhangsan',
  age: 50,
  son: {
    name: 'lisi',
    ageLi: 30,
    son: {
      name: 'wangwu',
      ageWang: 18,
      son: {
        name: 'zhaoliu',
        ageZhao: 3
      }
    }
  }
}
let { son: { son: { son } } } = person;
console.log(son); // {name: "zhaoliu", ageZhao: 3}
let { son: { son: { son: { ageZhao }, ageWang }, ageLi } } = person;
console.log(ageLi, ageWang, ageZhao); // 30 18 3
let { 
  son: {
    son: {
      son: { 
        ageZhao
      }, 
      ageWang 
    }, 
    ageLi 
  } 
} = person;
console.log(ageLi, ageWang, ageZhao); // 30 18 3
// 主张通过合理的缩进来规范解构赋值。为了增加可读性可以使用缩进。

var x = 200,
    y = 300,
    z = 100;
var obj = { x:  { y: 42 }, z: { y: z } };
({ y: x = { y: y } } = obj); // undefined -> x = { y: 300 }
({ z: y = { y: z } } = obj); // y = { y: z } -> y = { y: 100 }
({ x: z = { y: x } } = obj); // z = { y: 42 } 
console.log(x.y, y.y, z.y); // 300 100 42
// 使用对象或数组解构赋默认值的这种方式,慎用(可读性较差)。

// 以数组解构的方式传递参数。
function test ([x, y]) {
  console.log(x, y); 
} 
test([1, 2]); // 1 2
test([1]); // 1 undefined
test([]); // undefined undefined
test(); // 报错

function foo ({ x, y }) {
  console.log(x, y);
}
foo({ y: 2, x: 1 }); // 1 2
foo({ x: 1, y: 2 }); // 1 2
foo({ x: 1 }); // 1 undefined
foo({ }); // undefined
foo(); // 报错

function foo ({ x = 10 } = {}, { y } = { y: 10 }) {
  console.log(x, y);
}
foo(); // 10 10
foo({}, {}); // 10 undefined
foo({ x: 2 }, { y: 3 }); // 2 3

({ x = 10 } = {}); 
console.log(x); // 10 
({ y } = { y = 10 });
console.log(y); // 10

解构的隐式转换问题

js
const [a, b, c, d, e] = 'hello';
console.log(a, b, c, d, e); // h e l l o
// 字符串隐式转换为类数组,从而可以打印出对应值。

let { length: len } = 'hello';
console.log(len); // 5            

let { toString: toStr } = 123;
console.log(toStr); // function toString () { }
console.log(toStr === Number.prototype.toString); // true

let { toString: toStr } = false;
console.log(toStr === Boolean.prototype.toString); // true

// boolean、number、string 都能进行隐式转换。
js
let { prop  } = undefined;
console.log(prop); // 报错

let { prop  } = null;
console.log(prop); // 报错

// undefined、null 没有包装类,不能进行隐式转换。

this指向、箭头函数基本形式、rest运算符

参数默认值问题

js
function test (a, b) { }
test(1);
console.log(test.length); // 2

function test (a, b, c = 1) { }
test(1);
console.log(test.length); // 2

function test (c = 1, a, b) { }
test(1);
console.log(test.length); // 0

function test (a, b, c = 1, d, e) { }
test(1);
console.log(test.length); // 2

// 根据当前给的默认值,实际打印的形参长度会发生变化。
// 依据当前赋默认值的位置,向前计数。
js
function test (a, b, c, d) {
  arguments[1] = 7;
  console.log(b); // 7
}
test(1, 2, 3, 4, 5);

function test (a, b, c = 1, d) {
  arguments[1] = 7;
  console.log(b); // 2
}
test(1, 2, 3, 4, 5);

function test (a, b, c, d = 1) {
  arguments[1] = 7;
  console.log(b); // 2
}
test(1, 2, 3, 4, 5);

// 当一旦给默认值时,arguments的映射关系就不存在。
js
function foo ({ x, y = 5  }) {
  console.log(x, y);
}
foo({}); // undefined 5 
foo({ x: 1 }); // 1 5
foo({ x: 1, y: 2 }); // 1 2
foo(); // 报错  undefine和null不能经过包装

function foo ({ x, y = 5 } = {}) {
  console.log(x, y);
}
foo(); // undefined 5 不会报错,达到兼容性的处理

function fetch (url, { body = "", method = "GET", header = {} } = { }) { } 
// fetch的兼容性处理

var x = 1;
function foo (x = x) {}
// 报错 x is not defined  TDZ 暂时性死区

// undefined 和 is not defined

// undefined 没有值
// is not defined 变量没有声明

var x = 1;
function foo (x, y = function() { x = 2; console.log(x) }) {
  var x = 3;
  y();
  console.log(x);
}
foo(); // 2 3
console.log(x); // 1

var x = 1;
function foo (x, y = function() { var x = 2; console.log(x) }) {
  var x = 3;
  y();
  console.log(x);
}
foo(); // 2 3
console.log(x); // 1
// 参数存在参数作用域(块级作用域)。

var x = 1;
function foo (x, y = function() { x = 2; console.log(x) }) {
  x = 3;
  y();
  console.log(x);
}
foo(); // 2 2
console.log(x); // 1

this 指向问题

js
// 1. 默认绑定
// 函数内部默认this指向window,严格模式下指向undefined

// 2. 隐式绑定
// 谁调用this就指向谁

// 3. 显示绑定
// call/apply/bind 改变this指向,如果不是对象,会转为对应的包装类

call(obj, a, b, c);
apply(obj, [a, b, c]);
bind(obj, a, b, c);

// 4. new 

// 构造函数实例化的时候,将this指向转变为实例化对象。
// new的优先级最高,显示绑定大于隐式绑定,隐式绑定大于默认绑定。
// new > 显示绑定 > 隐式绑定 > 默认绑定

箭头函数

js
// 只有一个参数

var f = ( a ) => a;
var f = function (a) {
  return a;
}

// 无参数

var f = () => 5;
var f = function () {
  return 5;
}

// 多个参数

var f = (a, b) => a + b;
var f = function (a, b) {
  return a + b;
}

// 不定长参数

var f = (..args) = 5;
js
let f = (a, b) => {
  var a = 3;
  var a = 4;
  console.log(a + b);
}
f(); // 7

// 箭头函数和解构赋值同时使用
const full = ({ first, last } = {}) => first + '' + last;
console.log(full({ first: 'yue', last: 'luo' }));

var arr = [1233, 2323, 2, 435, 121, 686, 3];
var arr2 = arr.sort((a, b) => a - b);
console.log(arr2); // [2, 3, 121, 435, 686, 1233, 2323]

var sum = (a, b) => {
  console.log(arguments);
  return a + b;
}
sum(1, 2); // arguments is not defined
// 箭头函数中不存在arguments。
// 箭头函数并不是使用function关键字来定义的,本质是两个东西。

rest 运算符

js
// rest运算符,也叫spread运算符。用来展开或收集。

var sum = (...args) => { // 收集原则
  console.log(args); // [1, 2, 3] 数组,不是类数组
}
sum(1, 2, 3); 

function foo (x, y, z) {
  console.log(x, y, z);
}
foo(...[1, 2, 3]); // 1 2 3 展开原则
foo.apply(null, [1, 2, 3]);      
foo.apply(undefined, [1, 2, 3]);
// 使用apply时,传入null或undefined,指向失败,调用方还是原来的this指向。

let a = [2, 3, 4],
    b = [1, ...a, 5];
console.log(b); // [1, 2, 3, 4, 5]
console.log([1].concat(a, [5])); // [1, 2, 3, 4, 5]     

let fn = (a, b, ...c) => {
  console.log(a, b, c);
}
fn(1, 2, 3, 4, 5, 6, 7); // 1 2 [3, 4, 5, 6, 7]
// 收集剩余参数的所有部分,拓展运算符必须是最后一个参数。
js
//数组排序:

function sortNum () {
  return Array.prototype.slice.call(arguments).sort(function (a, b) {
    return a - b;
  });
}
console.log(sortNum(12, 2313, 12, 2, 2124, 3434124, 1, 123)); //  [1, 2, 12, 12, 123, 2124, 2313, 3434124]

const sortNum = (...args) => args.sort((a, b) => a - b);
console.log(sortNum(12, 2313, 12, 2, 2124, 3434124, 1, 123)); //  [1, 2, 12, 12, 123, 2124, 2313, 3434124]

console.log((function (a) {}).length); // 1
console.log((function (...a) {}).length); // 0
console.log((function (b, ...a) {}).length); // 1
console.log((function (b, c, ...a) {}).length); // 2
console.log((function (b, c, d = 0, ...a) {}).length); // 2
// 默认值和rest操作符都不能通过length属性访问到长度,会访问之前参数的长度。length不再准确。

箭头函数的实质、箭头函数的使用场景

js
(a, b) => ({ a: 1, b: 4 })
// 返回一个对象的基本形式

// 不定长参数:rest运算符 - 拓展运算符 - 展开运算符 
function fn (first, last, ...args) { }
fn(1, 2, 3, 5, 4, 5);

// 箭头函数本身并不是由function关键字定义的,是由胖箭头(=>)的操作符来定义的。
// 本质上不是一个东西。

// 箭头函数this指向是由外层的函数作用域来决定的,和function不同。
// 箭头函数不能作为构造函数来使用。
// 箭头函数没有arguments对象,使用rest(拓展运算符)替代。
// 箭头函数使用generator,yield命令不能生效。

function foo () {
  return (a) => {
    console.log(this.a);
  }
}
var obj1 = { a: 2 },
    obj2 = { a: 3 };
var bar = foo.call(obj1);
bar.call(obj2); // 2
// 箭头函数的作用域是由外层函数决定的,执行时foo的this指向obj1。

const person = {
  eat () {
    console.log(this);
  },
  drink: () => {
    console.log(this);
  }
}
person.eat(); // person
person.drink(); // window

箭头函数的应用场景

js
(function () {
  function Button () {
    this.button = document.getElementById('button');
  }

  Button.prototype = {
    init () {
      this.bindEvent();
    },

    bindEvent () {
      // this.button.addEventListener('click', this.btnClick.bind(this), false);
      this.button.addEventListener('click', () => this.btnClick(), false);
    },

    btnClick () {
      console.log(this);
    }
  }

  new Button().init();
})();

嵌套时的this指向问题

js
function foo () {
  return () => {
    return () => {
      return () => {
        console.log('id', this.id);
      }
    }
  }
}
var f = foo.call({ id: 1 }),
    f1 = f.call({ id: 2 })()(),
    f2 = f().call({ id: 3 })(),
    f3 = f()().call({ id: 4 });
// 1 1 1 

//箭头函数内部没有this的机制,this指向是固化的,访问的都是最外层的this。
// 箭头函数this指向固化 -> 内部没有this机制 -> 只能通过父级作用域来获取this,闭包的this
//    -> 不能作为构造函数来使用 -> 使用call/apply/bind无效

箭头函数不存在arguments

js
var test = () => {
  console.log(arguments); // arguments is not defined
}
test();

function foo () {
  setTimeout(() => {
    console.log(arguments); // Arguments(5) [1, 2, 3, 4, 5, callee: ƒ, Symbol(Symbol.iterator): ƒ]
  }, 200);
}
foo(1, 2, 3, 4, 5); 
// 箭头函数的确不存在arguments对象,但是可以获取外层作用域的arguments对象。

// 一个函数执行导致另一个函数的定义,就会形成闭包。

function insert (value) {
  return {
    into: function (array) {
      return {
        after: function (afterValue) {
          array.splice(array.indexOf(afterValue) + 1, 0, value)
          return array;
        }
      }
    }
  }
}
console.log(insert(5).into([1, 2, 3, 4, 6, 7, 8]).after(4));

const insert = (value) => ({
  into: (array) => ({
    after: (afterValue) => {
      array.splice(array.indexOf(afterValue) + 1, 0, value)
      return array;
    }
  })
})
console.log(insert(5).into([1, 2, 3, 4, 6, 7, 8]).after(4));

箭头函数的使用场景:

js
// 1. 简单的函数表达式;

// 返回值相对单一;
// 函数内部没有this的引用;
// 函数内部不存在递归、事件绑定、事件解绑;

[1222, 22, 1, 3334, 212424, 21].sort((a, b) => a - b);

// 2. 内层的函数表达式,需要调用this(var self = this,bind),需要确保this指向正确的时候,可以使用箭头函数。

3. var args = Array.prototype.slice.call(arguments);

function sortNumber () {
  return Array.prototype.slice.call(arguments).sort((a, b) => a - b);
}

const sortNumber = (...numbers) => numbers.sort((a, b) => a - b); 
console.log(sortNumber(222, 111, 13334, 1, 132424));

不适合箭头函数的情况:

  • 函数声明,执行语句比较多同时需要用到递归,
  • 需要引用函数名以及事件绑定,解绑定等情况,应该避免使用箭头函数。

函数名/对象拓展、描述符、getter/setter

函数的拓展

js
var f = function () { }
console.log(f.name); // f
// ES5包括之前返回的空字符串。
// ES6返回值为函数名。

// console.log(Function);
// 每次声明function,都是实例化一个Function构造函数。

console.log(new Function().name); // anonymous
console.log((new Function).name); // anonymous
// 通过构造函数实例化的名称,函数名称是anonymous。

function foo () {}
console.log(foo.bind({}).name); // bound foo 
console.log(foo.call({}).name); // 报错 
console.log(foo.apply({}).name); // 报错

对象的拓展

js
const foo = "bar";
const baz = { foo };
console.log(baz); // { foo: 'bar' }

const person = {
  age: '12',
  say: function () {
    console.log(this.age);
  }
}
person.say();

let age = 12;
const person = {
  age
}
consoe.log(person.age);

var arr = [1, 2, 232, 12, 4, 121];
console.log(arr[2]); // 232
console.log(arr['2']); // 232
// 定义的属性都是字符串。

const obj = {
  class () {  }
}
// 不推荐写法,class是关键字。

let obj = {};
obj.foo = true;
obj['f' + 'o' + 'o'] = false;
console.log(obj); // { foo: false }

let a = 'hello';
let b = 'world';
let obj = {
  [a + b]: true,
  ['hello' + b]: 123,
  ['hello' + 'world']: undefined
}
console.log(obj); // { helloworld: undefined }

var myObj = {};
myObj[true] = 'foo';
myObj[3] = 'bar';
myObj[myObj] = 'baz';
console.log(myObj); // {3: "bar", true: "foo", [object Object]: "baz"}
// 对象指定键名赋值时,会调用本身原型上的toString方法,转换为字符串再赋值。

const a = { a: 1 };
const b = { b: 2 };
const obj= {
  [a]: 'value a',
  [b]: 'value b'
}
console.log(obj); // { '[object Object]': 'value b' }

const person = {
  sayName () {
    console.log('hello');
  }
}
console.log(person.sayName.name); // sayName

属性描述符

js
// es5之前 javascript没有提供检测属性的方法。
// 检测是否是只读属性、是否是可遍历属性、属性值是否只读。
// es5之后,提供属性描述符的东西。

let obj = { a: 2 };
console.log(Object.getOwnPropertyDescriptor(obj, 'a')); 
// {value: 2, writable: true, enumerable: true, configurable: true}

// configurable:可配置 
// enumerable:可枚举
// writable:可写
//value:值

// defineProperty 修改一个已有的属性,或添加一个新的属性。

let obj = {};
Object.defineProperty(obj, 'a', {
  value: 2,
  enumerable: true,
  writable: true,
  configurable: true
});
console.log(obj);
// { a: 2 }
console.log(Object.getOwnPropertyDescriptor(obj, 'a')); 
// {value: 2, writable: true, enumerable: true, configurable: true}

obj.a = 3; 
// writable为false的情况,不能赋值,可以删除。
// 1. 如果当前不是严格模式,会静默失败。
// 2. 如果当前是严格模式,会报错。

delete obj.a;
// configurable为false的情况,不能删除。
// 1. 非严格模式,静默失败。
// 2.严格模式,报错。

getter、setter

js
// 属性描述符的一种。

// get、put操作

let obj  = { a: 1 };

// obj.a;
// [[Get]] 默认操作:查找当前属性,如果没有,继续查找原型上的属性

// obj.a = 3;
// [[Put]] 默认操作(赋值)
// 1. getter,setter
// 2. writable:false,不可修改 true 可修改
// 3. 赋值

// ES5使用getter、setter来修改默认操作。

// get

var obj = {
  log: ['example', 'test'],
  get latest () {
    if (this.log.length === 0 ) {
      return undefined;
    }

    return this.log[this.log.length - 1];
  }
}
console.log(obj.latest); // test

var myObj = {
  get a () {
    return 2;
  }
}
Object.defineProperty(myObj, 'b', {
  get: function () {
    return this.a * 2;
  },
  enumerable: true,
  configuable: true
})
console.log(myObj.b); // 4

// 在定义get、set时,不能使用value和writable属性。
// 可以使用enumerable和configuable。

// MDN文档说明属性描述符和存取描述符不能一起使用。

// set

var lang = {
  set current (name) {
    this.log.push(name);
  },
  log: []
}
lang.current = 'ch';
lang.current = 'en';
console.log(lang.log); // ["ch", "en"]

// get、set

var obj = {
  get a () {
    return this._a;
  },
  set a (val) {
    this._a = val * 2;
  }
}
obj.a = 3;
console.log(obj.a); // 6

对象密封4种方式、assign、取值函数的拷贝

js
const obj = {
  get foo () {
  },
  set foo (x) {
  }
}

// console.log(obj.foo.name); // 报错
Object.getOwnPropertyDescriptor(obj, 'foo');
// {enumerable: true, configurable: true, get: ƒ, set: ƒ}
var descriptor =  Object.getOwnPropertyDescriptor(obj, 'foo');
console.log(descriptor.get.name); // get foo

var a = { a: 2 };
var descriptor =  Object.getOwnPropertyDescriptor(a, 'a');
console.log(descriptor);
// {value: 2, writable: true, enumerable: true, configurable: true}

对象常量的常见配置方式:

js
var obj = { a: 2 };

// 1. configurable: false  不可删除
// 2. writable: false 不可写
// 3. Object.preventExtensions(obj); 对象不可拓展

var obj = { a: 2 };
console.log(Object.isExtensible(obj)); // true 对象可拓展 
Object.preventExtensions(obj); // 设置对象不可拓展
obj.b = 2; // 严格模式下对对象进行拓展会报错
obj.a = 3;
console.log(Object.isExtensible(obj)); // false 对象不可拓展 
console.log(obj); // { a: 3 }
delete obj.a;
console.log(obj); // { }

// Object.defineProperty 定义的属性,属性描述符都是false。
// 通过对象点的语法添加属性,使用getOwnPropertyDescriptor查看,属性描述符都是true。

// Object.preventExtensions():禁止对象拓展,原有属性可修改,可删除

var obj = { a: 2 };
console.log(Object.isSealed(obj)); // false 对象未密封
Object.seal(obj); // 设置对象密封 
// 本质是调用preventExtensions,并将属性的configurable属性设置为false 
console.log(Object.isSealed(obj)); // true 对象密封
obj.b = 3;
obj.a = 2;
console.log(obj); // { a: 2 }
delete obj.a;
console.log(obj); // { a: 2 }

var arr = [1];
Object.seal(arr);
arr[0] = 2;
arr.length = 0; 
// arr.push(2); // 报错
console.log(arr); // [2]

// Object.seal():密封对象,不可拓展、不可删除、可修改,不能解决数组的问题,数组仍然可以赋值

var obj = { a: 2};
console.log(Object.isFrozen(obj)); // false 对象未密封
Object.freeze(obj); // 设置对象密封
console.log(Object.isFrozen(obj)); // true 对象密封
obj.a = 3;
obj.b = 2;
console.log(obj); // { a: 2 }
delete obj.a;
console.log(obj); // { a: 2}

var arr = [1];
Object.freeze(arr); 
arr[0] = 2;
console.log(arr); // [1] 
arr.push(2); // 报错
console.log(obj); 

Object.freeze():密封对象,不可拓展、不可删除、不可修改,对数组也有效,并没有深度冻结

自定义对象冻结方法

js
function freeze (obj) {
  Object.freeze(obj);
  for (const key in obj) {
    if (typeof(obj[key]) === 'object' && obj[key] !== null) {
      freeze(obj[key]);
    }
  }
}   

Object.is():比较元素符的拓展

js
console.log(NaN === NaN); // false
console.log(+0 === -0); // true

// ===:全等,严格相等 sameVal算法
// ==: 需要进行隐式类型转换,再进行判断

// 与全等区别:

Object.is(NaN, NaN); // true 
Object.is(+0, -0); // false

{} === {}; // false
Object.is({}, {}); // false

false === 0; // false
Object.is(false, 0); // false

Object.assign():合并一个对象

js
// 将需要合并的源对象合并到目标对象。

Object.assign(tar, ...sources);

let obj = { a: 1 };
let tar = {};
let copy = Object.assign(tar, obj);
console.log(tar); // { a: 1 }
console.log(copy); // { a: 1 }
console.log(copy === tar); // true

const tar = { a: 1 },
      tar2 = { b: 2 },
      tar3 = { c: 3 };
Object.assign(tar, tar2, tar3);
console.log(tar); // {a: 1, b: 2, c: 3}

const tar = { a: 1, b: 1 },
      tar2 = { b: 2, c: 2 },
      tar3 = { c: 3 };
Object.assign(tar, tar2, tar3);
console.log(tar); // {a: 1, b: 2, c: 3}

Object.assign(undefined, { a: 1 }); // 报错
Object.assign(null, { a: 1 }); // 报错
Object.assign(1, { a: 1 }); // Number(1, a: 1);
Object.assign(false, { a: 1 }); // Boolean(1, a: 1);
Object.assign('', { a: 1 }); // String(1, a: 1);

Object.assign({ a: 1 }, undefined); // { a: 1 }
Object.assign({ a: 1 }, 1); // { a: 1 }
Object.assign({ a: 1 }, 123); // { a: 1 }
Object.assign({ a: 1 }, '1'); // {0: "1", a: 1}
Object.assign({ a: 1 }, '123'); // {0: "1", 1: "2", 2: "3", a: 1}
Object.assign({ a: 1 }, true); // { a: 1 }
// 如果当前的参数转成对象,对象内的属性是否是可枚举的,如果可枚举,才可以拷贝。

const test1 = 'abc',
      test2 = true,
      test3 = 10;
Object.assign({}, test1, test2, test3); // {0: "a", 1: "b", 2: "c"}

new String('abc'); // String { "abc" } { 0: 'a', 1: 'b', 2: 'c' } 可枚举属性
new Booean(true); // Boolean { true } [[PrimitiveValue]]: true 不可枚举
new Number(10); // Number { true } [[PrimitiveValue]]: 10 不可枚举
js
var obj = { a: 3 };
Object.defineProperty(obj, 'b', {
  value: 4
});
Object.assign({ }, obj); // { a: 3 }

var obj = { a: 3 };
Object.defineProperty(obj, 'b', {
  value: 4,
  enumerable: true
});
Object.assign({ }, obj); // { a: 3, b: 4 }

// Object.cretae(自定义原型, 对象及属性描述符).
var obj = Object.create({ foo: 1 }, {
  bar: {
    value: 2
  },
  baz: {
    value: 3,
    enumerable: true
  }
});
console.log(obj); // { baz: 3, bar: 2 } prototype foo: 1
Object.assign({ }, obj); // {baz: 3}
// Object.assign只拷贝可枚举的属性,原型上的属性不会拷贝。继承属性和不可枚举的属性不能拷贝。

// Symbol()

// Symbol是一个函数,是新增的一个原始类型。

var a = Symbol(),
    b = Symbol();
// a === b; // false

var a = Symbol('a'),
    b = Symbol('a');
// a === b; // false

//Object.assign({ a: 'b' }, { [Symbol('c')]: 'd' }); // {a: "b", Symbol(c): "d"}
// Symbol类型的值可以被Object.assign拷贝,不会重复的值。

const obj1 = { a: { b: 1 } };
const obj2 = Object.assign({}, obj1);
obj1.a.b = 2;
console.log(obj2); // { a: { b: 2 } }
// Object.assign复制的对象是浅拷贝的对象。

const target = { a: { b: 'c', d: 'e' } };
const source = { a: { b: 'hello' } };
Object.assign(target, source); // { a: { b: 'hello' } } 
// 拷贝时,同名的属性直接整个替换掉。

Object.assign([1, 2, 3], [ 4, 5]); // [4, 5, 3]
// 拷贝数组时,根据属性名进行替换,数组的默认下标是从0开始。

const source = {
  get foo () {
    return 1;
  }
};
const target = {};
Object.assign(target, source); // { foo: 1 } 
// Object.assign拷贝 get、set时,直接将具体的值拷贝出来

// Object.assign 的用法

// 1. 给原型扩充方法

Object.assign(person.prototype, {
  eat () {},
  drink () {},
  age: 1
});

// 2. 克隆对象、合并对象

// 3. 默认值处理

const DEFAULT = {
  url: {
    host: 'http://www.yueluo.club',
    prot: 80
  }
}
function test (option) {
  option = Object.assign({}, DEFAULT, option);
  console.log(option);
}
test({ url: { port: 8080 } }); // { url: { port: 8080 } }

Object.defineProperties() 定义多个属性

js
var obj = {};
Object.defineProperties(obj, {
  a: {
    value: true,
    wirtable: true
  },
  b: {
    value: 'hello',
    writable: false
  }
});
console.log(obj); // {a: true, b: "hello"}

Object.getOwnPropertyDescriptors() 获取多个属性的描述

js
console.log(Object.getOwnPropertyDescriptors(obj));
// {
//   a: {value: true, writable: false, enumerable: false, configurable: false},
//   b: {value: "hello", writable: false, enumerable: false, configurable: false}
// }

// 可以使用它实现浅拷贝。

var obj = {
  a: 1,
  b: 2,
  c: 3
}

const cloneObj = Object.create(Object.getPrototypeOf(obj),
                               Object.getOwnPropertyDescriptors(obj));
console.log(cloneObj); // {a: 1, b: 2, c: 3}

const clone = obj => Object.create(
  Object.getPrototypeOf(obj),
  Object.getOwnPropertyDescriptors(obj)
);
console.log(clone(obj)); // {a: 1, b: 2, c: 3}

拷贝get、set问题(取值函数的拷贝问题)

js
const source = {
  set foo (value) {
    console.log(value);
  }
}
const tar = {};
Object.assign(tar, source); // { foo: undefined }

Object.defineProperties(tar, Object.getOwnPropertyDescriptors(source));
console.log(Object.getOwnPropertyDescriptor(tar, 'foo')); 
// {get: undefined, enumerable: true, configurable: true, set: ƒ}
console.log(tar);
// {
//   set foo (value) {
//     console.log(value);
//   }
// }

// 可以借助defineProperties和getOwnPropertyDescriptors完成get和set的拷贝。

部署对象的几种方式(定义对象)

js
const obj = { a: 1 };

const obj = Object.create(prot);
obj.foo = 123;

const obj = Object.assign(Object.create(prot), {
  foo: 123
});

const obj = Object.create(prot, Object.getOwnPropertyDescriptors({
  foo: 123
}));

super、4种遍历方式、原型、symbol遍历

js
function Person () {
  this.name = 'zhangsan';
  this.age = 18;
}
var person = new Person();
console.log(person);

prototype是构造函数的属性,也是实例化出来的所有对象的公共祖先。 原型的终点是Object.prototype。

js
person.__proto__ = {}; // 修改方式不合理,影响性能,也会影响到所有继承改属性的对象

__proto__ 系统内置属性,不建议使用

1. 语义化,内部属性,原则上应该修改
2. 访问效率特别慢
3. 所有继承自该原型的对象都会影响到

Object.create(指定原型,对象) 创建原型 Object.setPrototypeOf() 修改原型 Object.getPrototypeOf() 读取操作

Object.setPrototypeOf()、Object.getPrototypeOf()

js
let proto = {
  y: 20,
  z: 40
}
let obj = { x: 10 };
let obj1 = Object.setPrototypeOf(obj, proto);
console.log(obj1); 
console.log(obj1 === obj); // true
console.log(obj); 

let obj = Object.setPrototypeOf(1, { a: 1 });
console.log(obj); // 1
var proto = Object.getPrototypeOf(obj);
console.log(proto); // Number { 0, ... }  

Object.getPrototypeOf(Number(1)); // Numer { 0, ... }
console.log(Object.getPrototypeOf(1) === Number.prototype); // true
// 获取Object.setPrototypeOf()设置的原型时,经过包装类包装,获取的实际是Number的原型。
// 使用Object.setPrototypeOf()给不是对象的值设置原型属性时,是没有任何效果的。
// 使用Object.setPrototypeOf()给undefined和null设置原型属性,会报错。

let obj = Object.setPrototypeOf('foo', { a: 1, b: 2 });
console.log(Object.getPrototypeOf(obj)); // String { "", ... }
console.log(Object.getPrototypeOf(obj) === String.prototype); // true

Object.keys()、Object.values()、Object.entries()

js
const foo = {
  a: 1,
  b: 2,
  c: 3
}
Object.defineProperties(foo, {
  d: {
    value: 4,
    enumerable: true
  },
  f: {
    value: 5,
    enumerable: false
  }
});

console.log(Object.keys(foo)); // ["a", "b", "c", "d"]  
console.log(Object.values(foo)); // [1, 2, 3, 4]
console.log(Object.entries(foo)); // [ ["a", 1], ["b", 2], ["c", 3], ["d", 4] ]

// Object.keys():可以遍历自身可枚举属性的键名(不包含继承属性)
// Object.values():可以遍历自身可枚举属性的键值(不包含继承属性)
// Object.entries():可以遍历自身可枚举属性的键值集合(不包含继承属性)

Object.keys({}); // []
Object.keys(1); // []
Object.keys(true); // []
Object.keys('123'); // ['1', '2', '3']
Object.keys(undefined); // 报错
Object.keys(null); // 报错

super -> this

js
// this指向对象本身。
// super关键字指向对象的原型对象。

let proto = {
  y: 20,
  z: 40
};
let obj = {
  x: 10,
  foo: super.y
};
Object.setPrototypeOf(obj, proto);
console.log(obj); // 'super' keyword unexpected here

let proto = {
  y: 20,
  z: 40
};
let obj = {
  x: 10,
  foo: function () {
    console.log(super.y);
  }
};
Object.setPrototypeOf(obj, proto);
obj.foo(); // 'super' keyword unexpected here

let proto = {
  y: 20,
  z: 40
};
let obj = {
  x: 10,
  foo: () => {
    console.log(super.y);
  }
};
Object.setPrototypeOf(obj, proto);
obj.foo(); // 'super' keyword unexpected here

let proto = {
  y: 20,
  z: 40
};
let obj = {
  x: 10,
  foo () {
    console.log(super.y);
  }
};
Object.setPrototypeOf(obj, proto);
obj.foo(); // 20

// super关键字有很多限制,只可以在对象的简写形式的写法中才能生效。

let proto = {
  y: 20,
  z: 40,
  bar: function () {
    console.log(this.y);
  }
};
let obj = {
  x: 10,
  foo () {
    console.log(super.bar());
  }
};
Object.setPrototypeOf(obj, proto);
obj.foo(); // 20

symbol

js
// symbol是ES6引入的一种新的基本类型。Symbol是一种原始值类型。

// 引入Symbol用于解决对象属性名的重名问题。

// 原始值类型:string、number、boolean、undefined、null、symbol
// 引用值类型:Object、Array、Function

Symbol() // 不是构造函数,是普通函数,可以生成独一无二的值

new Symbol(); // 报错

console.log(Symbol() === Symbol()); // false
console.log(typeof(Symbol)); // symbol
console.log(Symbol()); // Symbol()

let s1 = Symbol();
s1.a = 1;
console.log(s1.a); // undefined

// 为了区分Symbol的值,可以传入不同的字符串识别不同的值。

let s1 = Symbol('foo');
console.log(s1); // Symbol(foo)

var obj = { a: 1 };
let s1 = Symbol(obj); // 使用对象参数时,使用Object.prototype.toString将参数转为字符串
console.log(s1); // Symbol([object Object])

console.log(Symbol(undefined)); // Symbol()
console.log(Symbol(null)); // Symbol(null)

let s1 = Symbol();
console.log(s1 + 1);
// Cannot convert a Symbol value to a number 
// 无法进行隐式类型转换,不能将Symbol类型转为number类型
console.log(Number(s1)); // 报错
console.log(String(s1)); // Symbol()
console.log(Boolean(s1)); // true
// 只有Number类型不能转换,会报错。
// Number、String、Boolean类型可以转换。

let s1 = Symbol(null);
console.log(Object.getPrototypeOf(s1)); // Symbol(...)
// 可以获取到Symbol的原型,有自己的构造函数。

console.log(Symbol().toString()); // 'Symbol()' 
console.log(!Boolean(Symbol(null))); // false
console.log(!Symbol(null)); // false 
// 可以直接取反进行隐式类型转换,隐式类型转换仅限于Boolean。

// Symbol的使用方式

let name = Symbol();
let person = {};
person[name] = 'zhangsan';
console.log(person); // {Symbol(): "zhangsan"}

let name = Symbol();
let person = {
  [name]: 'lisi'
}
console.log(person); // {Symbol(): "lisi"}

let name = Symbol(),
    person = {};
Object.defineProperty(person, name, {
  value: 'zhangsan'
});
console.log(person); // {Symbol(): "zhangsan"}
console.log(person.name); // undefined
console.log(person[name]); // zhangsan

const name = Symbol(),
      person = {};
person.name = 'zhangsan';
console.log(person[name]); // undefined
console.log(person['name']); // zhangsan

let name = Symbol(),
    eat = Symbol();
let person = {
  [name]: 'zhangsan',
  [eat] () {
    console.log(this[name]);
  }
}
person[eat](); // zhangsan

let s1 = Symbol;
console.log(s1); // Symbol() 构造函数,如果调用它,必须执行它,不能实例化它

Symbol.for()、Symbol.keyFor()

let s3 = Symbol('foo');
let s4 = Symbol('foo');
console.log(s3 === s4); // false
console.log(s3 === s1); // false  

let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');
console.log(s1 === s2); // true
// 可以使用Symbol.for()的方式获取到相同的Symbol值。

console.log(Symbol.keyFor(s1)); // foo 
console.log(Symbol.keyFor(s2)); // foo 
console.log(Symbol.keyFor(s1) === Symbol.keyFor(s2)); // true

let s = Symbol('foo');
console.log(Symbol.keyFor(s)); // undefined
// 使用Object.keyFor()打印的是以Object.for()声明的值。

// Symbol属性的遍历

const obj = {};
let a = Symbol('a'),
    b = Symbol('b');
obj[a] = 'hello';
obj[b] = 'world';
obj.a = 'hello world';

for (let key in obj) {
  console.log(key); 
}
// for in循环不能遍历Symbol属性

for (let key of obj) {
  console.log(key);
}
// 报错 obj is not iterable

var  copy = {};
Object.assign(copy, obj);
console.log(obj);
// {Symbol(a): "hello", Symbol(b): "world"}
// 使用Object.assign可以合并Symbol属性的对象

// 只针对Symbol属性的对象,用来遍历Symbol属性。

// Object.getOwnPropertySymbols():获取对象的Symbol属性集合(只会遍历Symbol属性的值)。
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(a), Symbol(b)] 

let arr = Object.getOwnPropertySymbols(obj);
arr.forEach(item => console.log(item)); 
// Symbol(a) 
// Symbol(b)

// 属性遍历案例  

const obj = { c: 1, d: 2 };

let a = Symbol('a'),
    b = Symbol('b'),
    _a = Symbol('_a'),
    _b = Symbol('_b');

obj[a] = 'hello';
obj[b] = 'world';

Object.defineProperties(obj, {
  e: {
    value: 5,
    enumerable: true
  },
  f: {
    value: 6,
    enumerable: false
  },
  [_a]: {
    value: -1,
    enumerable: true
  },
  [_b]: {
    value: -2,
    enumerable: false
  }
});

let h = Symbol('h'),
    i = Symbol('i'),
    j = Symbol('j');

const obj1 = {
  g: 7,
  [h]: 8
}

Object.defineProperties(obj1, {
  [i]: {
    value: 9,
    enumerable: true
  },
  [j]: {
    value: 10
  },
  k: {
    value: 11
  }
});

Object.setPrototypeOf(obj, obj1);

for (let i in obj) {
  console.log(i);
}
// c d e g 
// for in 可以遍历自身和原型上的可枚举属性(不包含Symbol类型的值)

console.log(Object.keys(obj)); 
// ["c", "d", "e"]
// Object.keys() 可以遍历自身的可枚举属性(不包含Symbol类型的值)

console.log(Object.getOwnPropertySymbols(obj)); 
// [ Symbol(a), Symbol(b)m Symbol(_a), Symbol(_b) ]
// Object.getOwnPropertySymbols() 可以遍历自身Symbol类型的值(可枚举、不可枚举都可以遍历)

var obj3 = Object.assign({}, obj);
console.log(obj3); 
// Object.assign() 拷贝自身可枚举的属性(包含Symbol类型的值、不包含原型上的属性)

console.log(JSON.parse(JSON.stringify(obj)));
// JSON.stringify() 可以拷贝自身可枚举的属性(不包含原型上的属性,不包含Symbol类型的属性)

Symbol、iterator、forOf、typeArray

js
function foo () {
  return function () {
    return function () {
      return function () {
        console.log('id:', this.id);
      }
    }
  }
}
var f = foo.call({ id: 1 });  
var t1 = f.call({ id: 2 })()(); // id: undefined
var t2 = f().call({ id: 3 })(); // id: undefined
var t3 = f()().call({ id: 4 }); // id: 4

... 拓展运算符 ES2017得到进一步的加强

js
var obj = {
  a: 1, 
  b: 2,
  c: 3 
};
var obj1 = {
  a: 4,
  d: 5,
  e: 6
}

var obj2 = {};
Object.assign(obj2, obj, obj1);
console.log(obj2); // {a: 4, b: 2, c: 3, d: 5, e: 6}

var obj3 = {
  ...obj,
  ...obj1
}
console.log(obj3); // {a: 4, b: 2, c: 3, d: 5, e: 6}

symbol、iterator

js
var s = Symbol(); 
console.log(Object.getPrototypeOf(s)); // Symbol( ... )

// foo instance Foo
// FOO[Symbol.hasInstance]
// 用户调用instaceof判断时,会调用FOO[Symbol.hasInstannce]接口。

// isConcatSpreadable:是否可以连接拓展。

iterator: Symbol(Symbol.iterator) 迭代器接口
  
// 迭代器:iterator

let arr = [1, 2, 3, 4];
console.log(arr); 
//可以看到原型上有Symbol(Symbol.iterator),说明当前对象已经部署迭代器接口

  let arr = [1, 2, 3, 4, 5];

console.log(arr[Symbol.iterator]); // ƒ values() { [native code] }
let iter = arr[Symbol.iterator]();
console.log(iter); // Array Iterator {}

console.log(iter.next()); // {value: 1, done: false}
console.log(iter.next()); // {value: 2, done: false}
console.log(iter.next()); // {value: 3, done: false}
console.log(iter.next()); // {value: 4, done: false}
console.log(iter.next()); // {value: 5, done: false}
console.log(iter.next()); // {value: undefined, done: true}
// 迭代是对数据结构读取的一种方式,是有序的、连续的、基于拉取的一种消耗数据的组织方式。

// 对象是无序的,键名是无序的。

// 常见数据结构:

// Array、
// 类数组:arguments、nodeList、
// TypeArray:用于保存二进制数据、二进制数据的缓存区
// Map、Set、WeakMap、WeakSet

// 数据读取不能统一用for循环的方式来遍历数据,可以使用迭代有序的抽取数据。
// 提供统一迭代的方式,为所有的数据类型部署迭代接口。
// 迭代器:可以让部署了iterator接口的数据类型,能够以统一的方式进行迭代。

// [Symbol.iterator]: 隐式的迭代器接口

// 自定义迭代对象
function makeIterator (array) {
  var nextIndex = 0,
      total = array.length;

  return {
    next: function () {
      return nextIndex < total ? { value: array[nextIndex++] , done: false }
      : { value: undefined, done: true };
    }
  }
}

const arr = [1, 2, 3, 4];

const iter = makeIterator(arr); 

console.log(iter.next()); // { vaue: 1, done: false }
console.log(iter.next()); // { vaue: 2, done: false }
console.log(iter.next()); // { vaue: 3, done: false }
console.log(iter.next()); // { vaue: 4, done: false }
console.log(iter.next()); // { vaue: undefined, done: true }

// TypeArray 类型数组

// JavaScript没有TypeArray的构造函数。只是一种概念,JS中不存在。
console.log(TypeArray); // TypeArray is not defined

// TypeArray是用来处理二进制数据的。

const tArray = new Int8Array(); // 声明8进制数据

const tArray = new Int8Array(4);
console.log(tArray); // Int8Array(4) [0, 0, 0, 0]
// 默认填充的数据都是0。

const tArray = new Int8Array(4);
tArray[0] = 100;
console.log(tArray); // Int8Array(4) [100, 0, 0, 0]

// for of

// ES6中借鉴了c++、Java、c#、python语言,提供简单统一的接口,for of循环,
// 实际上调用了Symbol.iterator接口。

let arr = [1, 2, 3, 4, 5];

for (let i of arr) {
  console.log(i);
}
// 1
// 2
// 3
// 4
// 5

// 部署了Symbol.iterator接口的对象,都可以使用for of进行迭代。

// for in 拿到的是下标,用于遍历对象。
// for of 用来迭代部署过iterator接口的值。

// 迭代器读取是一种有序的,连续的,不适用于对象。

let obj = {
  start: [1, 2, 3, 4],
  end: [5, 6, 7]
}
for (let i of obj) {
  console.log(i); //  obj is not iterable
}

//没有部署iterator接口,无法迭代对象,可以手动部署iterator接口,用于对象的迭代。

let obj = {
  start: [1, 3, 2, 4],
  end: [5, 7, 6],
  [Symbol.iterator] () {
    let index = 0,
        arr = [...this.start, ...this.end],
        len = arr.length;

    return {
      next () {
        if (index < len) {
          return {
            value: arr[index++],
            done: false
          }
        } else {
          return {
            value: undefined,
            done: true
          }
        }
      }
    }
  }
}
for (let i of obj) {
  console.log(i);
}
// 1 3 2 4 5 7 6

array/数值拓展、ArrayOf、ArrayFrom

迭代器补充部分

js
let obj = {
  start: [1, 2, 3, 4, 5],
  end: [7, 8, 9],
  [Symbol.iterator] () {
    let index = 0,
        arr = [...this.start, ...this.end],
        len = arr.length;

    return {
      next () {
        if (index < len) {
          return {
            value: arr[index++],
            done: false
          }
        } else {
          return {
            value: undefined,
            done: true
          }
        }
      }
    }
  }
}

var iter = obj[Symbol.iterator]();

console.log(iter.next());
console.log(iter.next()); 
console.log(iter.next()); 

var iter = obj[Symbol.iterator]();

console.log(iter.next());
console.log(iter.next()); 
console.log(iter.next());

for (let i of obj) {
  console.log(i);
}
// 1 2 3 4 5 6 7 8 9

// 也可以使用拓展运算符把部署了iterator接口的对象进行拓展。
console.log(...obj); // 1 2 3 4 5 6 7 8 9

数组的拓展 Array 相关方法

js
Array.of() // 用来声明一个数组

  Array.of(); // []
  Array.of(3); // [3] 数值为3,可以弥补new Array()的缺点
  Array.of(3, 4, 6); // [3, 4, 6]

// Array.from() // 将类数组,数组,部署过iterator接口的对象转为真正的数组

  Array.of(obj); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

  // 存在三个参数 (arrayLike, mapFn, thisArg)
  
  let arr = [1, 2, 3, 4];
  let result = arr.map(function (val) {
    return val * 2;
  });
  console.log(result); // [2, 4, 6, 8]

  let arr = Array.from([1, 2, 3, 4], function (val, idx) {
    return val * 2;
  });
  console.log(arr); // [2, 4, 6, 8]

  // Array.from 可以将转换后的数组,经过第二个参数进行map处理。第三个参数用于处理this指向问题。

// fill(value, start, end) // 填充元素,修改原数组

  let arr = [1, 2, 3, 4];
  arr.fill(5);
  console.log(arr); // [5, 5, 5, 5]

  let arr = [1, 2, 3, 4];
  arr.fill(5, 1);
  console.log(arr); // [1, 5, 5, 5] 第三个参数默认是数组的长度

  let arr = [1, 2, 3, 4];
  arr.fill(5, 1, 2);
  console.log(arr); // [1, 5, 3, 4]  
  // 左闭右开区间  [1, 2)

  let arr = [1, 2, 3, 4];
  arr.fill(5, 1, 1);
  console.log(arr); // [1, 2, 3, 4]  
  // 如果起始下标和结束下标一致,不做处理。

  let arr = [1, 2, 3, 4];
  arr.fill(5, -3, -2);
  console.log(arr); // [1, 5, 3, 4]  

  let arr = [1, 2, 3, 4];
  arr.fill(5, NaN, -2);
  console.log(arr); // [5, 5, 3, 4]   
  // 如果起始下标为NaN,按照0处理

  let arr = [1, 2, 3, 4];
  arr.fill(5, NaN, NaN);
  console.log(arr); // [1, 2, 3, 4]
  // 如果起始下标和结束下标一致,不做处理

  let arr = [1, 2, 3, 4];
  arr.fill(5, 2, 5);
  console.log(arr); // [1, 2, 5, 5]
  // 结束下标超出,不做处理,不会添加新元素
  
  console.log([].fill.call({ length:3 }, 4));
  // {0: 4, 1: 4, 2: 4, length: 3}
  // 可以给对象填充元素。

// keys() / values() / entries()

  let obj = {
    a: 1,
    b: 2,
    c: 3
  }
  console.log(Object.keys(obj)); // ["a", "b", "c"]
  console.log(Object.values(obj)); // [1, 2, 3]
  console.log(Object.entries(obj)); // [ ["a", 1], ["b", 2], ["c", 3] ]

  let arr = ['a', 'b', 'c'];
  console.log(arr.keys()); // Array Iterator {}
  console.log(arr.values()); // Array Iterator {}
  console.log(arr.entries()); // Array Iterator {}

  var iter = arr.entries();
  console.log(iter.next()); // {value: Array(2), done: false}
  console.log(iter.next()); // {value: Array(2), done: false}
  console.log(iter.next()); // {value: Array(2), done: false}
  console.log(iter.next()); // {value: undefined, done: true}

  var iter = arr.values();
  for (let i of iter) {
    console.log(i); 
  }
  // a b c

  var iter = arr.keys();
  for (let i of iter) {
    console.log(i); 
  }
  // 0 1 2

  var iter = arr.entries();
  for (let i of iter) {
    console.log(i); 
  }
  // [0, 'a'] [1, 'b'] [2, 'c']

  // 数组使用keys() / values() / entries(),返回的是一个迭代对象,可以使用for of来处理。

// copyWithin()  将数组内部的成员移动到相应位置

  var arr = [1, 2, 3, 4, 5];
  arr.copyWithin(2);
  console.log(arr); // [1, 2, 1, 2, 3]

  var arr = [1, 2, 3, 4, 5];
  arr.copyWithin(-2);
  console.log(arr); // [1, 2, 3, 1, 2]

  var arr = [1, 2, 3, 4, 5];
  arr.copyWithin(0, 3, 4);
  console.log(arr); // [4, 2, 3, 4, 5]

  var arr = [1, 2, 3, 4, 5];
  arr.copyWithin(-2, -3, -1);
  console.log(arr); // [1, 2, 3, 3, 4]

  // 参数和fill一致。第一个参数确认位置,2、3个参数确认复制的元素,
  // 不会新添数组项,只会在原有的数组项进行修改。

  console.log([].copyWithin.call({ length: 5, 3: 1 }, 0, 3));
  // {0: 1, 3: 1, length: 5}
  console.log([].copyWithin.call({ length: 5, 3: 1, 4: 2 }, 0, 3));
  // {0: 1, 1: 2, 3: 1, 4: 2, length: 5}

// find()  / findIndex()

  var arr = [1, 2, 3, 4];
  var res = arr.find(function (value, index, arr) {
    return value > 2;
  });
  console.log(res); // 3
  var arr = [1, 2, 3, 4];
  // find 返回第一个条件为true的值

  var arr = [1, 2, 3, 4];
  var res = arr.findIndex(function (value, index, arr) {
    return value > 2;
  });
  console.log(res); // 2
  // findIndex 返回第一个条件为true的值的下标

  console.log([NaN].indexOf(NaN)); // -1
  console.log([NaN].findIndex(x => Object.is(NaN, x))); // 0         
  // 使用findIndex可以一定程序弥补indexOf的不足。

// includes()  判断元素是否包含在数组中

  console.log([1, 2, 3].includes(1)); // true
  console.log([1, 2, 3].includes(4)); // false

  console.log([1, 2, NaN].includes(NaN)); // true
  // ES6提供的方法已经从本质是解决NaN不等于NaN。
  // 使用includes语义化比较好。

  console.log([NaN].indexOf(NaN)); // -1
  console.log([NaN].includes(NaN)); // true

// 数组在ES6中提供的方法都是比较实用的。

数值的拓展 主要包括方法的调整和新增方法

js
// 十六进制
console.log(0x1f7); // 503 
// ES5便存在16进制的表示方法。

// ES6提供二进制和八进制的表示方法。 

// 八进制
console.log(0o767); // 503 
console.log(0O767); // 503

// 十进制转二进制
console.log(Number.prototype.toString.call(503, 2)); // 111110111

// 二进制转十进制
console.log(parseInt(111110111, 2)); // 503

// 二进制
console.log(0b111110111); // 503
console.log(0B111110111); // 503

// 数值的拓展包括全局方法的调整和定义新增方法。

// 将全局方法parseInt、isNaN等定义到Number的构造器上。

// isNaN()

  console.log(isNaN(1)); // false
  console.log(isNaN(NaN)); // true
  console.log(isNaN('NaN')); // true

  // 全局的方法isNaN()添加到Number的构造器上。
  
  console.log(Number.isNaN(NaN)); // true
  console.log(Number.isNaN('NaN')); // false

  console.log(isNaN('NaN')); // true 提供隐式转换
  console.log(Number.isNaN('NaN')); // false  修正 不会进行隐式转换

// isFinite() 判断数字是否是有限(有效)的数字

  console.log(isFinite(42)); // true

  console.log(isFinite(NaN)); // false
  console.log(isFinite(Infinity)); // false
  
  console.log(isFinite(Infinity)); // false
  console.log(Number.isFinite(Infinity)); // false
  
  console.log(isFinite(Infinity)); // false
  console.log(Number.isFinite(Infinity)); // false

  console.log(isFinite(42)); // true
  console.log(Number.isFinite(42)); // true

  console.log(isFinite('42')); // true
  console.log(Number.isFinite('42')); // false
  // 不提供隐式转换

// parseInt / parseFloat

  // 和ES5相同,只是重新定义在Number的构造器上,为了减少原型上的属性。

// isInteger 判断数值是否是一个整数

  console.log(Number.isInteger(24)); // true
  console.log(Number.isInteger(24.0)); // true

  // javascript引擎中相同的整数和浮点数认为是同一个值。

  console.log(Number.isInteger(24.1)); // false

// 安全整数:

// MAX_SAFE_INTEGER: 9007199254740991 现阶段JS引擎能够解析的最大的整数。更大的数值虽然能够解析,但是不准确。
// MIN_SAFE_INTEGER: -9007199254740991 现阶段JS引擎能够解析的最小的整数。更小的数值虽然能够解析,但是不准确。

// console.log(Number.MAX_SAFE_INTEGER === Math.pow(2, 53) - 1); // true
// console.log(Number.MIN_SAFE_INTEGER === -Math.pow(2, 53) + 1); // true

// MAX_VALUE:能够处理的最大的数值(包括浮点数)
// MIN_VALUE:能够处理的最小的数值(包括浮点数)

// isSafeInteger 判断数值是否是安全范围之内的一个整数

Math 拓展 内置对象,不存在构造器

Math.abs 绝对值
...

正则方法、修饰符yus、UTF_16编码方式

/\w/gim

逻辑部分  修饰符部分

g global      全局匹配
i ignoreCase  忽略大小写
m mutli-line  多行匹配

var str = 'sdjflssefvjeio',
reg = /\w/gim;

str.match(reg); // ["s", "d", "j", "f", "l", "s", "s", "e", "f", "v", "j", "e", "i", "o"]

元字符

  • \w 代表数字、字母、下划线 可能是word的缩写

  • \W 除了\w以外的所有的字符

  • \d 代表数字 可能是digit的缩写

  • \s 代表和制表符相关的所有字符 \t \n \v \f 。 可能是space的缩写

    \n  换行
    \r  回车
    \t  制表
    \v  垂直换行
    \f  换页
    
    \b  代表单词边界  可以记作bridge的简写,桥的两边
      
    . 除了 “ \n \r 行分隔符 段分隔符 ” 之外的字符都可以匹配
    
    /[\w\W]/gim; // 所有字符
    
    正向预查(先行断言):一种查询方式    先行否定断言。
    /x(?=y)/ 只匹配y前面的x规则的值
    

正则新增特性

  1. 声明正则的变化方式
  2. 将字符串上的正则方法进行了调整(主要是位置上的调整)
  3. 新增修饰符 y、u、s
js
// 1. 声明正则的变化方式

var reg1 = new RegExp('xyz', 'ig'),
    reg2 = /xyz/ig;

var str  = 'xyzxyzxyzxyzxyzxyzxyz';

console.log(str.match(reg1));
// ["xyz", "xyz", "xyz", "xyz", "xyz", "xyz", "xyz"]
console.log(str.match(reg2));
// ["xyz", "xyz", "xyz", "xyz", "xyz", "xyz", "xyz"]

// ES5中两种声明方式是等效的。

var reg = new RegExp(/xyz/gi),
    str = 'xyz2exyzwfaxyz';
console.log(str.match(reg)); // ["xyz", "xyz", "xyz"]
// ES5种第一个参数传递正则表达式的形式,会报错。

var reg = new RegExp(/xyz/gi, 'm'),
    str = 'xyz2exyz\nwfaxyz';
console.log(str.match(reg)); // ["xyz", index: 0, input: "xyz2exyz↵wfaxyz", groups: undefined]
// 第一个参数和第二个参数同时存在时,以当前的第二个参数为准,忽略原有表达式关于修饰符的部分。

// 2. 正则方法位置的调整

console.log(RegExp.prototype); // {constructor: ƒ, exec: ƒ, …}

// 原型上属性

//  flags 
//  global     g
//  ignoreCase i
//  multiline  m 
//  source     正则本身
//  sticky 

// 原型上方法

//  test
//  toString
//  compile
//  exec

Symbol(Symbol.match)  
Symbol(Symbol.replace)
Symbol(Symbol.search)
Symbol(Symbol.split)
// 原本方法是定义在String的原型上的,ES6把这部分方法定义到正则对象的原型上。

// String.prototype.match  ->  RegExp.prototype[Symbol.match]
// String.prototype.matchAll  ->  RegExp.prototype[Symbol.matchAll]
// String.prototype.replace  ->  RegExp.prototype[Symbol.replace]
// String.prototype.search  ->  RegExp.prototype[Symbol.search]
// String.prototype.split  ->  RegExp.prototype[Symbol.split]

// 3. 新增的修饰符 y u s

var reg = new RegExp('xyz', 'gi');
console.log(reg.global); // true
console.log(reg.ignoreCase); // true
console.log(reg.multiline); // false

// y (sticky) 修饰符 粘连

var reg = new RegExp('xyz', 'gi');
console.log(reg.sticky); // false 

var reg = new RegExp('xyz', 'giy');
console.log(reg.sticky); // true 

// 每一个修饰符对应的就是一个属性。

var str = 'aaa_aa_a',
    reg1 = /a+/g,
    reg2 = /a+/y;
console.log(reg1.exec(str)); // ["aaa", index: 0, input: "aaa_aa_a", groups: undefined]
console.log(reg2.exec(str)); // ["aaa", index: 0, input: "aaa_aa_a", groups: undefined]
console.log(reg1.exec(str)); // ["aa", index: 4, input: "aaa_aa_a", groups: undefined]
console.log(reg2.exec(str)); // null

// g   剩余位置中存在当前匹配项,可以匹配
// y   剩余位置中存在当前匹配项,且匹配的值必须和上一次的值连接

var reg = /\wsss/giy;
// source 返回的是正则的主体
console.log(reg.source); // \wsss 
// flags 返回的是正则的修饰符
console.log(reg.flags); // giy  

// u (unicode) 修饰符  

// 码点:

// JavaScript中每一个字符串都是以一定的方式存储和编码的,以UTF-16的方式进行存储。

// unicode 分区定义

// 17个平面  

// 2*16 (BMP 基本平面)

// U+0000 - u+FFFF (BMP范围)码点范围 UTF-16的存储极限

// 0000就是4个码点

// 有时,两个字节表明不了一个汉字,使用的4个字节。

// U+D800 - U+DFFF 
// D800以上不存在字符和码点对应的,800以上的是需要映射的内容,每一个映射的内容都表示一个具体的字符
// D800以上的可以使用4个字节来处理。

console.log('\u20bb7'); // ₻7 16进制表示方法 
console.log('\uD842\uDFB7'); // 𠮷 

// 当码点数大于D800时,需要用4个字节的方法来表示。

// ES6中新增一种表示方式,可以正确解析字符。
console.log('\u{20bb7}'); // 𠮷

// 情报编码相关内容可以了解下。

console.log(/^\uD83D/.test('\uD83D\uDC2A')); // true 一种错误的匹配方式
console.log(/^\uD83D/u.test('\uD83D\uDC2A')); // false 认为是一整个匹配方式,所以是false

var s = '\uD842\uDFB7';
console.log(s); // 𠮷
console.log(/^.$/.test(s)); // false
// 整个UTF-16的存储的物理极限是U+0000 - U+FFFF,一旦超出,. 并不能匹配出这个字符。
console.log(/^.$/u.test(s)); // true 可以让.识别超过800的编码

// u 是针对之前编码的缺陷推出的措施。对unicode的标识进行进一步的优化。

console.log('\u{20bb7}'); // 𠮷
// ES6可以使用{}的方式识别5位的编码。

console.log(/a{2}/.test('aa')); // true
console.log(/{20bb7}/u.test('𠮷')); // 报错
console.log(/\u{20bb7}/.test('𠮷')); // false
console.log(/\u{20bb7}/u.test('𠮷')); // true
// /u 用来说明不是量词,用来表示unicode编码。
// u 用来匹配超出物理极限的值,当作一个字符处理

// s dotAll  代表所有的方式

// ES6中新的提案,ES2018年已经被实现,让.可以匹配任何字符,目前并没有兼容。

// \n \r 行分隔符 段分隔符

// U2028 - U2029 点所不能代表的字符

console.log(/foo.bar/.test('foo\nbar')); // false 
console.log(/foo.bar/s.test('foo\nbar')); // true 

console.log(/foo.bar/.dotAll); // false
console.log(/foo.bar/s.dotAll); // true

Unicode表示法、字符串方法、模板字符串

y 修饰符

js
console.log('x##'.split(/#/y)); // ["x", "", ""]
console.log('x##'.split(/#/g)); // ["x", "", ""]

console.log('##x'.split(/#/y)); // ["", "", "x"]
console.log('##x'.split(/#/g)); // ["", "", "x"]

console.log('#x#'.split(/#/y)); // ["", "x", ""]
console.log('#x#'.split(/#/g)); // ["", "x", ""]

console.log('##'.split(/#/y)); // ["", "", ""]
console.log('##'.split(/#/g)); // ["", "", ""]

// y修饰符和g修饰符相比较现阶段并不能体现匹配第一位的特点。

字符串拓展(ES6拓展)

js
console.log("\u61"); // 报错 不是十六进制

console.log("\u0061"); // a
// 以十六进制的方式来表示当前数字的方式,使用的码是Unicode码。

console.log('\u20BB7'); // ₻7
// 当码点数超过物理极限时,只会解析部分码点(以4位进行截取,再进行解析)。

// JavaScript字符串是以UTF-16的编码方式进行储存的。

// UTF-16两个字节的物理极限是 FFFF。
// 标识字符以外的字符时,需要用4个字节来表示。

console.log("\u20BB7"); // ₻7 
console.log("\u{20BB7}"); // 𠮷
// ES6提供 {} 的方式,可以正确解析超过4位数的码点。

// U+0000 - U+FFFF 物理极限取值  有部分汉字无法表示,就需要使用4个字节来表示一个字符
// U+DB00 - B+DFFF 此区间是一个预留的空位。超出800按照4个字节进行解析。

console.log('\uD842\uDFB7'); // 𠮷

// ES6提供的 unicode的字符串的表示方式
console.log('\u0041'); // A
console.log('\u0041\u0042\u0043'); // ABC
console.log('\u{0041}\u{0042}\u{0043}'); // ABC
console.log('\u{41}\u{42}\u{43}'); // ABC

console.log('\uD842\uDFB7' === '\u{20BB7}'); // true
console.log('\uD842\uDFB7' === "\u{20BB7}"); // true  JavaScript是不区分单引号和双引号的。

var s = '\u{20BB7}';
console.log(s.length); // 2
// JavaScript内部以UTF-16的方式进行编码,通常所说的字符长度,每个字符占两个字节。
// '\u{20BB7}' -> '\uD842\uDFB7' 
//length本质是在判断字符的个数,所以对应的长度为2。

// 输出索引所对应的字符 ES5
console.log(s.charAt(0)); // � 
console.log(s.charAt(1)); // �
// 单独读一个字符,不能正确表示一个字符,所以输出乱码

// 输出字符所对应的unicode码的十进制码点 ES5
console.log(s.charCodeAt(0)); // 55362 
console.log(s.charCodeAt(1)); // 57271

console.log(s.charCodeAt(0)); // 55362 
console.log(s.charCodeAt(1)); // 57271

console.log(Number.prototype.toString.call(55362, 16)); // d842
console.log(Number.prototype.toString.call(57271, 16)); // dfb7

// codePointAt() 返回在指定下标处的码点 ES6

var s = '\u{20BB7}a';
console.log(s); // 𠮷a
console.log(s.length); // 3

console.log(s.codePointAt(0)); // 134071
console.log(s.codePointAt(1)); // 57271
console.log(s.codePointAt(2)); // 97
console.log(Number.prototype.toString.call(134071, 16)); // 20bb7
console.log(Number.prototype.toString.call(97, 16));

// codePointAt() 可以匹配当前正确的字符。

// 字符串上存在迭代器接口

var s = '𠮷a';

for (let value of s) {
  console.log(value); // 𠮷 a
}

// ES6底层中可以通过正确的解析方式将超出字符串极限的值打印出来。

// 一个字节占8个byte。

function is32Bit (c) {
  return c.codePointAt(0) > 0xFFFF;
}
console.log(is32Bit('𠮷')); // true
console.log(0xFFFF); // 65535 
// 比对的时候,自动转为10进制,再进行比较。

// is32Bit 在处理二进制数据时,可以使用is32Bit。

// formCodePoint / formCharCode 返回指定码点的字符

// ES5不能识别超出物理极限的码点。

console.log(String.fromCharCode(0x20BB7)); // ஷ

// fromCharCode的处理方式是舍弃第一位,只处理后几位。
console.log(String.fromCharCode(0x0BB7)); // ஷ

console.log(String.fromCodePoint(0x20BB7)); // 𠮷 正确解析

for (let code of 'foo') {
  console.log(code); // f o o
}

// for of 可以正确处理超出字符极限的字符

let str = String.fromCodePoint(0x20BB7);
for (let i = 0; i < str.length; i++) {
  console.log(str[i]); // � �
}
for (let i of str) {
  console.log(i); // 𠮷
}

let s = 'Hello World!';

console.log(s.startsWith('Hello')); // true       
console.log(s.endsWith('!')); // true
console.log(s.includes('o')); // true 

// repeat  重复

console.log('x'.repeat(3)); // xxx
console.log('x'.repeat(2.9)); // xx
console.log('x'.repeat(NaN)); // 空位 
console.log('x'.repeat(0)); // 空位
console.log('x'.repeat('3')); // xxx  

// 转换方式 底层使用parseInt转整

// padStart()、padEnd()  填充开始、填充结束

console.log('x'.padStart(5, 'ab')); // ababx 
console.log('x'.padStart(4, 'ab')); // abax 

console.log('x'.padEnd(5, 'ab')); // xaba 
console.log('x'.padEnd(4, 'ab')); // xabab 

模板字符串

js
let name = 'web',
    info= 'developer';

let msg = `i am a ${name} ${info}`;
console.log(msg); // i am a web developer

console.log(`
    <div>
      <div>${name}</div>
      <div>${info}</div>
    </div>      
`);
// <div>
//    <div>web</div>
//    <div>developer</div>
//  </div>      

let x = 1, y = 2;
console.log(`${x} + ${y} = ${x + y}`); // 1 + 2 = 3
console.log(`${x} + ${y * 2} = ${x + y * 2}`); // 1 + 4 = 5
js
let obj = { x: 1, y: 2 };
console.log(`${obj.x + obj.y}`); // 3

// ${} {} 是一个表达式,可以进行计算。
js
function fn () {
  return 'hello world';
}
console.log(`foo ${fn()} bar`); // foo hello world bar

function fn () {
  return [1, 2, 3, 4];
}
console.log(`foo ${fn()} bar`); // foo 1,2,3,4 bar

let msg = 'Hello, ${place}';
console.log(msg); // 报错

let msg = 'Hello, ${'place'}';
console.log(msg); // Hello, place 模板字符串和传统字符串存在嵌套

const temp = data => `
  <table>
    ${
      data.map(addr => `
        <tr><td>${addr.first}<td/></tr>
        <tr><td>${addr.last}<td/></tr>
      `).join('')
    }
  </table>
`
const data = [
  {
    first: 'zhang',
    last: 'san'
  },
  {
    first: 'li',
    last: 'si'
  }
];
console.log(temp(data));

// <table>
//  <tr><td>zhang<td/></tr>
//  <tr><td>san<td/></tr>
// 
//  <tr><td>li<td/></tr>
//  <tr><td>si<td/></tr>
// </table>

// 如果传入的数组的first的值为 'alert('sss')',当前这种模板渲染方式存在问题。

ES5模板替换方式

js
new RegExp(/{{(.*?)}}/gim);

标签模板

js
let a = 5,
    b = 10;

console.log(`Hello ${a + b} world ${a * b}`);  

tag`Hello ${a + b} world ${a * b}`;
  
function tag ($, $1, $2) {
  console.log($, $1, $2);
}
//  ["Hello ", " world ", "", raw: Array(3)] 15 50
  
// 可以使用标题模板防止恶意注入。
js
function safeHtml ($) {
  let s = $;
  
  for (let i= 1; i < arguments.length; i++) {
    let arg = arguments[i] + '';
    
    s += arg.replace(/</g, '$lt;')
            .replace(/>/g, '$gt;');
    
    s += $[i];
  }
  
  return s;
}
  
let sender = '<script>alert("abc")</script>';

let msg = safeHtml`<p>${sender} has sent your msg</p>`
  
console.log(msg); 
// <p>, has sent your msg</p>$lt;script$gt;alert("abc")$lt;/script$gt; has sent your msg</p>

map与set

数组本身是一种特殊的对象。

稀松数组、密集数组 如果值存在empty,就是稀松数组。

Set

ES6新增的数据类型,Set是一个构造函数,需要实例化。

成员是唯一的"数组"。数据结构类似于对象。

Map

ES6新增的数据类型,Map是一个构造函数,需要实例化。

Set、Map、Promise、Proxy 都是ES6新增的语法。babel无法对其降级,可以使用polyfill、babel-polyfill插件进行处理。

Set

js
add、clear、delete、entries、keys、values、has、forEach

var set = new Set();
set.add(5);
set.add(4);
set.add(6);
console.log(set); // Set(3) {5, 4, 6}

var set = new Set([5, 7]);
console.log(set); // Set(2) {5, 7}
// 传入的参数只能是部署过迭代器接口的数据结构(数组、类数组等)。

// Set的成员是唯一的。可以使用Set结构来去重,取交集并集等。

  var set = new Set([undefined, undefined]);
  console.log(set); // Set(1) {undefined}

  var set = new Set([null, null]);
  console.log(set); // Set(1) {null}

  var set = new Set([5, '5']);
  console.log(set); // Set(2) {5, "5"}

  var set = new Set([true, 1]);
  console.log(set); // Set(2) {true, 1}

  var set = new Set([NaN, NaN]);
  console.log(set); // Set(1) {NaN}

  var set = new Set([{}, {}]);
  console.log(set); // Set(2) {{…}, {…}}

// Set已经处理NaN不等于NaN的问题。

// add 添加数据

  var x = { id: 1},
      y = { id: 2 };

  var set = new Set();
  set.add(x);
  set.add(y);
  set.add(x);
  console.log(set); // Set(2) {{…}, {…}

  var set = new Set();
  set.add(x)
      .add(y)
      .add(x);
  console.log(set); // Set(2) {{…}, {…}

  // Set可以进行链式调用。添加子项时返回值是set结构本身。

// size  Set的长度

  var set = new Set([1, 2]);
  console.log(set.size); // 2

// delete 删除数据

  var set = new Set([1, 2]);

  set.delete(2); // true
  set.delete(2); // false

  console.log(set); // Set(1) {1}

// clear 清空

  var set = new Set([1, 2]);
  set.clear(); // undefined 
  console.log(set); // Set(0) {}

  var set = new Set([1, 2]);
  console.log(set); // Set(0) {}
  set.clear(); // undefined 
  console.log(set); // Set(0) {}

// has 用于判断set中是否包含某一项

  var set = new Set([1, 2]);

  console.log(set.has(1)); // true
  console.log(set.has(3)); // false

// 注意:Set的 clear和delete操作是实时的,只要执行,无论在哪执行也会有变化。

const set = new Set([2, 3, 4, 5]);

// keys、entries、values

  console.log(set.keys()); // SetIterator {2, 3, 4, 5}
  console.log(set.entries()); // SetIterator {2 => 2, 3 => 3, 4 => 4, 5 => 5}
  console.log(set.values()); // SetIterator {2, 3, 4, 5}

  // Set 不存在键名,键名和键值是一致的。 keys和values是一致的。
  // keys、entries、values的返回值都是可迭代的。

  for (let i of set.keys()) {
    console.log(i); // 2 3 4 5
  }

  for (let i of set) {
    console.log(i); // 2 3 4 5
  }

  // console.log(Set.prototype[Symbol.iterator] === Set.prototype.values); // true

// forEach

  const set = new Set(['a', 'b', 'd', 'c', 'f']);

  set.forEach((val, key, arr) => {
    console.log(val, key, arr);
  });
  // a a Set(5) {"a", "b", "d", "c", "f"} 
  // b b Set(5) {"a", "b", "d", "c", "f"}
  // d d Set(5) {"a", "b", "d", "c", "f"}
  // c c Set(5) {"a", "b", "d", "c", "f"}
  // f f Set(5) {"a", "b", "d", "c", "f"}

  console.log(...set); // a b d c f
  // 可以使用拓展运算符展开set。拓展运算符可以拓展部署迭代器接口的数据结构。

// 数组去重

  console.log([...new Set([1, 2, 1, 2 , 3])]); //  [1, 2, 3]

  // 可以使用set配合展开运算符进行数组去重操作。

// 数组映射关系处理 - 值 * 2

  let set = new Set([1, 2, 3, 4, 5, 6]),
      set1 = new Set([...set].map(val => val * 2));
  console.log(set1); // Set(6) {2, 4, 6, 8, 10, …}

// parseInt()

  var arr = [1, 2, 3, 4];
  var arr1 = arr.map(parseInt);
  console.log(arr1); //  [1, NaN, NaN, NaN]

  // parseInt参数为两个时,第二个参数当作进制处理

  parseInt(1, 0); // 1
  parseInt(2, 1); // NaN  2 以 1进制数处理
  parseInt(3, 2); // NaN  3 以 2进制数处理
  parseInt(4, 3); // NaN  4 以 3进制数处理

  let set = new Set([1, 2, 3, 4, 5, 6]);
  let set2 = new Set([...set].filter(x => (x % 2) === 0));
  console.log(set2); // Set(3) {2, 4, 6}

// 实现交集、并集、差集
  
  let a = new Set([1, 2, 3]),
      b = new Set([4, 3, 2]);

  // 并集

    let union = new Set([...a, ...b]);
    console.log(union); // Set(4) {1, 2, 3, 4}

  // 交集

    let intersect = new Set([...a].filter(x => b.has(x)));
    console.log(intersect); // Set(2) {2, 3}

  // 差集
    
    let difference = new Set([...a].filter(x => !b.has(x)));
    console.log(difference); // Set(1) {1}

// 同步获取set结构值的方法

  let set = new Set([1, 2, 3]);

  let set1 = new Set(Array.from(set));
  console.log(set1); // Set(3) {1, 2, 3}

  let set2 = new Set([...set]);
  console.log(set2); // Set(3) {1, 2, 3}

Map

js
// 键和值一一对应。

var m = {},
    x = { id: 1 },
    y = { id: 2 };

m[x]= 'foo';
m[y] = 'bar';

console.log(m[x]); // bar
console.log(m[y]); // bar

// 传统对象处理对象作为键存储时,会调用toString方法,对象并不能实现真正的键值一一对应。
js
var m = new Map(),
    x = { id: 1 },
    y = { id: 2 };

m.set(x, 'foo');
m.set(y, 'bar');

console.log(m.get(x)); // foo
console.log(m.get(y)); // bar

// Map可以实现键值一一对应。

// Map是具备Iterator接口的数据结构,传参必须是一个双元的数组结构。

let m = new Map([
  ['name', 'zhangsan'],
  ['title', '23']
]);
console.log(m); // Map(2) {"name" => "zhangsan", "title" => "23"}

let m = new Map();
m.set('name', 'zhangsan');
m.set('age', '34');
console.log(m); // Map(2) {"name" => "zhangsan", "age" => "34"}

var items = [
  ['name', 'zhangsan'],
  ['title', '23']
];
let m = new Map();
items.forEach(([key, value]) => m.set(key, value));      
console.log(m); // Map(2) {"name" => "zhangsan", "title" => "23"}

// 键名相同,存在值覆盖的情况。

const map = new Map();
map.set(1, 'foo');
map.set(1, 'bar');
console.log(map); // Map(1) {1 => "bar"}
console.log(map.get(1)); // bar 
console.log(map.get(2)); // undefined

const map = new Map();
map.set([5], 5555);
console.log(map.get([5])); // undefined

const map = new Map();
map.set(-0, 123);
console.log(map.get(+0)); // 123

console.log(+0 === -0); // true
console.log(Object.is(+0, -0)); // false

const map = new Map();

map.set(true, 1);
map.set('true', 2);
console.log(map.get(true)); // 1

map.set(undefined, 1);
map.set(null, 2);
console.log(map.get(undefined)); // 1
console.log(map.get(null)); // 2

// Map真正实现了键和值的关系。

map.set(NaN, '123');
console.log(map.get(NaN)); // 123

// Map也具备Symbol.iterator接口。

// 基本方法和Set数据结构保持一致。使用方法和Set也相同。

console.log(map[Symbol.iterator] === map.entries); // true

for (let [key, values] of map) {
  console.log(key, values);
}

WeakMap与WeakSet、proxy与reflect

Map转数组结构

js
const map = new Map();

map.set(true, 7)
    .set({ foo: 3 }, ['abc']);

console.log(...map); // [true, 7] (2) [{…}, Array(1)]
console.log([...map]); // [Array(2), Array(2)]

Map转对象

js
const map = new Map();

map.set(true, 7)
    .set({ foo: 3 }, ['abc']);

console.log(strMapToObj(map)); // {true: 7, [object Object]: Array(1)}

function strMapToObj (map) {
  let obj = Object.create(null);
  
  for (let [key, value] of map) {
    obj[key] = value;
  }

  return obj;
}

对象转Map

js
console.log(objectToMap({
  true: 7,
  no: false
})); // Map(2) {"true" => 7, "no" => false}

function objectToMap (obj) {
  let map = new Map();

  for (let key of Object.keys(obj)) {
    map.set(key, obj[key]);
  }

  return map;
}

Map、Set、Array、Object

js
// 增加元素

  map.set() 

  set.add()

  obj[key] = value;

  array.push()

// 查找元素

  map.has()

  set.has()

  // value in object 可以遍历原型属性
  // object.hasOwnProperty(value) 可以遍历原型属性
  
  array.find(() => { })

// 修改元素

  map.set()

  set.forEach(item => item.t ? item.t = 2 : ''); // 针对引用值

  object[key] = value;

  arr.forEach(item => (item.t ? item.t = 2 : '')); // 针对引用值
  arr.findIndex() // 针对原始值

// 删除元素

  map.delete()
  
  set.forEach(item => item.t ? set.delete(item) : '');

  delete object[value]

 // array.findIndex(() => {})
    
 //   -> array.splice(index, 1)

// Map 和 Set 也部署迭代器接口,并且在底层优化方面做的更好,使用也比较简单、优雅。

WeakMap WeakSet

js
// 可以当作是阉割版的 Map 和 Set 。

// 1. 不存在遍历方法
// 2. 成员只能是对象
// 3. 回收机制不同,弱引用,不考虑引用,直接释放。

// WeakMap 和 WeakSet 行为是无法预测的,最好不要使用。

const set = new WeakSet();
set.add(1); // Invalid value used in weak set

const map = new WeakMap();
map.set({ id: 1 }, 1); // WeakMap {{…} => 1}
map.set(1, 1); // Invalid value used as weak map key

// WeakMap的键名只能是对象。

Proxy

js
// 代理模式的一种实现。

let star = {
  name: 'yang',
  age: '23',
  phone: 'star 15900000000'
}

let agent = new Proxy(star, {
  get (target, key) {
    // 拦截取值操作 

    if (key === 'phone') {
      return 'agent 15200000000';
    }

    if (key === 'price') {
      return 200000;
    }

    return target[key];
  },

  set (target, key, value) {
    // 拦截赋值操作 

    if (value < 200000) {
      throw new Error('价格太低');
    } else {
      target[key] = value;

      return true;
    }
  },

  has (target, key) {
    // 可以拦截in操作符,不包括 for in

    if (key === 'customPrice') {
      return target[key];
    } else {
      console.log('请联系 agent 15200000000');
      return false;
    }
  },

  deleteProperty () {
    // 删除相关的操作
  },

  ownKeys () {

  }
});

console.log(agent.phone); 
console.log(agent.price); 
console.log(agent.name);

agent.customPrice = 250000;
// agent.customPrice = 150000;

console.log('customPrice' in agent); // true
console.log('customPrice2' in agent); // false

Reflect

js
// Reflect上面对应的方法就是一个个操作。也说明Proxy可以拦截什么操作。

let obj = {
  a: 1,
  b: 2,
  c: 3
}

console.log(Reflect.get(obj, 'a'));

Reflect.set(obj, 'a', 10);

console.log(Reflect.has(obj, 'a'));

console.log(obj);

// Reflect 语义化相对好一些。

// Object.defineProperty =>  Reflect.defineProperty
// try catch 的方式捕获错误   可以根据返回值判断是否定义成功

// 将构造器上的方法移到Reflect的原型上,底层做了一些优化,语义化更好。其实意义不大。

class与对象

function

js
function Person (name, age) {
  this.name = name;
  this.age = age;
}

// Person(); // 普通函数执行
// new Person(); // 实例化对象

Person.prototype.say = function () {
  return `my name is ${this.name}, my age is ${this.age}`;
}

const person = new Person('yang', '13');

person.say();

console.log(Object.getPrototypeOf(person)); // { say: ƒ, constructor: ƒ } ...

console.log(Object.getPrototypeOf(person).constructor === Person); // true
// 原型上的构造器是其本身。

console.log(Person.prototype === Object.getPrototypeOf(person)); // true

console.log(person.__proto__ === Person.prototype); // true 
console.log(person.__proto__ === Object.getPrototypeOf(person)); //  true

console.log(Object.keys(Person.prototype)); // ['say']
// 通过传统方式定义的函数,是可枚举的。

class 类

js
// ES6 新推出的关键字,ES6之前是保留字。

// JavaScript 中并不存在真正的类,本质是只是一个语法糖(模拟类的方式)。

class Person {
  constructor (name, age) {
    // 实例化的属性配置:私有属性
    this.name = name;
    this.age = age;
  }

  // 公有属性
  say () {
    console.log( `my name is ${this.name}, my age is ${this.age}`);
  }

  eat () {
    console.log('I can eat');
  }

  drink () {
    console.log('I can drink');
  }
}

const person = new Person('yang', 23);

console.log(person); // Person {name: "yang", age: 23}

person.say(); // my name is yang, my age is 23
person.eat(); // I can eat
person.drink(); // I can drink

console.log(Object.keys(Person.prototype)); // [] 
// 通过class关键字定义的方法是不可枚举的。
js
class Person {}
console.log(new Person());
// 当没有定义constructor时,会默认添加一个constructor。
js
class Person {
  constructor () { }
}
console.log(new Person() instanceof Person); // true

class Person {
  constructor () {
    return Object.create(null);
  }
}
console.log(new Person() instanceof Person); // false
js
const Person = class {
  say () {
    console.log(1);
  }
}
console.log(new Person().say()); // 1
// class 依然可以使用表达式的方式来定义。
js
const Person = class {
  say () {
    console.log(2);
  }
}();
Person.say(); // error
// 构造函数必须通过new的方式来定义。
js
const person = new class {
  say () {
    console.log(2);
  }
}();
person.say(); // 2
// 不建议使用。

const person = new class {
  constructor (name, age) {
    this.name = name;
    this.age = age;
  }

  say () {
    console.log(2);
  }
}('yang', 24);
// console.log(person); // {name: "yang", age: 24}
js
console.log(new Person()); 
class Person { }
// class 关键字定义的对象 不存在变量提升,存在暂时性死区(TDZ)
js
class Person {
  a = 1;
}
console.log(new Person()); // Person {a: 1}
// 这样定义的变量,默认放到私有属性中,ES7新出的特性。
js
// 如何让公有的函数私有化?

  const eat = Symbol('eat');
  class Person {
    say () {
      console.log('say');
    }

    [eat] () {
      console.log('eat');
    }
  }
  const person = new Person();
  person.say(); // say
  person[eat](); // eat
  // 可以通过Symbol的方式定义私有方法。

  class Person {
    say (baz) {
      children.call(this, baz);
    }
  }
  function children (baz) {
    return this.bar = baz;
  }
  // 也可以使用定义函数的方式,在外部定义函数,不暴露这个方法。

// static  静态方法

  class Person {
    static b = 2;

    static a () {
      console.log('a');
    }
  }

  console.log(Person.a()); // a

  // 静态方法不会被实例继承的,可以通过当前类来定义。

  // 目前,也可以定义静态属性。ES2017 就已经支持。        
  console.log(Person.b); // 2

  // static 只能在类中生效。

var obj = {
  get a () {
    console.log(1);
  },
  set b (value) {
    console.log(2);
  }
}
obj.a; // 1
obj.b = 2; // 2 
js
class Person {
  get a () {
    console.log('a');
  }

  set b (val) {
    console.log(val);
  }
}
const person = new Person();
person.a; // a
person.b = 3; // 3
// 可以在 class 中定义 getter 和 setter       

// 在类(class)中,默认是使用严格模式的。
js
// 私有属性:构造器上的属性

// 公有属性:原型上的属性,公有属性只存在方法,变量声明都被转换成私有的
          
          // 共有属性私有化:一种是Symbol、一种是函数包装

// 静态属性:static  可以定义静态属性和方法

// class 特征:

  // 1. 函数声明可以提升,class不能提升,存在暂时性死区(TDZ)
  // 2. class 中共有属性的方法不可枚举
  // 3. class 中默认是严格模式
  // 4. 没有手动定义constructor时,会默认添加构造器,不会报错
  // 5. 必须通过new的方式来执行

// 继承(extends):

  // class中继承是非常简单的。

  class Parent {
    constructor (name = 'yang') {
      this.name = name;
    }

    say () { }

    static a () {  }
  }

  class Child extends Parent {
  }

  let child = new Child();
  console.log(child); // Child {name: "yang"}

  // 子类(派生类)不能继承父类的静态属性,可以继承公有属性。

  // 派生类中,必须通过super关键字来指定this。不适用super定义派生类的构造器,会报错。
  class Parent {
    constructor (name = 'yang') {
      this.name = name;
    }
  }
  class Child extends Parent {
    constructor (name = 'wang', age = 23) {
      super(name);
      this.type = 'child';
      this.age = age;
    }
  }
  console.log(new Child()); // Child {name: "wang", type: "child", age: 23}
js
  // super可以指向原型对象。
  let proto = {
    y: 20,
    z: 40
  }
  let obj = {
    x: 10,
    foo () {
      console.log(super.y);
    }
  }
  Object.setPrototypeOf(obj, proto);
  obj.foo(); // 20
super 作为对象的时候:

1. 在对象当中指代对象的原型
2. 静态方法中,指向自己的父类

class 源码部分

js
// 1. TDZ 暂时性死区
// 2. 严格模式 use strict
// 3. 原型上的方法不可枚举
// 4. 只能够通过 new 来执行
// 5. 不声明构造器时,存在默认的构造器

class Person {
  constructor (name, age) {
    this.name = name;
    this.age = age;
  }

  say () {
    console.log('Hello World');
  }

  drink () {
    console.log('drink');
  }

  static eat () {
    console.log('eat');
  }
}

// 使用babel进行转译

修饰器模式(装饰器)

代理模式 在目标对象设置代理层,起到拦截的功能。

装饰器模式 可以访问对象,只是修饰作用,在原有功能基础上附加新的功能。

修饰器模式:为对象添加新的功能,不改变原有的结构和功能。

装饰器在现阶段无法实现的,需要安装第三方包(babel-ployfill)实现。
@readOnly 

插件:babel-plugin-transform-decorators-legacy

npm i babel-plugin-transform-decorators-legacy --save-dev

.babelrc

  {
    "presets": ["env"],
    "plugins": [
      "babel-plugin-transform-decorators-legacy"
    ]
  }
    
@testable
class Person { }
let person = new Person();
function testable (target) {
 	console.log(1);
	target.isTestable = true;
}

class Person {
  @readonly
  say () {

  }
}
let person = new Person();
person.say();
function readonly (target, name, descriptor) {
  console.log(target, name, descriptor);
  dscriptor.writable = false; // 设置属性不可写
}

可以使用  npx babel index.js --watch --out-file bundle.js  动态监听文件

使用装饰器 可以让业务和逻辑分离,可以用来埋点分析。
js
// 埋点案例

  let log = (type) => {
    return function (target, name, descriptor) {
      let src_method = descriptor.value;

      descriptor.value = (...arg) => {
        src_method.apply(target, arg, );

        console.log(type); // 打印日志
      }
    }
  }
  class AD {
    @log('show')
    show () {
      console.log('ad is show');
    }

    @log('click')
    click () {
      console.log('ad is click');
    }
  }

  let ad = new Ad();
  ad.show(); // show
  ad.click(); // click

  // 修饰器模式 开放封闭原则 拓展原有方法,不修改现有方法。

异步的开端-promise

js
const fs = require('fs');

fs.readFile('./name.txt', 'utf-8', (err, data) => {
  if (err) {
    console.log(err);
    return;
  }
  
  if (data) {
    fs.readFile(data, 'utf-8', (err, data) => {

      fs.readFile(data, 'utf-8', (err, data) => {
        console.log(data);
      });
    });
	}
});

回调处理异步的时候,难于维护,功能无法拓展,

try catch 只能捕获同步代码的错误,异步代码是无法捕获错误的。

js
try {
  console.log(a);
} catch (error) {
  console.log(error.message); // a is not defined
}

try {
  setTimeout(() => {
    console.log(a);
  }, 30)
} catch (error) {
  console.log(error.message); // 直接报错,无法捕获错误
}

回调地狱:

1. 难于维护,不便拓展
2. try catch 无法捕获异步代码的异常
3. 同步并发问题(同步执行两个异步操作) 

jQuery中实现回调管理的工具:Callbacks

js
let cb = $.Callbacks();
function a (x, y) {
  console.log('a', x, y);
}      
function b (x, y) {
  console.log('b', x, y);
}
cb.add(a, b);
cb.fire(10, 20);

​ deferer 使用Callbacks管理回调。 ​

js
function waitHandle () {
  var dtd = $.Deferred();

  var wait = function () {
    var task = function () {
      dtd.resolve();
    }

    setTimeout(task, 2000);
    returen dtd;
  }
  
  return wait(dtd);
}

var w = waitHandle();

w
  .done(function () {
    console.log('ok') 
  })
  .fail(function () {
    console.log('error');
  });

w
  .then(
    function () {

    },
    function () {

    }
  )

w.reject(); // 异步失败后,全部都是失败。可以在外层显示调用reject方法。
js
function waitHandle () {
  var dtd = $.Deferred();

  var wait = function () {
    var task = function () {
      dtd.resolve();
    }

    setTimeout(task, 2000);
    returen dtd.promise();
  }

  return wait(dtd);
}

jQuery中处理异步的方式 Callbacks -> Deferred -> Promise promise最初的形式
promise 无法显示调用reject() 方法。

promise 是一种规范。 promise A+ 规范

不止jQuery一种方式实现promise,有很多库也实现了promise。 都是遵循promise A+ 规范定义promise的行为和方法。

bluebird:promiseA+规范定义的工具包

ES6中的promise

js
// executor 执行者
new Promise(function (resolve, reject) {
  console.log('promise');
});
console.log(1);

// promise 
// 1
// 同步执行。

// promise 存放以后才会结束的事件,可以被当作一个容器。

// 异步操作 状态

  // 1. pending 进行中
  // 2. fufilled(resolve)已成功
  // 3. reject 已失效

  // a. 异步操作的状态不受外界影响;
  // b. 状态存在不可逆性,一旦状态发生变化,不会再改变;

      // promise固化之后,再对promise对象添加回调函数,可以直接拿到结果。
      // 如果是事件的话,一旦错过,就是真的错过了。 

      // 进行中 -> 已成功
      // 进行中 -> 已失效

new Promise(function (resolve, reject) {
  console.log('promise');
  reject();
});
// 通过函数调用的方式改变promise的状态。

const promise = new Promise(function (resolve, reject) {
  setTimeout(function () {
    Math.random() * 100 > 60 ? resolve()
                            : reject();
  }, 300)
});

promise
  .then(res => {
    console.log('函数执行完成');
  })
  .catch(err