Skip to content

zesicus/ES6-Note

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

16 Commits
 
 

Repository files navigation

ES6 笔记

ES6学习笔记

注:此笔记是我在主要用Swift语言开发,对比整理的一些我感觉常用的功能特性点,匆匆忙忙,粗粗浅浅,以偏概全,仅自以为容易混淆和类比以及不同的知识点。

目 录

var let const

  • var let 区别

    var 在以下这样的代码块里依然是全局变量,但是 let 在里面是局部变量。

    {
        var a = 1;
        let b = 2;
    }
    //输出: 1
    console.log(a);
    // 报错!
    console.log(b);

    在 function 里面,var 和 let 则都不可取到,其声明都是以局部变量呈现,在我看来它们都是一样的。

    function func() {
        var a = 1;
        a = 3;
        //输出 3
        console.log(a);
        let b = 2;
        b = 4;
        //输出 4
        console.log(b);
    }
    func();
    // 报错!
    console.log(a);
    // 报错!
    console.log(b);

    且 var 命令存在 “变量提升”,也就是只要用 var 定义了变量,就算是在声明之前引用它,都不会报错;但是对 let 来说,它就很安全了,在定义之前的引用,都会报错!

    //undefined
    console.log(a);
    var a = 1;
    
    // 报错!
    console.log(b);
    let b = 1;

    **总结:**现在我还不知道多文件全局变量中怎么声明,但是在单文件中,一个 let 声明就足够了!只要在代码块里,let 就是无敌的,在代码块外,let 什么都不是。

  • const

    它就是声明常量用的,注意规范:常量全部用大些字母表示

    const PI = 3.14159;
    // 报错!
    PI = "what?";

    但是 const 也仅仅能保证

  • 对比语言

    • Swift

      在 Swift 中声明变量的方法只有 var 属性,这个 var 在单文件中,其作用就是ES6中的 let !

      而 ES6 中的 const,则是声明常量的方法,作用和 Swift 里的 let 一摸一样。

解构赋值

  • 什么是解构赋值?

    按照一定模式,对变量赋值。JS支持数组、对象的解构赋值。

    let [a, b, c] = [1, 2, 3];
    //输出:1 2 3
    console.log(a, b, c)
    let {a, b} = {a: "a", b: "b"}
    //输出:a b
    console.log(a, b)
  • 字符串的解构赋值

    可以直接用字符串去映射一个数组。

    let [a, b, c] = "hello"
    // 输出:h e l
    console.log(a, b, c)
  • 对比语言

    • Swift

      swift中的解构赋值体现在元祖上面,其他的嘛,就没有JS这么宽泛了。

