JavaScript 内部对象

发布于 2016-11-02 17:19 阅读数 662

本文必须得到作者授权后,方可转载,摘要引流随意。
By 依韵 , From https://blog.cdswyda.com/post/20161102_2

本文主要对一些JavaScript中放在对象上的常用的方法进行了介绍,由于是基本方法,都比较简单,因此补充了一些ES5和ES6中的方法,并对不支持ES5和ES6的环境下给出了替代方案。

ES5中的方法,基本上都需要IE9+。

ES6中的方法,最新版的Chrome和FireFox基本都已经实现,可以直接测试,而即便是IE11也基本上都不支持。

Object

JavaScript原生提供一个Object对象,所有其他对象都继承自这个对象。Object本身也是一个构造函数,可以直接通过它来生成新对象。

构造函数

Object作为构造函数使用时,可以接受一个参数。如果该参数是一个对象,则直接返回这个对象;如果是一个原始类型的值,则返回该值对应的包装对象。

1
2
3
4
5
6
7
8
var o = new Object(); // 等同于var o = {};

var o1 = {a: 1};
var o2 = new Object(o1);
o1 === o2 // true

new Object(123) instanceof Number
// true

为了代码简介,以及更高的可读性以及效率,大多数情况下都试用字面量形式来创建对象。

工具方法

Object本身当作工具方法使用时,可以将任意值转为对象。这个方法常用于保证某个值一定是对象。如果参数是原始类型的值,Object方法返回对应的包装对象的实例。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
Object(); // 返回一个空对象

Object(undefined); // 返回一个空对象

Object(null); // 返回一个空对象

Object(1); // 等同于 new Number(1)
Object(1) instanceof Object; // true
Object(1) instanceof Number; // true

Object('foo'); // 等同于 new String('foo')
Object('foo') instanceof Object; // true
Object('foo') instanceof String; // true

Object(true); // 等同于 new Boolean(true)
Object(true) instanceof Object; // true
Object(true) instanceof Boolean; // true

如果Object方法的参数是一个对象,它总是返回原对象。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var arr = [];
Object(arr); // 返回原数组
Object(arr) === arr; // true

var obj = {};
Object(obj); // 返回原对象
Object(obj) === obj; // true

var fn = function () {};
Object(fn); // 返回原函数
Object(fn) === fn; // true

实例方法

除了Object对象本身的方法,还有不少方法是部署在Object.prototype对象上的,所有Object的实例对象都继承了这些方法。

Object实例对象的方法,主要有以下六个。

  • valueOf():返回当前对象对应的值。

  • toString():返回当前对象对应的字符串形式。数组、字符串、函数、Date对象都分别部署了自己版本的toString方法,覆盖了Object.prototype.toString方法。

  • toLocaleString():返回当前对象对应的本地字符串形式。

  • hasOwnProperty():判断某个属性是否为当前对象自身的属性,还是继承自原型对象的属性。

  • isPrototypeOf():判断当前对象是否为另一个对象的原型。

  • propertyIsEnumerable():判断某个属性是否可枚举。

静态方法

这些方法基本都是在ECMAScript 5.1中的规定的,IE9+浏览器环境才支持,因此只做罗列,更多介绍请看:MDN - Obiect

Object.keys方法和Object.getOwnPropertyNames方法很相似,一般用来遍历对象的属性。它们的参数都是一个对象,都返回一个数组,该数组的成员都是对象自身的(而不是继承的)所有属性名。它们的区别在于,Object.keys方法只返回可枚举的属性(关于可枚举性的详细解释见后文),Object.getOwnPropertyNames方法还返回不可枚举的属性名。

Object 还有以下一些方法,但是并不常用:

(1)对象属性模型的相关方法

  • Object.getOwnPropertyDescriptor():获取某个属性的attributes对象。

  • Object.defineProperty():通过attributes对象,定义某个属性。

  • Object.defineProperties():通过attributes对象,定义多个属性。

  • Object.getOwnPropertyNames():返回直接定义在某个对象上面的全部属性的名称。

(2)控制对象状态的方法

  • Object.preventExtensions():防止对象扩展。

  • Object.isExtensible():判断对象是否可扩展。

  • Object.seal():禁止对象配置。

  • Object.isSealed():判断一个对象是否可配置。

  • Object.freeze():冻结一个对象。

  • Object.isFrozen():判断一个对象是否被冻结。

(3)原型链相关方法

  • Object.create():生成一个新对象,并该对象的原型。

  • Object.getPrototypeOf():获取对象的Prototype对象。

Array

构造函数

ArrayJavaScript的内置对象,同时也是一个构造函数,可以用它生成新的数组。

1
2
3
var arr = new Array(2);
arr.length; // 2
arr; // [, ,]

上面代码中,Array构造函数的参数2,表示生成一个两个成员的数组,每个位置都是空值。

如果没有使用new,运行结果也是一样的。

但是请不要使用这种方式来创建一个数组,不仅仅是因为其书写不直观,而且对于Array构造函数来说,不同的参数,其行为结果不一致:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 无参数时,返回一个空数组
new Array() // []

// 单个正整数参数,表示返回的新数组的长度
new Array(1); // [ ,]
new Array(2); // [ , ,]

// 非正整数的数值作为参数,会报错
new Array(3.2); // RangeError: Invalid array length
new Array(-3); // RangeError: Invalid array length

// 单个非正整数参数(比如字符串、布尔值、对象等),
// 则该参数是返回的新数组的成员
new Array('abc'); // ['abc']
new Array([1]); // [Array[1]]

// 多参数时,所有参数都是返回的新数组的成员
new Array(1, 2); // [1, 2]
new Array('a', 'b', 'c'); // ['a', 'b', 'c']

实例方法

valueOf(),toString()

valueOf方法返回数组本身。toString方法返回数组的字符串形式。

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

a.valueOf(); // [1, 2, 3]
a.toString(); // "1,2,3"

push()

push方法用于在数组的末端添加一个或多个元素,并返回添加新元素后的数组长度。注意,该方法会改变原数组。

pop()

