这篇文章主要介绍JavaScript中的属性描述符、ES6中的module、箭头函数。

JavaScript的属性描述符

一、对象的属性
  属性描述符: 对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个具有值的属性,该值可能是可写的,也可能不是可写的。存取描述符是由getter-setter函数对描述的属性。描述符必须是这两种形式之一,不能同时是两者。
  数据描述符(数据属性)和存取描述符(访问器属性)均具有的可选键值:
    configurable: 当且仅当该属性的 configurable 为 true 时,该属性能从对应的对象上被删除,以及除value和writable特性外的其他特性是否可以被修改。默认为 false。
    enumerable: 当且仅当该属性的enumerable为true时,该属性才能够出现在对象的枚举属性中。默认为 false。
  数据描述符(数据属性)的可选键值:
    value: 该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined。
    writable: 当且仅当该属性的writable为true时,value才能被赋值运算符改变。默认为 false。
  存取描述符(访问器属性)的可选键值:
    get: 一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。当访问该属性时,该方法会被执行,方法执行时没有参数传入,但是会传入this对象(由于继承关系,这里的this并不一定是定义该属性的对象)。默认为 undefined。
    set: 一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。当属性值修改时,触发执行该方法。该方法将接受唯一参数,即该属性新的参数值。默认为 undefined。
  注: 如果一个描述符不具有value,writable,get 和 set 任意一个关键字,那么它将被认为是一个数据描述符。如果一个描述符同时有(value或writable)和(get或set)关键字,将会产生一个异常。

二、设置、修改对象单个属性的描述
  Object.defineProperty(): 该方法会直接在对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。IE8部分支持。
  Object.defineProperty()的语法:
    Object.defineProperty(obj, prop, descriptor)
      obj: 要在其上定义属性的对象。
      prop: 要定义或修改的属性的名称。
      descriptor: 将被定义或修改的属性描述符。

    Object.defineProperty(obj, 'val', {
        value: 1,
        writable: false,
        enumerable: true,
        configurable: true
    })
    obj.val = 2;
    console.log(obj.val); // 1

    Object.defineProperty(obj, 'val', {
        value: 1,
        writable: true,
        enumerable: true,
        configurable: true
    })

    obj.val = 3;
    console.log(obj.val); // 3

    Object.defineProperty(obj1, 'val', {
        get() {
            num += 1;
            return num;
        },
        set(newVal) {
            num = newVal;
        },
        enumerable: true,
        configurable: true
    })
    console.log(obj1.val); // 2
    console.log(obj1.val); // 3

属性的赋值器(setter)和取值器(getter): 利用ES6属性的简洁表示法,使用get和set关键字来创建访问器属性。

    const obj = {
        num: 1,
        get num1() {
            return this.num;
        },
        set num1(newVal) {
            this.num += 1;
        }
    }

三、设置、修改对象多个属性的描述
  Object.defineProperties(): 直接在一个对象上定义新的属性或修改现有属性,并返回该对象。
  Object.defineProperties()语法: Object.defineProperties(obj, props) / Object.defineProperties(obj, {property: descriptor, property: descriptor})
    obj: 在其上定义或修改属性的对象。
    property: 要定义或修改的属性的名称。
    descriptor: 将被定义或修改的属性描述符。

    var obj = {};
    Object.defineProperties(obj, {
        'property1': {
            value: true,
            writable: true
        },
        'property2': {
            value: 'Hello',
            writable: false
        }
    });

四、获取对象属性描述
  Object.getOwnPropertyDescriptor(): 返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)。IE8支持。
  Object.getOwnPropertyDescriptor()的语法:
    Object.getOwnPropertyDescriptor(obj, prop)
      obj: 需要查找的目标对象
      prop: 目标对象内属性名称

    let obj = {},
        obj1 = {};
    Object.defineProperty(obj, 'num', {
        value: 1
    })
    obj1.num = 1;
    console.log(Object.getOwnPropertyDescriptor(obj, 'num'));  // {value: 1, writable: false, enumerable: false, configurable: false}
    console.log(Object.getOwnPropertyDescriptor(obj, 'num'));  // {value: 1, writable: true, enumerable: true, configurable: true}

参考来自:
  MDN--Object.defineProperty()
  MDN--Object.defineProperties()
  MDN--Object.getOwnPropertyDescriptor()

ES6module

一、module的定义
  模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

