ES5简介

这是我组织的 ES6 特性分享的预热篇,主要介绍 ES5 的特性。

既是入门,又是回顾

  • 相信很多人还没有完全在项目中使用

  • 2011年正式发布的IE9实现了几乎所有ES5的特性

  • ES6ES7……都已经或将要来了

ES5 中最有用的特性:JSON

JSON 简介

Douglas Crockford

  1. Douglas Crockford 在 2002 年创建,
  2. JSON JavaScript 对象标记(Notation)
  3. JavaScript 语言的一个子集
  4. 一种轻量级的数据交换格式
  5. 易于人阅读和编写,同时也易于机器解析和生成。

更多细节:RFC7159ECMA-404介绍 JSON

那段不堪回首的过去

1
var data = eval('(' + response + ')');

1
2
3
4
<script src="/javascript/json2.js"></script>
<script>
var data = JSON.parse(response);
</script>

JSON.stringify(value[, replacer [, space]])

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var obj = { a: 'x', b: { c: 1 } };
var string = JSON.stringify(obj);
//
var string = JSON.stringify(obj, function(key, value) {
return typeof value === 'number' ? 0 : value;
});
console.log(string); //{"a":"x","b":{"c":0}}

//美化JSON
var string = JSON.stringify(obj, null, '\t');
var string = JSON.stringify(obj, null, 2);
console.log(string);

obj.toJSON = function() {
return 'something';
};
var string = JSON.stringify(obj);
console.log(string); //nothing

JSON.parse(text[, reviver])

1
2
3
4
5
6
7
8
9
//将字符串解析成JSON对象
var data = JSON.parse('{"a":1,"b":{"c":1}}');

//将字符串解析成JSON对象,并对象中的每个数值加1
var data = JSON.parse('{"a":1,"b":{"c":1}}', function(key, value) {
console.log(key, value);
return typeof value === 'number' ? value + 1 : value;
});
console.log(JSON.stringify(data)); //{"a":2,"b":{"c":2}}

JSON 用法举例

  1. 解析 JSON 格式的 AJAX 响应
  2. localStorage,sessionStorage 只能存储字符串
  3. PHP 使用 SpiderMonkey 引擎创建 JS 上下并输入数据

JSON 的替代协议 Protocol Buffers

Protocol BuffersGithub
Protocol Buffers 截屏

JSON 的替代协议 MessagePack

MessagePack
MessagePack 截屏

ES5 中最强大的特性:Object 扩展

以下是 ES5 对 Object 对象的所有扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
Object.defineProperty
Object.defineProperties
Object.create
Object.getPrototypeOf
Object.keys
Object.seal
Object.freeze
Object.preventExtensions
Object.isSealed
Object.isFrozen
Object.isExtensible
Object.getOwnPropertyDescriptor
Object.getOwnPropertyNames

Object.defineProperty(obj, prop, descriptor)

直接在一个对象上定义一个新属性,或者修改一个已经存在的属性, 并返回这个对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var obj = {};
Object.defineProperty(obj, 'key', {
value: 'val', //默认 undefined
configurable: false, //默认 false,属性是否可以删除,除 writable 之外其他特性能够被修改
enumerable: false, //默认 false,属性是否可以枚举
writable: false, //默认 false,能够使用赋值运算符改变属性值
get: function() {
return this.value;
},
set: function(newValue) {
this.value = newValue;
},
});
//以上代码仅为示例,get/set 不能和 value,writable同时出现。

Object.defineProperty 数据描述符 configurable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var obj = {};
Object.defineProperty(obj, 'key', {
value: 'val',
// 默认 false,属性是否可以删除,除 writable 之外其他特性能够被修改
configurable: true,
});

Object.defineProperty(obj, 'key', { value: 'changed', configurable: false });
console.log(obj.key); //changed

try {
Object.defineProperty(obj, 'key', {
configurable: true,
});
} catch (e) {
console.log(e);
} //TypeError: Cannot redefine property: key(…)

delete obj.key;
console.log(obj.key); //changed

Object.defineProperty 数据描述符 enumerable