pop方法用于删除数组的最后一个元素,并返回该元素。注意,该方法会改变原数组。

pushpop结合使用,就构成了“后进先出”的栈结构(stack)。

join()

join方法以参数作为分隔符,将所有数组成员组成一个字符串返回。如果不提供参数,默认用逗号分隔。

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

a.join(' '); // '1 2 3 4'
a.join(' | '); // "1 | 2 | 3 | 4"
a.join(); // "1,2,3,4"

// 还可以传递一个空的字符串进去,将会把数组进行连接
a.join('') ; // "1234"

concat()

concat方法用于多个数组的合并。它将新数组的成员,添加到原数组的尾部,然后返回一个新数组(浅拷贝,复合类型为引用),原数组不变。

除了接受数组作为参数,concat也可以接受其他类型的值作为参数。它们会作为新的元素,添加数组尾部。

1
2
3
4
5
6
7
[1, 2, 3].concat([4, 5, 6]); 
// [1, 2, 3, 4, 5, 6]

// 等同于
[1, 2, 3].concat(4, 5, 6);
[1, 2, 3].concat(4, [5, 6]);
[1, 2, 3].concat([4], [5, 6]);

类似于jQuery中的jQuery.merge(),不同的是原生的concat方法返回一个新数组,而jQuery中的jQuery.merge()是修改第一个数组且其参数只能是数组或类数组

shift()

shift方法用于删除数组的第一个元素,并返回该元素。注意,该方法会改变原数组。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
var myFish = ['angel', 'clown', 'mandarin', 'surgeon'];

console.log('myFish before: ', myFish);
// "myFish before: angel,clown,mandarin,surgeon"

var shifted = myFish.shift(); 

console.log('myFish after: ' , myFish); 
// "myFish after: clown,mandarin,surgeon" 

console.log('Removed this element: ', shifted); 
// "Removed this element: angel"

pushshift结合使用,就构成了“先进先出”的队列结构(queue)。

unshift()

unshift方法用于在数组的第一个位置添加元素,并返回添加新元素后的数组长度。注意,该方法会改变原数组。

1
2
3
4
5
6
7
var arr = [1, 2];

arr.unshift(0); // the new array length is 3
// arr is [0, 1, 2]

arr.unshift(-2, -1); // length =  5
// arr is [-2, -1, 0, 1, 2]

reverse()

reverse方法用于颠倒数组中元素的顺序,返回改变后的数组。注意,该方法将改变原数组。

1
2
3
4
5
var a = ['one', 'two', 'three'];
var reversed = a.reverse(); 

console.log(a);        // ['three', 'two', 'one']
console.log(reversed); // ['three', 'two', 'one']

slice()

slice方法用于提取原数组的一部分,返回一个新数组,原数组不变。

它的第一个参数为起始位置(从0开始),第二个参数为终止位置(但该位置的元素本身不包括在内)。如果省略第二个参数,则一直返回到原数组的最后一个成员。

1
2
3
4
5
var fruits = ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango'];
var citrus = fruits.slice(1, 3); // from index 1 to index 3 

// fruits contains ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango']
// citrus contains ['Orange','Lemon']

slice也可以将类似数组的对象转为真正的数组。

1
2
3
4
5
6
7
8
// nodelist
var divNodeList = document.getElementsByTagName("div");
// 转为数组
var divArray = [].slice.call(divNodeList);

// 检验
Array.isArray(divArray); // true 
$.isArray(divArray); // true 

splice()

splice方法用于删除原数组的一部分成员,并可以在被删除的位置添加入新的数组成员,返回值是被删除的元素。注意,该方法会改变原数组。

splice的第一个参数是删除的起始位置,第二个参数是被删除的元素个数。如果后面还有更多的参数,则表示这些就是要被插入数组的新元素。

Remove 1 element from index 3

1
2
3
4
5
var myFish = ["angel", "clown", "drum", "mandarin", "surgeon"];
var removed = myFish.splice(3, 1);

// removed is ["mandarin"]
// myFish is ["angel", "clown", "drum", "surgeon"]

Remove 1 element from index 2, and insert "trumpet"

1
2
3
4
5
var myFish = ["angel", "clown", "drum", "surgeon"];
var removed = myFish.splice(2, 1, 'trumpet');

// myFish is ["angel", "clown", "trumpet", "surgeon"]
// removed is ["drum"]

Remove 2 elements from index 0, and insert "parrot", "anemone" and "blue"

1
2
3
4
5
var myFish = ["angel", "clown", "trumpet", "surgeon"];
var removed = myFish.splice(0, 2, "parrot", "anemone", "blue");

// myFish is ["parrot", "anemone", "blue", "trumpet", "surgeon"] 
// removed is ["angel", "clown"]

Remove 2 elements from index 2

1
2
3
4
5
var myFish = ["parrot", "anemone", "blue", "trumpet", "surgeon"]
var removed = myFish.splice(myFish.length -3, 2);

// myFish is ["parrot", "anemone", "surgeon"] 
// removed is ["blue", "trumpet"]

demo来自MDN

sort()

sort方法对数组成员进行排序,默认是按照字典顺序排序。排序后,原数组将被改变。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
['d', 'c', 'b', 'a'].sort();
// ['a', 'b', 'c', 'd']

[4, 3, 2, 1].sort();
// [1, 2, 3, 4]

[11, 101].sort();
// [101, 11]

[10111, 1101, 111].sort();
// [10111, 1101, 111]

由于其按字符串的字典顺序进行排序,而不是数值大小,因此直接使用很少。它可以接收一个回调函数作为排序规则。此回调函数接收两个参数,分别表示进行比较的两个元素,返回值大于0,表示第一个在第二个后面,否则都是第一个在第二个前面。

以下是使用回调函数进行自定义排序的例子:

 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
29
30
31
32
33
// 升序
[10111, 1101, 111].sort(function (a, b) {
  return a - b;
});
// [111, 1101, 10111]


// 降序
[10111, 1101, 111].sort(function (a, b) {
  return b - a;
});
// [10111, 1101, 111]

var personArr = [];