二、export命令
  1) 定义: 一个模块就是一个独立的文件,改文件内部的所有变量,外部无法获取,只有通过export关键字输出该变量、函数或类,外部才能够读取该变量、函数或类。
  2) 注意点:
    a、export命令规定的是对外的接口,必须与模块内部的变量建立一一对应的关系,所以不能通过export直接输出一个变量或值
    b、export命令必须处于模块顶层,不能处于块级作用域内,import也是如此。这是因为处于条件代码块之中,就没有办法做静态优化了,违背了ES6模块的设计初衷
使用方法:

    // 输出一
    export var a = 1;

    // 输出二
    var b = 1;
    var c = 2;
    export { b, c };



    // 错误写法,因为没有提供对外的接口
    var d = 3;
    export d;
    export 1;

三、export default命令
  1) 定义: 为模块指定默认输出,在输入时无需知道原模块输出的变量名,可以用任意名称来指向该模块输出的内容
  2) 注意点:
    a、export命令用于指定模块的默认输出,一个模块只能有一个默认输出,因此export default命令只能使用一次,不然会报错
    b、本质上export default就是输出一个叫做default的变量或方法,然后系统允许你为它取任意名字。它后面不能跟变量声明语句
使用方法:

    // 输出一个变量
    var a = 1;
    export defualt a;

    // 输出一个值
    export default 1;

    // 输出一个方法
    export default funciton () {

    }

    // 输出一个方法
    function add() {}
    export default add;

    // 输出一个对象
    export default {

    }

四、import命令
  1) 定义: 使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块
  2) 注意点:
    a、加载export输出的变量、函数、类时,需要使用import命令接受一对大括号,里面指定要从其他模块导入的变量名,大括号里的变量名,必须与被导入模块对外接口的名称一样(import {variable} from './index.js');加载export default输出的变量、函数、类时,可以使用任意名称指向输出的方法,import后面不使用大括号(import allFn from '/index.js' )
    b、加载模块所有的输出值,利用星号(*)指定一个对象,所有输出值都加载在这个对象上面,包括export default
    c、import命令输入的变量都是只读的,不允许在加载模块的里面改写接口。只有当输出的是一个对象时,可以改写其属性
    d、import命令具有提升效果,会提升到整个模块的头部,首先执行。import命令是编译阶段执行的,在代码运行之前
    e、如果多次重复执行同义句import语句,那么只会执行一次,而不会执行多次。当多次使用import引入同一个module的不同方法时,引入的方法对应的都是一个module,import语句是单例模式
    f、import是静态执行的,所以不能使用表达式、变量、条件语句、代码块
    g、import输入时,语法要求不带as的默认值(export default输出的)永远在最前
使用方法:

    // 执行所加载的模块,引用它的模块无法获取它的任何信息
    import 'lodash';

    // 加载export输出的变量
    import { firstName } from './index.js';

    // 加载export default输出的变量
    import allFn from './index.js';

    // 在一条import语句中同时输入默认方法和其他接口
    import _, { each } from './lodash';

    // 加载模块所有的输出值,利用星号(*)指定一个对象,所有输出值都加载在这个对象上面
    import * as circle from './index.js';

    // 单例的表现
        // test1.js
        var a = 1;
        export function add() {
            a++;
            console.log(a);
        }
        // test2.js
        export { add } from './text1.js';
        // index.html
        import { add } from './test.js';
        import { add as getCount } from './test1.js';
        add();  // 2
        getCount(); // 3

五、as关键字:
  1) 定义: 使用as关键字可以为输出或输入的变量重新命名
  2) 注意点: as关键字的前面表示本来的名字,后面的是新名字(oldName as newName)
使用方法:

    // 输出时重命名
    var a = 1,
        b = 2;
    export {
        a as num1,
        b as num2
    }
    // 输入时重命名
    import * as numModule from './index.js';

    // 输入时重命名
    import {x as num1} from './index.js'

六、export和import的复合用法
  1) 定义: 在一个模块中,先输入后输出同一个模块,import语句可以和export语句写在一起
  2) 注意点:
    a. export和import写成一行时,接口其实没有被导入当前模块,只是相当于对外转发了这个接口,当前模块不能直接使用该接口
    b. 在export和import复合使用时,* 代表的是全部的其他接口(export输出的),不包含默认接口(export default输出的)