函数

  • 函数参数默认值

    ES6提供了函数默认值,所以这样一个函数的写法为:

    function hello(a, b = "b") {
        console.log(a);
        console.log(b);
    }
    //输出:a
    //     b
    hello("a")
  • 函数的 length 属性

    函数的 length 返回参数的个数,但是有默认值的参数却不算。

    举个🌰

    let f = function (a, b, c = 0) {};
    console.log(f.length);
    //这样虽然不会报错,但也不会改变length的值
    f.length = 5;
    console.log(f.length);
  • 函数的 rest 参数

    函数的 rest 参数允许函数传入多个参数。

    function add(...values) {
        let sum = 0;
    
        for (const val of values) {
          sum += val;
        }
    
        return sum;
    }
    //输出:6
    console.log(add(1, 2, 3));
  • 箭头函数

    ES6出现的一种表现函数的新方式,箭头函数:

    let f = v => v;
    //等同于
    let f = function (v) { return v };
    
    let f = (a, b) => a + b;
    //等同于
    let f = function (a, b) { return a + b; }

    所以,下面这么写也是可以的

    let f = (a, b) => {
        a += 1;
        b += 1;
        return a + b;
    }
    //输出:5
    console.log(f(1, 2));
  • this

    this 据说是一个很让人搞糊涂的东西,其修饰的对象往往是上下文中的对象。

    第一个实验:

    我看了很多博客写的 this 能够取到顶层的东西,但是我自己实验,发现并不能,比如:

    let a = 10;
    function helloA() {
        this.a++;
        console.log(this.a);
    }
    //输出 NaN
    helloA();

    或许是我运行环境的原因,或是 ES5 和 ES6 对 this 的指向改变了?感觉后者的可能小。

    第二个实验

    为了验证 this 是否能够取到上下文的内容,对 ES5 的便捷函数 和 ES6 的箭头函数 做了下实验:

    function helloB() {
        this.b = 10;
        let func = function() {
            this.b++;
            console.log(this.b);
        };
        func();
    }
    //输出:11
    helloB();

    看来能够取到 helloB 块区里的 this.b,如果之前 this.b = 10; 这条语句用 let b = 10;,那么最后输出的结果就为 NaN 了。So, why? 我也不知道为什么这个样子,我以为替换语句也依然可以引用到呢。奇怪奇怪。

    接下来是 ES6 的箭头函数测试:

    function helloC() {
        this.c = 10;
        let func = () => {
            this.c++;
            console.log(this.c);
        };
        func();
    }
    //输出:11
    helloC();

    OK,和上面得到的结果一样,那么继续替换 this.c = 10;let c = 10; 实验呢,最后输出的结果也为 NaN 了。

    所以这个实验下来,真的有些云里雾里,this 的意义何在,我全程用 let 去定义,结果也是一样的……

    第三个实验

    这个实验是对对象进行的实验,同样对便捷函数和箭头函数做了测试。

    先来便捷函数:

    var obj2 = {
        ab: 1,
        func: function() {
            this.ab++;
            console.log(this.ab);
        }
    };
    //输出:2
    obj2.func()

    OK,用 this 确实取到了对象里的 ab 变量。

    再来箭头函数:

    var obj1 = {
        ab: 1,
        func: () => {
            this.ab++;
            console.log(this.ab);
        }
    };
    //输出:NaN
    obj1.func();

    So, WTF, again?

    好吧,到这做了三个实验已经 萌萌哒 了,至于书里还有对 this 各种情况下的各种研究,好了,够了,I'm done here. 🙌

  • 双冒号运算符

    ‼️

    书上这里内容太少,理解不透彻,先留在这 😓

  • 尾调

    JS 的尾递归,只保留一个调用记录,减少了复杂度和栈溢出。

    即最后 return 的一定是函数本身就可以了。

  • 对比语言

    • Swift

      关于默认值,Swift 里也有。

      函数的 length 属性,我在 swift 里没有用到过,可能是参数传递没传过那么灵活吧,也就是 Rest 参数,我在 swift 里顶多就传个数组了;但是在灵活的 ES 里,这样用 length 判断可能挺有必要的吧。

      箭头函数这种新颖的函数表达方式,额,很容易让我联想到 Swift 的闭包,但是 JS 也有闭包,所以箭头函数只是函数的另一种表现形式罢了,不过好像 JS 的闭包也是函数形式的?

      this 确实是一个让人头疼的东西,涉猎虽浅,但已经有些云雾里了。Swift 里没有这样一个 this, 不也用的挺好的嘛。在我看来这个 this 就像一个语法糖样的东西,当然肯定不是语法糖能概括的,但是,搞得那么复杂,真的有必要吗?

      最后关于“尾递归”,这个在刚才学习的过程专门去搜索 Swift 里的尾递归,发现真的有这样的问题,其原理和 JS 的大概是一样的,所以在 Swift 开发里也要注意这个问题,避免复杂度过高。

数组的扩展

开始之前先给自己一个合理解释......

