这篇文章主要介绍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入门--箭头函数
以上内容如有不对,希望大家指出,谢谢。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。