personArr.push({
    name: "张三",
    age: 30
}, {
    name: "李四",
    age: 24
}, {
    name: "王五",
    age: 28
});
personArr.sort(function(prev, next) {
    return prev.age - next.age;
});
// [
//   { name: "李四", age: 24 },
//   { name: "王五", age: 28  },
//   { name: "张三", age: 30 }
// ]

map()

ES5 map方法对数组的所有成员依次调用一个函数,根据函数结果返回一个新数组。其回调函数接收三个参数,分别是当前成员、当前位置和数组本身。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var arr = [1, 2, 3];

arr.map(function(elem, index, arr) {
    console.log(arr, index, elem);
    return elem * index;
});
// [1, 2, 3] 0 1
// [1, 2, 3] 1 2
// [1, 2, 3] 2 3

arr; // [1, 2, 3]

map 方法可接收第二个参数,表示回调函数执行时this所指向的对象。

此方法可使用jQuery中的jQuery.map()替换,第一个参数为数组或对象,第二个参数为遍历函数,返回一个新数组。

forEach()

forEach方法与map方法很相似,也是遍历数组的所有成员,执行某种操作,但是forEach方法一般不返回值,只用来操作数据。如果需要有返回值,一般使用map方法。

forEach方法的参数与map方法一致,第一个为函数,第二个为this指向的对象。数组的所有成员会依次执行该函数,它接受三个参数,分别是当前位置的值、当前位置的编号和整个数组。

forEach方法无法中断执行,总是会将所有成员遍历完。如果希望符合某种条件时,就中断遍历,要使用for循环。

此方法可使用jQuery中的jQuery.each()替换,第一个参数为数组或对象,第二个参数为遍历函数,返回原数组。

filter()

ES5filter方法的接收两个参数第一个为一个函数,第二个为函数运行时this的指向,所有数组成员依次执行该函数,此函数接收三个参数,第一个参数是当前数组成员的值,这是必需的,后两个参数是可选的,分别是当前数组成员的位置和整个数组。,返回结果为true的成员组成一个新数组返回。该方法不会改变原数组。

1
2
3
4
[1, 2, 3, 4, 5].filter(function (elem, index, arr) {
  return index % 2 === 0;
});
// [1, 3, 5]

此方法可使用jQuery中的jQuery.grep()替换,第一个参数为数组,第二个参数为遍历函数,返回符合条件的新数组。

some(),every()

ES5 这两个方法类似“断言”(assert),用来判断数组成员是否符合某种条件。

它们接受一个函数作为第一个参数,一个对象作为第二个参数,表示执行第一个函数时的this指向。所有数组成员依次执行第一个参数位的函数,返回一个布尔值。该函数接受三个参数,依次是当前位置的成员、当前位置的序号和整个数组。

some方法是只要有一个数组成员的返回值是true,则整个some方法的返回值就是true,否则false

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var arr = [1, 2, 3, 4, 5];
arr.some(function (elem, index, arr) {
  return elem >= 3;
});
// true

var arr = [1, 2, 3, 4, 5];
arr.every(function (elem, index, arr) {
  return elem >= 3;
});
// false

reduce(),reduceRight()

ES5reduce方法和reduceRight方法依次处理数组的每个成员,最终累计为一个值。

它们的差别是,reduce是从左到右处理(从第一个成员到最后一个成员),reduceRight则是从右到左(从最后一个成员到第一个成员),其他完全一样。

这两个方法的第一个参数都是一个函数。该函数接受以下四个参数。

  1. 累积变量,默认为数组的第一个成员
  2. 当前变量,默认为数组的第二个成员
  3. 当前位置(从0开始)
  4. 原数组

find()和findIndex()

ES6数组实例的find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined。其中find方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组。

1
2
3
[1, 5, 10, 15].find(function(value, index, arr) {
  return value > 9;
}); // 10

数组实例的findIndex方法的用法与find方法非常类似,不同的是其返回的是符合条件的值的索引。

1
2
3
[1, 5, 10, 15].findIndex(function(value, index, arr) {
  return value > 9;
}); // 2

此方法可使用jQuery中的jQuery.inArray()方法替代,第一个参数为要查找的值,第二个参数为要查找的数组,第三个参数为开始查找的位置,返回值为查找到的值的索引,未找到则为-1

静态方法

Array.isArray()

ES5 Array.isArray方法用来判断一个值是否为数组。

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

typeof a; // "object"
Array.isArray(a); // true

此方法可使用jQuery中的jQuery.isArray()方法替代,参数和返回结果完全一致。

Array.from()

ES6 Array.from 方法可以将一个类数组对象或可遍历对象转换成真正的数组。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 将类数组对象(arguments)转换成数组
(function () {
    var args = Array.from(arguments);
    return args;
})(1, 2, 3);                            // [1, 2, 3]

// nodelist
var divNodeList = document.getElementsByTagName("div");
// 转为数组
var divArray = Array.from(divNodeList);

// 检查是否为数组
$.isArray(divNodeList);                     // false
$.isArray(divArray);                        // true

此方法可使用jQuery中的jQuery.makeArray()方法替代。

Array.of()

ES6 Array.of方法用于将一组值,转换为数组。

1
2
3
Array.of(3, 11, 8); // [3,11,8]
Array.of(3); // [3]
Array.of(3).length; // 1

此方法可通过数组的slice方法实现。

1
2
3
4
5
function ArrayOf(){
  return [].slice.call(arguments);
}

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

Function

函数声明

函数声明有两种方式:

function命令

function命令声明的代码区块,就是一个函数。function命令后面是函数名,函数名后面是一对圆括号,里面是传入函数的参数。函数体放在大括号里面。

1
2
3
function sayHello () {
    console.log('Hello!');
}

sayHello为函数名,调用时通过sayHello()即可。

函数表达式

其实就是常规的声明一个变量,将一个匿名函数赋值给这个变量。

1
2
3
var sayHello = function () {
    console.log('Hello!');
};

需要注意的是,此时函数结尾的大括号后应该有一个;,而且function后面不需要写函数名了,= 前面的变量就是函数名。

