ES6新增特性

历史

浏览器经历阶段:

  • 1991- 1997: HTML1/HTML2/HTML3/IETF(The Internet Engineering Task Force)国际互联网工程任务组
  • 1997: HTML3.2 W3C

ECMA-262 Ecmascript 脚本语言规范:

  • 1995 LiveScript JavaScript
  • 1996 JavaScript 1.0/1.1
  • 1997 Jscript
  • 1997.6 ECMAScript 1.0
  • 1998.6 ECMAScript 2.0
  • 1999.12 ECMAScript 3.0
  • 2000 ECMAScript 4.0草案(未通过) TC39(technical committe 39)成员反对
  • 2007 ECMAScript 4.0准备发布 但没发布
  • 2008.7 ECMAScript 3.1过渡到5.0 , 大会项目代号(hamony) JavaScript.next / JavaScript.next.next
  • 2009.12

    • ECMAScript 5.0 正式发布
    • JavaScript.next(放入草案) ES6
    • JavaScript.next.next(放入草案) ES7
  • 2011.6 ECMAScript 5.1
  • 2013.3 JavaScript.next(草案冻结)
  • 2013.6 JavaScript.next(草案发布)
  • 2015.6 ECMAScript 6.0正式发布, 从此每年6月出一个新的小版本
  • ECMAScript2016
  • ECMAScript2017
  • ECMAScript2018
  • ECMAScript2019
  • ECMAScript2020

let

作用域 [[scope]] 存储 AO/ GO

var的问题:

  • 变量提升 undefined
  • 重复声明会有变量污染

ES5解决方案:

  • 立即执行函数
  • 但定义函数内部仍会存在变量污染的问题

kiss原则

keep it simple 保持简单/傻瓜,通过函数提纯,让功能单一,解决变量污染问题

如何解决变量污染问题?

let语法 块级作用域

常见的块级作用域:

  1. if(1){}
  1. for(){}
  1. {
  2. }

关于暂时性死区:

TDZ(Temporal Dead Zone)

let特点:

  • 同一作用域下,let不允许重复声明
  • let不会声明提升,会产生一个暂时性死区
  • let只能再当前的块级作用域下生效

let本质:为了JS增加一个块级作用域

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

  1. //let不允许重复声明
  2. function test(){
  3. let a = 1;
  4. var a = 1;
  5. }
  6. test();
  7. //报错: SyntaxError: Identifier 'a' has already been declared
  1. //预编译的情况下已经声明a了
  2. function test(a){
  3. let a = 10;
  4. console.log(a);
  5. }
  6. test();
  7. //报错: SyntaxError: Identifier 'a' has already been declared
  1. function test(a){
  2. {
  3. let a = 10;
  4. }
  5. console.log(a);
  6. }
  7. test();
  8. //undefined
  1. function test(a){
  2. {
  3. let a = 10;
  4. console.log(a);
  5. }
  6. }
  7. test();
  8. //10
  1. //let不会声明提升,会产生一个暂时性死区
  2. console.log(a);
  3. let a = 10;
  4. //ReferenceError: a is not defined
  1. function test(){
  2. console.log(a);
  3. let a = 10;
  4. }
  5. test();
  6. //ReferenceError: a is not defined

问题1:

  1. var a = a;
  2. console.log(a);
  3. //undefined
  4. var b = b;
  5. console.log(b);
  6. //ReferenceError: b is not defined

经典问题2:

  1. //ES6语法 形参默认值
  2. //暂时性死区导致的问题
  3. //先将y赋值给x, 但y未被定义, y是在赋值之后被定义的
  4. function test(x = y, y = 2){
  5. console.log(x, y); //报错y is not defined 2
  6. }
  7. test();

如何更改才能执行?

  1. //先赋值再引用值
  2. function test(x = 2, y = x){
  3. console.log(x, y); //2 2
  4. }
  5. test();

let引起的暂时性死区使typeof结果也更改了

  1. console.log(typeof a); //undefined
  1. console.log(typeof a);
  2. let a;
  3. //ReferenceError: a is not defined