1
2
3
4
5
6
7
8
9
10
11
12
var obj = {};
Object.defineProperty(obj, 'key', {
value: 'val',
enumerable: false, // 默认 false,属性是否可以枚举
});

console.log(obj.propertyIsEnumerable('key')); // false

console.log(Object.keys(obj)); // []
for (var k in obj) {
console.log(k); //什么也不会打印
}

使用 Object.defineProperty 调试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function debugObject(obj, key){
//将相应值保存起来
var value = obj.key;
//重新定义该属性
Object.defineProperty(obj, key, {
get: function(){
return value;
},
set: function(val){
debugger;
value = val;
}
});
}

在 ES7 草案中,还有一个方法Object.observe(ob, callback)可以更加方便地监听对象变化。

Object.defineProperties(obj, props)

添加或修改 1+个属性,并返回该对象。

1
2
3
4
5
6
7
8
9
var obj = {};
Object.defineProperties({
key1: {
writable: false,
},
key2: {
enumerable: false,
},
});

Object.create(prototype, descriptors)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var Man = {
race: function() {
console.log('race');
},
};
var peter = Object.create(Man, {
firstName: { value: 'Xiaoming' },
lastName: { value: 'Li' },
fullName: {
get: function() {
return this.firstName + ' ' + this.lastName;
},
},
});
console.log(peter.fullName); // Xiaoming Li

Object.create(prototype, descriptors)

1
2
3
4
5
6
7
8
9
var obj = Object.create(null);

console.log( Object.keys(obj) ); //[]

console.log( typeof obj ); //object

console.log( Object.prototype.toString.call(obj) ); //[object Object]

console.log( obj instanseof Object ); //false

Object.getOwnPropertyDescriptor(obj, prop) 获取属性描述符

1
2
3
4
5
6
7
8
9
10
11
12
13
var obj = { a: 1 };
Object.defineProperty(obj, 'b', {
//value: 2,
enumerable: false,
get: function() {
return 1;
},
});

console.log(Object.getOwnPropertyDescriptor(obj, 'a'));

var descriptor = Object.getOwnPropertyDescriptor(obj, 'b');
console.log(descriptor);

Object.getOwnPropertyNames(obj) 获取所有自有属性名

获取所有自有属性名,包含可枚举以及不可枚举的,但是不包含原型链上的。

1
2
3
4
5
6
7
8
9
10
11
12
var prototype = { a: 0 };
var obj = Object.create(prototype, {
b: {
value: 1,
},
c: {
value: 2,
enumerable: false,
},
});

console.log(Object.getOwnPropertyNames(obj)); // ["b", "c"]

Object.freeze(obj) 冻结一个对象

冻结一个对象,意味着该对象的属性不能增删改,属性的各个特性也不能改。
Object.isFrozen(obj)可以用来判断一个对象有没有冻结。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var obj = { key: 'val', key1: {} };
Object.freeze(obj);

console.log(Object.isFrozen(obj)); // true

obj.key2 = 1;
console.log(obj.key2); // undefined
delete obj.key;
console.log(obj.key); // val
obj.key = 1;
console.log(obj.key); // val
obj.key1.a = 2;
console.log(obj.key1.a); // 2 ------冻结仅仅冻结一层

//Uncaught TypeError: Cannot redefine property: key
Object.defineProperty(obj, 'key', { enumerable: false });

怎样完全冻结一个对象

1
2
3
4
5
6
7
8
9
10
11
12
13
function deepFreeze(obj) {
for (var key in obj) {
var val = obj[key];
//忽略原型链上的属性
if (!obj.hasOwnProperty(key)) continue;
//忽略不是对象的属性
if (typeof val !== 'object') continue;
//忽略已经冻结的属性
if (Object.isFrozen(val)) continue;
//递归地冻结对象属性
deepFreeze(val);
}
}

Object.seal(obj) 密封一个对象

密封,比冻结宽松一点,仅仅能修改属性的值。
Object.isSealed(obj)可以用来判断一个对象有没有密封。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var obj = { key: 'val', key1: {} };
Object.seal(obj);

console.log(Object.isSealed(obj)); // true

