var 和 let/const 的比较
除了 var ,我们现在还可以使用两个新的标识符来定义一个变量 — let 和 const。
和 var 不一样的是,let 和 const 不存在变量提升。
使用var的栗子:1
2
3
4
5
6
7
8
9var name = 'global';
function getFood(food) {
if (food) {
var name = 'local';
return name;
}
return name;
}
getFood(false); // undefined
当我们用 let 代替 var 的时候,观察会发生什么:1
2
3
4
5
6
7
8
9let name = 'global';
function getFood(food) {
if (food) {
let name = 'local';
return name;
}
return name;
}
getFood(false); // 'global'
当我们重构使用 var 的老代码的时候应该注意上面的变化。盲目地使用 let 替换 var 可能会出现出乎意料的情况。
1 | console.log(x); // ReferenceError: x is not defined |
注意: let 和 const 是块级作用域,因此在变量未被定义之前使用它会产生一个 ReferenceError。
最佳实践: 在早前代码中使用 var 声明意味着需要很小心地编写代码;而当在新的项目编写代码,使用 let 声明一个可以改变的变量,用 const(常量)声明一个不能被重新赋值的变量。
用块级作用域代替 IIFES
函数立即执行表达式的常见用法是创造一个闭包作用域的函数,在 ES6 中,我们能够创造一个块级作用域
而不单单限于函数作用域。
IIFES:1
2
3
4(function () {
var name = 'ChaChaHui';
}());
console.log(name); // Reference Error
使用 ES6 的块级作用域:1
2
3
4{
let name = 'ChaChaHui';
}
console.log(name); // Reference Error
箭头函数
我们经常需要给嵌套函数维护一个作用域的上下文 this。
看看这个栗子:1
2
3
4
5
6
7
8function Person(name) {
this.name = name;
}
Person.prototype.prefixName = function (arr) {
return arr.map(function (character) {
return this.name + character; // Cannot read property 'name' of undefined
});
};
一个常用的解决办法是把 this 存在一个变量中:1
2
3
4
5
6
7
8
9function Person(name) {
this.name = name;
}
Person.prototype.prefixName = function (arr) {
var that = this; // Store the context of this
return arr.map(function (character) {
return that.name + character;
});
};
我们也可以传递一个合适的 this 上下文:1
2
3
4
5
6
7
8function Person(name) {
this.name = name;
}
Person.prototype.prefixName = function (arr) {
return arr.map(function (character) {
return this.name + character;
}, this);
}
我们还可以绑定上下文:1
2
3
4
5
6
7
8function Person(name) {
this.name = name;
}
Person.prototype.prefixName = function (arr) {
return arr.map(function (character) {
return this.name + character;
}.bind(this));
};
使用 箭头函数,this 将不会受到影响,并且我们可以重写上面的函数:
1 | function Person(name) { |
最佳实践:当你需要维护一个 this 上下文的时候,如在回调函数中推荐使用箭头函数,这样就可以绑定上下文 this
在我们写一个函数的时候,箭头函数更加简洁并且可以很简单地返回一个值:1
var squares = arr.map(function (x) { return x * x }); // Function Expression
1 | const arr = [1, 2, 3, 4, 5]; |
最佳实践:尽可能的使用箭头函数代替原来的写法。
字符串
在 ES6 中,标准库升级了很多,在这些变化中有许多新的字符串方法,比如 .includes( ) 和 .repeat( )。
.includes()1
2
3var string = 'food';
var substring = 'foo';
console.log(string.indexOf(substring) > -1);
之前我们使用 .indexOf( ) 函数的返回值是否 >-1 来判断字符串是否包含某些字符串,现在我们更简单地使用 .includes ( )来返回一个布尔值来判断:1
2
3const string = 'food';
const substring = 'foo';
console.log(string.includes(substring)); // true
.repeat( )1
2
3
4
5
6
7function repeat(string, count) {
var strings = [];
while(strings.length < count) {
strings.push(string);
}
return strings.join('');
}
在 ES6 中,可以更简便地实现:1
2// String.repeat(numberOfRepetitions)
'meow'.repeat(3); // 'meowmeowmeow'
模板字符串
使用模版字符串我们就可以不用对某些特殊字符进行转义处理了:1
var text = "This string contains \"double quotes\" which are escaped.";
1 | let text = `This string contains "double quotes" which don't need to be escaped anymore.`; |
模板字符串还支持插入,可以把变量值和字符串连接起来。1
2
3var name = 'Tiger';
var age = 13;
console.log('My cat is named ' + name + ' and is ' + age + ' years old.');
ES6语法更简单:1
2
3const name = 'Tiger';
const age = 13;
console.log(`My cat is named ${name} and is ${age} years old.`);
在 ES5 中,需要换行时,需要这样:1
2
3
4
5var text = (
'cat\n' +
'dog\n' +
'nickelodeon'
);
或者这样:1
2
3
4
5var text = [
'cat',
'dog',
'nickelodeon'
].join('\n');
模板字符串可以支持换行并且不需要额外的处理:1
2
3
4let text = ( `cat
dog
nickelodeon`
);
模板字符串还支持表达式:1
2let today = new Date();
let text = `The time and date is ${today.toLocaleString()}`;
解构
解构可以让我们用一个更简便的语法从一个数组或者对象(即使是深层的)中分离出来值,并存储他们。
解构数组
1 | var arr = [1, 2, 3, 4]; |
数组解构赋值:1
2
3let [a, b, c, d] = [1, 2, 3, 4];
console.log(a); // 1
console.log(b); // 2
解构对象
最基本的解构
ES5:1
2
3var luke = { occupation: 'jedi', father: 'anakin' };
var occupation = luke.occupation; // 'jedi'
var father = luke.father; // 'anakin'
对象解构赋值:1
2
3
4let luke = { occupation: 'jedi', father: 'anakin' };
let {occupation, father} = luke;
console.log(occupation); // 'jedi'
console.log(father); // 'anakin'
解构并使用别名
有时接口定义的字段往往带有下划线,但我们的前端更便好于驼峰式命名,那么可以使用别名(rename):1
2
3
4
5
6const user = {
id: 123,
nick_name: 'hehe'
};
const {nick_name: nickName} = user;
console.log(nickName); //prints: hehe
解构嵌套对象
有时我们会遇到嵌套对象,如果我们了解未足够多时,会写出这种解构:1
2
3
4
5
6
7
8
9
10
11const user = {
id: 123,
name: 'hehe',
education: {
degree: 'Masters'
}
};
// 假设我们要提取degree
const {education} = user;
const {degree} = education;
我们会写两行,一层层的剥开,明显繁琐,如果这个对象有三四层结构那简直无法入目。其实可以用解构一步到位的:1
2
3
4
5
6
7
8
9const user = {
id: 123,
name: 'hehe',
education: {
degree: 'Masters'
}
};
const {education: {degree}} = user;
console.log(degree); //prints: Masters
没错,就是比别名方法多了一个{ }
如果没有外层怎么办
假设要解构的数据是由接口返回的,由于某种原因会导致某个字段丢失。我们会往往遇到这种意外:1
2
3
4
5const user = {
id: 123,
name: 'hehe'
};
const {education: {degree}} = user; // TypeError: Cannot match against 'undefined' or 'null'.
这时你是否会觉得还是我们原始的方法好使:1
2const education = user || {};
const degree = education.degree;
其实,神奇的解构可以让你定义一个缺省值,这样,我们不仅可以达到数据防御的目的,而且告别啰嗦的写法了:1
2
3
4
5
6
7
8
9
10
11const user = {
id: 123,
name: 'hehe'
};
const {
education: {
degree
} = {}
} = user;
console.log(degree); //prints: undefined
这明显是一股清流啊。
更深层次的对象怎么办?
1 | const user = { |
这样整体给education设置一个缺省值,可读性更强,这又是一股清流。
在代码中灵活使用解构不仅可以使代码简洁可读,而且逼格大大提升。
模块
在 ES6 之前,我们使用Browserify这样的库来创建客户端的模块化,在node.js中使用require。
在 ES6 中,我们可以直接使用所有类型的模块化(AMD 和 CommonJS)。
使用CommonJS的导出
1 | module.exports = 1; |
使用ES6的导出
在ES6中我们可以暴露多个值,使用exports:1
2export let name = 'David';
export let age = 25;
或者暴露一个对象列表:1
2
3
4
5
6
7function sumTwo(a, b) {
return a + b;
}
function sumThree(a, b, c) {
return a + b + c;
}
export { sumTwo, sumThree };
我们还可以暴露函数、对象和其他的值,通过简单地使用 export 这个关键字:1
2
3
4
5
6export function sumTwo(a, b) {
return a + b;
}
export function sumThree(a, b, c) {
return a + b + c;
}
最后,我们还可以绑定一个默认的输出:1
2
3
4
5
6
7
8
9
10
11
12
13
14function sumTwo(a, b) {
return a + b;
}
function sumThree(a, b, c) {
return a + b + c;
}
let api = {
sumTwo,
sumThree
};
export default api;
/* Which is the same as
* export { api as default };
*/
最佳实践:总是在模块的最后面使用 export default 方法,可以让暴露的东西更加清晰并且可以节省时间去找出暴露出来值的名字。尤其是在 CommonJS 中,常见做法就是暴露一个简单的值或者对象。通过这种模式,可以让我们的代码更加易读,并且允许我们在 ES6 和 CommonJS 模块之间插值。
ES6 中的导入
在 ES6 中同样提供了多样的导入方式,我们可以这么导入一个整个文件:1
需要着重注意的一点是简单的导入整个文件会在那个文件的顶部执行所有的代码
和 Python 中类似,我们可以命名导入的值:1
import { sumTwo, sumThree } from 'math/addition';
我们还可以重命名导入:1
2
3
4import {
sumTwo as addTwoNumbers,
sumThree as sumThreeNumbers
} from 'math/addition';
另外,我们可以导入所有的东西(整体加载):1
import * as util from 'math/addition';
最后,我们可以从一个模块中导入一个值的列表:1
2import * as additionUtil from 'math/addition';
const { sumTwo, sumThree } = additionUtil;
可以像这样导入默认绑定的输出:1
2import api from 'math/addition';
// Same as: import { default as api } from 'math/addition';
虽然最好保持导出的简单,但如果需要的话我们有时可以混合默认的导入和混合导入。当我们这样导出的时候:1
2// foos.js
export { foo as default, foo1, foo2 };
我们可以这样导入它们:1
import foo, { foo1, foo2 } from 'foos';
当我们用Commonjs的语法导入一个模块的暴露出口时(比如 React),我们可以这样做:1
2import React from 'react';
const { Component, PropTypes } = React;
还有更精简的写法:1
import React, { Component, PropTypes } from 'react';
注意:导出的值是动态引用的,而不是拷贝。因此,在一个模块中改变一个变量的绑定将影响输出模块中的值。应该避免改变这些导出值的公共接口。
参数
在 ES5 中,在函数中我们需要各种操作去处理默认参数、不定参数和重命名参数等需求,在 ES6 中我们可以使用更简洁的语法完成这些需求:
默认参数
1 | function addTwoNumbers(x, y) { |
ES6 中,函数的参数可以支持设置默认值:1
2
3function addTwoNumbers(x=0, y=0) {
return x + y;
}
1 | addTwoNumbers(2, 4); // 6 |
rest 参数
在 ES5 中,我们需要这么处理不定参数:1
2
3
4
5function logArguments() {
for (var i=0; i < arguments.length; i++) {
console.log(arguments[i]);
}
}
使用rest,我们就可以处理不确定数目的参数:1
2
3
4
5function logArguments(...args) {
for (let arg of args) {
console.log(arg);
}
}
命名参数
在 ES5 中是使用配置对象的模式来处理命名参数,jQuery 中的使用:1
2
3
4
5function initializeCanvas(options) {
var height = options.height || 600;
var width = options.width || 400;
var lineStroke = options.lineStroke || 'black';
}
我们可以利用解构的一个函数的形参实现相同的功能:1
2
3function initializeCanvas({ height=600, width=400, lineStroke='black'}) {
// Use variables height, width, lineStroke here
}
如果我们想使整个值可选择,我们可以解构赋值一个空的对象:1
2
3function initializeCanvas({ height=600, width=400, lineStroke='black'} = {}) {
// ...
}
展开操作
在 ES5 中,我们可以 apply Math.max 方法来获得一个数组中的最大值:1
Math.max.apply(null, [-1, 100, 9001, -32]); // 9001
在 ES6 中,我们可以通过展开操作把一个数组的值作为参数传递给一个函数:1
Math.max(...[-1, 100, 9001, -32]); // 9001
我们可以更简洁地使用这个语法来合并数组:1
2let cities = ['San Francisco', 'Los Angeles'];
let places = ['Miami', ...cities, 'Chicago']; // ['Miami', 'San Francisco', 'Los Angeles', 'Chicago']
类Classes
在 ES6 之前,我们通过构造函数来创造一个类,并且通过原型来扩展属性:1
2
3
4
5
6
7
8function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
Person.prototype.incrementAge = function () {
return this.age += 1;
};
然后可以这样继承类:1
2
3
4
5
6
7
8
9
10
11
12function Personal(name, age, gender, occupation, hobby) {
Person.call(this, name, age, gender);
this.occupation = occupation;
this.hobby = hobby;
}
Personal.prototype = Object.create(Person.prototype);
Personal.prototype.constructor = Personal;
Personal.prototype.incrementAge = function () {
Person.prototype.incrementAge.call(this);
this.age += 20;
console.log(this.age);
};
在 ES6 中,提供了更多的语法糖,可以直接创造一个类:1
2
3
4
5
6
7
8
9
10class Person {
constructor(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
incrementAge() {
this.age += 1;
}
}
使用 extends 关键字来继承一个类:1
2
3
4
5
6
7
8
9
10
11
12class Personal extends Person {
constructor(name, age, gender, occupation, hobby) {
super(name, age, gender);
this.occupation = occupation;
this.hobby = hobby;
}
incrementAge() {
super.incrementAge();
this.age += 20;
console.log(this.age);
}
}
最佳实践:虽然使用 ES6 的语法创造类的时候,js引擎是如何实现类以及如何操作原型是令人费解的,但是未来对初学者来说这是一个好的开始,同时也可以让我们写更简洁的代码。
Symbols
Symbols在 ES6 之前就已经存在,但是我们现在可以直接使用一个开发的接口了。Symbols 是不可改变并且是独一无二的,可以在任意哈希中作一个key。
Symbol()
调用 Symbol() 或者 Symbol(description) 可以创造一个独一无二的符号,但是在全局是看不到的。Symbol() 的一个使用情况是给一个类或者命名空间打上补丁,但是可以确定的是你不会去更新它。比如,你想给 React.Component 类添加一个 refreshComponent 方法,但是可以确定的是你不会在之后更新这个方法:1
2
3
4const refreshComponent = Symbol();
React.Component.prototype[refreshComponent] = () => {
// do something
}
Symbol.for(key)
Symbol.for(key) 同样会创造一个独一无二并且不可改变的 Symbol,但是它可以全局看到,两个相同的调用 Symbol.for(key) 会返回同一个 Symbol 类:1
2
3Symbol('foo') === Symbol('foo') // false
Symbol.for('foo') === Symbol('foo') // false
Symbol.for('foo') === Symbol.for('foo') // true
对于 Symbols 的普遍用法(尤其是Symbol.for(key))是为了协同性。它可以通过在一个第三方插件中已知的接口中对象中的参数中寻找用 Symbol 成员来实现,比如:1
2
3
4
5
6
7
8
9function reader(obj) {
const specialRead = Symbol.for('specialRead');
if (obj[specialRead]) {
const reader = obj[specialRead]();
// do something with reader
} else {
throw new TypeError('object cannot be read');
}
}
在另一个库中:1
2
3
4
5
6
7const specialRead = Symbol.for('specialRead');
class SomeReadableType {
[ ]() {
const reader = createSomeReaderFrom(this);
return reader;
}
}
Maps
Maps 在 JavaScript 中是一个非常必需的数据结构,在 ES6 之前,我们通过对象来创建哈希映射:1
2
3var map = new Object();
map[key1] = 'value1';
map[key2] = 'value2';
然而有时我们用特殊的方法去修改对象属性时会报错1
2 getOwnProperty({ hasOwnProperty: 'Hah, overwritten'}, 'Pwned');
TypeError: Property 'hasOwnProperty' is not a function
实际上Maps允许我们对值进行 set、get 和 search 操作:1
2
3
4let map = new Map();
> map.set('name', 'david');
> map.get('name'); // david
> map.has('name'); // true
Maps更令人惊奇的部分就是它不仅限于使用字符串作为 key,还可以用其他任何类型的数据作为 key:1
2
3
4
5
6
7
8
9
10
11let map = new Map([
['name', 'david'],
[true, 'false'],
[1, 'one'],
[{}, 'object'],
[function () {}, 'function']
]);
for (let key of map.keys()) {
console.log(typeof key);
// > string, boolean, number, object, function
}
注意:但我们使用 map.get() 方法去测试相等时,如果在 Maps 中使用 函数 或者 对象 等非原始类型值的时候测试将不起作用,所以我们应该使用 Strings, Booleans 和 Numbers 这样的原始类型的值。
我们还可以使用 .entries() 来遍历迭代:1
2
3for (let [key, value] of map.entries()) {
console.log(key, value);
}
WeakMaps
在 ES6 之前,为了存储私有变量,我们有各种各样的方法去实现,其中一种方法就是用命名约定:1
2
3
4
5
6
7
8class Person {
constructor(age) {
this._age = age;
}
_incrementAge() {
this._age += 1;
}
}
但是命名约定在代码中仍然会令人混淆并且并不会真正的保持私有变量不被访问。现在,我们可以使用WeakMaps来存储变量:1
2
3
4
5
6
7
8
9
10
11
12
13let _age = new WeakMap();
class Person {
constructor(age) {
_age.set(this, age);
}
incrementAge() {
let age = _age.get(this) + 1;
_age.set(this, age);
if (age > 50) {
console.log('Midlife crisis');
}
}
}
在 WeakMaps 存储变量很酷的一件事是它的 key 他不需要属性名称,可以使用 Reflect.ownKeys() 来查看这一点:1
2
3 const person = new Person(50);
person.incrementAge(); // 'Midlife crisis'
Reflect.ownKeys(person); // []
一个更实际的实践就是可以 WeakMaps 储存 DOM 元素,而不会污染元素本身:1
2
3
4
5
6
7
8
9
10let map = new WeakMap();
let el = document.getElementById('someElement');
// Store a weak reference to the element with a key
map.set(el, 'reference');
// Access the value of the element
let value = map.get(el); // 'reference'
// Remove the reference
el.parentNode.removeChild(el);
el = null;
// map is empty, since the element is destroyed
如上所示,当一个对象被垃圾回收机制销毁的时候, WeakMap 将会自动地删除关于这个对象的键值对。
注意:为了进一步说明这个例子的可行性,请参考jQuery如何存储与具有引用的DOM元素相对应的对象缓存。使用WeakMap,一旦它从文档中删除,jQuery可以自动释放与特定DOM元素相关联的任何内存。一般来说,WeakMap对于包装DOM元素的任何库都非常有用。
Promises
Promises 可以让我们远离平行的代码(回调地狱):1
2
3
4
5
6
7
8
9
10
11func1(function (value1) {
func2(value1, function (value2) {
func3(value2, function (value3) {
func4(value3, function (value4) {
func5(value4, function (value5) {
// Do something with value 5
});
});
});
});
});
转变成垂直代码:1
2
3
4
5
6
7func1(value1)
.then(func2)
.then(func3)
.then(func4)
.then(func5, value5 => {
// Do something with value 5
});
在 ES6 之前,我们使用bluebird或者Q,现在我们可以使用原生的Promise了。1
2
3new Promise((resolve, reject) =>
reject(new Error('Failed to fulfill Promise')))
.catch(reason => console.log(reason));
我们有两个处理器,resolve(当Promise是 fulfilled 时的回调)和 reject(当Promise是rejected时的回调):
Promises的好处:对错误的处理使用一些嵌套回调错误提示会使代码很混乱,使用Promise ,我们有一个清晰的路径来处理冒泡的错误并适当地处理它们,在Promis 确定了resolved/rejected之后,他的值是不可改变的--它永远不会改变。
这是使用 Promise 的一个实际的栗子:1
2
3
4
5
6
7
8
9
10var request = require('request');
return new Promise((resolve, reject) => {
request.get(url, (error, response, body) => {
if (body) {
resolve(JSON.parse(body));
} else {
resolve({});
}
});
});
我们还可以使用 Promise.all() 来 并行 处理多个异步函数:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20let urls = [
'/api/commits',
'/api/issues/opened',
'/api/issues/assigned',
'/api/issues/completed',
'/api/issues/comments',
'/api/pullrequests'
];
let promises = urls.map((url) => {
return new Promise((resolve, reject) => {
$.ajax({ url: url })
.done((data) => {
resolve(data);
});
});
});
Promise.all(promises)
.then((results) => {
// Do something with results of all our promises
});
Generators 生成器
就像 Promises 可以帮我们避免回调地狱,Generators 可以帮助我们让代码风格更整洁--用同步的代码风格来写异步代码,它本质上是一个可以暂停计算并且可以随后返回表达式的值的函数。
一个简单的栗子使用 generators:
1
2
3
4
5
6
7
8
9
10
11 function* sillyGenerator() {
yield 1;
yield 2;
yield 3;
yield 4;
}
var generator = sillyGenerator();
> console.log(generator.next()); // { value: 1, done: false }
> console.log(generator.next()); // { value: 2, done: false }
> console.log(generator.next()); // { value: 3, done: false }
> console.log(generator.next()); // { value: 4, done: false }
next 可以回去到下一个 yield 返回的值,当然上面的代码是非常不自然的,我们可以利用 Generators 来用同步的方式来写异步操作:1
2
3
4
5
6// Hiding asynchronousity with Generators
function request(url) {
getJSON(url, function(response) {
generator.next(response);
});
}
这里的 generator 函数将会返回需要的数据:1
2
3
4
5
6function* getData() {
var entry1 = yield request('http://some_api/item1');
var data1 = JSON.parse(entry1);
var entry2 = yield request('http://some_api/item2');
var data2 = JSON.parse(entry2);
}
通过 yield,我们可以保证 entry1 有 data1 中我们需要解析并储存的数据。
虽然我们可以利用 Generators 来用同步的方式来写异步操作,但是确认错误的传播变得不再清晰,我们可以在 Generators 中加上 Promise:1
2
3
4
5function request(url) {
return new Promise((resolve, reject) => {
getJSON(url, resolve);
});
}
然后我们写一个函数逐步调用 next 并且利用 request 方法产生一个 Promise:1
2
3
4
5
6
7
8
9function iterateGenerator(gen) {
var generator = gen();
(function iterate(val) {
var ret = generator.next();
if(!ret.done) {
ret.value.then(iterate);
}
})();
}
在 Generators 中加上 Promise 之后我们可以更清晰的使用 Promise 中的 .catch 和 reject来捕捉错误,让我们使用新的 Generator,和之前的还是蛮相似的:1
2
3
4
5
6iterateGenerator(function* getData() {
var entry1 = yield request('http://some_api/item1');
var data1 = JSON.parse(entry1);
var entry2 = yield request('http://some_api/item2');
var data2 = JSON.parse(entry2);
});
Async Await
当 ES6 真正到来的时候,async await 可以用更少的处理实现 Promise 和 Generators 所实现的异步处理:
async await 是Generators的语法糖
1 | var request = require('request'); |
在 js 引擎中,它所实现的和 Generators 其实是一样的,我更推荐使用 async await + Promises,更多的资源和使用 ES7 和 用 babel 转化可以看这里。
Getter/Setter 函数
ES6 已经开始实现了 getter 和 setter 函数,比如下面这个栗子:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29class Employee {
constructor(name) {
this._name = name;
}
get name() {
if(this._name) {
return 'Mr. ' + this._name.toUpperCase();
} else {
return undefined;
}
}
set name(newName) {
if (newName == this._name) {
console.log('I already have this name.');
} else if (newName) {
this._name = newName;
} else {
return false;
}
}
}
var emp = new Employee("James Bond");
// uses the get method in the background
if (emp.name) {
console.log(emp.name); // Mr. JAMES BOND
}
// uses the setter in the background
emp.name = "Bond 007";
console.log(emp.name); // Mr. BOND 007
最新版本的浏览器也支持对象中的 getter 和 setter 函数,我们可以使用它们来实现 计算属性,对对象属性添加监听和预处理。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17var person = {
firstName: 'James',
lastName: 'Bond',
get fullName() {
console.log('Getting FullName');
return this.firstName + ' ' + this.lastName;
},
set fullName (name) {
console.log('Setting FullName');
var words = name.toString().split(' ');
this.firstName = words[0] || '';
this.lastName = words[1] || '';
}
}
person.fullName; // James Bond
person.fullName = 'Bond 007';
person.fullName; // Bond 007