js 中的引用类型(内置对象)
前言
本篇笔记的目标是深刻理解引用类型的概念、理解基本的 JavaScript 类型、了解基本类型的方法和使用基本包装类型。需要了解每种引用类型的详细介绍可以查阅 MDN 文档。其他相关笔记还有如下几篇:
- JavaScript 基础(超详细)
- js 中的引用类型(内置对象)
- js 中的对象属性——configurable、writable 等(数据属性和访问器属性)
- js 中原型、原型链和继承概念(详细全面)
- 函数使用进阶——递归——闭包
1. 引用类型概述
初学面向对象编程时,特别是看了有关继承的概念时。我作为一个初学者,对 Object、Array、Date 等原生引用类型和自定义类(自定义引用类型)对象总有一种朦朦胧胧的感觉。理不清它们之间的关联和一些本质上的东西。这可能也只是我自己一个人才存在的问题(因为当时只是跟着教学视频去学习)。我曾一度潜移默化受类的继承的影响,脑子出现了下图二错误的构思。不知道当初的你是否也有相似的历程,欢迎评论区留言。
进入正题。什么是引用类型?引用类型是一种数据结构,用于将数据和功能组织在一起,它通常也被称为类(ES6 引入的概念)。引用类型也被称为对象定义,它们描述的是一类对象所具有的属性和方法。每个对象都是某特定引用类型的一个实例。ECMAScript 提供了很多原生引用类型(例如 Object、Date 等)。新对象是使用 new 操作符后跟构造函数来创建的(当然也可以通过字面量的形式创建)。构造函数本身就是一个函数,只不过该函数用途比较特殊,是出于创建某类(或者说某特定引用类型)的实例对象的目的而定义的。
var arr0 = new Array();
var obj0 = new Object();
上述两行代码中,分别创建了两个新的对象(实例)。它们分别是 Array 型和 Object 型的对象。创建不同引用类型的实例时会为新对象定义了默认的属性和方法。
结合书上的概念写下了这么多笔记,那我自己的脑里当时是怎么想的呢?接下来就是我的个人想法,如果不同或者有误欢迎评论区给予指正。
面向对象编程中一切皆对象(Object),但是对象又分为很多类型(对象属于引用型数据,因此这些分类又叫引用类型)。受定式思维的影响,以前我常将引用型数据中的 Object 类型数据与对象(Object)的概念死死地绑在一起。现在我想彻底将它们隔离开来理解引用类型。首先,ECMAScript 提供了很多原生引用类型,即在进入全局环境前就已经替开发者定义了很多种常用的引用类型(可以创建哪些类型对象)。其中所有对象都属于 Object 类型的对象,即其他任何类型都继承自该父类。其次,每类对象的定义都与一个内置对象(构造函数)有关。而这些内置对象又属于一个函数类型的实例对象(例如内置对象 Object、Array、Date)。另外开发者也可以自定义引用类型,即自定义某类对象。当然它们也跟内置引用类型一样,继承自 Object 类且和一个构造函数关联。
需要深刻理解 Object、Array、Date 等内置对象的概念,它们只是执行环境定义好的一些函数类型的对象,调用它们可以创建某特定类型的对象实例。而特定引用类型具有哪些属性方法也是跟关联的内置对象有关,当然我们也可以更改内置引用类型的方法和属性。
console.log(typeof Array); // function
console.log(typeof Date); // function
console.log(typeof Object); // function
console.log(Array instanceof Function); // true
理解内置对象和引用类型的概念。
2. Object 类型
Object 是 ECMAScript 中使用最多的一个类型。虽然 Object 的实例不具备多少功能,但是对于在应用程序中存储和传输数据而言,它确实是非常理想的选择。
2.1 创建 Object 实例
// 方式 1:使用 new 操作符后跟 Object 构造函数。
var blogger = new Object();
blogger.name = 'TKOP_';
blogger.age = 23;
// 方式 2:使用对象字面量表示法
var blogger = {
name: 'TKOP_',
age: 23
};
有关对象字面量的总结:
- 对象字面量是对象定义的一种简写形式,目的在于简化创建包含大量属性的对象的过程。
- 属性名和属性值间冒号隔开,属性名会自动转换为字符串。
- 不同键值对间使用逗号隔开,最后一个键值对后面建议不要使用逗号(老浏览器会导致错误)。
- 如果花括号的内容为空,则创建的是一个只包含默认属性和方法的空对象。
- 通过对象字面量定义对象时,实际上不会调用 Object 构造函数。
对于最后一点可能有人会疑惑,对象字面量创建对象没有调用构造函数,那它是怎么实现的呢?
首先要清楚使用 new 操作符后跟一个构造函数时,js 引擎会帮我们创建一个对象。之后调用构造函数并将 this 指向创建的对象,调用构造函数目的是初始化并返回这个实例对象。然后对于对象字面量的方式,在遇到花括号时 js 引擎根据表达式上下文断定其为对象类型的数据值(而不是语句块)。所以不会再去调用构造函数,而是自动进行默认的初始化操作。
/*
* 1、js 引擎将"{}"解析为 Object 引用类型对象
* 2、将该对象的"__proto__"指向"Object.prototype"
* 3、上两步实质上就是构造函数的初始化内容
* 4、初始化(添加)自定义属性和方法
*/
var blogger = {
name: 'TKOP_',
age: 23
};
console.log(blogger instanceof Object); // true
2.2 访问实例对象属性
// 方式 1:使用点表示法访问对象属性
console.log(blogger.age); // 输出blogger对象的age属性值
// 方式 2:使用方括号来访问对象的属性。
console.log(blogger['age']);
方括号方式访问对象属性总结:
- 方括号里面的属性必须是字符串的形式。
- 可以通过字符串类型的变量访问属性。
- 可以访问不合乎命名规范的属性。
blogger['first name'] = '陈';
var firstName = 'first name';
console.log(blogger[firstName]); // ->'陈'
console.log(blogger[age]); // ->age is Not defined
2.3 常用属性和方法
Object 对象的常用属性和方法(Object 引用类型的静态成员)很少。想详细了解的可以查看文档,本人认为值得重点记住的有如下几个:
属性
- Object.prototype
- Object.__proto__
- Object.prototype.constructor
- Object.prototyoe.__proto__
实例方法:所有的对象都具有 toLocalString()、toString()、valueOf() 方法。数组引用类型详细介绍。
prototype 是每个函数(function)对象都具有的属性(其他类型对象没有),__proto__ 则是所有对象都具有的属性。不在这里详细总结,在原型链和继承相关笔记总结。
3. Array 类型
Array 引用类型的对象是 ECMAScript 中常用的类型。ECMAScript 中的数组表示的是数据的有序列表,里面的每一项可以保存任何类型的数据(但是一般用来保存同一类型的数据)。需要特别注意的是 ECMAScript 数组的大小是可以动态调整的,即可以随着数据的添加自动增长以容纳新增数据。
3.1 创建数组和访问数组元素
1、创建数组
创建 Array 实例的方式跟创建 Object 实例大同小异。都可以通过构造函数和字面量的方式(也没有调用 Array 构造函数)创建。但是在创建时需要注意一些问题。
// 构造函数创建 Array 实例对象
let arr0 = new Array(); // ->[]
let arr1 = new Array(3); // ->[undefined, undefined, undefined]
let arr2 = new Array('TKOP_', '扬尘'); // ->['TKOP_', '扬尘']
let arr3 = new Array(3, 2, 1); // ->[3, 2, 1]
let arr4 = new Array('TKOP_'); // ->['TKOP_']
// 字面量创建 Array 实例对象
let arr0 = [];
let arr1 = [undefined, undefined, undefined];
let arr2 = ['TKOP_', '扬尘'];
let arr3 = [3, 2, 1];
注意的问题(可能以后也遇不到这种问题):
- 使用构造函数时可以传入参数。传入的参数多于一个时,这些参数即为实例对象的每一项元素。传入一个参数时如果是数值则解析为创建指定长度的数组,如果不是数值可解析为创建长度为 1 的数组,传入的参数为数组元素.
- 使用字面量创建数组对象时会有一个跟浏览器版本有关的问题。问题如下,所以我们都不要在数组的最后一个元素后面加逗号就可以了。
let arr0 = [1, 2, 3,]
// ->在IE9以下会解析为[1, 2, 3, undefined],其他的浏览器解析为[1, 2, 3]
let arr1 = [1, 2, 3, undefined];
let arr2 = [1, 2, 3, , 4] // ->[1, 2, 3, undefined, 4]
2、访问数组元素
访问数组中的元素使用的是方括号并提供响应值的基于 0 的数字的索引。如果索引小于数组中的项数则返回该项元素的值。如果设置的索引值太大,数组中没有对应的元素则返回 undefined 值。
let arr0 = ['板井泉水', '陈奕迅'];
console.log(arr0[0]) // ->'板井泉水'
console.log(arr0[2]) //->undefined
3、更新数组元素
更新数组元素时如果对应的元素在数组中存在则将其进行更改,如果不存在则进行添加(数组的 length 属性也会动态改变)。
arr0[2] = arr0[1];
arr0[1] = '张学友';
console.log(arr0) // ->['板井泉水', '张学友', '陈奕迅']
arr0[4] = 'IU';
// ->arr0 = ['板井泉水', '张学友', '陈奕迅', undefined, 'IU']
注意一个在浏览器控制台输出查看对象时初学者容易疑惑的一个问题:
这是在添加 arr0[4] 语句前输出 arr0 的结果,可以看到数组此时的长度为 3 ,下面展开时却有 arr0[4] 了。也就是说展开看到的实际是该数组的最后状态并不是数组此时的状态。所以我们要了解数组此时的状态只需要看第一行就可以了。这点在查看对象的属性是否在某时改变时是很容易犯糊涂的,我初学时就踩过这个坑。
4、数组对象的 length 属性
数组对象的 length 属性不是一个只读属性。因此可以通过设置这个属性来实现从数组的末尾移除项或者向数组中添加新项。
arr0.length = 2;
// ->arr0 = ['板井泉水', '张学友']
arr0.length = 5;
// ->arr0 = ['板井泉水', '张学友', undefined, undefined]
arr0[arr0.length] = '邓紫棋';
// ->arr0 = ['板井泉水', '张学友', undefined, undefined, '邓紫棋']
3.2 Array 具有的方法
Array.isArray(value) 是引用类型 Array 具有的方法,该方法用于判断一个值是否是数组类型的数据。如果是则返回 true ,不是则返回 false 。使用 instanceof 操作符也可以判断一个数据是否是 Array 类型,那为什么还要使用 isArray 方法呢?
instanceof 操作符的问题在于只能判断该数组对象是否是某全局执行环境下的 Array 构造函数的一个实例。假设网页中包含多个框架实际 上就存在两个以上不同的全局执行环境,即不同全局执行环境下内置的引用类型 Array 是不同的,会存在两个不同版本的 Array 构造函数。如果从一个框架向另一个框架传入一个数组,那么该数组并非是第二个框架下的 Array 的一个实例对象。它与第二个框架中原生创建的数组分别具有各自不同的构造函数。
console.log(Array.isArray(arr0)); // ->true
console.log(Array.isArray({})); // ->false
3.2 Array 实例具有的方法
3.2.1 转换方法
1、toString() 方法会在数组实例调用该方法时,调用每一项元素的 toString() 方法然后再调用数组的 toString() 方法。
let obj = {
name: 'TKOP_',
age: 18
};
let arr = [1, obj, 3, [12, 0]];
console.log(arr.toString()); // ->'1,[object Object],3,12,0'
// 原理:1、每项元素调用 toString() 方法得到的结果
// ['1', [object Object], '3', ['12','0']]
// ['1', [object Object], '3', '12', '0']
// 2、调用数组的 toString() 方法
// ['1', [object Object], '3', '12', '0']
2、toLocaleString() 方法会在数组实例调用该方法时,调用每一项元素的 toLocaleString() 方法然后再调用数组的 toLocaleString() 方法。与 toString() 方法的区别是他会按照系统所在位置(本地)的习惯显示数据,时间的显示就是一个很明显的例子。
arr[arr.length - 1] = new Date();
console.log(arr.toString());
console.log(arr.toLocaleString());
两者的输出结果如下图:
3、对于方法 valueOf() 直接看看 W3school 解释就好。
4、join() 方法,Array 引用类型上面的三个方法其实均继承自 Object 引用类型,只能使用默认的逗号将不同元素进行分隔。join() 方法则可以使用不同的分隔符来构建这个字符串。join() 方法只接收一个参数,即表示用作分隔符的字符串。
let arr = ['I', 'love', 'you'];
console.log(arr.join(' ')); // 这里我是用空字符串
// ->I love you 如果不使用字符串分割会出现以下结果
// ->Iloveyou
3.2.1 栈方法(push 和 pop)
ECMAScript 数组提供了一些让数组的行为类似于其他数据结构行为的方法。栈方法就是数组与栈(LIFO,Last-In-First-Out)这种数据结构行为类似的的方法。栈中项的插入(推入)和移除(弹出)只发生在栈的顶部(栈定)。ECMAScript 为数组专门提供了 push() 和 pop() 方法来实现类似栈的行为。
// push() 方法可以在数组的末尾添加任意项新元素,并返回数组的新长度
let arr0 = [0, 1, 2];
console.log(arr0.push(3, 4, 10)); // ->6
console.log(arr0) // ->[0, 1, 2, 3, 4, 10]
// pop() 从数组移除最后一项,减少 length 属性值,并返回移除的项
console.log(arr0.pop()); // ->10
console.log(arr0); // ->[0, 1, 2, 3, 4]
3.2.2 队列方法(shift() 和 unshift())
栈数据结构的访问规则是 LIFO(后进先出),而队列数据结构的访问规则 FIFO(先进先出)。ECMAScript 为数组专门提供了 shift() 和 unshift() 方法来实现类似栈的行为。
// unshif() 在在数组的前面添加任意项新元素,并返回数组的新长度
let arr0 = [1, 2];
console.log(arr0.unshift(3, 4, 10)); // ->5
console.log(arr0) // ->[3, 4, 10, 1, 2]
// shift() 方法移除第一项并返回该项
console.log(arr0.shift()); // ->3
console.log(arr0); // ->[4, 10, 1, 2]
3.2.3 重排序方法
数组中已经存在两个可以直接用来排序的方法:reverse() 和 sort() 。它们都与排序有关。
sort() 方法需要注意以下内容:
- 默认是升序排序,且会调用每个数组项的 toString() 方法后根据得到的字符串确定如何排序。
- 它可以接收一个比较函数作为参数,该函数可以指定排序模式。
- 比较函数接收两个参数,如果第一个参数应该排在第二个参数之前则返回一个负数,如果位置不变则返回 0 ,否则返回一个正数。
// reverse() 翻转数组
let arr0 = [1, 4, 10, 5, 11];
console.log(arr0.reverse()); // ->[11, 5, 10, 4, 1]
// sort() 对数组元素进行排序,默认升序
arr0.sort();
console.log(arr0); // ->[1, 10, 11, 4, 5]
arr0.sort((arg0, arg1) => {
return arg0 - arg1; // 负数:arg0 arg1 (agr0<arg1)
// 正数:arg1 arg0 (agr0>arg1)
});
console.log(arr0); // ->[1, 4, 5, 10, 11]
arr0.sort((arg0, arg1) => {
return arg1 - arg0;
})
console.log(arr0); // ->[11, 10, 5, 4, 1]
3.2.4 操作方法
操作方法是指 ECMAScript 为包含在数组中的项提供的方法。主要有三种方法,concat()、slice()、splice() 。
1、concat() 方法用于在该数组的副本末尾进行拼接数组(如果传入的参数不是数组,则参数的值会被简单地添加到副本的末尾,例如下例的 “yellow”)。
let arr0 = ['red', 'green', 'blue'];
let arr1 = arr0.concat('yellow',['black', 'pink']);
console.log(arr0); // ->['red', 'green', 'blue']
console.log(arr1); // ->['red', 'green', 'blue', 'yellow', 'black', 'pink']
2、slice() 方法即切片方法,它返回一个包含原数组中指定起始项到结束项的数组。也不会影响原数组。
let arr2 = arr1.slice(1);
let arr3 = arr1.slice(1,4);
console.log(arr2); // ->["green", "blue", "yellow", "black", "pink"]
console.log(arr3); // ->["green", "blue", "yellow"]
- 切片不包含结束项,例如上面实例 slice(1, 4) 不包括 arr1[4] 即 “black” 这项
- 如果参数有一个负数,则用数组的长度加上该数来确定相应的位置。例如 slice(-2, -1) 等价于 slice(4, 5)。其实就是索引从数组的末尾以 -1 开始。
- 结束位置不能小于位置,否则返回空数组。
3、splice() 方法,首先 splice 具有移接、粘接的意思。所以这个方法个人的记忆方式就是将数组看成是基因片段,可以使用这个方法对其中的某部分进行切割、替换,所以原数组会发生改变。
// splice()方法可以传递任意个参数但是不传入参数没意义(没有任何操作)
// 1、第一个参数表示操作的起始位置
// 2、第二个参数表示要的操作项数,如果没有,则表示从起始位置到数组结束
// 3、其余表示要将操作的项替换成什么内容,如果不指定则表示直接删除了这些项
let arr = ['red', 'green', 'blue', 'yellow', 'black', 'pink'];
let result0 = arr.splice(1, 2);
console.log(result0, arr);
// ['green', 'blue'] || ['red', 'yellow', 'black', 'pink']
let result1 = arr.splice(1, 2, result0);
console.log(result1, arr);
// ['yellow', 'black'] || ['red', ['green', 'blue'], 'pink']
let result2 = arr.splice(1, 0, result1, 'green');
console.log(result2, arr);
// [] || ['red', ['yellow', 'black'], 'green', ['green', 'blue'], 'pink']
let result3 = arr.splice(1);
console.log(result3, arr);
// [['yellow', 'black'], 'green', ['green', 'blue'], 'pink'] || ['red']
3.2.5 位置方法
ES5 提供两个在数组中搜索某个项的方法,indexOf() 和 lastIndexOf()。 它们的用法相同,都是查找数组中是否具有某项元素(第一个参数),也都可以指定查找的起始位置(第二个参数)。不同点是一个向后查找,另一个向前查找。在找到时返回该项的索引(如果数组中有多个这种值则找到一个后停止查找),如果没有该值则返回 -1 。注意查找时采用的是全等比较。
let arr = [1, 2, 3, 4, 5, 4, 3, 2, 1];
console.log(arr.indexOf(6)); // -1
console.log(arr.indexOf(3)); // 2
console.log(arr.lastIndexOf(3)); // 6
console.log(arr.lastIndexOf(3, 3)); // 2
console.log(arr.indexOf(3, 3)); // 6
3.2.6 迭代方法(重要)
数组迭代(遍历)的方法有:forEach()、map()、filter()、some()、every()。
1、forEach() 方法:遍历数组元素,接收一个函数作为参数,并默认向该函数传递三个参数。
// arr.forEach((currentItem, index, arrValue) => { ... })
// 没有用到的形参可以省略
let arr = [1, 2, 3];
arr.forEach((item, index) => {
console.log(index + ':' + item);
})
2、filter() 方法创建一个新的数组,新数组中的元素是通过检查数组中符合条件的所有元素,主要用于筛选数组。(注意它会返回一个新的数组)
// arr.filter((currentItem, index, arrValue) => { ... })
// 没有用到的形参可以省略
let arr = [12, 33, 5, 77];
let newArr = arr.filter((item, index) => {
return item > 20;
});
console.log(newArr); // ->[33, 77]
为了加深理解我自己写了一下实现方向选择的方法,代码如下:
Array.prototype.newFilter = function(callback) {
let newArr = [];
for (let i = 0; i < this.length; i++) {
if (!callback(this[i], i, this)) {
newArr[newArr.length] = this[i];
};
};
return newArr;
};
let arr = [12, 33, 5, 77];
let arr0 = arr.newFilter((item, index) => {
return item > 20;
});
console.log(arr0);
3、some() 方法用于检测数组中是否存在满足条件的元素。它的返回值是一个布尔值,如果存在则返回 true ,如果不存在则返回 false 。需要注意的是:在找到第一个满足条件的元素后,就返回 true ,不再继续查找。
// arr.forEach((currentItem, index, arrValue) => { ... })
// 没有用到的形参可以省略
var arr = [12, 33, 5, 77];
var result = arr.some((item, index) => {
return item > 20;
});
console.log(result); // true
var result = arr.some((item, index) => {
return item > 80;
});
console.log(result); // false
自己的理解并实现:
Array.prototype.newSome = function(callback) {
let flag = false;
for (let i = 0; i < this.length; i++) {
if (callback(this[i], i, this)) {
flag = true;
break;
};
};
return flag;
};
let arr0 = arr.newSome((item, index) => {
return item > 80;
});
console.log(arr0);
4、every() 方法用于检测数组中是否所有元素都满足条件。它的返回值是一个布尔值,如果所有元素均满足则返回 true ,如果存在不满足的元素则返回 false 。需要注意的是:在找到第一个不满足条件的元素后,就返回 false ,不再继续查找。(对比 some() 可以更好地理解)
其原理跟 some 一样改变判断条件和返回值即可,这里不贴代码。
5、map() 方法也返回一个数组,返回的是每项都经回调函数处理过得新数组(处理过程可能相同也可能不同)。
3.2.7 归并方法
。。。。。。
3.2.8 数组去重
这是一个运用数组方法的重要案例,需求:去除数组 [‘c’, ‘a’, ‘z’, ‘a’, ‘x’, ‘a’, ‘x’, ‘c’, ‘b’] 中重复的元素。
/*
* 目标:把旧数组里面不重复的元素选取出来放到新数组中,重复的元素只保留一个,放到新数组中去重。
* 实现原理:遍历旧数组,使用遍历项查询新数组,如果在新数组中没有出现过则将该项添加至新数组,否则不添加。
* 其他:使用ES6新增 set 类型配合数组结构、扩展运算符也可以实现数组去重。
*/
3.2.9 ES6新增方法
1、Array 的静态方法 Array.from()
该方法可以将伪数组或者可遍历对象转换为真正的数组。第一个参数为伪数组或者可遍历对象,第二个可选参数是回调函数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
var arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
var arr = Array.from(arrayLik, item => item * 2);
// 使用扩展符也可以实现转换
var arrAnother = [...arrayLike];
2、实例方法 find()
用于找出第一个符合条件的数组成员,如果没有则返回 undefined。
let ary = [{
id: 1,
name: '张三‘
}, {
id: 2,
name: '李四‘
}];
let target = ary.find((item, index) => item.id == 2);
3、实例方法 findIndex()
// 用于找出第一个符合条件的数组成员的位置,如果没有找到返回-1
let ary = [1, 5, 10, 15];
let index = ary.findIndex((value, index) => value > 9);
console.log(index); // 2
4、实例方法 includes()
表示某个数组是否包含给定的值,返回布尔值。
[1, 2, 3].includes(2) // true
[1, 2, 3].includes(4) // false
4. Date 类型
ECMAScript 中 Date 类型的内置对象与时间有关,它使用自 UTC(Coordinated Universal Time,国际协调时间)1970 年 1 月1日零时开始经过的毫秒数来保存日期。
4.1 Date 实例化
4.1.1 实例化时的参数
需要使用某个时间对象时,可以使用 new 操作符和构造函数 Date 即可。
var now = new Date();// ->Wed Mar 17 2021 16:58:56 GMT+0800 (中国标准时间)
在调用 Date 构造函数而不传递参数时,函数内部默认传入的是表示当前时间的时间戳(也就是上述的那个用来保存时间的毫秒数)。当然让开发者直接传递这个毫秒数是不现实的。所以 ECMAScript Date 对象提供了两个将指定时间字符串转换为毫秒数的方法。它们分别是 Date.parse() 和 Date.UTC()。关于这两个方法了解即可,前者会因为浏览器和系统设置的地区不同接受的字符串格式不同,后者只有年份和月份两个参数为必需,其它有默认值。
当我们给构造函数传递参数时,他会自动调用上述的两个方法获取对应的时间戳创建时间对象。参数的格式不同,调用的方法也不同。我们来实验一下:
var time1 = new Date('2020-3-10 02:3:50'); // 默认使用 Date.parse()
var time2 = new Date(2020, 3, 10); // 默认使用 Date.UTC()
var time3 = new Date('2021/10/23'); // 默认使用 Date.parse()
console.log(time1, time2, time3)
// Tue Mar 10 2020 02:03:50 GMT+0800 (中国标准时间)
// Fri Apr 10 2020 00:00:00 GMT+0800 (中国标准时间)
// Sat Oct 23 2021 00:00:00 GMT+0800 (中国标准时间)
以上几种参数传递方式也是常用的传参方式。
4.1.2 Date.now() 和 +new Date()
Date.now() 和 +new Date() 是两种获取当前时间戳的方式。它们都返回当前时间戳,前者低版本浏览器(ie6~8)不支持,可以使用后者代替(+new Date() 可以传递参数)。
var time1 = Date.now();
var time2 = +new Date();
var time3 = +new Date(2021, 3, 17);
console.log(time1, time2, time3)
// 1615974218883 1615974218883 1618588800000
4.1.3 月份传参注意问题
表示月份是使用的是 0 ~ 11 (与星期几是一样的),所以当你给 new Date() 传递表示月份的参数时需要理解创建的实例对象真实的月份是多少。
还有当月份超过了 11 后会增加一年再确定月份。
var date0 = new Date(2021, 3, 14);
console.log(date0);
// ->Sun Apr 14 2021 00:00:00 GMT+0800 (中国标准时间) 月份是4月
var date0 = new Date(2021, 13, 14);
console.log(date0);
// ->Fri Feb 14 2022 00:00:00 GMT+0800 (中国标准时间) 月份是二月
4.2 常用方法
4.2.1 常用方法介绍
实例化日期对象后,如果需要获取日期对象的指定部分则需要使用实例对象的以下几种方法。
- getFullYear() 获取年份
- getMonth() 获取月份(0~11)
- getDate() 获取日期(某日)
- getDay() 返回该日期是星期几(周日到周六,即 0~6)
- getHours() 获取时间小时
- getMinutes() 获取时间分钟
- getSeconds() 获取时间秒
- getTime() 返回表示日期的毫秒数;与 valueOf() 方法的返回值相同
4.2.2 倒计时案例
原理:截至时间减去现在的时间就是剩余时间,即倒计时。但是时间的运算要使用时间戳进行。
利用剩余时间获得剩余的天、小时、分钟和秒数可以使用以下计算。
- days = parseInt(count / 60 / 60 / 24); // count 剩余总秒数
- hours = parseInt(count / 60 / 60 % 24);
- minutes = parseInt(count / 60 % 60);
- seconds = parseInt(count % 60);
也可以使用 Math.floor() 方法进行取整。后面 Math 内置引用类型会用到。
// 计时函数,给定截止时间返回包含剩余的天、小时、分钟、秒的数组
function countDown(deadline) {
let inputTime = new Date(deadline);
inputTime = inputTime.getTime();
let nowTime = +new Date();
let times = (inputTime - nowTime) / 1000;
let d = Math.floor(times / 60 / 60 / 24);
d = d >= 10 ? d : '0' + d; // 转换为两位表示 ,少于10前面补0
let h = Math.floor(times / 60 / 60 % 24);
h = h >= 10 ? h : '0' + h;
let m = Math.floor(times / 60 % 60);
m = m >= 10 ? m : '0' + m;
let s = Math.floor(times % 60);
s = s >= 10 ? s : '0' + s;
return [d, h, m, s];
}
var countElements = document.querySelectorAll('.time');
// 需要先调用一次,防止进入页面1秒后才开始显示计时内容
show();
function show() {
let timeArr = countDown('2021-3-19');
timeArr.forEach((item, index) => {
countElements[index].innerHTML = item;
})
}
setInterval(show, 1000);
效果如下图所示:
5. RegExp 类型
正则表达式(Regular Expression)是用于匹配字符串中字符组合的模式。在 JavaScript 中,正则表达式也是对象。它通常被用来检索、替换那些符合某个模式(规则)的文本,例如表单验证:用户名表单只能输入英文字母、数字或者下划线,昵称输入框可以输入中文(匹配)。此外,正则表达式还常用于过滤掉页面内容中的一些敏感词(替换),或者从字符串中获取我们想要的特定部分(提取)等。
下面只是有关正则基本使用的笔记,太深入的知识没有在此记录。有关正则的分组和反向引用,如果有必要以后再回来补充。所以与分组有关的 exec() 方法这里也不回去探究。
当然也就不会在此去总结正则的实例方法和静态方法了。
5.1 正则表达式的创建和检测
5.1.1 创建
可以使用构造函数或则字面量的方式创建正则表达式。
// var expression = /pattern/flag ;字面量的形式创建
var pattern1 = /abc/g;
// new 加构造函数的方式创建
var pattern2 = new RegExp('[c]at', 'i');
了解字面量创建的正则在一些低版本的浏览器中会有什么区别。
5.1.2 测试正则表达式 test
test() 正则对象方法,用于检测字符串是否符合该规则,该方法会返回 true 或者 false ,参数是需要检测的字符串。
// regexpObj.test(str);
var regexp1 = new RegExp('[cd]at', 'i');
console.log(regexp1.test('asvdDatdfs')); //true
- regexpObj 是正则对象
- str 是需要检测的文本
- 目的是检测 str 文本是否符合正则表达式规范
5.1.2 replace()
replace() 方法可以实现替换字符串操作,用来替换的参数可以是一个字符串或者一个正则表达式。
// stringObject.replace(regexp/substr, replacement);
var str = '我也许很强';
str = str.replace(/也许/, '真的');
console.log(str); // '我真的很强'
- 第一个参数:被替换的字符串 或者 正则表达式
- 第二个参数:替换为的字符串
- 返回值是替换后的新字符串
5.2 正则表达式的组成
正则表达式由模式(pattern)和标识(flag)组成。模式部分可以是任何简单或则复杂的表达式,可以包含字符类、限定类、分组、向前查找以及方向引用。每个正则表达式都可以带有一个或者多个标识,用以表明正则表达式的行为。
5.2.1 正则表达式的模式
正则表达式的模式可以由简单的字符构成,例如 /abd/,也可以是简单字符和特殊字符的组合,例如 /ab*c/ 。其中特殊字符也被称为原字符,在正则中是具有特殊意义的专用符号。下列将元字符分为几类进行学习:
1、边界符:用来标明字符所处的位置(又叫位置符)。
边界符 | 说明 |
---|---|
^ | 表示匹配行首的文本(以谁开始) |
$ | 表示匹配行尾的文本(以谁结束) |
如果 ^ 和 $ 一起使用,则表示必须是精确匹配(正则测试的文本与正则模式一样)。
2、可选符:表示一系列字符可供选择,只要匹配其中一个就可以了。所有可选择的字符都放在方括号内。
// 1、方括号表示字符可选
/[bm]a/.test('wo ai wo ba ma'); // true
// 2、内部可以使用 - 表示范围
/^[a-z]$/.test('c'); // true
/[a-z0-9]true/.test('dafd0truesf') // true
// 3、内部可以使用 ^ 表示取反
/[^abc]/.test('andy') // false
3、量词符:量词符用来设定某个模式出现的次数。
量词符 | 说明 |
---|---|
* | 重复 0 次或者更多次 |
+ | 重复 1 次或者更多次 |
? | 重复 0 次或者 1 次 |
{n} | 重复 n 次 |
{n,} | 重复 n 次或者更多次 |
{n, m} | 重复 0 次到 m 次 |
4、预定义类:是指某些常见模式的简写方式。
预定义类 | 说明 |
---|---|
\d | 匹配 0 - 9 之间的任一数字,等价于 [ 0-9 ] |
\D | 匹配除了 0 - 9 的其他字符,等价于 [ ^0-9 ] |
\w | 匹配任意的字母、数字和下划线,等价于 [ A-Za-z0-9_ ] |
\W | 匹配除了字母、数字和下划线的其他字符,等价于 [ ^A-Za-z0-9_ ] |
\s | 匹配空格(包含换行符、制表符和空格等),等价于 [ \t\r\n\v\f] |
\S | 匹配非空格的字符,等价于 [ ^\t\r\n\v\f] |
5.2.2 正则表达式的行为标识
- g : 表示全局(global)模式,即模式被应用于所有字符串,而非在发现第一个匹配项时立即停止。
- i : 表示不区分大小写(case-insensitive)模式,即在确定匹配项时忽略模式与字符串的大小写。
- m : 表示多行(multiline)模式,即在到达一行文本末尾时还会继续查找下一行中是否存在与模式匹配的项。
6. Function 类型
有关 Finction 类型的一些基础知识已经在博客笔记 [JavaScript 基础(超详细)——2.6 初识函数] 总结。下面是更深入的去学习该引用类型。这里主要总结对函数引用类型本质的理解、几种特殊的函数、调用函数时函数内部的 this 对象的指向问题、函数的属性和方法(有关 this 指向问题的一些方法)。函数其他有关知识例如闭包、函数递归等不在此处总结(篇幅实在太长了)。
6.1 函数本质和函数分类
6.1.1 函数是什么
函数实际上是对象,每个函数都是 Function 类型的一个实例,而且与其他引用类型一样具有属性和方法。在声明函数的三种方式中,使用构造函数创建函数实例就是函数本质的直观表现。
var sum = new Function("num1", "num2", "return num1 + num2");
一句话:函数是对象,函数名是指针,使用函数名不带括号访问的是函数指针,而非调用函数。这就是函数的本质。
function sum(num1, num2) {
return num1 + num2;
}
alert(sum(10, 10)); // 20
var anotherSum = sum;
sum = null;
alert(anotherSum(10, 10)); // 20
如上示例代码,首先定义一个求和函数 sum (实例化一个函数对象),此时 sum 保存了一个指向函数对象的指针。再定义一个变量 anotherSum 并拷贝 sum 的值。两者保存了相同的指针,虽然最后将 sum 设置为 null 但 anotherSum 依旧指向该函数对象,所以依旧可以用其调用。
6.1.2 函数分类
函数分类有普通函数、匿名函数、闭包函数。这里只想总结匿名函数,匿名函数就是在创建函数实例时没有给该函数命名的函数。示例如下:
// 1、函数表达式(匿名函数)、将函数赋值给了一个变量因此也就没必要给其命名
// 注意这个 xx 并不是函数名,所以这也是匿名函数
// var xx = function() { ... };
// function xx() { ... }; 此时xx才是函数名
// 2、立即执行函数、该函数在声明的同时执行。因此也没有必要命名标识
(function(a) { ... })(x);
// 3、某些只需在一特定函数或异步API内使用的回调函数
// 当然这只是指那些比较简洁可以直接作为实参声明传入函数的回调函数
setTimeout(function() { ... }, 3000);
可能在学习过程中会遇到回调函数、立即执行函数、箭头函数(ES6 新增)、构造函数、对象的方法等这些概念。这些东西可能会阻碍你构建自己的认识函数知识体系,但其实它们都不难。
6.1.3 作为值的函数
函数名本身就是变量,所以函数也可以作为值来使用。也就是说,函数可以作为一个参数传入另一个函数使用(回调函数),也可以在函数内作为返回值返回(后面知识闭包的基础)。
1、作为参数的函数(回调函数)
var fn1 = function(a, b) {
return a + b
};
function fn2(callback) {
let x = 3;
let y = 4;
let sum = callback(x, y);
return sum;
}
alert(fn2(fn1));
2、作为返回值的函数
可以从一个函数中返回另一个函数(其实就是上面的示例 sum ),而且这也是极为有用的一种技术。如下示例:
var users = [{
unae: 'TKOP_',
age: 18
}, {
uname: '扬尘_',
age: 20
}, {
uname: '某人',
age: 10
}];
// 接收一个用于排序的属性值和一个排序规则(升序还是降序)作为参数。
// 返回一个函数,默认是升序 schema 为-1
function compariseFn(comparision, schema = -1) {
return function(obj1, obj2) {
if (schema > 0) {
return obj2[comparision] - obj1[comparision];
} else {
return obj1[comparision] - obj2[comparision];
}
};
}
// 使用返回的函数作为 sort() 方法的参数
users.sort(compariseFn('age'));
console.dir(users[0]);
users.sort(compariseFn('age', 1));
console.dir(users);
输出结果如下图:
6.2 函数的属性和方法
6.2.1 函数内部属性(arguments 和 this)
1、关于 arguments 属性前面已经总结过,这里主要是要了解 arguments.callee 属性。arguments.callee 属性指向的是拥有该 arguments对象的指针。主要是防止在函数递归操作时函数名发生改变,内层仍然使用原函数名无法进行递归操作,示例如下:
// 阶乘操作
function factorial(num) {
if(num <= 1) {
return 1;
} else {
return num*factorial(num - 1);
// return num*arguments.callee(num - 1);
}
};
console.log(factorial(4)); // 24, 没有问题
// 将原先递归函数赋值给某变量
var trueFactorial = factorial;
console.log(trueFactorial(4)); // 24 也没有问题
// factorial 指针改变
factorial = null;
console.log(trueFactorial(4)); // 报错,因为里面使用的是不存在的 factorial 函数
但是如果使用 arguments.callee 代替递归函数名进行调用则不会出现问题,因为该属性始终指向的都是使用该 arguments 对象的函数。
2、函数内部的另一个特殊的对象是 this ,this 引用的是函数执行的环境对象(this 的值)。在调用函数之前 this 的值并不确定,因此 this 可能在代码执行过程中引用不同的对象。下表是函数不同方式调用时 this 指向的对象。
调用方式 | this 指向 |
---|---|
普通函数调用 | window |
定时器回调函数 | window |
立即执行函数 | window |
new 加构造函数时调用 | 实例对象,原型对象里面的方法也指向实例对象。 |
对象方法调用 | 调用该方法的对象 |
事件绑定的处理函数 | 绑定事件的对象 |
3、与 this 有关的一个属性是函数对象的一个 caller 属性。这个属性中保存着调用当前函数的执行环境产生者的引用。若在全局执行环境中调用当前函数,则返回 null。
function outer() {
inner();
}
function inner() {
alert(inner.caller);
// alert(arguments.callee.caller);
}
outer();
6.2.2 函数的方法(call()、apply() 和 bind())
JavaScript 中的函数对象为我们专门提供了一些方法更加优雅地处理其内部的 this 的指向问题,常用的有 bind()、call()、apply() 三种方法。以下我只以简单的代码示例用法,因为我想留多些笔墨来探究它们的实现。
var uname = 'TKOP_0';
function fn(num1, num2) {
console.log(this.uname);
return num1 + num2;
}
fn();
// 1、fn.call(thisArg, arg1, arg2, ...),主要用于实现类的继承
var obj = {
uname: 'TKOP_obj'
};
console.log(fn.call(obj, 1, 2));
// 2、fn.apply(thisArg, [argArray])可以用来处理与数组有关的方法
console.log(fn.apply(obj, [2, 3]));
var arr = [12, 24, 50, 4, 9]; // 我想取最大值
console.log(Math.max.apply(Math, arr));
// 3、fn.bind(thisArg, arg1, arg2, ...)不会调用函数
// 返回值是绑定特定对象为this的一个函数副本
// 常用于处理某些异步API回调函数的 this 指向绑定
var btns = document.querySelectorAll('button');
btns.forEach(item => {
item.onclick = function() {
console.log(this);
this.disabled = true;
/* setTimeout(() => {
this.disabled = false;
}, 3000); */
/* setTimeout(function() {
this.disabled = false;
}, 3000); */
setTimeout(function() {
this.disabled = false;
console.log(this);
}.bind(this), 3000);
}
})
/*
* 方法1可以,因为箭头函数的this指向与其定义时的环境有关,是静态的
* 方法2不可以,是典型的 this 指向错误问题
* 方法3可以,因为此时已经将this指向的按钮绑定给了计时器的回调函数
*/
它们是如何做到改变 this 的指向的,怎么去模拟?我想了一下,出现了以下思路。要改变里面的 this 指向,只能将其作为该对象的方法然后调用。但是这样会使这个对象多出一个方法,可以 delete 。如下图,因为调用在删除之前,所以对象显示还是有 fn 。但是打开通过 console.dir 输出的对象则看到确实已经删除了。
// Function.prototype.call.length = 1,所以参数的传递方式是有问题的
Function.prototype.newCall = function(objOfThis, ...arg) {
objOfThis.fn = this;
let returnValue = objOfThis.fn(...arg);
delete objOfThis.fn;
return returnValue;
};
function fn() {
console.log(this);
};
fn();
var obj = {
uname: 'TKOP_'
};
fn.call(obj)
fn.newCall(obj);
然后百度了一下,虽然模拟方式不同但是大致想法一样。还看到大家讨论 eval() 执行在执行 fn() 方法时的参数问题。
Function.prototype.call2 = function(context) {
context.fn = this;
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}
var result = eval('context.fn(' + args +')');
delete context.fn; //删除挂载在借用对象上的fn属性
return result;
}
6.2.3 函数属性(length 和 prototype)
length 属性标识函数希望接收的命名参数个数,prototype 是保存它们所有实例方法的真正所在,即实例对象所共有的一些方法实际上都保存在 prototype 属性下。在原型和原型链笔记再详细介绍。
function fn1(a1) {}
function fn2(a1, a2) {}
function fn3(a1, a2, a3) {}
alert(fn1.length);
alert(fn2.length);
alert(fn3.length);
7. 基本包装类型
基本包装类型的意义在于方便操作基本类型值,需要创建实质上的包装类型数据貌似没有遇到过。包装类型有三种 Boolean、Number 和 String 。可以通过 new 加相应的构造函数来实例化包装类型对象,但是基本上没必要。
var booleanObject = new Boolean(false);
var numberObject = new Number(0);
var stringObject = new String('I am TKOP_');
但是需要了解的是在基本类型调用对应的包装类型的方法时,后台会自动完成的处理工作。
var s1 = 'I am TKOP_';
var s2 = s1.substring(5);
console.log(s1, s2); // 'I am TKOP_' 'TKOP_'
// 执行第二句代码时后台隐性执行的操作
var temp = new String(s1);
var s2 = temp.substring(5);
temp = null;
- 创建 String 类型的一个实例;
- 在实例上调用指定方法并返回指定结果;
- 销毁创建的实例;
自动创建的包装类型实例对象在使用后立即被销毁,这也是为什么我们不能给基本类型设置属性的原因。但是使用构造函数手动实例化的包装类型对象是可以和上面介绍的其他类型一样的。
var s1 = 'I am TKOP_';
s1.name = 'TKOP_';
concole.log(s1.name); // ->undefined
var str = new String('我是 TKOP_');
console.log(typeof str); // ->String
str.name = 'TKOP_'; // ->Object
console.log(str.name); // ->'TKOP_'
7.2 Boolean 类型
。。。。。了解即可,建议永远不要使用 Boolean 对象。
7.3 Number 类型
。。。。。。了解即可。
7.4 String 类型
String 类型是字符串的对象包装类型。String 对象的方法也可以在所有基本的字符串值中访问到。其中的一些方法跟 Array 类型类似。
// length 属性返回字符串长度,注意即使是双字节字符也算一个
var str = '我是 TKOP_';
console.log(str.length); // ->8
因为字符串是不可变的基本类型数据,以下方法实质均是隐式转化为包装类型后调用方法并返回值。因此原字符串不会发生改变。
7.4.1 基本方法
1、字符方法 charAt() 和 charCodeAt()
charAt() 和 charCodeAt() 是访问字符串中特定字符的两个方法。它们都是接收一个索引参数(基于 0),其中 charAt() 会返回该位置的字符(单字符字符串),charCodeAt() 则返回对应字符的编码。
var str = '我是 TKOP_';
console.log(str.charAt(1)); // ->'是'
console.log(str.charCodeAt(3)); // ->84
console.log(str[1]); // ->'是'
2、字符串操作方法 concat()、slice()、substring()、substr()
concat() 方法用于拼接多个字符串并返回拼接得到的新字符串,接收任意多个参数。
slice() 、substring() 和 substr() 都是基于子字符串创建新字符串的方法,都会返回被操作字符串的一个子字符串。它们都接收两个参数,第一个参数指子字符串开始的位置,第二个参数都可以省略,省略则将字符串的末尾作为子字符串的结束位置。当第二个参数没有省略时,在 slice() 和 substring() 中表示子字符串结束的位置(不包含此位置的字符),substr() 中则表示截取的子字符串长度。
对于参数取负值的情况了解即可。
var str1 = 'hello ',
str2 = 'world',
str3 = '!';
var str0 = str1.concat(str2, str3);
alert(str0.slice(3)); // 'lo world!'
alert(str0.substring(3)); // 'lo world!'
alert(str0.substr(3)); // 'lo world!'
alert(str0.slice(3, 7)); // 'lo w'
alert(str0.substring(3, 7)); // 'lo w'
alert(str0.substr(3, 7)); // 'lo worl'
3、split() 方法
split() 方法可以基于指定的分隔符将一个字符串分隔成多个子字符串,并将结果放在一个数组中。第一个参数是分隔符,可以是字符串也可以是正则表达式。第二个参数是可选参数,表示返回数组的最大长度(length)。
var text = 'cat, bat, sat, fat';
var arr1 = text.split(',');
var arr2 = text.split(',', 3);
console.log(arr1); // ->["cat", " bat", " sat", " fat"]
console.log(arr2); // ->["cat", " bat", " sat"]
4、字符串位置方法 indexOf() 和 lastIndexOf()
这两个方法的使用跟数组中的对应方法一致。
5、去除字符串首尾所有空格的方法 trim()
var str = ' 我前面两空格,后面一个空格 ';
var str1 = str.trim();
console.log(str.length, str1.length); // ->16 13
console.log(str1); // ->'我前面两空格,后面一个空格'
一些主流浏览器还支持分标准的 trimLeft() 和 trimRight() 方法,分别用于删除字符串开头和末尾的空格。
6、字符串大小写转换方法
toLowerCase()、toUpperCase()、toLocaleLowerCase() 和 toLocaleUpperCase() 是四个与字符串大小写转换有关的方法。其中后面两个方法是针对特定地区的实现。在不知道自己的代码将在哪种语言运行环境中运行的情况下,还是针对地区的方法更稳妥一些。
var s1 = 'I am TKOP_';
var lowerS1 = s1.toLowerCase(); // ->'i am tkop_'
var upperS1 = s1.toUpperCase(); // -> 'I AM TKOP_'
var localeLow = s1.toLocaleLowerCase(); // ->'i am tkop_'
var localeUpp = s1.toLocaleUpperCase(); // ->'I AM TKOP_'
7、localeCompare() 方法
该方法用于比较两个字符串,返回值有三种,分别是负值、0、正值。
- 负值 :字符串在字母表中应该排在字符串参数之后。
- 0 : 两个字符串一样。
- 正值 :字符串在字母表中应该排在字符串参数之后。
8、fromCharCode() 方法
这是 String 构造函数本身的静态方法。该方法接收一个或多个表示字符字符编码,然后将其转换成字符串。
var arr = [];
var str0 = '我是 TKOP_';
for(let i = 0; i < str0.length; i++){
arr.push(str0.charCodeAt(i));
};
console.log(arr); // ->[25105, 26159, 32, 84, 75, 79, 80, 95]
var str = String.fromCharCode(...arr);
console.log(str); // ->'我是 TKOP_'
9、ES6 新增
- startsWith() 判断是否参数字符串开头
- endsWith() 判断是否以参数字符串结尾
- repeat( n ) 将原字符串重复 n 次并返回
7.4.2 字符串的模式匹配方法
1、match() 方法的调用,本质上与调用 RegExp 的 exec() 方法相同。
var text = 'cat, bat, sat, fat';
var pattern = /.at/;
var matches = text.match(pattern);
alert(matches[0]); // 'cat'
alert(matches.index); // 0
alert(pattern.lastIndex); // 0
2、search() 方法接收一个字符串或者正则表达式作为参数。返回字符串中第一个匹配项的索引,如果没有则返回 -1 .
var text = 'cat, bat, sat, fat';
var pattern = /at/;
var pos = text.search(pattern);
alert(pos); // 1
3、replace() 方法用于替换匹配的字符。接收两个参数,第一个是需要被替换的子字符串(或者匹配的正则),第二个是替换的字符串。
var text = 'cat, bat, sat, fat';
var pattern = /at/;
var result = text.replace(pattern, 'ond');
alert(result); // 'cond, bat, sat, fat'
// 全局替换
var gResult = text.replace(/at/g, 'ond');
alert(gResult); // 'cond, bond, sond, fond';
8. 单体内置对象
内置对象的定义是:由 ECMAScript 提供的、不依赖于宿主环境的对象,这些对象在 ECMAScript 程序执行前就已经存在了。也就是说,开发人员不必显式地实例化内置对象,因为它们已经实例化了。例如前面所学的 Object、Array 和 Function ,它们都是内置对象而且都是 Function 类型的实例对象。接下来还需学习两个单体内置对象。
8.1 Global 对象
Global 对象的是 ECCMAScript 中最顶层的对象。事实上没有全局变量和全局函数,它们都不过是 Global 对象的属性和方法而已。例如内置的 Object 、Function 对象都是它的属性。ECMAScript 并没有指出如何直接访问 Global 对象,但 Web 浏览器都是将这个全局对象作为 window 对象的一部分加以实现。
8.1.1 URI 的编码和解码
Global 对象提供了几个与 URI(通用资源标识符)进行编码和解码的方法。
- encodeURI()
- encodeURIComponent()
- decodeURI()
- decodeURIComponent()
前两个只会对对空格进行操作,其他字符原封不动。
后面两个会操作所有非字母字符。
8.1.2 eval() 方法
eval() 方法会将字符串参数解析为 ECMAScript 语句并在该方法的位置插入执行。也就是说里面的语句是在 eval() 执行是才执行,所以也不会有变量和函数提升。还有就是注意在严格模式下,外部访问不到 eval() 中创建的任何变量或者函数。
8.1.3 Global 对象的属性
- 我们熟悉的 undefined、NaN、infinity、Object、Array 等。
- 还有一些报错时抛出的错误类型构造函数 Error、SyntaxError、TypeError、RefereneceError 等。
8.2 Math 对象
Math 对象是 ECMAScript 内置的一个用于保存数学公式和信息的对象。里面有很多与数学计算有关的属性和方法。
8.2.1 Math 对象的属性(了解)
- Math.E 自然对数的底数,即 e
- Math.PI 圆周率 π 的值
其他的略了
8.2.2 Math 对象的方法
- max() 和 min()
- Math.ceil()、Math.floor() 和 Math.round()
- Math.random()
- Math.abs()、Math.exp()、Math.pow(num, power)、Math.sqrt()
// 数组对象借用Math方法实现某些操作(上面有提到)
// 三个舍入方法的区别和负数时需要特别注意一些特性
// random() 方法取随机数的运用
// 得到a到b 或者 b 到a的随机整数(包含这两个数)
function getRandom(a, b) {
return Math.floor((Math.abs(a - b) + 1) * Math.random()) + Math.min(a, b);
}