obj.key2 = 1;
console.log(obj.key2); // undefined
delete obj.key;
console.log(obj.key); // val
obj.key = 1;
console.log(obj.key); // 1----跟冻结唯一不同的地方
obj.key1.a = 2;
console.log(obj.key1.a); // 2

//Uncaught TypeError: Cannot redefine property: key
Object.defineProperty(obj, 'key', { enumerable: false });

Object.preventExtensions(obj) 不能添加新属性

Object.isExtensible用来检查一个对象能不能添加新属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var obj = { key: 'val', key1: {} };
Object.preventExtensions(obj);

console.log(Object.isExtensible(obj)); // false

obj.key2 = 1;
console.log(obj.key2); // undefined,不能添加新属性
obj.key = 'value';
console.log(obj.key); // value,能够修改已有属性

Object.defineProperty(obj, 'key', { enumerable: false });
console.log(Object.keys(obj)); // ["key1"],能够修改属性特性

delete obj.key;
console.log(obj.key); // undefined,能够删除已有属性

obj.key1.a = 'a';
console.log(obj.key1.a); // a,不影响给扩展已有属性

Object.getPrototypeOf(obj)

1
2
3
4
5
6
7
function Car() {}
var prototype = (Car.prototype = {});
Object.getPrototypeOf(new Car()) === prototype; // true

var prototype = {};
var car = Object.create(prototype);
Object.getPrototypeOf(car) === prototype; // true

Array 扩展

Array.isArray(obj) 判断一个对象是不是数组

1
2
3
4
5
6
7
8
9
var array = [1, 2, 3];

Array.isArray(array); //true,

//等价于

function isArray(obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
}

Array 实例扩展

1
2
3
4
var array = [1, 2, 2, 3];

array.indexOf(2); // 1
array.lastIndexOf(2); // 2

保留字作为属性名

1
2
3
4
5
6
7
8
var obj = {
if: 1,
var: 's',
switch: true,
};
console.log(obj.if);
console.log(obj.var);
console.log(obj.switch);

使用 get/set 进行属性初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var obj = {
firstName: '',
lastName: '',
get fullName(){
return (this.firstName + ' ' + this.lastName).trim();
},
set fullName(fullName){
if(!fullName) return;
var index = fullName.indexOf(' ');
this.firstName = fullName.substring(0, index);
this.lastName = fullName.substring(index+1);
}
};

console.log( obj.fullName ); // 空字符串
obj.fullName = 'San Zhang';
console.log( obj.firstName ); // San
console.log( obj.lastName ); // Zhang
console.log( obj.fullName ); // San Zhang

全局 undefined 对象

以前,undefined 对象是这样产生的。

1
2
3
4
5
6
7
8
9
10
11
var undef;
console.log(typeof undef === 'undefined'); // true

var undef = 1;
undef = void 0;
console.log(typeof undef === 'undefined'); // true

//jQuery 1.8.3源码片段
(function(window, undefined) {
console.log(typeof undefined === 'undefined'); // true
})(window);

在 ES5 中有了不可变的全局的undefined对象。

1
2
3
4
5
6
7
8
9
10
var undefined = 1;
console.log(undefined); // undefined

(function(undefined) {
undefined = 1;
console.log(undefined); // 1

window.undefined = 1;
console.log(window.undefined); // undefined
})();

Function 实例扩展

Function.prototype.bind

fun.bind(thisArg[, arg1[, arg2[, ...]]])

1
2
3
4
5
6
7
8
9
function a() {
console.log(this, arguments);
}

a.bind({ key: 'a' }, 1, 2); // function bound a() { [native code] }

a.bind('ab', 1, 2)(); //String {0: "a", 1: "b", length: 2, [[PrimitiveValue]]: "ab"} [1, 2]

a.bind({ key: 'a' }, 1, 2).bind({ key: 'b' }, 3, 4)(); // {key: 'a'}, [1,2,3,4]

Curry(柯里化)

预先设定函数的前面若干个实参,生成一个接受剩余实参的新函数。

1
2
3
4
5
6
7
function curry(fn) {
var slice = Array.prototype.slice,
args = slice.call(arguments, 1);
return function() {
return fn.apply(null, args.concat(slice.apply(arguments)));
};
}

