JavaScript引用类型

前言

Javascript虽然是面向对象的语言,它的新对象都是使用new后跟一个构造函数来创建的。构造函数本身是一个函数,它存在的目的就是创建新对象。如果构造函数没有参数可以直接省略构造函数后面的括号,不过通常都建议将括号加上避免混乱。现在来学习一下JS中各种对象类型,首先从最简单的Object类型开始。

Object类型

创建Object类型实例有两种方式,一种直接使用Object的构造函数实现,另外一种使用字面常量值实现。字面常量值仔细观察就会发现其实就是JSON格式的对象,前面的键值不加引号在有些浏览器中会造成错误,最好为每个键值都加上引号防止出现意外。

var user = new Object()
user.name = "zhangsan"
user.age = 18

var man = {
   "name": "lisi",
   "age": 30
}

alert(user.name + "  " + user.age) // zhansan  18
alert(man.name + "  " + man.age) // lisi  30

在JS中大括号包含的语句如果出现在if、do等后面就代表是语句上下文,这时表是一个语句块的开始;如果在赋值号后面出现就代表是表达式上下文,这时代表表达式的开始,表达式是一个能够返回数值的语句。这种字面量经常被用在向函数传递大量的参数,这时每个参数就是对象的一个属性值。

function save(args) {
  alert(args.id + " " + args.name + " " + args.desc) 
}

save({
   "id": 100,
   "name": "zhangsan",
   "age": 30,
   "desc": "Very good",
   "weight": "80kg"
})

// 100  zhangsan  Very good

Array类型

JavaScript的数组类型和其他语言的数组很不一样,虽然它是一个有序列表,不过Array内部的任何位置可以添加任何类型的数据,不必所有的数据类型都一致,Array还支持动态扩展,不会因为索引超出容量导致异常。数组的定义也分成new加构造函数定义和字面量定义两种方式。

var colors = new Array("red", "green", "blue")
var values = [1, 2, 3, 4]
values[6] = 10
for (var i = 0; i < values.length; i++) {
     alert(values[i])
}

// 1 2 3 4 undefined undefined 10

values.length = 2
for (var i = 0; i < values.length; i++) {
     alert(values[i])
}

// 1 2

values.length = 4
for (var i = 0; i < values.length; i++) {
     alert(values[i])
}
// 1 2 undefined undefined

数组中添加元素,中间缺少的元素会自动设置为undefined,如果修改了数组的length属性,那么就会截断原数组大小,再恢复原来数组的长度会发现以前的元素都不见了。除了动态扩展的功能,Array还支持很多数据结构的操作,最常见的就是栈和队列的操作。

var values = [1, 2, 3, 4]

values.push(10)
values.push(20);
for (var i = 0; i < values.length; i++) {
     alert(values[i])
}
// 1 2 3 4 10 20
alert(values.pop())
// 20
for (var i = 0; i < values.length; i++) {
     alert(values[i])
}
// 1 2 3 4 10

push和pop对应就是栈的入栈和出栈操作,它们都是在数组的尾部实现操作的。Array的shift方法移除数组的第一项并返回该值,unshift方法能够在数组的最前方添加一项并返回添加之后数组的长度,所以push操作和shift操作或者pop操作和unshift操作能够作为队列来实现。

var values = [1, 2, 3, 4]

values.push(10)
values.push(20);
for (var i = 0; i < values.length; i++) {
     alert(values[i])
}
// 1 2 3 4 10 20

alert(values.shift())
// 1
alert(values.shift())
// 2
for (var i = 0; i < values.length; i++) {
     alert(values[i])
}
// 3 4 10 20

Array的concat方法会生成一个新的数组并包含元素组和传入的参数;slice会根据offset和length创建数组的子数组,如果省略length那么就是从offset到结尾都是新数组的元素;splice能够实现批量的添加、删除和替换数组中的元素,其中offset代表开始操作位置,length代表要删除的元素个数,后面的值就是需要插入的新元素,splice返回值是被删除的元素数组,前面concat和slice都不会影响原来的数组,而splice的所有修改都是在原来的数组上做的。

var values = [1, 2, 3, 4]
var newArr = values.concat(10, 20)
// 1 2 3 4 10 20

var sliceArr = newArr.slice(3)
// 4 10 20

var values = [1, 2, 3, 4, 10, 20]
var removed = values.splice(0, 0, 50)
alert(removed)
// 什么都没有
for (var i = 0; i < values.length; i++) {
    alert(values[i])
}
// 50 1 2 3 4 10 20

removed = values.splice(0, 2)
alert(removed)
// 50 1
for (var i = 0; i < values.length; i++) {
    alert(values[i])
}
// 2 3 4 10 20
removed = values.splice(0, 1, 70, 100)
alert(removed)
// 2
for (var i = 0; i < values.length; i++) {
    alert(values[i])
}
// 70 100 3 4 10 20