使用方法:

    // 使用一
        // 先输入后输出其他接口(export输出的),该模块中无法使用foo、bar
        export { foo, bar } from './index.js';
        // 别的模块中使用
        import { foo, bar } from './util.js';
    // 使用二
        // 整体输出其他接口(export输出的)
        export * from './index.js';
        // 别的模块中使用
        import * as all from './util.js';
    // 使用三
        // 先输入后输出默认接口(export default输出的),该模块中无法使用该方法
        export { default } from './index.js';
        // 别的模块中使用
        import allFn from './util.js';

七、跨模块使用
   定义: 是一个模块里的接口跨多个文件使用
使用方法:

    // test1.js
    export const A = 1;

    // test2.js
    export const B = 2;

    // 合并所有的接口在index.js中
    export { A } from './test1.js';
    export { B } from './test2.js';

八、ES6模块的好处
  1) ES6模块可以按需加载。在加载时,只加载import命令输入的方法和变量,模块中其他方法和变量不加载。这种加载称为"编译时加载"或者静态加载,即ES6可以在编译时就完成模块加载。而CommonJS和AMD模块,只能在运行时确定模块的的依赖关系、输入输出变量,是"运行时加载",因为只有运行时才能得到这个对象。比如CommonJS模块加载时,实质上是整体加载,生成一个对象,再从对象上读取加载的方法。
  2) 不再需要UMD模块格式了,将来服务器和浏览器都会支持 ES6 模块格式。目前,通过各种工具库,其实已经做到了这一点。
  3) 将来浏览器的新 API 就能用模块格式提供,不再必须做成全局变量或者navigator对象的属性。
  ) 不再需要对象作为命名空间(比如Math对象),未来这些功能可以通过模块提供。

九、ES6模块注意点
  ES6的模块自动采用严格模式,关于严格模块通常需要注意的就两点,一是变量必须声明后再使用,二是顶层的this指向undefined。

十、脚本和模块的区别
  脚本的特点:
     1) 是JavaScript源文件的一种
     2) 通过<script type="application/javascript">标签的src属性,引入外部js文件
     3) 在没有src属性时可以直接在<script type="application/javascript">标签内部写代码。由于浏览器脚本的默认语言是 JavaScript,因此type="application/javascript"可以省略
     4) 兼容性好,无法在其中使用export和import
     5) 脚本默认是同步加载的,可以在<script>上添加async(脚本一旦下完,渲染引擎就会中断渲染,执行这个脚本之后,再渲染)、defer(该脚本要等到整个页面在内存中正常渲染结束,也就是DOM结构完全生成,以及其他脚本执行完成后才会执行)属性来让其异步加载
  模块的特点:
     1) 是JavaScript源文件的一种
     2) 浏览器对于type="module"的<script>都是异步加载的,不会造成阻塞浏览器,即等到整个页面渲染完,再执行模块脚本,等同于打开了<script>标签的defer属性,如果有多个<script type="module">,会按照在页面出现的顺序依次执行。也可以自己在<script>设置async属性,这时只要加载完成,渲染引擎就会中断渲染立即执行,执行完成后再恢复渲染。一旦使用了async属性,<script type="module">就不会按照在页面出现的顺序执行,而是只要该模块加载完成,就执行该模块
     3) 通过<script type="module">标签的src属性引入外部模块,执行所加载的模块,引用它的模块无法获取它的任何信息
     4) 在没有src属性时,在<script type="module"></script>中间可以内嵌到网页中,语法行为与加载外部脚本完全一致。可以在其中使用import、export、export default命令
    5) 兼容性不好,IE不支持,Edge16支持
参考:
  ES6阮一峰--Module 的语法
  ES6阮一峰--Module 的加载实现

箭头函数的特点

一、箭头函数的特点
  1) 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this(也就是this的指向向上提一级)。正是因为它没有this,所以也就不能用作构造函数。
  2) 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
  3) 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
  4) 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
  5) 箭头函数没有自己的this,所以不能使用call()、apply()、bind()方法去改变this的指向
  6) 箭头函数中没有arguments、super、new.target,它们指向外层函数的对应变量。

二、箭头函数转换成ES5的代码

    // ES6
    function foo() {
        setTimeout( () => {
            console.log({'id', this.id});
        }, 100)
    } 
    // ES5
    function foo() {
        var _this =  this;
        setTimeout( function () {
            console.log('id', _this.id);
        }, 100)
    }

  来源: 阮一峰ES6入门--箭头函数

以上内容如有不对,希望大家指出,谢谢。


雨夜望月
207 声望13 粉丝