柯里化案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
model.groups = _.filter(model.groups, function(g) {
return g.id !== group.id;
});
model.groups = _.sortBy(model.groups, 'updateTime');
model.groups = _.map(model.groups, 'name');

// _.prototype 上的方法都是 _ 上相应方法的柯里化
model.groups = _.chain(groups)
.filter(function(g) {
return g.id !== group.id;
})
.sortBy('updateTime')
.map('name')
.value();

_.curry & _.curryRight

1
2
3
4
5
6
7
8
9
10
11
var abc = function(a, b, c) {
return [a, b, c];
};

var curried = _.curry(abc);

curried(1)(2)(3); //[1,2,3]

curried(1, 2)(3); //[1,2,3]

curried(_, 2)(_, 3)(1); //[1,2,3]

严格模式

整段代码某个函数放在严格操作的环境中运行,严格环境有诸多限制:

  1. 将失误转成异常
  2. 简化变量的使用
  3. 让 eval 和 arguments 变的简单
  4. “安全的” JavaScript
  5. 为未来的 ECMAScript 版本铺平道路

严格模式:将失误转成异常

  1. 意外地创建全局变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(function() {
'use strict';
variable = 10;
})(); // ReferenceError
//解决方法1:明确指定我要创建全局变量
(function() {
'use strict';
window.variable = 10;
})();
//解决方法2:事先定义好全局变量
var variable;
(function() {
'use strict';
variable = 10;
})();
  1. 不再静默失败,而是抛出异常
1
2
3
var fixed = {};
Object.preventExtensions(fixed);
fixed.attr = 'value'; // SyntaxError
  1. 形参命名不能重复
1
(function(a, a){'use strict'; })(); //SyntaxError

注意:严格模式下,ES5 中定义变量时,字段名也不能重复,但是 ES6 又允许了。

  1. 严格模式禁止八进制数字语法
1
2
3
4
//FireFox: SyntaxError, Chrome/IE10: SyntaxError
(function(){'use strict'; console.log(010); })();
//FireFox: SyntaxError, Chrome/IE10: 19
(function(){'use strict'; console.log(019); })();

严格模式:简化变量的使用

  1. 禁用with
1
2
3
4
5
6
7
8
(function(){
'use strict';
var key = 'attr';
var obj = {key: 1};
with(obj){ //SyntaxError
console.log(key);
}
})();
  1. eval不再为上层作用域引入新变量
1
2
3
4
5
6
(function() {
'use strict';
var x = 1;
var y = eval('var x = 2; x'); //== eval('"use strict"; var x = 2; x')
console.log(x, y); // 1, 2 在正常模式下,返回2, 2
})();

严格模式:简化变量的使用

  1. call & applythis的修改规则简化
1
2
3
4
5
6
7
8
9
(function() {
'use strict';
function printThis() {
console.log(this);
}
printThis.call(2); // 2 ,不再包装成对象类型
printThis.call(null); // null,不再自动替换成window对象
printThis.call(); // undefined,不再自动替换成window对象
})();
  1. 不能使用callercallee
1
2
3
4
5
6
7
(function restricted(a) {
'use strict';
console.log(restricted.caller); //TypeError
console.log(restricted.arguments); //TypeError
console.log(arguments.caller); //TypeError
console.log(arguments.callee); //TypeError
})(1);

其他小的特性

1. Date.prototype.toISOString

转换成 YYYY-MM-DDTHH:mm:ss.sssZ 这种格式

2. Date.now()

new Date().getTime() 一样。

3. String.prototype.trim

$.trim_.trim一样。

4. 把字符串当做数组一样访问

1
'foobar'[1] === 'o'; // true

5. parseInt 忽略前置 0

1
2
parseInt('012'); // 12,在ES3中,因为前置0而按照八进制解析成10
parseInt('0X12'); // 18,仍然因为前置0X而按照十六进制解析成18

思考题

  1. 以下这种情况是为什么?
1
2
3
var array = [];
console.log( array.length ); // 0
console.log( Object.keys(array) ); // []

参考

  1. MDN Javascript 参考文档
  2. es5-shim
  3. Douglas Crockford