至于跳过字符串扩展那章这张却没跳过,是因为字符串处理这块,相似的东西很多,而且大多也都是现用现查的。数组这块的东西确是经常处理的,至少我在iOS编程这块是的,所以掌握高效率的方法很有必要,那么开始吧!

  • 扩展运算符

    就是 “...[1, 2, 3]” 这样的三点运算符,和函数里的 Rest 参数作用相反,该运算符将数组解构。

    function func1(...values) {
        console.log(values);
    }
    
    function func2(x, y) {
        console.log(x + y);
    }
    
    //输出:[ 1, 2 ]
    func1(1, 2);
    //输出:3
    func2(...[1, 2]);

    作用有:

    • 可用于数组的合并。

      let arr = [1, 2];
      arr.push(...[3, 4]);
      //输出:[1, 2, 3, 4]
      console.log(arr);

      当然,还有另一种写法:

      let arr = [1, 2];
      arr = [...arr, ...[3, 4]];
      //输出:[ 1, 2, 3, 4 ]
      console.log(arr);
    • 数组的深复制

      普通的 “=” 运算符只是浅复制,要想进行数组的深复制也用到扩展运算符。

      let a1 = [1, 2];
      let a2 = [...a1];
      //输出:[ 1, 2 ]
      console.log(a2);
    • 字符串转化为字符数组

      let hello = "Hello";
      let helloArr = [...hello];
      //输出:[ 'H', 'e', 'l', 'l', 'o' ]
      console.log(helloArr);
  • Array.from()

    是一个将类数组的对象转化为数组的函数。我试过将那种类似字典的对象直接用这个函数转化,但没成功。

    但是将字符串转化为数组倒是可行的。

    let hello = "hello";
    let helloArr = Array.from(hello);
    //输出:[ 'h', 'e', 'l', 'l', 'o' ]
    console.log(helloArr);
  • Array.of()

    这个是将一组值,转化为数组的函数。

    Array.of(3, 11, 8) // [3,11,8]
  • find() 和 findIndex()

    findfindIndex可以接受三个参数,分别是 当前值、当前值序号以及原数组。不同的是,find返回要找的那个值,findIndex返回要找的那个值的位置。

    let value = [1, 5, -10, 15].find((n) => n < 0);
    //输出:-10
    console.log(value);
    let index = [1, 5, -10, 15].findIndex((n) => n < 0);
    //输出:2
    console.log(index);

    如果找不到的情况呢

    let value = [1, 5, 10, 15].find((n) => n < 0);
    //输出:undefined
    console.log(value);
    let index = [1, 5, 10, 15].findIndex((n) => n < 0);
    //输出:-1
    console.log(index);
  • fill()

    fill用于初始化数组非常方便。

    let arr = Array(3).fill(0);
    //输出:[ 0, 0, 0 ]
    console.log(arr);

    但是,如果填充类型为对象,那么形成的数组为浅拷贝,因为填充的对象的指针都指向同一地址。

    let arr = Array(3).fill({name: "Mike"});
    arr[0].name = "Dean";
    //输出:[ { name: 'Dean' }, { name: 'Dean' }, { name: 'Dean' } ]
    console.log(arr);

    但是,如果直接去赋值,那么OK:

    let arr = Array(3).fill({name: "Mike"});
    arr[0] = {name: "Dean"};
    //输出:[ { name: 'Dean' }, { name: 'Mike' }, { name: 'Mike' } ]
    console.log(arr);
  • keys()、values() 和 entries()

    是 键、值、键值对 的遍历

    let arr = ["a", "b"];
    
    for (const index of arr.keys()) {
        console.log(index);
    }
    //输出:0
    //     1
    
    for (const value of arr.values()) {
        console.log(value);
    }
    //输出:a
    //     b
    
    for (const keypair of arr.entries()) {
        console.log(keypair);
    }
    //输出:[ 0, 'a' ]
    //     [ 1, 'b' ]
  • include()

    是否包含。

    let arr = ["a", "b"];
    //输出:true
    console.log(arr.includes("b"));
  • flat() 和 flatMap()

    flat将多维数组拉伸成一位数组。

    flatMap这个方法在我的 VSCode 上面运行没有得到预想的结果,不知道其他环境怎么样,这里我用 flat().map()替代。

    let arr = [1, [2, 3], [4]].flat();
    //输出:[ 1, 2, 3, 4 ]
    console.log(arr);
    
    let arr1 = [1, [2, 3], [4]].flat().map((n) => n * 2);
    //输出:[ 2, 4, 6, 8 ]
    console.log(arr1);
  • 其他方法

    // forEach方法
    [,'a'].forEach((x,i) => console.log(i)); // 1
    
    // filter方法
    ['a',,'b'].filter(x => true) // ['a','b']
    
    // every方法
    [,'a'].every(x => x==='a') // true
    
    // reduce方法
    [1,,2].reduce((x,y) => x+y) // 3
    
    // some方法
    [,'a'].some(x => x !== 'a') // false
    
    // map方法
    [,'a'].map(x => 1) // [,1]
    
    // join方法
    [,'a',undefined,null].join('#') // "#a##"
    
    // toString方法
    [,'a',undefined,null].toString() // ",a,,"
  • 对比语言

    • Swift

      在写这一块的时候我就在想深浅拷贝的问题,因为 JS 里直接赋值也只是指针的指向改变了,依稀记得在 OC 语言里也是有深浅拷贝的问题,所以在 Playground 里做了个实验,发现直接赋值就是深拷贝,因为原数组变化后和直接赋值的数组并不相同。至于有文章说 swift 里 struct 是深拷贝,class 是浅拷贝,不深究了,至少一般做项目没遇到这么麻烦的。

      其他的一些方法在 swift 里也有,可能也有没有的,用到的一些方法两者都是有的,反正脚本语言嘛,大多都是互相借鉴的。

对象的扩展

下面是普通对象类型的一个例子:

let person = {
    name: "张三",
    birth: "1991.01.01",
    hello() { console.log("Hello,", this.name); }
}

person.hello();
  • 属性名表达式

    对象内部变量的引用有两种方式:

    //方法一
    console.log(person.name);
    
    //方法二
    console.log(person["name"]);

    如果有变量型的“key”,可以用下面表示方法:

    let abc = "abc";
    
    let obj = {
        foo: true,
        [abc]: 123
    };  
    //输出:123
    console.log(obj[abc]);

    当然,还可以表现成这样:

    let abc = "abc";
    
    let obj = {
        ["h" + "ello"]: "Hi"
    };  
    
    console.log(obj.hello);
  • Object.is()

    此方法是判断对象是否严格相等的方法,比 “===” 更加的严格,其具体严格之处有两个:1⃣️ +0不等于-0,2⃣️ NaN等于自身。

    //输出:true
    console.log(Object.is(NaN, NaN));
    
    //输出:false
    console.log(Object.is({}, {}));
  • Object.assign()

    合并对象的方法,一次性可以合并 n 个。但其是浅拷贝!

    const a = {a: 1};
    const b = {a: 2, b: 3};
    const c = {b: 4, c: 5};
    
    Object.assign(a, b, c);
    //输出:{ a: 2, b: 4, c: 5 }
    console.log(a);
  • Object.getOwnPropertyDescriptors()

    返回对象自身属性的描述对象,看例子:

    const obj = {
        hello: "hello",
        say() {
            console.log("What?");
        }
    }
    /*
    输出:
    { hello:
       { value: 'hello',
         writable: true,
         enumerable: true,
         configurable: true },
      say:
       { value: [Function: say],
         writable: true,
         enumerable: true,
         configurable: true } }
    */
    console.log(Object.getOwnPropertyDescriptors(obj));
  • 对比语言

    • Swift

      ES6 中对象的写法很像 Swift 中的字典,不过,功能更加强大,因为里面可以包含变量还有方法。应该是用途甚广的一个核心存在。

Symbol

ES6 引入的一种新的原始数据类型 Symbol,用来表示独一无二的值,是JS的第七种数据类型。因为其是原始类型值,所以不可以用 new 初始化。

let s1 = Symbol('foo');
let s2 = Symbol('bar');

s1 // Symbol(foo)
s2 // Symbol(bar)

s1.toString() // "Symbol(foo)"
s2.toString() // "Symbol(bar)"

... 里面有很多使用方法,但我决定先提前略过,到用时再做记录。

Set 和 Map

  • Set

    ES6 提供了新的数据结构Set, 类似于数组,但是内部成员都是唯一的,没有重复值。

  • Map

    类似于对象的键值对的集合,其“键”的范围不限于字符串,包括对象都能当作键。

  • 对比语言

    • Swift

      对于 Swift 来说,我真正用到 Set 的时候,也就是对一个数组进行去重的时候。

Proxy

这个Proxy“代理”,做的工作正如其字面意思所述,在目标对象前进行“转发”,过滤后给你。但如果你只是设置代理,却不写任何方法,则会把原对象原封不动给你。

以下只列举两个使用方法,其中还有针对很多函数的拦截,等用到再回顾。

  • get()

    对读取做拦截。

    let person = {
        name: "张三"
    };
    
    let proxy = new Proxy(person, {
        get: function(target, property) {
            if (property in target) {
                return target[property];
            } else {
                throw new ReferenceError("出错啦,没有这个属性!")
            }
        }
    });
    
    //输出:张三
    console.log(proxy.name);
    // 报错!
    console.log(proxy.age);
  • set()

    对输入进行改写。

    let person = {
        name: "张三"
    };
    
    let proxy = new Proxy(person, {
        set: function(target, prop, value) {
            if (prop === 'age') {
                if (!Number.isInteger(value)) {
                    throw new TypeError("格式出错啦!");
                } else if (value > 200) {
                    throw new RangeError("你确定你见过活过200岁的人?")
                }
                target[prop] = value + "岁";
            } else {
                target[prop] = value;
            }
        }  
    });
    
    proxy.age = 11;
    //输出:11岁
    console.log(proxy.age);

Reflect

"反射"

早在 Java 语言上听说过的大名鼎鼎的 “反射”。在 ES6 中,Reflect设计的目的有如下几个:

  • 取得内部方法

    Object对象的一些明显属于语言内部的方法,放到Reflect对象上。也就是说,从Reflect对象上,可以拿到语言内部方法。

  • 修改某些Object方法的返回结果

  • ......