特点3示例:

  1. //let只能再当前的块级作用域下生效
  2. //不在同一作用域下的情况不生效
  3. {
  4. let a =2;
  5. }
  6. console.log(a);
  7. //ReferenceError: a is not defined
  1. function test(){
  2. let a = 2;
  3. }
  4. console.log(a);
  5. test();
  6. //ReferenceError: a is not defined
  1. if(1){
  2. let a = 2;
  3. }
  4. console.log(a);
  5. //ReferenceError: a is not defined
  1. //永远死循环,永远不执行代码,但不报错
  2. for(;1;){
  3. let a = 1;
  4. }
  5. console.log(a);
  1. //死循环结束,执行代码,报错
  2. for(;1;){
  3. let a = 1;
  4. break;
  5. }
  6. console.log(a);
  7. //ReferenceError: a is not defined

关于for循环的坑:

  1. //i无法提升,外部无法访问
  2. for(let i = 0; i < 10; i++){}
  3. console.log(i);
  4. //ReferenceError: i is not defined

为什么打印0-9 而不是 10个10?

  1. var arr = [];
  2. for(var i = 0; i < 10; i++){
  3. arr[i] = function(){
  4. console.log(i);
  5. }
  6. }
  7. //这里循环完但未执行 i = 10
  8. //但这里又开始循环重复赋值i 所以又开始0-9的执行
  9. for(var i = 0; i < 10; i++){
  10. arr[i]();
  11. }
  12. }

for循环里面的两个块级作用域会不会存在冲突?

  1. //这里存在两个块级作用域
  2. for(var i = 0; i < 10; i++){
  3. i = 'a';
  4. console.log(i);
  5. }
  6. //a
  7. 分析:
  8. var i = 0
  9. for(; i < 10; ){
  10. i = 'a';
  11. console.log(i);
  12. i++
  13. }
  14. //1. i = 0
  15. //2. 进入循环 被赋值为a i++
  16. //3. 'a' + 1 = 'a1' 变为 NaN
  17. //4. NaN < 10 ? false 不进循环 所以只打印一次 a
  1. //这里i仍处于同一作用域下
  2. for(let i = 0; i < 10; i++){
  3. i = 'a';
  4. console.log(i);
  5. }
  6. //a
  1. //说明let和var不在同一作用域下
  2. for(let i = 0; i < 10; i++){
  3. var i = 'a';
  4. console.log(i);
  5. }
  6. //SyntaxError: Identifier 'a' has already been declared
  1. //不在同一作用域下的不影响let的声明
  2. for(let i = 0; i < 10; i++){
  3. let i = 'a';
  4. console.log(i);
  5. }
  6. //10 个 a

以上例子说明for循环里面是父级作用域,像以下情况

  1. if(1){
  2. let a = 1;
  3. {
  4. console.log(a);
  5. }
  6. }
  7. //1
  1. //不同作用域下let不影响
  2. if(1){
  3. let a = 1;
  4. {
  5. let a = 10;
  6. console.log(a);
  7. }
  8. }
  9. //1
  1. //不限嵌套次数
  2. {{{{{}}}}}

ES5{}嵌套的函数是不合法但可以解析的,在ES6中就允许了

  1. {
  2. function test(){}
  3. }
  1. if(i){
  2. function test(){}
  3. }
  1. try{
  2. function test(){}
  3. }catch(e){
  4. function test1(){}
  5. }

上述写法并不推荐,但可以用函数表达式替代声明

  1. try{
  2. var test = function(){}
  3. }catch(e){
  4. var test2 = function(){}
  5. }
  1. //块级作用域:是没有返回值的
  2. {
  3. return
  4. }

块级作用域等于匿名函数的调用吗? 不等于

const

定义常量,不可变的量,不期望变量被更改

  1. const test = require('http');

const特点:

  • 定义的常量必要要赋值
  • 一旦定义必须赋值,值不能被更改、
  • 有块级作用域,存在暂时性死区
  • 常量不能重复声明
  1. //定义的常量必要要赋值
  2. const a;
  3. console.log(a);
  4. //Uncaught SyntaxError: Missing initializer in const declaration
  1. //存在暂时性死区
  2. {
  3. const a = 12;
  4. }
  5. console.log(a);
  6. //Uncaught ReferenceError: a is not defined
  1. //存在暂时性死区,且不能变量提升
  2. {
  3. console.log(a);
  4. const a = 12;
  5. }
  6. //Uncaught ReferenceError: a is not defined
  1. //const不能重复声明
  2. {
  3. const a = 12;
  4. let a = 10;
  5. }
  6. console.log(a);
  7. //Uncaught SyntaxError: Identifier 'a' has already been declared