两种方式的不同点在于,直接的function命令存在变量提升,而后者不行。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
sayHello1(); // Hello!
sayHello2;  // undefined
sayHello2(); // sayHello2 is not a function(…)

function sayHello1() {
    console.log('Hello!');
}
var sayHello2 = function() {
    console.log('Hello!');
};

sayHello1即使被写了调用的后面,也依然能执行,这是因为存在变量提升,实际是将function sayHello1 的定义提升到了代码最前部分,所以能够正常运行。

sayHello2则不行,原因在于,JavaScript中变量提升,提升的仅仅是var sayHello2这一句,之后将的赋值语句尚未执行,因此使用sayHello2没有语法错误,但是作为函数调用时sayHello2()出错了。

此外,还可以通过new Function()的形式创建函数,但是其写法复杂,太丑陋,基本不使用。

属性和方法

  • name 返回函数的函数名。(实际并不一定是真正的函数名,实际返回的是紧跟function关键字的那个字符串,没有则为空字符串)

  • length 属性返回函数预期传入的参数个数,即函数定义之中的参数个数。

  • toString() 函数的toString方法返回函数的源码,包括函数大括号内的注释部分。

参数

不管函数定义时,指定了多少个参数,在调用时,传递任意个参数都,语法上都没有问题。

参数的默认值

ES6之前,JavaScript没有提供设置参数默认值的方式,需要在函数代码中进行设置。以下是示例:

1
2
3
4
var sayName = function(name) {
    name = name || 'noName'; // 或 name = name  != undefined ? name : 'noName';
    console.log('My name is', name);
};

ES6中可直接为函数的参数指定默认值:

1
2
3
var sayName = function(name = 'noName') {
    console.log('My name is', name);
};

传值方式

javascript没有提供指定传值的方式,其参数传递时,引用类型还是值类型,仅取决于参数本身的类型。

  • 参数为原始类型(数值、字符串、布尔值) ,则传递方式为值传递

  • 参数为复合类型(数组,对象,函数,以及ES6新增的SetWeakSetMapWeakMap),则传递方式为引用传递。

argument对象

此对象仅存在于函数内部,表示函数的实参参数列表,是一个类数组对象,每个实参可以通过arguments[index]获取到,其中index表示实参的顺序,从0开始。

1
2
3
4
5
6
function bind(fun, context) {
    var args = [].slice.call(arguments, 2);
    return function() {
        fun.apply(context || this, args.concat([].slice.call(arguments)));
    };
}

以上是一个模拟没有提供bind方法的示例,作用类似于$.proxy,传入一个函数,以及上下文环境,返回一个替换了上下文环境的新函数。其中内部两次使用的arguments对象。外层获取arguments对象从第三个参数开始的其余参数(除去传入的函数和上下文环境),如果没有则返回一个空数组。返回的函数中,再次使用了arguments对象,apply方法第一个参数的数组为,外层截获的参数连接上实际调用时的参数。 因此其作用是在,生成新函数和实际调用返回函数时,都可以指定参数,如果生成新函数时指定了额外参数,则实际使用时的参数依次后移。

严格模式下,arguments对象只读。

函数作用域

每一个新的函数就形成了一个新的作用域。在ES6之前,函数也是JavaScript中唯一形成作用域的唯一方式(ES6中,一个{}就可以形成一个作用域)。

JavaScript中广泛使用一个立即调用的函数,来形成一个子作用域。

立即调用的函数表达式(IIFE)

JavaScript中函数调用时,使用()即可。

以下形式都可以:

1
2
3
4
5
(function(){ /* code */ }());
// 或者
(function(){ /* code */ })();
// 还可以
!function(){ /* code */ }();

立即执行的函数表达式,它的目的有两个:一是不必为函数命名,避免了污染全局变量;二是IIFE内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量。

以下是一个复杂页面js代码示例。为了不同区块归类,都使用了立即执行的函数表达式。其一是使用了函数作用域来限定不同的区块,同时使用了立即表达式,来减少对函数的命名,再调用的过程。

 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
29
// 主页面导航栏菜单 
(function(win, $) {
    // TODO
}(this, jQuery));

// 顶层Menu
(function(win, $) {
    // TODO
}(this, jQuery));

// ContextMenu
(function(win, $) {
    // TODO
}(this, jQuery));

//main frame message reminder list
(function(win, $) {
    // TODO
}(this, jQuery));

// 消息提醒
(function(win, $) {
    // TODO
}(this, jQuery));

// 页面右下角-快捷菜单
(function(win, $) {
    // TODO
}(this, jQuery));

我们看到,在上面在使用IIFE时,还传递了参数,最后的括号里是实参。传递的this在全局下即为浏览器的window对象,在函数内部使用了win来简写,函数内部可以访问window对象,但是依然传入,是为了直观明了,表示此段内容会公开一些方法放到window对象上,供之后使用;而第二个参数传递jQuery进去,内部使用$简写,这有两个作用:1、即使引入的jQuery后又交出了$的使用权限,在函数内部依然可以使用$符号代表jQuery。2、类似于前面的window,可以放一下扩展到jQuery上去,但是一般不这么做

闭包

讲到了JavaScript,又讲到了其中的函数,自然不能忘了JavaScript中的一大特色——闭包

简单来讲,闭包就是: 在函数内部,再定义一个函数并将此函数返回,这样就形成了一个闭包。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var addNum = (function() {
    var result = result || 0;
    return function(num) {
        result += num;
        return result;
    };
})();
console.log('累加函数addNum');
console.log('addNum(10)', addNum(10)); // addNum(10)  10
console.log('addNum(12)', addNum(12)); // addNum(12)  22
console.log('addNum(30)', addNum(30));  // addNum(30)  52

上面就是一个使用闭包的例子,addNum后是一个立即调用的函数,此函数内部又定义并返回了一个函数。那么它有什么作用呢?我们看其运行结果,其每次都是在上一次运行后的结果上进行递增的。实质是:返回的这个函数可以访问到立即调用函数里面定义的result变量。