有很多关于对象、Proxy的使用方法,由于我并没有使用过,就不列出来了。

下面试验一下列出来的一些用法。

  • Reflect.get(target, name)

    let myObject = {
        foo: 1,
        bar: 2,
        get baz() {
            return this.foo + this.bar;
      }
    };
    // 输出:1
    console.log(Reflect.get(myObject, 'foo'));
    // 输出:2
    console.log(Reflect.get(myObject, 'bar'));
    // 输出:3
    console.log(Reflect.get(myObject, 'baz'));

    用于得到对象中元素的值。

  • Reflect.set(target, name, value)

    let myObject = {
        foo: 1,
        bar: 2,
        get baz() {
            return this.foo + this.bar;
        }
    };
    // 输出:true
    console.log(Reflect.set(myObject, 'foo', 2));
    // 输出:4
    console.log(Reflect.get(myObject, 'baz'));

    用于修改对象中元素的值。

  • Reflect.has(obj, name)

    let myObject = {
        foo: 1,
        bar: 2,
        get baz() {
            return this.foo + this.bar;
        }
    };
    // 输出:true
    console.log(Reflect.has(myObject, 'foo'));

    判断对象是否有该元素。

  • Reflect.deleteProperty(obj, name)

    let myObject = {
        foo: 1,
        bar: 2,
        get baz() {
            return this.foo + this.bar;
        }
    };
    // 输出:true
    console.log(Reflect.deleteProperty(myObject, 'foo'));
    // 输出:{ bar: 2, baz: [Getter] }
    console.log(myObject);

    删除元素。

  • Reflect.construct(target, args)

     function Greeting(name) {
         this.name = name;
         console.log(this.name);
     }
     	
     // 输出:张三
     new Greeting("张三");
     // 输出:李四
     Reflect.construct(Greeting, ['李四']);

    Reflect实现初始化。

  • ......

    这些是不懂怎么具体运用的方法,先暂且跳过了。

Promise

异步的一种处理方式,基本用法如下:

function timeout(ms) {
    return new Promise((resolve, reject) => {
        setTimeout(resolve, ms, 'Finished');
    });
}
// 输出:'Finished'
timeout(1000).then((value) => {
    console.log(value);
});

在实际运用之前决定先跳过此节,在此之前先对比一下熟悉的语言。

  • 对比语言

    • Swift

      在GitHub上有一个库,叫做PromiseKit,可能就是根据js的特性模仿编写出来的吧,暂时多用于网络处理。

Iterator

提供便利的接口,下面是一个自定义Iterator的例子。

let it = makeIterator(["a", "b"]);

it.next(); //{ value: 'a', done: false }
it.next(); //{ value: 'b', done: false }
it.next(); //{ done: true }

function makeIterator(array) {
    var nextIndex = 0;
    return {
        next: function() {
            console.log(nextIndex < array.length ?
                {value: array[nextIndex++], done: false} :
                {done: true});
        }
    };
}

当然,像数组这样东西自带遍历器:

let arr = ["a", "b"];
let iter = arr[Symbol.iterator]();

console.log(iter.next()); //{ value: 'a', done: false }
console.log(iter.next()); //{ value: 'b', done: false }
console.log(iter.next()); //{ value: undefined, done: true }

Generator

Generator是ES6提供的一种一步解决方案,其会返回一个遍历器对象。

function* gen() {
    yield "hello";
    yield "world";
    return "!";
}

const result = gen();

// 输出:Object [Generator] {}
console.log(result);
// 输出:{ value: 'hello', done: false }
console.log(result.next());
  • yield

    yield可以说是一个暂停标志,而紧跟在后面的表达式的值,作为返回对象value的值。而且,只能放到Generator函数中使用,其他地方会报错。

待补充......

Class

Class是ES6提供的接近传统语言写法的“类”,一个简单示例如下:

class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
    toString() {
        return '(' + this.x + ', ' + this.y + ')';
    }
}
let point = new Point(1, 2);
// 输出:(1, 2)
console.log(point.toString());
  • prototype

    ES5中有prototype这个东西,在ES6中继续存在的,实际上,类的所有方法都定义在prototype属性上面。

    class Point {
        constructor() {
          // ...
        }
    
        toString() {
          // ...
        }
    
        toValue() {
          // ...
        }
    }
    
    // 等同于
    
    Point.prototype = {
        constructor() {},
        toString() {},
        toValue() {},
    };

About

ES6学习笔记

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published