const的值是引用值情况,栈能保证不变,堆不能保证

  1. //说明const只保证指针地址没错,但不保证地址里数据内容不被更改
  2. const obj = {};
  3. obj.name = 'zhangsan';
  4. console.log(obj);
  5. //{name: "zhangsan"}

可以通过Object.freeze()方法冻结const声明的引用值,使之不能更改引用值数据

  1. const obj = {};
  2. Object.freeze(obj);
  3. obj.name = 'zhangsan';
  4. console.log(obj);
  5. //{name: "zhangsan"} freeze()之后=> {}

进一步封装冻结函数

  1. function myFreeze(obj) {
  2. Object.freeze(obj);
  3. //深度递归对象里面的对象
  4. for (var key in obj) {
  5. //该对象的值为object且不能是null
  6. if (typeof (obj[key] === 'object') && obj[key] !== null) {
  7. Object.freeze(obj[key]);
  8. }
  9. }
  10. }
  11. var person = {
  12. son: {
  13. lisi: '18',
  14. zhangsan: '19'
  15. },
  16. car: ['benz', 'mazda', 'bmw']
  17. }
  18. myFreeze(person);
  19. person.son.wangwu = '20';
  20. person.car[3] = 'toyota';
  21. console.log(person);
  22. /**
  23. * 没注释myFreeze(person);
  24. * 打印:
  25. * {son: {…}, car: Array(4)}
  26. * car: (4) ["benz", "mazda", "bmw", "toyota"]
  27. * son: {lisi: "18", zhangsan: "19", wangwu: "20"}
  28. * __proto__: Object
  29. *
  30. * 注释myFreeze(person);
  31. * 打印:
  32. * {son: {…}, car: Array(3)}
  33. * car: (3) ["benz", "mazda", "bmw"]
  34. * son: {lisi: "18", zhangsan: "19"}
  35. * __proto__: Object
  36. */

不用Object.freeze()冻结的情况:

  1. //这里require返回的是实例化对象被常量const接收
  2. //这种引入库的写法从源头上已经不能更改该对象的内容(因为是实例化对象)
  3. const http = require('http');

内容补充1:

顶层对象指window,顶层对象的属性

  1. //早期的JavaScript写法存在问题但也能解析
  2. //存在不容易发现错误
  3. a = 1;
  4. console.log(a); //undefined window
  1. //ES6为了改变,保持兼容性 允许var 不允许let const
  2. //所以建议用let或const写
  3. let a = 1;
  4. console.log(a);

内容补充2:

专业术语:falsy假的值(虚值) 通过boolean转化为false的值

  1. function foo(x, y) {
  2. x = x || 1;
  3. y = y || 2;
  4. console.log(x + y);
  5. }
  6. foo(); //1+2=3
  7. foo(5, 6); //5+6=11
  8. foo(5); //5+2=7
  9. foo(null, 6); //null -> false 1+6=7
  10. foo(0, 5); //0 -> false 1+5=6

ES5形参为默认值写法

  1. function foo(x, y) {
  2. var x = typeof (arguments[0]) !== 'undefined' ? arguments[0] : 1;
  3. var y = typeof (arguments[1]) !== 'undefined' ? arguments[1] : 2;
  4. console.log(x + y);
  5. }
  6. foo(); //3
  7. foo(5, 6); //11
  8. foo(5); //7
  9. foo(null, 6); //7
  10. foo(0, 5); //5

ES6形参为默认值写法

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

情况1:形参默认值会影响函数内声明造成重复声明报错

  1. function foo(x = 2) {
  2. let x = 2;
  3. console.log(x);
  4. }
  5. foo(10);
  6. //Uncaught SyntaxError: Identifier 'x' has already been declared