这其实和函数作用域息息相关,一个函数形成一个作用域,上例中,立即执行的匿名函数形成了一个作用域,内部变量result,外部无法访问。此函数内部还存在一个函数,此函数又形成了一个作用域,此函数是在匿名函数中的,因此它可以访问上一层中,从而读写result。由于此函数被返回出来,所以外部可以调用这个函数,从而对直接访问不到的变量进行读写。

基于这个原理,可以用闭包实现对对象中私有属性的封装:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Person(name) {
    var _age;

    function setAge(n) {
        // 还可以加上逻辑判断
        _age = n;
    }

    function getAge() {
        return _age;
    }

    return {
        name: name,
        getAge: getAge,
        setAge: setAge
    };
}

var p1 = Person('张三');
p1.setAge(25);
p1.getAge(); // 25

以上就是利用闭包实现了,对私有属性_age的封装,读写只能通过get set

在ES5之后,这个可用通过对象的属性描述对象来完成,在其中可以为每个属性设置自己的get set访问器。VUE能实现数据变化的监控,依赖的就是这个,需要IE9+

String

String对象是JavaScript原生提供的三个包装对象之一,用来生成字符串的包装对象。

实际上,字符串的包装对象是一个类似数组的对象。

1
2
new String('hello');
// String {0: "h", 1: "e", 2: "l", 3: "l", 4: "o", length: 5,[[PrimitiveValue]]: "hello"}

除了用作构造函数,String对象还可以当作工具方法使用,将任意类型的值转为字符串。

1
2
String(true); // "true"
String(5); // "5"

静态方法

  • String.fromCharCode() 该方法的参数是一系列Unicode码点,返回对应的字符串。
1
2
3
String.fromCharCode(65) ;  // "A"

String.fromCharCode(104, 101, 108, 108, 111); // "hello"

正则相关方法

  • match()

    match方法用于确定原字符串是否匹配某个子字符串,返回一个数组,成员为匹配的第一个字符串。如果没有找到匹配,则返回null。返回数组还有index属性和input属性,分别表示匹配字符串开始的位置和原始字符串。

1
2
3
var matches = 'cat, bat, sat, fat'.match('at');
matches.index; // 1
matches.input; // "cat, bat, sat, fat"
  • search()

search方法的用法等同于match,但是返回值为匹配的第一个位置。如果没有找到匹配,则返回-1。

1
'cat, bat, sat, fat'.search('at'); // 1
  • replace()

replace方法用于替换匹配的子字符串,一般情况下只替换第一个匹配(除非使用带有g修饰符的正则表达式)。

1
'aaa'.replace('a', 'b'); // "baa"
  • split()

split方法按照给定规则分割字符串,返回一个由分割出来的子字符串组成的数组。split方法还可以接受第二个参数,限定返回数组的最大成员数。

1
2
3
4
5
'hello'.split(''); // ["h", "e", "l", "l", "o"]

'hello'.split('',2); // ["h", "e"]

'margin-left'.split('-'); // ["margin", "left"]

之所以将这几个方法单独归为正则相关方法,是因为其都可以使用一个正则表达式作为参数,而且在ES6中,这几个方法在语言内部全部调用RegExp的实例方法,从而做到所有与正则相关的方法,全都定义在RegExp对象上。

实例方法和属性

length

length属性返回字符串的长度。

charAt()

charAt方法返回指定位置的字符,参数是从0开始编号的位置。

charCodeAt()

charCodeAt方法返回给定位置字符的Unicode码点(十进制表示),相当于String.fromCharCode()的逆操作。

1
'hello'.charCodeAt(0); // 104

concat()

concat方法用于连接字符串,返回一个新字符串,不改变原字符串。

1
2
3
var hello = 'Hello';
var sayHello = hello.concat(' ,Kevin', ' have a nice day.');
// "Hello ,Kevin have a nice day."

此方法基本上可以使用++=来替换,不过对于此方法来说如果参数不是字符串,concat方法会将其先转为字符串,然后再连接。而+在两个运算数都是数值是进行数值相加。

slice()

slice方法用于从原字符串取出子字符串并返回,不改变原字符串。

它的第一个参数是子字符串的开始位置,第二个参数是子字符串的结束位置(不含该位置),可省略,则表示子字符串一直到原字符串结束。

1
2
'JavaScript'.slice(0, 4); // "Java"
'JavaScript'.slice(4); // "Script"

参数为负数表示从结尾开始倒数计算的位置,即该负值加上字符串长度。可读性很差,非必要请不要使用。

substring()

substring方法用于从原字符串取出子字符串并返回,不改变原字符串。它与slice作用相同,但有一些奇怪的规则,因此不建议使用这个方法,优先使用slice

substr()

substr方法用于从原字符串取出子字符串并返回,不改变原字符串。

substr方法的第一个参数是子字符串的开始位置,第二个参数是子字符串的长度,可省略,表示一直取到结束。

1
2
3
4
5
'JavaScript'.substr(4, 6); // "Script"
'JavaScript'.substr(4); // "Script"

'JavaScript'.substr(-6); // "Script"
'JavaScript'.substr(4, -1) // ""

如果第一个参数是负数,表示倒数计算的字符位置。如果第二个参数是负数,将被自动转为0,因此会返回空字符串。

indexOf(),lastIndexOf()

这两个方法用于确定一个字符串在另一个字符串中的位置,都返回一个整数,表示匹配开始的位置。如果返回-1,就表示不匹配。两者的区别在于,indexOf从字符串头部开始匹配,lastIndexOf从尾部开始匹配。

此方法接收两个参数,第一个为要匹配的字符串,第二个为开始位置(此位置开始向前或此位置开始向后)。

1
2
3
4
5
6
'hello world'.indexOf('o'); // 4
'JavaScript'.indexOf('script'); // -1

'hello world'.lastIndexOf('o'); // 7
'hello world'.indexOf('o', 6); // 7
'hello world'.lastIndexOf('o', 6); // 4

trim()

ES5trim方法用于去除字符串两端的空格,返回一个新字符串,不改变原字符串。

1
2
3
4
5
6
var str = "         lots of spaces before and after         ";

str.trim();    // "lots of spaces before and after"

str;            // "         lots of spaces before and after         "
$.trim(str);    // "lots of spaces before and after"