数组的迭代方法every用来判定数组中每个元素都符合某个条件,some用来判断有元素符合某个条件;filter用来过滤符合条件的元素生成新的数组,map负责将原始的元素转换成新的元素,forEach遍历每个元素值,它们都接收一个function(item, index, array)类型的函数作为参数。

var values = [1, 2, 3, 4, 10, 20]
var val = values.every(function(item, index, array){
    return item % 2 == 0
})
alert(val) // false

val = values.some(function(item, index, array){
    return item % 2 == 0
})
alert(val) // true

var arr = values.filter(function(item, index, array){
    return item % 2 == 0
})

for (var i = 0; i < arr.length; i++) {
    alert(arr[i])
}
// 2 4 10 20

arr = values.map(function(item, index, array){
    return item * 2
})
arr.forEach(function(item, index, array){
    alert(item)
})
// 2 4 6 8 20 40

归约方法reduce和reduceRight它们会遍历数组中的所有元素并且将它们合并成一个值返回,reduce是从第一个开始遍历到最后一个,reduceRight从最后一个遍历到第一个元素。

var values = [1, 2, 3, 4, 10, 20]
var val = values.reduce(function(prev, item, index, array) {
    return prev += item
})
alert(val) // 40

var valRight = values.reduceRight(function(prev, item, index, array) {
    return prev += item
})

alert(valRight) // 40

Function类型

JS中的Function其实是对象类型,每个函数都是Function类型的实例,和其他的引用类型一样,Function类型的对象也有属性和方法,由于函数是对象所以函数名其实是一个指向函数对象的指针,并非是函数指针。也正是因为函数名实际是一个引用,所以在JS中根本不存在函数重载,因为相同的函数名不同的函数体最终是后一个覆盖前一个声明。前面已经提到过函数声明和函数变量不是完全等同的,解析器会把函数声明提升到执行上下文中,但是函数变量依然要到执行阶段才会初始化。

alert(add(1, 2))
// 会被提升到执行上下文中,前面的add没有问题
function add(x, y) {
    return x + y;
}
// 不会被提升到执行上下文中,前面引用的add是undefined
var add = Function(x, y) {
    return x + y;
}

由于Function本身就是变量,函数自然可以作为参数和返回值使用,在闭包语法中通常都是把Function作为返回值从执行函数中返回。函数的内部还包含了arguments和this,这两个特殊的对象,arguments主要用来保存函数参数,他还有一个叫callee的属性,该属性是一个指针指向了arguments所在的函数。函数内部还有一个this对象,this引用的就是函数的执行上下文对象。

function factor(num) {
   if (num == 0) {
       return 1;
   }

   return num * arguments.callee(num - 1);
}

// 后面可以给函数换一个名字,不用担心里面的递归函数名
alert(factor(5)) // 120

函数作为对象自然包含属性和方法,每个函数都包含两个属性length和prototype,其中length表示函数希望接收的命名参数个数,prototype是保存所有实例方法的真正所在,比如toString()、valueOf()等方法都是保存在这个对象里,在创建自定义引用类型及实现继承时都会使用prototype属性。

每个函数都会包含apply和call两个方法,这两个方法用途都是在特定的作用域中调用函数,实际上等于设置函数体内this对象的值。apply接收两个参数,一个是this,另外一个是参数数组。call与apply基本类似,不过它要求传递的参数必须要一个一个传递,不能作为数组传递。这两个方法的存在并不是为了传递参数而是为了修改函数的运行作用域,不同的作用域内部解析this的时候会有所不同。除了call和apply还有一个bind方法,它用来将函数的this绑定到一个对象上并且生成一个函数对象。

var age = 30
var person = {
   "age": 18
}

function print() {
    alert(this.age)
}

print() // 30
print.apply(person) // 18

var printAge = print.bind(person)
printAge() // 18

内置对象

不依赖于宿主环境在JS程序运行之前就已经存在的对象就称作内置对象,JS中常用的内置对象包括Global和Math。在JS中不属于任何对象的属性和方法都是Global对象的属性,isNaN、parseInt、parseFloat等都是它的方法,除了这些方法还有一些常用的方法,比如encodeURI和encodeURIComponent,它们对应的decodeURI和decodeURIComponent负责解码,还有eval函数主要负责将字符串解释为js代码并执行。

var url = "http://localhost/hello world"
alert(encodeURI(url))
// http://localhost/hello%20world
alert(encodeURIComponent(url))
// http%3A%2F%2Flocalhost%2Fhello%20world

eval("alert(3)")
// 3

Math对象主要保存了数学公式和常用的数学常量,可以使用它来执行很多数学计算,它的计算执行效率要高于直接使用JS代码编写的计算功能快。Math.random() 方法返回大于等于 0 小于 1 的一个随机数,套用这个公式就可以得到想要的随机值。

值 = Math.floor(Math.random() * 可能值的总数 + 第一个可能的值)

猜你喜欢

转载自blog.csdn.net/xingzhong128/article/details/80341640