特殊情况:里层声明时拿不到父级作用域的变量会报错

  1. //如果不声明里层是可以拿到父级作用域的变量的
  2. var x = 1;
  3. {
  4. console.log(x);
  5. }
  6. //1
  7. //里层一旦let声明,就会形成块级作用域,将无法访问父级作用域
  8. var x = 1;
  9. {
  10. let x = x;
  11. console.log(x);
  12. }
  13. //Uncaught ReferenceError: Cannot access 'x' before initialization
  14. //这里()里会形成单独的作用域
  15. //这里的写法类似上面第一个的写法情况
  16. //里层声明时拿不到父级作用域的变量会报错
  17. function foo(x = x) {
  18. console.log(x);
  19. }
  20. foo();
  21. //Uncaught ReferenceError: Cannot access 'x' before initialization
  22. //这里()里的z=z+1 约等于 let z = z + 1,所以拿不到父级作用域,所以z没有定义
  23. function foo(x = w + 1, y = x + 1, z = z + 1) {
  24. console.log(x, y, z);
  25. }
  26. foo();
  27. //Uncaught ReferenceError: Cannot access 'z' before initialization

惰性求值:当函数的参数为表达式的时候,会重新计算表达式的值

  1. //优先访问里层作用域里存在的变量的值
  2. let a = 99;
  3. function foo(b = a + 1) {
  4. console.log(b);
  5. }
  6. foo(); //100
  7. a = 100;
  8. foo(); //101

解构赋值

解构赋值依然是一个赋值的过程

模式匹配(结构化赋值)

数组解构

  1. //数组的模式匹配
  2. let [a, b, c] = [1, 2, 3];
  3. console.log(a, b, c); //1 2 3
  1. let [d, [e, [f]]] = [1, [2, [3]]];
  2. console.log(d, e, f); //1 2 3
  1. //解构失败
  2. //变量多,值少的情况以undefined填充
  3. let [a, [b, [c]]] = [, [2, [3]]];
  4. console.log(a, b, c);//undefined 2 3
  1. //不完全解构
  2. //变量少,值多
  3. let [a, [b, [c, []]]] = [1, [2, [3, [4]]]];
  4. console.log(a, b, c);
  5. //1 2 3
  1. //解构默认值
  2. //这里默认值为6
  3. let [a = 6] = [1];
  4. console.log(a); //1
  5. let [b = 6] = [];
  6. console.log(b); //6
  1. let [a, b = 2] = [1];
  2. console.log(a, b); //1 2
  3. let [c, d = 2] = [1, undefined];
  4. console.log(c, d); //1 2
  5. let [e, f = 2] = [1, null];
  6. console.log(e, f); //1 null
  7. let [g, h = 2] = [1, false];
  8. console.log(g, h); //1 false
  9. let [j, k = 2] = [1, '1'];
  10. console.log(j, k); //1 '1'
  1. //有默认值时,有值找值,没值找默认值
  2. function test() {
  3. console.log(10);
  4. }
  5. let [x = test()] = [1];
  6. console.log(x); //1
  7. let [y = test()] = [];
  8. console.log(y); //10 undefind
  1. let [x = 1, y = x] = [];
  2. console.log(x, y); //1 1
  3. //重复声明
  4. let a = 5;
  5. let [a = 1, b = a] = [];
  6. console.log(x, y);
  7. //Uncaught SyntaxError: Identifier 'a' has already been declared
  1. let [x = 1, y = x] = [2];
  2. console.log(x, y); //2 2
  1. let [x = 1, y = x] = [1, 2];
  2. console.log(x, y); //1 2
  1. //暂时性死区
  2. let [x = y, y = 1] = [];
  3. console.log(x, y);
  4. //Uncaught ReferenceError: Cannot access 'y' before initialization

对象解构

对象的解构是不存在顺序的

  1. let {
  2. a: a,
  3. b: b,
  4. c: c
  5. } = {
  6. a: 1,
  7. b: 2,
  8. c: 3
  9. }
  10. console.log(a, b, c);

常见对象的解构方式

  1. const {son} = person;

对象规则解构

  1. //数组也是特殊的对象,也能进行解构赋值
  2. let arr = [1, 2, 3];
  3. let {0: first, [arr.length - 1]: last} = arr;
  4. console.log(first, last); //1 3

默认值兼容

  1. //默认值
  2. function fetch(url, {
  3. body: body = '',
  4. method: method = 'GET',
  5. header: header = {}
  6. } = {}) {
  7. console.log(method);
  8. }
  9. fetch('http://www.baidu.com');
  10. fetch('http://www.baidu.com', {}); //GET

简写

对象简写

  1. var name = 'zhangsan';
  2. var age = 14;
  3. var person = {
  4. name,
  5. age,
  6. sex: 'male',
  7. eat() {
  8. console.log(1);
  9. }
  10. }
  11. console.log(person);