可使用jQuery中的jQuery.trim()替代。

toLowerCase(),toUpperCase()

toLowerCase方法用于将一个字符串全部转为小写,toUpperCase则是全部转为大写。它们都返回一个新字符串,不改变原字符串。

Number

Number对象是数值对应的包装对象,可以作为构造函数使用,也可以作为工具函数使用。

作为构造函数时,它用于生成值为数值的对象。作为工具函数时,它可以将任何类型的值转为数值。

1
2
3
4
5
var n = new Number(1);
typeof n; // "object"

Number('hello'); // NaN
Number('12545'); // 12545

Number对象的属性

Number对象拥有以下一些属性。

  • Number.POSITIVE_INFINITY:正的无限,指向Infinity。
  • Number.NEGATIVE_INFINITY:负的无限,指向-Infinity。
  • Number.NaN:表示非数值,指向NaN。
  • Number.MAX_VALUE:表示最大的正数,相应的,最小的负数为-Number.MAX_VALUE。
  • Number.MIN_VALUE:表示最小的正数(即最接近0的正数,在64位浮点数体系中为5e-324),相应的,最接近0的负数为-Number.MIN_VALUE。
  • Number.MAX_SAFE_INTEGER:ES6 表示能够精确表示的最大整数,即9007199254740991。

  • Number.MIN_SAFE_INTEGER:ES6 表示能够精确表示的最小整数,即-9007199254740991。

实例方法

toString()

Number对象部署了自己的toString方法,用来将一个数值转为字符串形式。

toString方法可以接受一个参数,表示输出的进制。如果省略这个参数,默认将数值先转为十进制,再输出字符串;否则,就根据参数指定的进制,将一个数字转化成某个进制的字符串。

1
2
3
(10).toString(2); // "1010"
(10).toString(8); // "12"
(10).toString(16); // "a"

toFixed()

toFixed方法用于将一个数转为指定位数的小数,返回这个小数对应的字符串。参数取值范围为0到20,省略则取0.

1
2
3
(10).toFixed(2); // "10.00"
10.005.toFixed(2); // "10.01"
10.005.toFixed(); // "10"

toExponential()

toExponential方法用于将一个数转为科学计数法形式。参数表示小数点后有效数字的位数,范围为0到20,超出这个范围,会抛出一个RangeError

1
2
3
4
5
6
7
(10).toExponential(); // "1e+1"
(10).toExponential(1); // "1.0e+1"
(10).toExponential(2); // "1.00e+1"

(1234).toExponential() ; // "1.234e+3"
(1234).toExponential(1); // "1.2e+3"
(1234).toExponential(2); // "1.23e+3"

toPrecision()

toPrecision方法用于将一个数转为指定位数的有效数字。toPrecision方法的参数为有效数字的位数,范围是1到21,超出这个范围会抛出RangeError错误。

1
2
3
4
5
(12.34).toPrecision(1); // "1e+1"
(12.34).toPrecision(2); // "12"
(12.34).toPrecision(3); // "12.3"
(12.34).toPrecision(4); // "12.34"
(12.34).toPrecision(5); // "12.340"

toPrecision方法用于四舍五入时不太可靠,跟浮点数不是精确储存有关。一下代码展示尾数同为5时,不同的处理结果。

1
2
3
4
(12.35).toPrecision(3); // "12.3"
(12.25).toPrecision(3); // "12.3"
(12.15).toPrecision(3); // "12.2"
(12.45).toPrecision(3); // "12.4"

数值相关方法

下面的两个方法是全局方法,和Number对象没有直接关系。但是出于其实际和Number相关,且在ES6中将其移到了Number下,因此放在了此处。

parseInt()

parseInt() 函数将给定的字符串以指定基数(radix/base)解析成为整数。接收两个参数:第一个为字符串,表示要被解析的值。如果参数不是一个字符串,则将其转换为字符串,字符串开头的空白符将会被忽略。第二个为基数,一个2到36之间的整数值,用于指定转换中采用的基数。比如参数"10"表示使用我们通常使用的十进制数值系统。总是指定该参数可以消除阅读该代码时的困惑并且保证转换结果可预测。

1
2
3
4
parseInt("      -15", 10);   // -15 
parseInt("-15e1", 10); // 15  科学计数法表示数值的字符串

parseInt("Hello",10); // NaN

parseFloat

parseFloat()方法将参数中指定的字符串解析成为一个浮点数字并返回。

parseFloat将它的字符串参数解析成为浮点数并返回.如果在解析过程中遇到了正负号(+或-),数字(0-9),小数点,或者科学记数法中的指数(e或E)以外的字符,则它会忽略该字符以及之后的所有字符,返回当前已经解析到的浮点数.同时参数字符串首位的空白符会被忽略。

如果参数字符串的第一个字符不能被解析成为数字,则parseFloat返回NaN

1
2
3
4
5
6
7
8
// 3.14 
parseFloat("3.14");
parseFloat("314e-2");
parseFloat("0.0314E+2");
parseFloat("3.14more non-digit characters");

// NaN
parseFloat("F3.14");

Boolean

最好不要使用Boolean作为构造函数来使用,因为1、可读性不高。2、容易引起错误。

请看以下示例:

1
2
3
if (new Boolean(false)) {
    alert('傻了吧!');
} 

以上代码将会弹窗,出乎意料吧,这就是不要使用Boolean作为构造函数来创建布尔值的原因。这个原因是这样的,new Boolean(false) 创建的是一个值为false的包装对象(注意它是对象),对对象进行布尔运行,其值自然是true。这也就是为什么上面代码会弹窗的原因。

Boolean对象除了可以作为构造函数,还可以单独使用,将任意值转为布尔值。这时Boolean就是一个单纯的工具方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Boolean(undefined); // false
Boolean(null); // false
Boolean(0); // false
Boolean(''); // false
Boolean(NaN); // false

Boolean(1); // true
Boolean('false'); // true
Boolean([]); // true
Boolean({}); // true
Boolean(function () {}); // true
Boolean(/foo/); // true

