ES6+新特性说明
ES全称ECMAScript,ECMAScript是ECMA制定的标准化脚本语言。目前JavaScript使用的ECMAScript版本为ECMA-417。关于ECMA的最新资讯可以浏览 ECMA news查看。
一、ES6新特性(2015)
ES6的特性比较多,在 ES5 发布近 6 年(2009-11 至 2015-6)之后才将其标准化。两个发布版本之间时间跨度很大,所以ES6中的特性比较多。 在这里列举几个常用的:
- Let与Const
- 类
- 模块化
- 箭头函数
- 函数参数默认值
- 模板字符串
- 解构赋值
- 延展操作符
- 对象属性简写
- Promise
- Map 与 Set
- function*
- Object.defineProperty()
1. Let与Const
(1)let
let声明的变量,块级变量,声明的对象具有块级作用域。
四大特性:
-
块级作用域:{}下起作用。
-
同级作用域下,只能存在一个(let声明的变量不能重复声明)。
-
不能变量提升。
-
let 在循环中经常被使用(重点),i变量只在for()内有效,不污染其他区域。
实例:
let a = 3;
if(1){
console.log(a); // 结果:error
//注意:块级变量不会变量提升
let a = 4;
}
console.log(a); // 结果:3
let a = 1;
let a = 2;
console.log(a); // 结果:error
//i变量只在for()内有效,不污染其他区域
var arr = ['apple', 'banana', 'peal'];
for (let i = 0; i < arr.length; i++) {
}
console.log(i); // 报错:ReferenceError: i is not defined
(2)const
const声明的变量称为常量,即不可改变其值的量。
四大特性:
-
不能修改常量值。
-
不能重复声明。
-
声明后必须赋值,因为const声明的常量不能被修改。
-
常量不可改的是其地址,声明obj对象就会生成指向其内容的地址(不可修改),但是可以修改其内容中的属性。
实例:
const i = 20;
console.log(i); //=> 20
/* 不能修改常量值 */
// i = 30;
// console.log(i); //=> error
/* 不能重复声明 */
// const i = 30;
// console.log(i); //=> error
/* 声明后必须赋值 */
// const c;
// console.log(c); //=> error
/* 不能改变地址指向 */
const obj = {name:'yaodao',age:20};
obj.name = 'yaodao2';
console.log(obj); //=> { name: 'yaodao2', age: 20 }
obj = 2;
console.log(obj); //=> error
2. 类
ES6 引入了class(类),让JavaScript的面向对象编程变得更加简单和易于理解。
class Person {
/* 构造器 */
constructor(name, sex, age) {
this.name = name;
this.sex = sex;
this.age = age;
}
/* 原型 */
walk() {
if (this.age <= 2) {
return console.log('我不会走路');
}
if (this.age >2 && this.age < 4) {
return console.log('我会走路了');
}
return console.log('走路');
}
study(skill) {
console.log('学习' + skill);
}
introduce() {
console.log(`我是${this.name},我是一个${this.sex === 'male' ? "男" : "女"}孩,今年${this.age}岁了。`);
}
}
/* 继承类 */
class Boy extends Person { // extends关键字继承
/* 构造器 */
constructor(name, age) {
/* super语法可以调用父对象上的函数 */
// 子类必须要在constructor中指定super 函数,否则在新建实例的时候会报错。
// 如果没有置顶consructor,默认带super函数的constructor将会被添加。
super('汤姆', 'male', 14);
}
study(e) {
console.log(super.study(e));
}
}
var boy = new Boy();
boy.introduce(); // 我是汤姆,我是一个男孩,今年 14 岁了。
boy.study('English');//学习English
console.log(boy instanceof Boy);// true
console.log(boy instanceof Person);//true
3. 模块化
ES5不支持原生的模块化,在ES6中模块作为重要的组成部分被添加进来。模块的功能主要由 export 和 import 组成。每一个模块都有自己单独的作用域,模块之间的相互调用关系是通过 export 来规定模块对外暴露的接口,通过import来引用其它模块提供的接口。同时还为模块创造了命名空间,防止函数的命名冲突。
(1)export(导出)
ES6允许在一个模块中使用export来导出多个变量或函数。
导出变量
exportVar.js:
var a = 10;
var b = 20;
/* 导出变量 */
export {a,b};
导出常量
export const sqrt =Math.sqrt;
导出函数
export function fn(e){
return e;
}
(2)import(导入)
importVar.js:
定义好模块的输出以后就可以在另外一个模块通过import引用。
导入变量
import {a,b} from './exportVar.js'
导入常量
import {sqrt} from './exportVar.js'
导入函数
import './exportVar.js';
console.log(fn('hello'));
扩展:Module看import和require区别
import和require,开发中一定不少见,尤其是需要前端工程化的项目现在都已经离不开node了,在node环境下这两者都是大量存在的,大体上来说他们都是为了实现JS代码的模块化,那为什么会出现两种方案呢,又有什么不同呢?
模块化的不同解决方案
requirejs遵循AMD,seajs遵循CMD,node的module遵循CommonJS规范,虽然写法上有所不同,都是为了能够间接实现模块化的基础上保持较为一致的代码风格。
随着ES2015的发布,官方标准定义了一种模块化的方案,那就是import、export。可是,标准毕竟是标准,各大浏览器和node终端要实现标准还是有一段距离的,目前来说都2018年了主流浏览器都还没实现,还得依赖转换工具(例如babel)转为ES5的代码之后浏览器才能解析。所以这也就解释了为什么我们的工程化代码中nodeJS遵循的CommonJS规范和ES6的模块化方案并存的现象。
两者的区别:
- import是ES6标准中的模块化解决方案,require是node中遵循CommonJS规范的模块化解决方案。
- 后者支持动态引入,也就是require(${path}/xx.js),前者目前不支持,但是已有提案。
- 前者是关键词,后者不是。
- 前者是编译时加载,必须放在模块顶部,在性能上会比后者好一些,后者是运行时加载,理论上来说放在哪里都可以。
- 前者采用的是实时绑定方式,即导入和导出的值都指向同一个内存地址,所以导入值会随着导出值变化。而后者在导出时是指拷贝,就算导出的值变化了,导入的值也不会变化,如果想要更新值就要重新导入。
- 前者会编译成require/exports来执行。
require/exports用法:
导出变量
// test.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
module.exports = { firstName, lastName, year };
引入变量
// demo.js
const test = require('./test.js');
console.log(test); // {firstName: "Michael", lastName: "Jackson", year: 1958}
其他导出引入内容同上…
总结
import和require就是两种不同的JS模块化实现方式而已,由于之前npm生态的很多包都是基础CommonJS规范写的,所以相当一段时间之内必然是import和require这两种模块引入方式共存的。
总体来说时代总是发展的,ES6作为语言规范是迟早会被各大主流浏览器支持的,不然也就称不上主流浏览器了。所以为了长远考虑,我们还是尽量使用ES6的import来引入模块,等以后浏览器支持了我们也就可以少改一些代码了。
4. 箭头(Arrow)函数
箭头函数的箭头=>
之前是一个空括号、单个的参数名、或用括号括起的多个参数名,而箭头之后可以是一个表达式(作为函数的返回值),或者是用花括号括起的函数体(需要自行通过return来返回值,否则返回的是undefined)。
实例用法:
var fn1 = function(s1,s2){
return s1+s2;
}
// ES6中的新声明函数方式:箭头函数
var fn2 = (s1,s2) =>{
return s1+s2;
}
console.log(fn1(1,2)); //=> 3
console.log(fn2(1,2)); //=> 3
使用技巧:
1)有一个参数,括号可以省略。
var fn = s =>{
//内容...
}
2)没有一个参数,一定要有括号。
var fn = ()=>{
//内容...
}
3)多于一个参数,一定要用逗号隔开。
var fn = (s1,s2,s3)=>{
//内容...
}
4)如果方法体只有一句代码,可以省略{},如果是返回值可以省略{}----局限性比较大。
var fn = (s1,s2) => s1+s2;
fn(1,2);
var fn = (s1,s2) => console.log('yaodao');
5)如果方法体不止一句代码,就不可以省略,每句代码需要用;隔开。
var fn = s =>{
var a = 2;
var b = a+s;
console.log(b);
}
fn(2); //=> 4
注意点:
-
箭头函数没有自己的this,函数内部写的this,指的是外层代码块中的this,所以在箭头函数内部不要使用this。
-
this指向定义时的对象,也就是说箭头函数一旦定义完成,它的指向是固定的,没法改变,它的指向是定义时所在的作用域,而不是执行时的作用域。
-
箭头函数不能用作构造函数。
-
箭头函数内部不存在arguments,箭头函数体中arguments其实指向的是外层函数的arguments。
var obj1 = {
name:'yaodao',
fn:()=>{
console.log(this.name);
console.log(this);
},
}
obj1.fn();
结果:
个人总结:
1.箭头函数的this指向是定义(声明)时就绑定的,和执行无关
2.箭头函数没有自己的this,继承了当前所在环境执行时的this指向
遇到箭头函数解题:
-
看当前箭头函数定义的环境是什么?( 小技巧:找上一个{})
-
遇到箭头函数的执行或调用,忽略,不看,对箭头函数this指向没有影响
-
判断当前环境执行时this指向谁,箭头函数的this就指向谁
比如:
var url = "window";
function Antzone() {
let func = () => {
console.log(this.url);
}
func();
}
Antzone();
解析:箭头没有自己的this指向,它是定义在Antzone()函数中,当Antzone()调用的时候,this指向window,所以打印是window。
var name = 'window';
var obj = {
name: 'obj',
say: function () {
setTimeout(function () {
var b2 = () => this.name;
console.log(b2());
}, 100);
}
}
obj.say();
解析:b2()调用这个的时候,由于是箭头函数,没有自己的this,所以往上找,看他定义的环境,他是定义在延时器中,延时器中的this指向window,所以打印是window。
5. 函数参数默认值
使用默认值写法:
function foo(width=20,color='red'){
//...
}
不使用默认值:
function foo(width,color){
var width = width||20;
var color = color||'red';
//...
}
这样写一般没问题,但当 参数的布尔值为false
时,就会有问题了。比如,我们这样调用foo函数:
foo(0,'');
因为 0的布尔值为false
,这样height的取值将是50。同理color的取值为‘red’。
所以说, 函数参数默认值
不仅能是代码变得更加简洁而且能规避一些问题。
6. 模板字符串
ES6支持模板字符串,使得字符串的拼接更加的简洁、直观。
不使用模板字符串:
var first = 'hello';
var last = 'world';
var str = 'I use '+first+' '+last+'!!!'
console.log(str); //=> I use hello world!!!
使用模板字符串:
var first = 'hello';
var last = 'world';
var str = `I use ${first} ${last} !!!`;
console.log(str); //=> I use hello world!!!
在ES6中通过 ${}
就可以完成字符串的拼接,只需要将变量放在大括号之中。
解构赋值语法是JavaScript的一种表达式,可以方便的从数组或者对象中快速提取值赋给定义的变量。
7. 解构赋值
简而言之就是解析对象结构,对应赋值。
解构赋值语法是JavaScript的一种表达式,可以方便的从数组或者对象中快速提取值赋给定义的变量。
(1)获取数组中的值
从数组中获取值并赋值到变量中,变量的顺序与数组中对象顺序对应。
var foo = ["one", "two", "three", "four"];
var [one, two, three] = foo;
console.log(one); // "one"
console.log(two); // "two"
console.log(three); // "three"
//如果你要忽略某些值,你可以按照下面的写法获取你想要的值
var [first, , , last] = foo;
console.log(first); // "one"
console.log(last); // "four"
//或者可以这样
var a, b; //先声明变量
[a, b] = [1, 2];
console.log(a); // 1
console.log(b); // 2
如果没有从数组中的获取到值,你可以为变量设置一个默认值。 (会依照次序赋值)
var a, b;
[a=1, b=2] = [10];
console.log(a); // 10
console.log(b); // 2
通过解构赋值可以方便的交换两个变量的值。
var a = 10;
var b = 20;
[a, b] = [b, a];
console.log(a); // 20
console.log(b); // 10
(2)获取字符串中的字符
// 字符串的解构赋值
let [w,e,r] = 'che';
console.log(w);// "c"
console.log(e);// "h"
console.log(r);// "e"
(3)获取对象中的属性值
const student = {
name:'yaodao',
age:20,
city:'Guangzhou'
};
const {name,age,city} = student;
console.log(name); // "yaodao"
console.log(age); // 20
console.log(city); // "Guangzhou"
8. 延展操作符("模式匹配"赋值)
延展操作符...
可以在函数调用/数组构造时, 将数组表达式或者string在语法层面展开;还可以在构造对象时, 将对象表达式按key-value的方式展开。
简单例子:
let [i,...p] = ['1','2','3','4','5']
console.log(i);
console.log(...p);
(1)用法
数组构造或字符串构造:
let [i,...p] = ['1','2','3','4','5']
深浅拷贝:
参考博客: https://blog.csdn.net/Errrl/article/details/104034726
(2)应用场景
在函数调用时使用延展操作符
function sum(x, y, z) {
return x + y + z;
}
const numbers = [1, 2, 3];
//不使用延展操作符
console.log(sum.apply(null, numbers));//6
//使用延展操作符
console.log(sum(...numbers));// 6
构造数组
没有展开语法的时候,只能组合使用 push,splice,concat 等方法,来将已有数组元素变成新数组的一部分。有了展开语法, 构造新数组会变得更简单、更优雅:
const stu = ['laoda','laoer'];
const school = [...stu,'laosan','laosi'];
console.log(school); //=> ["laoda", "laoer", "laosan", "laosi"]
和参数列表的展开类似, ...
在构造字数组时, 可以在任意位置多次使用。
深浅拷贝
/* 对象的拷贝 */
var sObj = {
name: 'chen',
age: 10
};
var cObj = {...sObj};
cObj.name= 'yaodao';
console.log(sObj); //=> {name: 'yaodao', age: 10}
/* 数组的拷贝 */
var arr = [1,2,3]
var res= [...arr]
res[0] = 10;
console.log(arr); //=> [1, 2, 3]
console.log(res); //=> [10, 2, 3]
连接数组、对象
/* 连接数组 */
var arr = [1,2,3];
var arr2 = [4,5,6];
var res = [...arr,...arr2];
console.log(res); //=> [1, 2, 3, 4, 5, 6]
/* 连接对象 */
var obj = {name:'chen'};
var obj2 = {age:20};
var res = {...obj,...obj2};
console.log(res); //=> {name:'chen',age:20}
9. 对象属性简写
在ES6中允许我们在设置一个对象的属性的时候不指定属性名。
没有使用ES6
const name='yaodao',age= 20,sex='male';
const introduct = {
name:name,
age:age,
sex:sex
};
console.log(introduct);//{name: "yaodao", age: 20, sex: "male"}
明显重复很多操作,显得非常累赘。
使用ES6
const name='yaodao',age= 20,sex='male';
const introduct = {
name,
age,
sex
};
console.log(introduct);//{name: "yaodao", age: 20, sex: "male"}
对象中直接写变量,非常简洁明了。
10. Promise
Promise 是异步编程的一种解决方案,比传统的解决方案callback更加的优雅。它最早由社区提出和实现的,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
举个简单的例子:
回调地狱:
/* 嵌套两个setTimeout回调函数 */
setTimeout(function(){
console.log('I'); // 1秒后输出"I"
setTimeout(function(){
console.log('use'); // 2秒后输出"am"
setTimeout(function(){
console.log('hello'); // 3秒后输出"use"
setTimeout(function(){
console.log('world'); // 4秒后输出"use"
//...回调地狱
}, 1000);
}, 1000);
}, 1000);
}, 1000);
使用Promise:
/* 封装一个promise函数 */
function fn(){
return new Promise(function(resolve,reject){
setTimeout(resolve,1000);
});
};
//链式调用
//注意每一个then一点要return函数fn。
fn()
.then(function(){
console.log('I');
return fn();
})
.then(function(){
console.log('use');
return fn();
})
.then(function(){
console.log('hello');
return fn();
})
.then(function(){
console.log('world');
})
上面的的代码使用四个.then来进行异步编程串行化,避免了回调地狱
11. Map 与 Set
JavaScript的默认对象表示方式{}
可以视为其他语言中的Map
或Dictionary
的数据结构,即一组键值对。
但是JavaScript的对象有个小问题,就是键必须是字符串。但实际上Number或者其他数据类型作为键也是非常合理的。
为了解决这个问题,最新的ES6规范引入了新的数据类型Map
。
(1)Map
Map
是一组键值对的结构,具有极快的查找速度。
举个例子,假设要根据同学的名字查找对应的成绩,如果用Array
实现,需要两个Array
:
var names = ['Michael', 'Bob', 'Tracy'];
var scores = [95, 75, 85];
给定一个名字,要查找对应的成绩,就先要在names中找到对应的位置,再从scores取出对应的成绩,Array越长,耗时越长。
如果用Map实现,只需要一个“名字”-“成绩”的对照表,直接根据名字查找成绩,无论这个表有多大,查找速度都不会变慢。用JavaScript写一个Map如下:
var m = new Map([['Michael', 95],['Bob', 75], ['Tracy', 85]]);
m.get('Michael'); // 95
初始化Map
需要一个二维数组,或者直接初始化一个空Map
。Map
具有以下方法:
- set(); 设置键值对
- has(); 是否存在键值对
- get(); 获取键值对
- delete(); 删除键值对
var m = new Map(); //创建Map
m.set('Adam', 67); //设置key-value
m.set('Bob', 59);
m.has('Adam'); //是否存在key 'Adam': true
m.get('Adam'); //获取键值,输出67
m.delete('Adam'); //删除key 'Adam'
m.get('Adam'); //undefined
由于一个key只能对应一个value,所以,多次对一个key放入value,后面的值会把前面的值冲掉:
var m = new Map();
m.set('Adam', 67);
m.set('Adam', 88);
m.get('Adam'); // 88
(2)Set
Set
和Map
类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在Set
中,没有重复的key。(达到数组去重的作用)
要创建一个Set
,需要提供一个Array
作为输入,或者直接创建一个空Set
:
var s1 = new Set();
var s2 = new Set([1, 2, 3]);
重复元素在Set
中自动被过滤:
var s = new Set([1, 2, 3, 3, '3']);
console.log(s); //=> Set(4) {1, 2, 3, "3"}
通过add(key)
方法可以添加元素到Set
中,可以重复添加,但不会有效果,原因就是key不能重复。
var s = new Set([1, 2, 3, 3, '3']);
s.add(4);
console.log(s); // Set(4) {1, 2, 3, 4}
s.add(4);
console.log(s); // 仍然是 Set {1, 2, 3, 4}
通过delete(key)
方法可以删除元素:
var s = new Set([1, 2, 3]);
console.log(s); // Set(3) {1, 2, 3}
s.delete(3);
console.log(s); // Set(2) {1, 2}
12. function*
function*
这种声明方式(function
关键字后跟一个星号)会定义一个生成器函数 (generator function),它返回一个 Generator
对象。
语法:
function* name([param[, param[, ... param]]]) { statements }
定义:
生成器函数在执行时能暂停,后面又能从暂停处继续执行。
调用一个生成器函数并不会马上执行它里面的语句,而是返回一个这个生成器的 迭代器 ( iterator)对象。当这个迭代器的 next()
方法被首次(后续)调用时,其内的语句会执行到第一个(后续)出现 yield 关键字的位置为止,yield后紧跟迭代器要返回的值。或者如果用的是 yield*(多了个星号),则表示将执行权移交给另一个生成器函数(当前生成器暂停执行)。注意:作为一个生成器函数所以不能被当作构造器使用。
next()
方法返回一个对象,这个对象包含两个属性:value 和 done,value 属性表示本次 yield
表达式的返回值,done 属性为布尔类型,表示生成器后续是否还有yield
语句,即生成器函数是否已经执行完毕并返回。
调用 next()
方法时,如果传入了参数,那么这个参数会传给上一条执行的 yield语句左边的变量,例如下面例子中的x
:
function *iter(){
yield 10;
x=yield 'hello';
yield x;
}
var iter_obj=iter();
console.log(iter_obj.next());// 执行 yield 10,返回 10
console.log(iter_obj.next());// 执行 yield 'hello',返回 'hello'
/* 传参与赋值 */
console.log(iter_obj.next(100));// 将 100 赋给上一条 yield 'foo' 的左值,即执行 x=100,返回 100
console.log(iter_obj.next());// 执行完毕,value 为 undefined,done 为 true
yield*讲解:
表示将执行权移交给另一个生成器函数(当前生成器暂停执行)。
function* otherIter(i) {
yield i + 1;
yield i + 2;
yield i + 3;
}
function* capitalIter(i){
yield i;
yield* otherIter(i);// 将执行移交给otherIter,暂停后面的yield
yield i + 10;
}
var cap = capitalIter(10);
console.log(cap.next().value); // 10
console.log(cap.next().value); // 11
console.log(cap.next().value); // 12
console.log(cap.next().value); // 13
console.log(cap.next().value); // 20
显性返回:
只要存在return就称为显性返回,其后的yield将无法被next()。
function* iter() {
yield "1";
return "2"; //显式返回处,可以观察到 done 也立即变为了 true
yield "不被执行了";// 不会被执行了
}
var it = iter()
console.log(it.next()); // { value: "1", done: false }
console.log(it.next()); // { value: "2", done: true }
console.log(it.next()); // { value: undefined, done: true }
使用迭代器将数组降维:
/* 迭代器(利用递归,yield*转移执行权) */
function* iterArr(arr) {
if (Array.isArray(arr)) {
for(let i=0; i < arr.length; i++) {
yield* iterArr(arr[i]);
}
} else {
yield arr;
}
}
测试:
var arr = ['a', ['b', 'c'], ['d', 'e']];
for(var x of iterArr(arr)) {
console.log(x); //=> a b c d e
}
/* 获取一维数组 */
/* 直接使用延展操作符操作 */
var arr = ['a', ['b', 'c'], ['d', 'e']];
var iter = iterArr(arr);
arr = [...iter]; //=> ["a", "b", "c", "d", "e"]
13. Object.defineProperty()(重点)
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
语法:
Object.defineProperty(obj, prop, descriptor);
参数定义与说明:
-
obj 要在其上定义属性的对象。
-
prop 要定义或修改的属性的名称。
-
descriptor 将被定义或修改的属性描述符。
该方法允许精确添加或修改对象的属性。通过赋值操作添加的普通属性是可枚举的,能够在属性枚举期间呈现出来(for…in或 Object.keys)。这个方法允许修改默认的额外选项(或配置)。默认情况下,使用 Object.defineProperty()
添加的属性值是不可修改的。
descriptor属性描述符
对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个具有值的属性,该值可能是可写的,也可能不是可写的。存取描述符是由getter-setter函数对描述的属性。描述符必须是这两种形式之一;不能同时是两者。
数据描述符和存取描述符均具有以下可选键值(默认值是在使用Object.defineProperty()定义属性的情况下):
-
configurable
(可配置)当且仅当该属性的 configurable 为 true 时,该属性描述符
才能够被改变,同时该属性也能从对应的对象上被删除。默认为 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)关键字,将会产生一个异常。
实例说明:
/************* 数据描述符 *************/
var o = {};
Object.defineProperty(o, "a", {
value : 37,
writable : true, //可写,确定属性值是否可以重新分配。
enumerable : true, //可枚举,确定是否可以在 for...in 循环和 Object.keys() 中被枚举
configurable : true //可配置,确定对象的属性是否可以被删除,以及除value和writable特性外的其他特性是否可以被修改。
});
//console.log(o.a); //=> 37
/************* 存取描述符 *************/
var bValue;
Object.defineProperty(o, "b", {
get : function(){
return bValue;
},
set : function(newValue){
bValue = newValue;
},
enumerable : true,
configurable : true
});
/* 设置一个初始化值,调用set */
o.b = 38;
/* 获取属性属性值,调用get */
console.log(o.b); //=> 38
/* 重置属性值,调用set */
o.b = 40;
console.log(bValue); //=> 40
上述代码,对 vue 响应式原理有很好的理解。可以这么说 vue 响应式原理就是基于此。