箭头函数

  1. () => {};
  1. () => a;

箭头函数的实质

  1. 箭头函数忽略任何形式this指向的改变
  2. 箭头函数this指向 由外层函数的作用域来决定的
  3. 不能作为构造函数来使用
  4. 没有arguments对象,用rest(拓展运算符替代)
  5. yield命令不能生效,在generator函数中

ES5this指向问题:

  • 默认规则
  • 隐式规则
  • 显示规则
  • new

优先级:

new > 显示 > 隐式 > 默认

示例1:

  1. //箭头函数指向
  2. function foo() {
  3. console.log(this);
  4. return (a) => {
  5. console.log(this.a);
  6. }
  7. }
  8. var obj1 = {
  9. a: 2
  10. };
  11. var obj2 = {
  12. a: 3
  13. };
  14. var bar = foo.call(obj1);
  15. //foo()返回函数体本身bar
  16. //{a: 2}
  17. bar.call(obj2);
  18. //此时bar是一个箭头函数, call不能改变this指向,所以还是指向父级作用域的foo.call(obj1)指向obj1,打印2

示例2:

  1. const person = {
  2. eat() {
  3. console.log(this);
  4. },
  5. drink: () => {
  6. console.log(this);
  7. }
  8. }
  9. person.eat();
  10. //隐式转换为person对象
  11. //指向调用者
  12. //{eat: ƒ, drink: ƒ}
  13. person.drink();
  14. //隐式转换箭头函数失败
  15. //箭头父级作用域决定指向window

示例3:

  1. //箭头函数嵌套
  2. //箭头函数没有this机制的,this是外层的
  3. //指向是固定的
  4. function foo() {
  5. console.log(this);
  6. return () => {
  7. console.log(this);
  8. return () => {
  9. console.log(this);
  10. return () => {
  11. console.log(this);
  12. console.log('id', this.id);
  13. }
  14. }
  15. }
  16. }
  17. var f = foo.call({
  18. id: 1
  19. });
  20. var f1 = f.call({
  21. id: 2
  22. })()();
  23. var f2 = f().call({
  24. id: 3
  25. })();
  26. var f3 = f()().call({
  27. id: 4
  28. });
  29. //id 1
  30. //id 1
  31. //id 1

示例4:

  1. //在箭头函数内部不存在arguments
  2. var test = () => {
  3. console.log(arguments);
  4. }
  5. test();
  6. //Uncaught ReferenceError: arguments is not defined

示例5:

  1. //箭头函数在定时器内部可以通过闭包访问父级作用域的属性
  2. function foo() {
  3. console.log(arguments);
  4. var a = 1;
  5. setTimeout(() => {
  6. console.log(a);
  7. console.log(arguments);
  8. })
  9. }
  10. foo(1, 2, 3, 4, 5, 6);
  11. //Arguments(6) [1, 2, 3, 4, 5, 6, callee: ƒ, Symbol(Symbol.iterator): ƒ]
  12. //1
  13. //Arguments(6) [1, 2, 3, 4, 5, 6, callee: ƒ, Symbol(Symbol.iterator): ƒ]

案例1:

  1. //功能:插指定入数组元素到数组的指定位置
  2. function insert(value) {
  3. return {
  4. into: function (array) {
  5. return {
  6. after: function (afterValue) {
  7. //索引+1
  8. //删除0位,添加value
  9. array.splice(array.indexOf(afterValue) + 1, 0, value);
  10. return array;
  11. }
  12. }
  13. }
  14. }
  15. }
  16. //插入值5到数组[1,2,3,4,6,7,8]里索引为4的位置
  17. console.log(insert(5).into([1, 2, 3, 4, 6, 7, 8]).after(4));
  18. //[1, 2, 3, 4, 5, 6, 7, 8]
  1. //箭头函数方式改造
  2. let insert = (value) => ({
  3. into: (array) => ({
  4. after: (afterValue) => {
  5. array.splice(array.indexOf(afterValue) + 1, 0, value);
  6. return array;
  7. }
  8. })
  9. })
  10. console.log(insert(5).into([1, 2, 3, 4, 6, 7, 8]).after(4));
  11. //[1, 2, 3, 4, 5, 6, 7, 8]

