第五章 引用类型
知识点目录
Object类型和Array类型
创建方式
对象和数组都有两种创建方式:
- 使用 new 操作符后跟 Object/Array 构造函数
- 对象字面量表示法
Array类型
1. 检测数组
前面我们提到使用 instanceof 操作符可以检测引用类型,(参考:检测基本类型和引用类型值)
instanceof 操作符的问题在于,它假定只有一个全局执行环境。如果网页中包含多个框架,那实 际上就存在两个以上不同的全局执行环境,从而存在两个以上不同版本的 Array 构造函数。如果你从 一个框架向另一个框架传入一个数组,那么传入的数组与在第二个框架中原生创建的数组分别具有各自 不同的构造函数。
为了解决这个问题,ES5新增了Array.isArray() 方法,判断是否是数组,如果是则返回true
const arr = []
Array.isArray(arr) // true
2
2. 转换方法
TODO
后续学习补充
3. 数组的栈方法
ES的数组提供了让数组的行为类似与栈的数据结构的方法,栈是后进先出的数据结构,只能操作栈顶
- pop()方法,从数组末尾移除后一项
- push()方法,可以接收任意数量的参数,把它们逐个添加到数组末尾
4. 数组的队列方法
ES的数组还提供了可以实现类似队列数据结构的方法
结合使用 shift()和 push()方法,可以像使 用队列一样使用数组
同时使用 unshift()和 pop()方法,可以 从相反的方向来模拟队列,即在数组的前端添加项,从数组末端移除项
- shift(),移除数组中的第一项并返回该项
- unshift(),在数组前端添加任意个项并返回新数组的长度
5. 排序
- reverse(), 反转数组顺序
- sort(), 排序,根据字符串比较,所以对于其他数据类型的排序兼容不好
Number数据类型使用sort()方法示例:
[1, 2, 13, 14, 24].sort() // [1, 13, 14, 2, 24]
为了解决sort方法的不足,我们可以给它传一个比较函数作为参数,如下:
// 通过返回大于0,小于0和等于0的值来影响排序结果
function compare(val1, val2) {
return val2 -val1
}
var arr = [1, 13, 14, 2, 24]
arr.sort(compare) // [24, 14, 13, 2, 1]
2
3
4
5
6
6. 操作方法
- concat(), 复制当前数组,还可以通过单个参数或者数组作为参数,拼接到数组末尾
var arr = ['red', 2]
arr.concat() // ["red", 2]
arr.concat(7, ['blue', 9]) // ["red", 2, 7, "blue", 9]
2
3
- slice(), 根据参数截取原始数组返回一个新数组
一个参数,从当前位置截取至末尾
两个参数,截取参数范围内的数组, 注意返回值不包含第二个参数的位置
注意
如果slice()的参数是负数,则用数组长度加上该数来确定相应位置
如果结束位置小于起始位置,则返回空数组
var arr = ["red", "green", "yellow", "gray"]
// 传入一个参数
arr.slice(1) // ["green", "yellow", "gray"]
// 两个参数,
arr.slice(1, 3) // ["green", "yellow"]
// 负数参数
arr.slice(-3, -1) // ["green", "yellow"]
arr.slice(-6, -5) // []
2
3
4
5
6
7
8
9
- splice(), 删除、插入、替换数组,返回删除的项,若没有删除的项则返回空数组
删除,传入两个参数,起始位置和删除的项数
插入,至少传入三个参数,起始位置、删除的项数(0)和插入的项(可以传入多个)
替换,至少三个参数,起始位置、删除的项数和插入的项(可以传入多个)
var arr = ["red", "green", "yellow", "gray"]
// 删除
arr.splice(0, 1) // ["red"]
// 插入
arr.splice(0, 0, "blue", "pink") // [] 因为没有删除的项
console.log(arr) // ["blue", "pink", "green", "yellow", "gray"]
// 替换
arr.splice(0, 1, "blue", "pink") // ["red"]
console.log(arr) // ["blue", "pink", "green", "yellow", "gray"]
2
3
4
5
6
7
8
9
7. 位置方法
- indexOf(), 从初始位置查找,返回查找项的索引,找不到则返回-1
- lastIndexOf(), 从末尾位置查找,返回查找项的索引,找不到则返回-1
上面两个方法都接收两个参数:
- 第一个参数,必填,要查找的项
- 第二个参数, 非必填,查找的起始位置
注意: 这两个方法查找会使用全等操作符,所以查找的项必须严格相等
var arr = ["red", "green", "yellow", "green", "gray"]
arr.indexOf("green") // 1
arr.indexOf("green", 2) // 3
arr.lastIndexOf("green") // 3
arr.lastIndexOf("green", 2) // 1
var obj = {name: 'fmy'}
var arr = [{name: 'fmy'}]
arr.lastIndexOf(obj) // -1
arr = [obj]
arr.lastIndexOf(obj) // 0
2
3
4
5
6
7
8
9
10
11
12
8. 迭代方法
下面共五个迭代方法,可接收三个参数
- 第一个参数,必填,数组每项的值
- 第二个参数,非必填,该项在数组中的位置
- 第三个参数,非必填,数组对象本身
- every(), 查询数组中的每一项是否满足条件,若有一项不满足返回false,全都满足返回true
var num = [1, 2, 3, 4, 5, 6]
num.every(item => item < 7) // true
num.every(item => item < 5) // false
2
3
- some, 查询数组中的是否有满足条件的项,若有至少一项满足返回true,全都不满足返回false
var num = [1, 2, 3, 4, 5, 6]
num.some(item => item < 4) // true
num.every(item => item < 0) // false
2
3
- filter(), 返回满足条件的项组成的数组
var num = [1, 2, 3, 4, 5, 6]
num.filter(item => item < 4) // [1, 2, 3]
num.filter(item => item < 0) // []
2
3
- forEach(), 没有返回值,对数组的每一项执行某些操作,类似for循环
var num = [1, 2, 3, 4, 5, 6]
num.forEach(item => {
console.log(item + 1) // 2 3 4 5 6 7
})
2
3
4
- map(), 返回操作后的数组
var num = [1, 2, 3, 4, 5, 6]
var afterNum = num.map(item => {
return item*2
})
console.log(afterNum) // [2, 4, 6, 8, 10, 12]
2
3
4
5
9. 归并方法
- reduce(), 从数组初始位置操作
- reduceRight(), 从数组末尾操作
以上两个方法接收四个参数:
- 第一个参数,必填, 前一个值
- 第二个参数,非必填, 当前值
- 第三个参数,非必填,项的索引
- 第四个参数,非必填,数组对象
可以用于数组中所有值的求和操作
// reduce
var num = [1, 2, 3, 4, 5]
var sum = num.reduce((pre, cur, index) => {
return pre + cur
})
console.log(sum) // 15
// reduceRight
var num = [1, 2, 3, 4, 5]
var sum = num.reduceRight((pre, cur, index) => {
return pre + cur
})
console.log(sum) // 15
2
3
4
5
6
7
8
9
10
11
12
13
Date类型和RegExp类型
TODO
待补充
Function类型
函数实际上是对象,每个函数都是Function类型的实例,而且都与其他引用类型一样具有属性和方法。
基于函数是对象,所以函数名实际上也是一个指向函数对象的指针
没有重载
重载的概念
方法的重载是指一个类中可以定义有相同的名字,但参数不同的多个方法。调用时,会根据不同的参数表选择对应的方法
js中没有重载的概念,上面提到函数名实际是一个指针,基于这个概念,更容易理解js为什么没有重载
我们看一个示例:
function add(num) {
return num + 1
}
function add(num, num1) {
return num + num1 + 1
}
var result_1 = add(10) // NaN
var result_2 = add(10, 20) // 31
2
3
4
5
6
7
8
9
10
为了更便于理解js为什么没有重载,上面的实例实际等价于:
var add = function (num) {
return num + 1
}
add = function (num, num1) {
return num + num1 + 1
}
var result_1 = add(10) // NaN
2
3
4
5
6
7
8
通过第二个示例,我们就知道,再创建第二个函数时,实际上覆盖了引用第一个函数的变量add
函数声明与函数表达式的区别
知识点
除了什么时候可以通过变量访问函数这一点区别之外,函数声明与函数表达式的语法其实是等价的
解析器在向执行环节加载数据时,对函数声明和函数表达式并非一视同仁。
解析器会率先读取函数声明,并使其在执行任何代码之前可用;
而对于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被执行
为了理解上面的知识点,我们看一个示例:
// 函数声明
console.log(add(10, 20)) // 30
function add(num1, num2) {
return num1 + num2
}
// 函数表达式
console.log(add(10, 20)) // add is not a function
var add = function (num1, num2) {
return num1 + num2
}
2
3
4
5
6
7
8
9
10
11
函数内部的属性
函数内部有两个特殊的对象:arguments 和 this
arguments
js的函数不介意传递进来多少个参数,也不介意参数是什么类型
之所以这样,是因为在函数内部,参数是用一个数组(arguments)表示
arguments 是一个类数组对象,包含着传入函数中的所有参数
arguments 对象只是与数组类似,并不是Array的实例。可以使用方括号语法访问它的元素,使用length属性来确定传进来多少个参数
我们看一个示例:
function sum() {
return arguments[0] + arguments[1]
}
sum('你好', '世界') // "你好世界"
2
3
4
arguments的主要用途是保存函数参数,除此外,arguments 对象还有一个属性 callee,该属性是一个指针,指向拥有 arguments 对象的这个函数
我们看一个经典阶乘函数:
function factorial(num) {
if (num <= 1) {
return 1
} else {
return num * factorial(num - 1)
}
}
2
3
4
5
6
7
上面的阶乘函数用到了递归算法,但是存在函数的执行与函数名耦合在一起的问题,为了消除这种耦合,可以使用arguments.callee
function factorial(num) {
if (num <= 1) {
return 1
} else {
return num * arguments.callee(num - 1)
}
}
2
3
4
5
6
7
this
this引用的是函数据以执行的环境对象
看一个示例:
window.color = 'red'
var obj = {
color: 'green'
}
function outputColor() {
console.log(this.color)
}
outputColor() // 'red'
obj.outputColor = outputColor
obj.outputColor() // 'green'
2
3
4
5
6
7
8
9
10
11
12
函数的属性和方法
我们现在知道函数是对象,函数也有属性和方法
知识点
每个函数都包含两个属性: length 和 prototype
length属性表示函数希望接收的命名参数个数, 如下:
function foo(num) {
return num
}
foo.length // 1
2
3
4
prototype 保存了所有的实例方法,prototype方法通过各自对象的实例访问,这个属性我们在后面章节详解
知识点
每个函数都包含两个非继承而来的方法:apply() 和 call()
apply() 和 call() 都用来设置函数体内this对象的值, 两者的区别仅在于接收参数的方式不同
apply() 接收两个参数:
- 第一个是运行函数的作用域
- 第二个是参数数组(可以是 Array 的实例也可以是 arguments 对象)
function sum(n1, n2) {
return n1 + n2
}
function callSum(n1, n2) {
return sum.apply(this, [n1, n2])
}
callSum(1, 3) // 4
2
3
4
5
6
7
8
9
call() 与apply()方法类似:
- 第一个是运行函数的作用域
- 其余参数直接传递给函数
function sum(n1, n2) {
return n1 + n2
}
function callSum(n1, n2) {
return sum.call(this, n1, n2)
}
callSum(1, 3) // 4
2
3
4
5
6
7
8
9
apply() 和 call() 能够改变运行作用域
window.color = 'red'
var obj = {
color: 'blue'
}
function sayColor () {
console.log(this.color)
}
sayColor() // red
sayColor.call(0) // blue
2
3
4
5
6
7
8
9
10
11
基本包装类型
ECMAScript 提供了 3 个特殊的引用类型:Boolean、Number 和 String
这三个类型与其他引用类型相似,具有各自特殊的方法
我们知道基本类型值不是对象,从逻辑上讲不应该有方法
为了实现基本类型的直观的操作,后台自动完成一系列的处理
我们看一个例子:
var str = 'hello'
var str2 = str.substring(1) // 'ello'
2
在上面例子中,后台自动完成了下列处理:
- 创建String类型的一个实例
var str = new String('hello')
- 在实例上调用方法
var str2 = str.substring(1)
- 销毁实例
str = null
Boolean和Number类型对应的布尔值和数字 也适用于上述步骤
引用类型与基本包装类型的区别
主要区别是对象的生存期不同
- 引用类型的实例,在执行流离开当前作用域之前一直保存在内存中
- 自动创建的基本包装类型的对象,则只存在于一行代码执行的瞬间,然后立刻销毁
这意味不能在运行时为基本类型值添加属性和方法
← 第四章 变量-作用域-内存 第六章 →