JS重点之引用类型
Object类型
创建Object
实例的方式有两种。一种是通过new
操作符后跟构造函数。另一种是对象字面量表示法。
let obj = {
name:'zhd',
0:'boy'
}
通常表示对象属性值,可以用obj.name
来表示或者动态添加一个属性,不过当属性名是数字时,如obj.0
这种表示法会直接报错,所以这种情况可以用另一种表示法,类似数组通过索引表示每一项的方式array[index]
,不过,当对象用这种表示方法的时候,注意,中括号内是一个字符串object['propertyName']
。
// 两种表示对象属性值的方法
object.propertyName = propertyValue
object['propertyName'] = propertyValue
Array
有一个经典问题,就是确定某个对象是不是数组的经典问题。对于一个网页,或者一个全局作用域而言,用instanceof
这个操作符就足够了。不过instanceof
这个操作符的问题在于,它假定只有一个全局执行环境。如果网页中包含多个框架,那就存在了两个或者以上的全局执行环境,从而存在两个以上不同版本的Array构造函数。也就是不能确定到底是哪个构造函数生成了要检测的数组。
ECMAScript5 新增了Array.isArray()
方法。这个方法的目的是最终确定某个值到底是不是个数组,而不管是在哪个全局执行环境中创建的。
迭代方法
ES5为数组定义了5个迭代方法。每个方法都接受两个参数,一个是处理数组的匿名函数,一个是运行该函数的作用域对象-影响this的值。传入的这个匿名函数接受三个参数:数组每一项,该项的索引和数组本身。
forEach()
: 对数组中的每一项运行给定的函数。这个方法没有返回值并且会改变原数组。
map()
: 对数组中的每一项运行给定的函数,返回每次函数调用的结果组成的数组。这个方法会返回一个新的数组并且不会改变原数组。
filter()
: 对数组中的每一项运行给定的函数,返回改函数会返回true的项组成的数组。这个方法会返回一个新数组并且不会改变原数组。
every()
: 对数组中的每一项运行给定的函数,如果该函数对每一项都返回true
,则返回true
。
some()
: 对数组中的每一项运行给定的函数,入股该函数对任一项返回true
,则返回true
。
归并方法
ES5新增了两个归并数组的方法。reduce()
和reduceRight()
。这两个方法都会迭代数组的每一项,然后国建一个最终返回的值。其中,reduce()
方法从数组的第一项开始,逐个遍历,reduceRight()
从数组的最后一项,向前逐个遍历。
这两个方法都接收两个参数:一个在每一项上调用的函数和(可选的)作为归并基础额初始值。传给reduce()和reduceRight()的函数接受4个参数:前一个值,当前值,项的索引和数组对象。这个函数返回的任何值都会作为第一个参数自动传给下一项。第一次迭代发生在数组的第二项上。因此第一个参数是数组的第一项,第二个参数是数组的第二项。
let arr = [1,2,3,4,5]
let result = arr.reduce(function(pre, cur, index, array) {
console.log('pre='+pre+',cur='+cur+',index='+index)
return pre + cur
},10)
结果如下图:
如果
reduce()
方法不传入第二个参数,则从1开始,传入则从10开始。如下所示:
let arr = [1,2,3,4,5]
let result = arr.reduce(function(pre, cur, index, array) {
console.log('pre='+pre+',cur='+cur+',index='+index)
return pre + cur
})
WechatIMG12.png
深拷贝和浅拷贝
长久以来,浅拷贝和深拷贝没有一个明确的定义,不过根据基本数据类型和引用数据类型的区别来看,最终的表现在于值的变化。也就是说一个变量的值赋值给另一个变量,其中一个变量的值的变化引起了另一个变量的值的变化,这就是浅拷贝。如果说一个变量的值的变化,不会导致另一个值的改变,则就是深拷贝。
说到底,深浅拷贝是对于引用数据类型来说的。一个基本类型值的变量赋值给另一个变量,只是浅拷贝。
对于引用类型值来说,浅拷贝只是把一个变量存储在栈内存中的指针赋给了另一个变量,两个变量访问的是同一个内存地址,所以一个变量的值得变化也能引起另一个变量值得变化。而深拷贝这是把一个变量完全复制了一份,包括值,指针和内存地址,两个变量相互独立,互不影响,一个变量的变化不会引起另一个变量的改变。
对如下对象实现浅拷贝和深拷贝:
let obj = {
person1:{
name: 'zhd',
age: '17',
sex: 'boy',
attrs: {
aa: 1,
bb: 2
},
prps: [3,4,5],
fn1: function() {
console.log(111)
}
},
person2:{
name: 'zy',
age: '27',
sex: 'girl',
attrs: {
aa: 6,
bb: 7
},
prps: [8,9,0],
fn2: function() {
console.log(222)
}
},
}
// 浅拷贝
// 第一种
let _obj = obj
// 第二种
let _obj = Object.assign({}, obj) // Object.assign()方法本身用来合并对象,不过这里与一个空对象合并,返回的还是之前的obj,这是此方法的巧妙运用。
// 深拷贝
// 第一种
let _obj = JSON.parse(JSON.stringify(obj))
// 第二种
let _obj = obj.slice()
// 第三种
let _obj = obj.concat()
// 第二种和第三种只适合数组
// 第四种
function createArray() {
return [] || new Array()
}
function createObject() {
return {} || new Object() || Object.create(null) || Object.create({})
}
function isObject(args) {
return Object.prototype.toString.call(args) === '[object Object]' && args instanceof Object &&
args.constructor === Object
}
function isObjectOrOther(args) {
return typeof args
}
function isArray(args) {
return Array.isArray(args) && args instanceof Array && args.constructor === Array
}
function isType(type) {
return function(data) {
return Object.prototype.toString.call(data) == '[object '+ type +']'
}
}
const deepClone = function(args) {
let result = isArray(args) ? createArray() : createObject()
for(let key in args) {
let val = args[key]
if(isObjectOrOther(val) === 'object') {
result[key] = arguments.callee(val)
} else {
result[key] = val
}
}
return result
}
let _obj = deepClone(obj)
用JSON.stringify()和JSON.parse()做深拷贝需要注意几点,请看这里
RegExp类型
与其他语言中的正则表达式类型,模式中使用的所有元字符都必须转义。正则表达式中的元字符包括:
{ [ ( \ ^ $ | ? * + . ) ] }
实例方法
RegExp对象的主要方法是exec()
,该方法主要为捕获组设计的。exec()
接受一个参数,即要应用模式的字符串,然后返回包含第一个匹配项信息的数组;或者在没有匹配项的情况下返回null。
返回的虽然是一个数组,但是包含额外的两个属性:index
,input
。其中,index指的是匹配到的字符的下标,input是要匹配的字符串。例子如下:
var str = 'mom and dad and baby' ;
var reg = /mom ( and dad ( and baby)?)?/gi ;
var matches = reg.exec(str) ;
对应的matches结果如下图:
WechatIMG8.png
对于exec()
方法而言,必须设置全局标志,并且多次调用exec()
方法才能返回全部匹配的项,否则即使设置了全局标志而不多次调用或者说不设置全局标志而多次调用只返回的是匹配的第一项,并不能返回全部匹配项。
函数内部属性
在函数内部有两个特殊的对象:arguments
和this
。arguments
主要用于保存函数实参,但这个对象还有一个名叫callee
的属性,该属性是一个指针,指向拥有arguments
对象的函数。跟这个有关系的是最经典的阶乘函数,如下:
function res(num) {
if(num < 1) {
return 1
} else {
return num * res(num - 1)
}
}
定义阶乘函数一般会用到递归算法。如果将上面函数赋给另一个变量,然后函数置为null,会是什么结果?来试一试。
var _res = res
res = null
_res(5) // Uncaught TypeError: res is not a function
会直接报错。也就是说res这个函数名绑定到了内部执行体并耦合了,那如何解耦呢?刚才提到的callee
就是解决这种情况的最佳办法。如下:
function res(num) {
if(num < 1) {
return 1
} else {
return num * arguments.callee(num - 1)
}
}
ES5也规范了另一个函数对象的属性:caller
。这个属性中保存着调用当前函数的函数的引用,如果在全局中调用函数,它的值为null。例如:
function outer() {
inner()
}
function inner() {
alert(inner.caller)
}
outer()
// 结果是:
// function outer() {
// inner
// }
有一点需要注意的是:caller
这个属性中保存着调用当前函数的函数的引用,必须是一个函数调用当前函数,不能是对象。
字符串的模式匹配方法
String类型定义了几个用于在字符串中匹配模式的方法。在这里就不全说了,说几个重要的。第一个方法就是match()
,在字符串上调用这个方法,本质上与调用RegExp的exec()
方法相同。match()
方法只接受一个参数,要么是一个正则表达式,要么是一个RegExp对象。来看下面的例子:
var str = 'cat, bat, sat, fat' ;
var reg = /.at/ ;
var matches = str.match(reg) ;
对应的matches结果如下图:
返回的结果跟
exec()
是一样的。另一个用于查找模式的方法是
search()
。这个方法的唯一参数与match()
方法的参数相同。search()
方法返回字符串中第一个匹配项的索引;如果没有找到匹配项,则返回-1,并且始终从字符串开头向后查找。第三个方法就是
replace()
方法,这个大家都知道,在这里就不多说了。