箭头函数使用场景:

  • 适合箭头表达式的情况:

    • 返回值单一,只有唯一的表达式,函数内部没有this引用
    • 递归
    • 事件处理函数绑定/解绑
    • 内层的函数表达式需要调用this,用var _self = this;
    • bind(this)确保适当的this指向时
    • var args = Array.prototype.slice.call(arguments) 用箭头函数比较好
  • 不适合箭头表达式的情况:

    • 函数声明,执行语句比较多的
    • 还需要用到递归
    • 还需要引用函数名
    • 事件绑定,解绑定

rest

展开运算符,展开/收集

... spread / rest 运算符

收集实参:得到的是数组而不是类数组

  1. var sum = (...args) => {
  2. console.log(args);
  3. }
  4. sum(1, 2); //[1, 2]

展开实参:得到的是单独的值

  1. function foo(x, y, z) {
  2. console.log(x, y, z);
  3. }
  4. foo(...[1, 2, 3]); //1 2 3
  5. //ES5模拟
  6. function foo(x, y, z) {
  7. console.log(x, y, z);
  8. }
  9. // foo(...[1, 2, 3]); //1 2 3
  10. foo.apply(null, [1, 2, 3]); //1 2 3

其他上下文展开

  1. //数组展开
  2. //ES6优势:语义化更强
  3. let a = [2, 3, 4];
  4. let b = [1, ...a, 5];
  5. console.log(b); //[1, 2, 3, 4, 5]
  6. //相当于ES5
  7. console.log([1].concat(a, [5])); //[1, 2, 3, 4, 5]
  1. //把剩余的所有参数都收集到c变量里形成数组展示
  2. //注意:拓展运算符必须是最后一位
  3. let fn = (a, b, ...c) => {
  4. console.log(a, b, c);
  5. }
  6. fn(1, 2, 3, 4, 5, 6, 7);
  7. //1 2 [3, 4, 5, 6, 7]
  1. //ES5:排序数组
  2. function sortNum() {
  3. return Array.prototype.slice.call(arguments).sort(function (a, b) {
  4. return a - b
  5. });
  6. }
  7. console.log(sortNum(12, 431, 24, 14, 1, 4, 125, 2, 35, 25));
  8. //[1, 2, 4, 12, 14, 24, 25, 35, 125, 431]
  9. //ES6:排序数组
  10. const sortNum2 = (...args) => args.sort((a, b) => a - b);
  11. console.log(sortNum2(12, 431, 24, 14, 1, 4, 125, 2, 35, 25));
  12. //[1, 2, 4, 12, 14, 24, 25, 35, 125, 431]

展开运算符不能通过length属性访问,只能访问实际实参的长度

  1. //访问length属性
  2. //ES5
  3. console.log((function (a) {}).length); //1
  4. //ES6
  5. //...args不能通过length属性访问,只能访问实际实参的长度
  6. console.log(((...a) => {}).length); //0
  7. console.log(((b, ...a) => {}).length); //1
  8. console.log(((b, c, ...a) => {}).length); //2

ES2017的展开运算符

可以 实现对象展开

  1. var obj = {
  2. a: 1,
  3. b: 2,
  4. c: 3
  5. };
  6. var obj1 = {
  7. a: 4,
  8. d: 5,
  9. e: 6
  10. }
  11. var obj2 = {
  12. ...obj,
  13. ...obj1
  14. }
  15. console.log(obj2);
  16. //{a: 4, b: 2, c: 3, d: 5, e: 6}

ES5写法是通过Object.assign()合并

  1. var obj = {
  2. a: 1,
  3. b: 2,
  4. c: 3
  5. };
  6. var obj1 = {
  7. a: 4,
  8. d: 5,
  9. e: 6
  10. }
  11. var obj2 = {}
  12. Object.assign(obj2, obj, obj1);
  13. console.log(obj2);
  14. //{a: 4, b: 2, c: 3, d: 5, e: 6}

拓展

函数名

  1. console.log(f.name); //f
  1. console.log((new Function).name); //anonymous
  1. console.log(foo.bind({}).name); //bound foo;

对象

  1. const foo = 'bar';
  2. //对象名称和函数名称相同时可以简写
  3. const baz = {foo};

属性名,可以通过[]访问对应的值

  1. var arr = [1, 23, 23, 45, 5];
  2. console.log(arr[1]); //23