不过个人更推荐使用!!来将其他类型的值转化为布尔值。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
!!undefined; // false
!!null; // false
!!0; // false
!!''; // false
!!NaN; // false

!!1; // true
!!'false'; // true
!![]; // true
!!{}; // true
!!function () {}; // true
!!/foo/; // true

Date

Date对象是JavaScript提供的日期和时间的操作接口。它可以表示的时间范围是,1970年1月1日00:00:00前后的各1亿天(单位为毫秒)。

Date对象可以作为普通函数直接调用,返回一个代表当前时间的字符串和是否具有参数无关

1
2
Date(); // "Mon Oct 31 2016 21:24:43 GMT+0800 (中国标准时间)"
Date(2016,11,11); // "Mon Oct 31 2016 21:24:50 GMT+0800 (中国标准时间)"

new Date()

Date还可以当作构造函数使用。对它使用new命令,会返回一个Date对象的实例。如果不加参数,生成的就是代表当前时间的对象。

1
2
var nowDate = new Date();
// Mon Oct 31 2016 21:27:51 GMT+0800 (中国标准时间)

上面代码生成的是一个Date对象,而不是时间类型的字符串。

其作为构造函数使用时,可接受多种形式的字符串。

new Date(milliseconds)

Date对象接受从1970年1月1日00:00:00 UTC开始计算的毫秒数作为参数,负值表示之前的时间。

1
2
3
4
var date1971 = new Date(365 * 24 * 60 * 60 * 1000);
// Fri Jan 01 1971 08:00:00 GMT+0800 (中国标准时间)
var date1969 = new Date(-365 * 24 * 60 * 60 * 1000);
// Wed Jan 01 1969 08:00:00 GMT+0800 (中国标准时间)

new Date(datestring)

Date对象还接受一个日期字符串作为参数,返回所对应的时间。

所有可以被Date.parse()方法解析的日期字符串,都可以当作Date对象的参数来生成时间。但是由于环境不同,产生结果可能不一致。

new Date(year, month [, day, hours, minutes, seconds, ms])

Date对象还可以接受多个整数作为参数,依次表示年、月、日、小时、分钟、秒和毫秒。如果采用这种格式,最少需要提供两个参数(年和月),其他参数都是可选的,默认等于0。

各个参数取值范围如下:

  • year:四位年份,如果写成两位数,则加上1900
  • month:表示月份,0表示一月,11表示12月
  • date:表示日期,1到31 日期特殊,不是从0开始的

  • hour:表示小时,0到23
  • minute:表示分钟,0到59
  • second:表示秒钟,0到59
  • ms:表示毫秒,0到999

这些参数如果超出返回会进行自动折算,例如0日表示上月最后一天,12月表示下一年的1月,请不要超过参数应有范围,因为不仅可读性非常差,而且在多个参数都超出范围的情况下,自己都看不出到底是指什么时间。

1
2
3
4
5
6
// 2016 年 1月1日
new Date(2016, 0); // Fri Jan 01 2016 00:00:00 GMT+0800 (中国标准时间)
new Date(2016, 0, 1); // Fri Jan 01 2016 00:00:00 GMT+0800 (中国标准时间)

// 如果天数设为0,这则表示上一天:下面结果变成了15年12月31日
new Date(2016, 0, 0);  // Thu Dec 31 2015 00:00:00 GMT+0800 (中国标准时间)

日期的运算

  • 两个日期对象进行减法运算,还有乘、除、取余数等操作,返回的都是它们间隔的毫秒数进行数学运算的结果;
  • 而进行加法运算,返回连接后的两个字符串。
1
2
3
4
5
6
7
8
var date2016 = new Date(2016,0);
var date2015 = new Date(2015,0);

date2016 - date2015; 
// 31536000000

date2016 + date2015; 
// "Fri Jan 01 2016 00:00:00 GMT+0800 (中国标准时间)Thu Jan 01 2015 00:00:00 GMT+0800 (中国标准时间)"

静态方法

Date.now()

Date.now方法返回当前距离1970年1月1日 00:00:00 UTC的毫秒数。

1
Date.now(); // 1477922298018

如果需要比毫秒更精确的时间,可以使用window.performance.now()。它提供页面加载到命令运行时的已经过去的时间,可以精确到千分之一毫秒。

1
window.performance.now();  // 1994585.115

Date.parse()

parse方法用来解析日期字符串,返回距离1970年1月1日 00:00:00的毫秒数。

标准的日期字符串的格式,应该完全或者部分符合RFC 2822ISO 8061,即YYYY-MM-DDTHH:mm:ss.sssZ格式,其中最后的Z表示时区。但是,其他格式也可以被解析,请看下面的例子。

1
2
3
4
5
6
7
8
Date.parse('Aug 9, 1995');
// 返回807897600000,以下省略返回值

Date.parse('January 26, 2011 13:51:50');
Date.parse('Mon, 25 Dec 1995 13:30:00 GMT');
Date.parse('Mon, 25 Dec 1995 13:30:00 +0430');
Date.parse('2011-10-10');
Date.parse('2011-10-10T14:48:00');

