首页 > 代码库 > JavaScript大杂烩13 - 总结ECMAScript 5新功能

JavaScript大杂烩13 - 总结ECMAScript 5新功能

  虽说这个标准已经出来很久了,所有的主流浏览器的最新版本也都支持了这些特性,但是很多的教程中并没有包含这个部分,这一节我们专门来总结一下这个标准中的新功能。
Object的新方法
  在最新的JavaScript规范(ECMAScript 5)中,Object扩展了很多不错的静态方法,下面来简单看一下:
1. create/getPrototypeOf方法 - 干净的原型链
  先说简单的getPrototypeOf方法,这个方法统一了获取对象原型的方式,使用这个对象可以获取到对象的原型,这个不多说了。
  在ECMAScript5中,Object添加了静态方法create去串接原型链 - 这样就得到干净的不带冗余成员的原型链。先看一下小例子:
var Dog = {  name : ‘dog‘,  speak : function () { alert(‘Woof!‘); }};var dog = Object.create(Dog);dog.speak();

  这里就是使用了这个方法创建了Dog的一个实例dog,这种方式的本质其实还是与我们之前讨论的使用空对象挂接原型对象是一样的,代码基本就像这样:

Object.create = function (o) {    function F() {}    F.prototype = o;    return new F();};

  使用这种方式较之使用构造函数加new的方式来说更加简洁,但是不能设置私有和公开成员,而且实例成员在各个实例之间不共享,所以也有一定的局限性。

  这里我们再回过头来看一下这个方法的定义:
Object.create(prototype, descriptors)参数prototype:必需。 要用作原型的对象。 可以为 null,一般也可以在需要的时候使用getPrototypeOf方法获取。descriptors: 可选。 定义的一组属性的对象字面量,该对象包含了每个属性的描述对象。属性描述对象称为属性的attributes对象,其含有如下成员:value:表示该属性的值,默认为undefined。writable:表示该属性的值(value)是否可以改变,默认为true。这个值如果是false的话,即使是继承的子类的话,也无法修改属性的值,这个也是讲得通的。enumerable: 表示该属性是否可枚举,默认为true,也就是该属性会出现在for...in和Object.keys()等操作中。configurable:表示“可配置性”,默认为true。如果设为false,表示无法删除该属性,也不得改变attributes对象(value属性除外),也就是configurable属性控制了attributes对象本身的可写性。get:表示该属性的取值函数(getter),默认为undefined。set:表示该属性的存值函数(setter),默认为undefined。返回值一个具有指定的内部原型且包含指定的属性(如果有)的新对象。

  毫无疑问,这个函数最有意义的应该就是第二个参数:一组属性描述对象,这个在别的语言中非常有用的特性姗姗来迟,虽然在JavaScript中可以直接使用dog.age=2这种方式定义和修改属性,但是这种数据成员终究不满足封装性的需求,而且不满足属性本质上是函数这种惯例,在这个版本中满足封装性的属性(本质上是函数)终于出现在大家视野中了,我们看一个例子来了解一下如何定义一组属性:

var Shape = { twoDimensional: true, color: ‘Red‘};var Square = Object.create(Shape, {  // 简单的属性,不包含任何的其他逻辑的属性  type: { value: ‘square‘, enumerable: true, writable: true },  // 复杂一点的属性,包含有一些逻辑的属性  size: {    get: function() { return this.__size__;},    set: function(pSize) {      if (pSize > 10) alert(‘Too large...‘);      // 其他的处理逻辑,例如简单的保存一下      this.__size__ = pSize;    },    enumerable: true  }});var square = Object.create(Square);square.type = ‘rectangle‘;alert(square.type);square.size = 9;alert(square.size);

使用起来还是很简单的,但是有一点不太优雅的代码就是this.__size__这个保存属性值的成员,为了满足封装性,我们得使用一下闭包的特性了:

var Square = Object.create(Shape, (function () {    var m_size;     return {      // 简单的属性,不包含任何的其他逻辑的属性      type: { value: ‘square‘, enumerable: true, writable: true },      // 复杂一点的属性,包含有一些逻辑的属性      size: {        get: function() { return m_size;},        set: function(pSize) {          // 常见的条件检查          if (pSize > 10) alert(‘Too large...‘);          // 其他的处理逻辑,例如简单的保存一下          m_size = pSize;        },        enumerable: true      }    };}()));

  把Square定义的那段代码换成上面这段,就有点样子了。

  这里有一点需要注意,像上面例子中定义type那样的简单属性,如果不强行把writable不设为true的话,则赋值会失败,这个不知是浏览器的默认设定还是JavaScript的Bug,不管是哪个,虽然那些属性的默认值都是true,大家需要的话还是写上吧,特别是writable这个属性。
2. defineProperty, defineProperties方法 - 迟到的属性
  上面我们看到了在create方法中定义属性的例子,如果是现有的对象,我们仍然可以给它定义新的属性,这是通过使用defineProperty, defineProperties方法实现的,前者定义一个属性,后者定义一组属性,让我们改写上面的例子中定义的type:
var Shape = { twoDimensional: true, color: ‘Red‘};var square = Object.create(Shape);Object.defineProperty(square, ‘type‘, {    value : ‘square‘,    enumerable : true,    writable: true});square.type = ‘rectangle‘;alert(square.type);

  使用defineProperties方法就与create中的第二个参数一致了,参见:

var Shape = { twoDimensional: true, color: ‘Red‘};var square = Object.create(Shape);Object.defineProperties(square, (function () {    var m_size;     return {      // 简单的属性,不包含任何的其他逻辑的属性      type: { value: ‘square‘, enumerable: true, writable: true },      // 复杂一点的属性,包含有一些逻辑的属性      size: {        get: function() { return m_size;},        set: function(pSize) {          // 常见的条件检查          if (pSize > 10) alert(‘Too large...‘);          // 其他的处理逻辑,例如简单的保存一下          m_size = pSize;        },        enumerable: true      }    };}()));square.type = ‘rectangle‘;alert(square.type);square.size = 9;alert(square.size);

  注意defineProperty/defineProperties这两个函数的返回值就是修改后的对象,有时候会使用这个返回值。

  很强大的属性功能吧,下面还有另外一种定义的方式,那就是使用get/set关键字,看个例子:
var Shape = {  get type() { return this.__type__;},  set type(value) { this.__type__ = value;}};var square = Object.create(Shape);square.type = ‘rectangle‘;alert(square.type);

  简单吧,很简洁,非常强大。

3. keys, getOwnPropertyNames方法 - 获取所有的属性
  这两个方法得到对象上面直接定义的(非原型链上的)所有属性,只不过keys只得到可枚举的属性,而getOwnPropertyNames方法可以得到所有属性,不管属性定义的时候enumerable是否为true。看一个例子:
var o = { name: ‘frank‘};Object.defineProperties(o, {        p1: { value: 1, enumerable: true },        p2: { value: 2, enumerable: false }});alert(Object.keys(o));alert(Object.getOwnPropertyNames(o));

  注意name这种原始的属性是可枚举的,以前通过for/in结合hasOwnProperty方法才能完成枚举对象自身定义的属性的功能现在直接可以通过遍历keys完成了。

4. preventExtensions/isExtensible,seal/isSealed,freeze/isFrozen - 禁止扩展对象
  这3组方法用于控制对象的状态:
preventExtensions方法禁止对象添加新属性,isExtensible检测对象是否使用了preventExtensions方法。seal方法禁止对象添加新属性和禁止删除已有属性,isSealed检测对象是否使用了seal方法。freeze方法禁止对对象做任何修改,不能添加新属性和删除已有属性,也不能修改对象的属性值,isFrozen检测对象是否使用了freeze方法。

  看到了吧,一个比一个严格,等到了使用freeze方法的时候,对象完全变成了只读对象,这个在很多场合下还是很有用的。看个简单的例子:

var o = { name: ‘frank‘};Object.preventExtensions(o);o.age = 10;alert(o.age); //undefined,代表无法添加新属性Object.seal(o);delete o.name;alert(o.name); // 删除不了Object.freeze(o);o.name = ‘dong‘;alert(o.name); // 无法修改

 

Array的新方法
  这里的新方法主要是新的实例方法(扩展了原型链对象)。
1. indexOf/lastIndexOf
  这一组方法通过方法名就知道其用途了,返回元素在集合中首次/最后一次出现的索引值,不存在的话返回-1。
var a = [‘a‘,‘b‘,‘c‘,‘b‘];alert(a.indexOf(‘b‘)); // 1alert(a.indexOf(‘y‘)); // -1alert(a.lastIndexOf(‘b‘)); // 3

2. every/some/forEach/filter/map/reduce/reduceRight

  这一组方法都与时髦的函数式编程紧密相关,这些方法的参数是一个函数,这个作为参数的函数本身可以接收三个参数:数组的当前元素item、该元素的位置index和整个数组arr。另外,上下文对象(context)可以作为第二个参数,传入forEach(), every(), some(), filter(), map()方法中,用来绑定函数运行时的上下文。
  下面来看看这些方法的意图:
map/forEach方法 - 遍历元素执行指定的操作
  map方法对所有元素依次调用一个函数,根据函数结果返回一个新数组。
[1, 2, 3].map(function(item, index, arr){    return item * item;});// [1, 4, 9]

  通过函数的call方法,map方法可以用于字符串。

[].map.call(‘abc‘, function (x) { return x.toUpperCase() })// [ ‘A‘, ‘B‘, ‘C‘ ]// 或者‘abc‘.split(‘‘).map(function (x) { return x.toUpperCase() })// [ ‘A‘, ‘B‘, ‘C‘ ]

  forEach方法对所有元素依次执行一个函数,它与map的区别在于不返回新数组,而是对原数组的成员执行某种操作,甚至可能改变原数组的值。

[1, 2, 3].forEach(function(item, index, arr){    console.log("array[" + index + "] = " + item);});// array[0] = 1// array[1] = 2// array[2] = 3

  从上面代码可以看到,map和forEach的参数格式是一样的,都是一个函数。该函数接受三个参数,分别是当前元素、当前元素的位置(从0开始)、整个数组。

  这两个方法都可以接受第二个参数,用来绑定函数中的this关键字。
var out = [];[1, 2, 3].map(function(item, index, arr){    this.push(item * item);}, out);out // [1, 4, 9]

  上面代码表示,如果提供一个数组作为第二个参数,则函数内部的this关键字就指向这个数组。

filter方法 - 选取满足指定条件的元素
  filter方法依次对所有数组成员调用一个测试函数,返回结果为true的成员组成一个新数组返回。
[1,2,3,4,5].filter(function(item){    return (item>3);})// [4,5]

  上面代码将大于3的原数组成员,作为一个新数组返回。

  filter方法的参数必须是一个返回布尔值的函数。该函数的第一个参数是当前数组成员的值,这是必需的,后两个参数是可选的,分别是当前数组成员的位置和整个数组。
[1, 2, 3, 4, 5].filter(function(item, index, arr){    return index % 2 === 0;});// [1, 3, 5]

  上面代码返回原数组偶数位置的成员组成的新数组。

some/every方法 - 检查集合是否满足指定的条件
  这两个方法用来判断数组成员是否符合某种条件。
  some方法对所有元素调用一个测试函数,只要有一个元素通过该测试,就返回true,否则返回false。
[1, 2, 3, 4, 5].some(function(item, index, arr){    return item >= 3;});// 返回true

  上面代码表示,如果存在大于等于3的数组成员,就返回true。

  every方法对所有元素调用一个测试函数,只有所有元素通过该测试,才返回true,否则返回false。
[1, 2, 3, 4, 5].every(function(item, index, arr){    return item >= 3;});// 返回false

  上面代码表示,只有所有数组成员大于等于3,才返回true。

  从上面的代码可以看到,some和every的使用方法与map和forEach是一致的,参数完全一模一样。也就是说,它们也可以使用第二个参数,用来绑定函数中的this关键字。
reduce/reduceRight方法 - 累计集合中的指定的元素
  reduce和reduceRight方法的作用,是依次处理数组的每个元素,最终累计为一个值。这两个方法的差别在于,reduce对数组元素的处理顺序是从左到右,reduceRight则是从右到左,其他地方完全一样。
reduce方法的第一个参数是一个处理函数。该函数接受四个参数,分别是:
用来累计的变量(即当前状态)数组的当前元素当前元素在数组中的序号(从0开始)原数组

  这四个参数之中,只有前两个是必须的,后两个则是可选的。

[1, 2, 3, 4, 5].reduce(function(x, y){    return x+y;});// 15

  上面代码的参数x表示累计变量,默认为0,y则是数组的当前元素。reduce方法依次将每个数组元素加入x,最终返回它们的总和15。

  利用reduce方法,可以写一个数组求和的sum方法。
Array.prototype.sum = function (){    return this.reduce(function (partial, value){        return partial + value;    })};[3,4,5,6,10].sum()// 28

  如果要对累计变量指定初值,可以把它放在reduce方法的第二个参数。

[1, 2, 3, 4, 5].reduce(function(x, y){    return x+y;}, 10);// 25

  上面代码指定参数x的初值为10,所以数组元素从10开始累加,最终结果为25。

  由于reduce方法依次处理每个元素,所以实际上还可以用它来搜索某个元素。比如,下面代码是找出长度最长的数组元素。
function findLongest(entries) {  return entries.reduce(function (longest, entry) {    return entry.length > longest.length ? entry : longest;  }, ‘‘);}

 

静态方法 - isArray
  这个方法不用多说,就是判断一个对象是否是数组,直接看个例子:
var names = [‘Collis‘, ‘Cyan‘];Array.isArray(names); // true

 

Function的新实例方法 - bind方法
  这个方法与apply/call类似,只不过apply/call是直接调用了方法,而bind只是把this绑定到了指定的参数上,而并不执行方法,看个例子:
// 仅仅绑定了this,还需要主动的通过对象去调用tooltip.show = showText.bind(tooltip);tooltip.show();overlay.show = showText.bind(overlay);overlay.show();// 直接调用的apply/call形式showText.call(tooltip);showText.call(overlay);

 

Date的新方法
静态方法 - now方法
  now方法返回当前距离1970年1月1日 00:00:00 UTC的毫秒数(Unix时间戳乘以1000)。看个例子:
Date.now()// 1364026285194类似的数字

  如果需要更精确的时间,可以使用window.performance.now()。它提供页面加载到命令运行时的已经过去的时间,单位是浮点数形式的毫秒。

window.performance.now()// 21311140.415类似的数字

  这一组方法可用于生成一种唯一的id标识。

实例方法 - toJSON方法
  自己写也不难,不过内置了也不错,看个例子:
alert(new Date().toJSON());

 

String的新实例方法 - 迟到的trim方法
  该方法用于去除字符串两端的空格,终于内置了,看个简单的例子:
" hello world ".trim()// "hello world"