属性经过一层包装,将传入的所有值进行包装变成一个字符串

  1. //所以说,定义的属性都为字符串
  2. var arr = [1, 23, 23, 45, 5];
  3. console.log(arr['1']); //23

属性名拼接

  1. //拼接的属性名会重写之前的属性名
  2. //先进行覆盖然后再找属性值
  3. let obj = {
  4. [a + b] : true,
  5. ['hello' + b] : 123,
  6. ['hello' + 'world'] : undefined
  7. }

当属性名为对象的时候,如何转换为对象?

  1. var myObject = {};
  2. myObject[true] = 'foo';
  3. myObject[3] = 'bar';
  4. myObject[myObject] = 'baz';
  5. console.log(myObject['true']); //foo
  6. console.log(myObject['3']); //bar
  7. console.log(myObject[myObject]); //baz
  8. console.log(myObject['[object Object]']); //baz
  9. //如何成为字符串?
  10. console.log(Boolean.prototype.toString.call(true)); //true
  11. console.log(Number.prototype.toString.call(3)); //3
  12. console.log(Object.prototype.toString.call(myObject)); //[object Object]
  13. console.log(Object.prototype.toString.call(true)); //[object Boolean]
  1. //只有一个属性,后一个属性覆盖前一个属性
  2. const a = { a: 1 };
  3. const b = { b: 2 };
  4. const obj = {
  5. [a]: 'valueA',
  6. [b]: 'valueB'
  7. }
  8. console.log(obj);
  9. //{[object Object]: "valueB"}

对象中的name属性

  1. const person = {
  2. sayName() {
  3. console.log('hello');
  4. }
  5. }
  6. console.log(person.sayName.name); //sayName

Symbol

ES6为什么要引入symbol?

场景:

ES5对象属性经常出现重名的情况,解决对象属性名重名的问题

symbol属于原始值类型的值,而不是构造函数

  • 原始值类型的值:number/boolean/null/undefined/symbol
  • 引用值类型的值:Object/Array/Function
  1. let s1 = Symbol('foo');
  2. //是原始值包装类
  3. console.log(typeof s1); //symbol
  4. //挂不上属性
  5. console.log(s1.a); //undefined
  6. //如何区别唯一的symbol值?可以通过传参区分
  7. //作为标识符存在的
  8. console.log(s1); //Symbol(foo)
  1. //通过生成完全不一样的不可重复的原始类型
  2. //针对拷贝属性覆盖的问题
  3. var a = Symbol('a');
  4. var b = Symbol('a');
  5. console.log(a === b); //false
  6. // Symbol的值能否被Object.assign()拷贝?
  7. //可以拷贝
  8. var test = Object.assign({ a: 'b' }, { [Symbol('c')]: 'd' });
  9. console.log(test);
  10. //{a: "b", Symbol(c): "d"}

如何避免重名?

  1. var obj = {
  2. a: 1
  3. };
  4. let s1 = Symbol(obj);
  5. console.log(s1);
  6. //说明对象自己调用了Object.prototype.toString()方法将自己变成字符串
  7. //也说明symbol的值永远是字符串
  8. //Symbol([object Object])

如何使用?作为属性名来用

  1. //独一无二的属性名
  2. let name = Symbol();
  3. let person = {};
  4. // 错误写法
  5. //此写法是字符串不能用Symbol创建的变量
  6. person.name = 'zhangsan';
  7. console.log(person);
  8. //{name: "zhangsan"}
  9. //正确的写法:
  10. person[name] = 'lisi';
  11. console.log(person);
  12. //{name: "zhangsan", Symbol(): "lisi"}
  13. //常用写法:
  14. let name = Symbol();
  15. let person1 = {
  16. [name]: 'wangwu'
  17. }
  18. console.log(person1);
  19. //{Symbol(): "wangwu"}
  20. //写法三:
  21. let name = Symbol();
  22. let person = {};
  23. Object.defineProperty(person, name, {
  24. value: 'zhangsan'
  25. })
  26. console.log(person);
  27. //{Symbol(): "zhangsan"}
  1. //挂载Symbol到变量里
  2. let name = Symbol();
  3. let eat = Symbol();
  4. let person = {
  5. [name]: 'zhangsan',
  6. [eat]: function(){}
  7. //简写
  8. [eat](){
  9. console.log(this[name]);
  10. }
  11. }