如果解析失败,返回NaNIE9+才支持ISO 8601 format (即这样的格式:"2011-10-10" (仅日期)或 "2011-10-10T14:48:00"

Date.UTC()

默认情况下,Date对象返回的都是当前时区的时间。Date.UTC方法可以返回UTC时间(世界标准时间)。该方法接受年、月、日等变量作为参数(格式和返回和new Date()的此用法相同),返回当前距离1970年1月1日 00:00:00 UTC的毫秒数。

1
2
3
4
5
6
// 格式
Date.UTC(year, month[, date[, hrs[, min[, sec[, ms]]]]]);

// 用法
Date.UTC(2016, 9, 31);
// 1477872000000

实例方法

Date对象的实例方法比较多,但也都很简单,下标列出了Date对象上的实例方法。来自W3Cschool - Date

方法 描述
Date() 返回当日的日期和时间。
getDate() 从 Date 对象返回一个月中的某一天 (1 ~ 31)。
getDay() 从 Date 对象返回一周中的某一天 (0 ~ 6)。
getMonth() 从 Date 对象返回月份 (0 ~ 11)。
getFullYear() 从 Date 对象以四位数字返回年份。
getYear() 请使用 getFullYear() 方法代替。
getHours() 返回 Date 对象的小时 (0 ~ 23)。
getMinutes() 返回 Date 对象的分钟 (0 ~ 59)。
getSeconds() 返回 Date 对象的秒数 (0 ~ 59)。
getMilliseconds() 返回 Date 对象的毫秒(0 ~ 999)。
getTime() 返回 1970 年 1 月 1 日至今的毫秒数。
getTimezoneOffset() 返回本地时间与格林威治标准时间 (GMT) 的分钟差。
getUTCDate() 根据世界时从 Date 对象返回月中的一天 (1 ~ 31)。
getUTCDay() 根据世界时从 Date 对象返回周中的一天 (0 ~ 6)。
getUTCMonth() 根据世界时从 Date 对象返回月份 (0 ~ 11)。
getUTCFullYear() 根据世界时从 Date 对象返回四位数的年份。
getUTCHours() 根据世界时返回 Date 对象的小时 (0 ~ 23)。
getUTCMinutes() 根据世界时返回 Date 对象的分钟 (0 ~ 59)。
getUTCSeconds() 根据世界时返回 Date 对象的秒钟 (0 ~ 59)。
getUTCMilliseconds() 根据世界时返回 Date 对象的毫秒(0 ~ 999)。
parse() 返回1970年1月1日午夜到指定日期(字符串)的毫秒数。
setDate() 设置 Date 对象中月的某一天 (1 ~ 31)。
setMonth() 设置 Date 对象中月份 (0 ~ 11)。
setFullYear() 设置 Date 对象中的年份(四位数字)。
setYear() 请使用 setFullYear() 方法代替。
setHours() 设置 Date 对象中的小时 (0 ~ 23)。
setMinutes() 设置 Date 对象中的分钟 (0 ~ 59)。
setSeconds() 设置 Date 对象中的秒钟 (0 ~ 59)。
setMilliseconds() 设置 Date 对象中的毫秒 (0 ~ 999)。
setTime() 以毫秒设置 Date 对象。
setUTCDate() 根据世界时设置 Date 对象中月份的一天 (1 ~ 31)。
setUTCMonth() 根据世界时设置 Date 对象中的月份 (0 ~ 11)。
setUTCFullYear() 根据世界时设置 Date 对象中的年份(四位数字)。
setUTCHours() 根据世界时设置 Date 对象中的小时 (0 ~ 23)。
setUTCMinutes() 根据世界时设置 Date 对象中的分钟 (0 ~ 59)。
setUTCSeconds() 根据世界时设置 Date 对象中的秒钟 (0 ~ 59)。
setUTCMilliseconds() 根据世界时设置 Date 对象中的毫秒 (0 ~ 999)。
toSource() 返回该对象的源代码。
toString() 把 Date 对象转换为字符串。
toTimeString() 把 Date 对象的时间部分转换为字符串。
toDateString() 把 Date 对象的日期部分转换为字符串。
toGMTString() 请使用 toUTCString() 方法代替。
toUTCString() 根据世界时,把 Date 对象转换为字符串。
toLocaleString() 根据本地时间格式,把 Date 对象转换为字符串。
toLocaleTimeString() 根据本地时间格式,把 Date 对象的时间部分转换为字符串。
toLocaleDateString() 根据本地时间格式,把 Date 对象的日期部分转换为字符串。
UTC() 根据世界时返回 1970 年 1 月 1 日 到指定日期的毫秒数。
valueOf() 返回 Date 对象的原始值。

Math

属性

Math对象提供以下一些只读的数学常数。

  • Math.E:常数e。

  • Math.LN2:2的自然对数。

  • Math.LN10:10的自然对数。

  • Math.LOG2E:以2为底的e的对数。

  • Math.LOG10E:以10为底的e的对数。

  • Math.PI:常数Pi。

  • Math.SQRT1\_2:0.5的平方根。

  • Math.SQRT2:2的平方根。

数值如下:

1
2
3
4
5
6
7
8
Math.E; // 2.718281828459045
Math.LN2; // 0.6931471805599453
Math.LN10; // 2.302585092994046
Math.LOG2E; // 1.4426950408889634
Math.LOG10E; // 0.4342944819032518
Math.PI; // 3.141592653589793
Math.SQRT1_2; // 0.7071067811865476
Math.SQRT2; // 1.4142135623730951

方法

Math对象提供以下一些数学方法。

  • Math.abs():绝对值

  • Math.ceil():向上取整

  • Math.floor():向下取整

  • Math.max():最大值 接受多个参数,返回最大值,本身不能传入数组,可使用apply 例如Math.max.apply([],[1,2,3]);

  • Math.min():最小值 接受多个参数,返回最小值,本身不能传入数组,可使用apply 例如Math.min.apply([],[1,2,3]);

  • Math.pow():指数运算,第一个参数为底数,第二个参数为指数。2的立方则为Math.pow(2, 3)`

  • Math.sqrt():平方根,参数为负值则返回NaN

  • Math.log():自然对数,即以数学常数e为底的对数。如果要计算以10为底的对数,可以先用Math.log求出自然对数,然后除以Math.LN10;求以2为底的对数,可以除以Math.LN2

  • Math.exp():e的指数

  • Math.round():四舍五入

  • Math.random():随机数,范围[0,1),需要生成a到b之间的随机数可写为Math.random() * (b - a) + a ,需要整数再取整即可。

还有一些三角函数相关方法:

  • Math.sin():返回参数的正弦

  • Math.cos():返回参数的余弦

  • Math.tan():返回参数的正切

  • Math.asin():返回参数的反正弦(弧度值)

  • Math.acos():返回参数的反余弦(弧度值)

  • Math.atan():返回参数的反正切(弧度值)

ES6 中对Math对象进行了一些扩展,添加了17个方法,但是这些方法都不常用,此处不列出了,可以查看 Math对象的扩展

RegExp

JSON

参考链接


Comments
Write a Comment