Symbol相关的几个方法

Symbol.for()拿到唯一的值

  1. //一般来说,Symbol的值都是不同的
  2. let s1 = Symbol('foo');
  3. let s2 = Symbol('foo');
  4. console.log(s1 === s2); //false
  5. //也有特殊的情况,可以值是相同的
  6. let s3 = Symbol.for('foo');
  7. let s4 = Symbol.for('foo');
  8. console.log(s3 === s4); //true

Symbol.keyFor全局中拿到当前的key

  1. let s1 = Symbol.for('foo');
  2. let s2 = Symbol.for('foo');
  3. //拥有同样的标识符
  4. console.log(Symbol.keyFor(s1)); //foo
  5. console.log(Symbol.keyFor(s2)); //foo

试着遍历

  1. const obj = {}
  2. let a = Symbol('a');
  3. let b = Symbol('b');
  4. obj[a] = 'hello';
  5. obj[b] = 'world';
  6. // console.log(obj);
  7. //{Symbol(a): "hello", Symbol(b): "world"}
  8. //遍历
  9. for (let i in obj) {
  10. console.log(i);
  11. //无打印结果
  12. //说明for in 不能遍历 Symbol属性的对象
  13. }

Object.getOwnPropertySymbols()新的API专门遍历symbol类型的值(唯一的遍历方法)

  1. const obj = {}
  2. let a = Symbol('a');
  3. let b = Symbol('b');
  4. obj[a] = 'hello';
  5. obj[b] = 'world';
  6. //只针对Symbol属性的对象遍历的方法
  7. console.log(Object.getOwnPropertySymbols(obj));
  8. //[Symbol(a), Symbol(b)]

总结:

  • for in:遍历自身和继承的可枚举属性(不包括含Symbol类型的值)
  • Object.key():遍历自身不包含Symbol类型的值
  • Object.getOwnPropertySymbols():遍历自身的Symbol类型的值
  • Object.assign():遍历自身可枚举的,包含Symbol类型的值
  • JSON.stringify():遍历自身可枚举的属性

Symbol定义唯一方法实现一个iterator接口:

对象是不连续的且无序的数据结构,一般来说不能用for of,但是可以通过部署迭代器接口的方式来使用for of 遍历

  1. //手动的编写一个iterator接口可以针对指定的数据类型进行迭代遍历
  2. //对象上写一个iterator接口
  3. let obj = {
  4. start: [1, 3, 2, 4],
  5. end: [5, 7, 6],
  6. //中括号包裹字符串的方式
  7. [Symbol.iterator]() {
  8. //定义指针
  9. let index = 0,
  10. //组合新数组
  11. arr = [...this.start, ...this.end],
  12. //新数组长度
  13. len = arr.length;
  14. //将新数组进行迭代
  15. return {
  16. next() {
  17. if (index < len) {
  18. return {
  19. //累加的结果
  20. value: arr[index++],
  21. done: false
  22. }
  23. } else {
  24. return {
  25. value: undefined,
  26. done: true
  27. }
  28. }
  29. }
  30. }
  31. }
  32. }
  33. for (let i of obj) {
  34. console.log(i); //1 3 2 4 5 6 7
  35. }

Reflect

是一个对象, 是JavaScript内置对象方法集合的容器

  1. Reflect = {}

整合了ES5原型上原有的方法的ES6API(静态方法)

apply()/defineProperty()/deleteProperty()/get()/getOwnPropertyDescriptor/getPrototypeof()/has()/isExtensible()/ownKeys()/preventExtensions()/set()/setPrototypeOf()

  1. //如何通过Reflect访问对象
  2. console.log(obj.a);
  3. console.log(Reflect.get(obj, 'a'));

利用函数式的写法重新定义Proxy构造函数,用方法去取值/赋值使得更为合理,利用底层的方法操作对象

  1. let proxy = new Proxy(target, {
  2. get(target, prop){
  3. //1.直接访问返回
  4. //return 'This is property value' + target[prop];
  5. //2.通过函数式返回
  6. return Reflect.get(target, prop);
  7. },
  8. set(target, prop, value){
  9. //target[prop] = value;
  10. Reflect.set(target, prop, value);
  11. }
  12. });