红宝书(javascirpt高级程序设计)学习笔记(三)

本文首发于Liubasara的个人博客,承蒙喜爱,欢迎转载。

第5章 引用类型

本章用于介绍ECMAScript中的引用类型,适合快速阅读,查缺补漏(PS:本章其实更推荐在阅读完第6章后阅读)

对象是某个特定引用类型的实例。新对象是使用new操作符后跟一个构造函数来创建的。构造函数本身就是一个函数,只不过该函数是出于创建新对象而定义的。

var person = new Object()
复制代码

这行代码创建了Object引用类型的一个新实例,使用的构造函数是Object。ECMAScript提供了很多原生的引用类型(构造函数),以便于开发人员使用。

5.1 Object类型

两种定义方式:

var person = new Object()
// or
person = {} // 对象字面量语法定义
复制代码

5.2 Array类型

两种定义方式

var colors = new Array()
// or
var colors = ['red', 'blue', 'green'] // 数组字面量表示法
var colors = ['red', 'blue',] // 不要这样!这样为创建一个2或3项的数组
复制代码

在上面最后一个例子中,这样写会导致在不同的浏览器中,colors是一个长度不同的数组,从而导致程序出bug。

  • 数组的length属性不是只读的,通过设置length属性,可以方便地在数组末尾减少或添加新项。

    var colors = ['red', 'blue']
    colors[colors.length] = 'black' // 在位置3添加一种颜色 colors: ['red', 'blue', 'black']
    colors.length = 1 // colors: ['red']
    复制代码
  • 对于全局作用域,使用 instanceof 可以确定一个变量是不是数组,但对于多个框架,存在两个以上不同的全局执行环境的场景,就要使用 Array.isArray() 方法来确定这个值到底是不是数组。

  • 入栈方法:push

  • 出栈(取出数组最后一项)方法:pop

  • 取出数组第一项方法:shift

  • 推入元素至数组第一项方法:unshift

5.2.5 重排序方法

数组中有两个可以直接用于重排序的方法:

  • reverse() 用于反转数组项的顺序
  • sort() 用于自定义排序

sort()方法在默认情况下会调用每个数组项的 toString() 转型方法,然后比较得到的字符串。即使数组中的每一项都是Number,sort方法比较的也是字符串。

所以就会出现像下面这种诡异的情况。

var values = [0, 1, 5, 10, 15]
values.sort()
alert(values) // 0, 1, 10, 15, 5
复制代码

上面这种比较值就是由于在测试字符串的顺序中,"10"是比"5"小的,所以sort()改变了原来哪怕顺序已经是对了的values中的值。

如果要自定义排序顺序,可以使用sort方法的比较函数。如下。

// 降序
var values = [0, 1, 5, 10, 15]
values.sort(function (value1, value2) {
    if (value1 < value2) {
        return 1
    } else if (value1 > value2) {
        return -1
    } else {
        return 0
    }
})
alert(values) // 15, 10, 5, 1, 0
复制代码

关于compareFunction的介绍,书里介绍的不是很明确,在此引用MDN的说法。

  • 如果 compareFunction(a, b) 小于 0 ,那么 a 会被排列到 b 之前;
  • 如果 compareFunction(a, b) 等于 0 , a 和 b 的相对位置不变。备注: ECMAScript 标准并不保证这一行为,而且也不是所有浏览器都会遵守(例如 Mozilla 在 2003 年之前的版本);
  • 如果 compareFunction(a, b) 大于 0 , b 会被排列到 a 之前。
  • compareFunction(a, b) 必须总是对相同的输入返回相同的比较结果,否则排序的结果将是不确定的。
5.2.6 操作方法

Array常用的操作方法:

  • concat() 用于连接数组,浅拷贝数组(不会影响原数组)
  • slice() 用于返回数组中的部分元素(不会影响原数组)
  • splice() 用于删除、插入、替换数组中的项(影响原数组)
5.2.7 位置方法

ECMAScript5中,数组查询位置有两个方法:

  • indexOf() 用于从数组的开头开始往后找
  • lastIndexOf() 用于从数组的末尾开始往前找

这两个方法初始位置都是0,在没找到的情况下都会返回-1。

5.2.8 迭代方法

对于ECMAScript5,数组有5个迭代方法。

  • every()
  • filter()
  • forEach()
  • map()
  • some()

上面比较少用到的是every()和some()方法,下面举一个例子来看看它们的应用场景。

var numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1]
var everyResult = numbers.every(function (item, index, array) {
  return (item > 2)
})
alert(everyResult) // false 
var someResult = numbers.some(function (item, index, array) {
    return (item > 2)
})
alert(someResult) // true
复制代码
5.2.9 缩小方法

ECMAScript 5新增了两个缩小数组的方法:

  • reduce()
  • reduceRight()

reduce方法从数组的第一项开始,逐个遍历到最后。而reduceRight()则从数组的最后一项开始,向前遍历到第一项。

这两个方法其实用处很多,特别是reduce()函数,书里没有介绍得非常清楚,建议通过MDN详细学习。

5.3 Date类型

Date类型是在早期 java.util.Date类 基础上构建的,所以Date类型使用自 UTC 1970年1月1日零点开始经过的毫秒数来保存日期。

// 创建一个日期对象
var now = new Date()
复制代码

Date.parse() 方法接收一个表示日期的 String 参数,然后尝试根据这个字符串返回相应的日期,如果接收的参数无法转换,该函数会返回NaN实际上,如果直接将表示日期的字符串传递给Date构造函数,也会在后台调用Date.parse()。换句话说,这两段代码是等价的。

var someDate = new Date(Date.parse("May 25, 2004"))
// 等价于
var someDate = new Date("May 25, 2004")
复制代码

5.4 RegExp 类型

ECMAScript通过RegExp类型来支持正则表达式。

// 创建一个正则表达式
var expression = / pattern / flags
复制代码

其中flags代表匹配模式,支持三种标志:

  • g: 表示全局(global)模式,即模式将被应用于所有字符串。而并非发现在第一个匹配项时立即停止
  • i:表示不区分大小写模式,即在匹配项时忽略模式与字符串的大小写
  • m:表示多行模式,即在到达一行文本末尾时还会继续查找下一行中是否存在模式匹配的项

精通正则表达式的难度不亚于再掌握一门计算机语言,所以在此不作深究。

5.5 Function类型

在ECMAScript中,Function是最为基础,也是最为强大的编程武器。而每个函数,究其根源,实际上是一个Function类型的对象的实例。对于ECMAScript来说,一个函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定

// 两种相差无几的定义函数方式
// 函数声明
function sum (num1, num2) {
    return num1 + num2
}
// 函数表达式
var sum = function (num1, num2) {
    return num1 + num2
}
复制代码

还有一种定义函数的方式是使用Function构造函数,像new一个对象一样创建一个函数出来,能更清楚的感受到函数时对象,函数名是指针这个概念。但从技术的角度来讲,一般不建议这么做。(因为会导致解析两次代码)

var sum = new Function("num1", "num2", "return num1 + num2")
复制代码
5.5.1 没有重载

既然函数名是指针,那么也就能理解为什么ECMAScript中没有函数重载的概念了。

var addSomeNumber = function (num) {return num + 100}
addSomeNumber = function (num) {return num + 200}
复制代码

如上所示,在创建了第二个函数时,实际上是把第一个函数的指针指向了一个新的函数,第一个函数失去了指针,自然也就无法被调用了。

5.5.2 函数声明与函数表达式

虽然通过函数声明和函数表达式都可以用来声明函数,并且效果相差无几,但还是有一点不同的,那就是在解析器中执行的顺序。

// 正常运行
alert(sum(10, 10))
function sum (num1, num2) {
    return num1 + num2
}
复制代码

在代码开始执行前,解析器就已经通过一个名为函数声明提升的过程,读取并将函数声明添加到了执行环境中。所以,即便声明函数的代码在调用它的代码的后面,JavaScript引擎也能把函数声明提升到顶部。

但如果把函数声明改成等价的函数表达式,就会在执行期间导致错误。

// 报错
alert(sum(10, 10))
var sum = function (num1, num2) {
    return num1 + num2
}
复制代码

PS:也有另一种表达式是 var sum = function sum () {} 这样的,但这种语法在某些浏览器(Safari)中会导致错误。

5.5.4 函数内部的属性

在函数内部,有两个特殊的对象:

  • arguments
  • this

arguments 是一个类数组对象,包含着传入函数中的所有参数。虽然它的主要途径是保存函数参数,但这个对象还有一个名叫 callee 的属性,该属性是一个指针,指向拥有这个arguments对象的函数。

使用 callee属性,可以像下面这样十分容易的调用自己。

function factorial (num) {
    if (num <= 1) {
        return 1
    } else {
        return num * factorial(num - 1)
    }
}
// 上面是一个经典递归阶乘函数,虽然能调用自己,但是依赖了factorial这个函数指针
// 而使用arguments.callee,则可以摆脱这种依赖
function factorial (num) {
    if (num <= 1) {
        return 1
    } else {
        return num * arguments.callee(num - 1)
    }
}
复制代码

如上所言,现在的factorial函数是一个纯粹的递归函数,不会被函数名所约束了。

另一个特殊对象 this ,其引用的是函数据以执行的环境对象。当在网页的全局作用域中调用函数时,this 对象引用的就是 window

除了 argumentsthis 外,还有一个ECMAScript5规范化的函数对象属性,这个属性中保存着当前函数的函数的引用。(在全局作用域中调用,该值为null)。

function outer () {
    inner()
}
function inner () {
    alert(inner.caller)
}
outer() // 显示outer的源代码
复制代码

如上所示,因为outer()调用了inner(),所以inner.caller就指向了outer()

PS:当函数在严格模式下运行时,访问arguments.callee会导致错误

5.5.5 函数的属性和方法

ECMAScript中的函数也是对象,因此也有属性和方法。每个函数包含两个属性:

  • length
  • prototype

length 属性表示函数希望接收的命名参数的个数,如下例子所示。

function A(){}
function B(num1){}
A.length // 0
B.length // 1
复制代码

prototype 属性是保存它们所有实例方法的真正所在(换句话说,像 toString()valueOf() 等方法实际上都保存在 prototype 名下,只不过是通过各自对象的实例访问罢了)。在第6章将会详细介绍 prototype 属性。

每个函数都包含着两个非继承而来的方法:apply()call() 。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内 this 对象的值。

apply用于接收一个数组,而call用于接收数个参数,详见MDN

这两个函数在第6章和第7章都会详细解释他们真正的作用。(剧透:其实就是拿来生成原型链子对象实例。)

ECMAScript 5 还定义了一个方法bind()。这个方法会创建一个函数的实例,其this值会被绑定到传给bind()函数的值。

// 示例
window.color = 'red'
var o = { color: 'blue' }
function sayColor () {
    alert(this.color)
}
var objectSayColor = sayColor.bind(o)
objectSayColor()
复制代码

(个人理解:其实bind就是一个没有执行的call函数,使用bind绑定后执行也可以达到像使用call函数的效果。)

// 以下这段组合继承的例子有些超纲,建议看完第6章后再来看
function SuperType () {
    this.name = 'super'
}
function SubType () {
    this.age = 18
    SuperType.call(this)
}
SubType.prototype = new SuperType()
var testA = new SubType()
/** 
	console.log(testA)
	
	SubType {age: 18, name: "super"}
    age: 18
    name: "super"
    __proto__: SuperType
**/

// 以上一段组合继承的原型链,使用bind方式可以等同于
function SuperType () {
    this.name = 'super'
}
function SubType () {
    this.age = 18
    var o = SuperType.bind(this) // 绑定
    o() // 执行
}
SubType.prototype = new SuperType()
var testA = new SubType()

// bind函数实际上等于返回一个没有执行的call函数
function myBind (func, oothis) {
    return function () {
        return func.call(oothis)
    }
}
// 所以说,如果给一个函数bind多个this值,返回值永远会以第一个this为准,以下为例子
var a = {name: 'a'}
var b = {name: 'b'}
function person () {console.log(this)}
person.bind(a).bind(b)() // {name: 'a'}
复制代码

5.6 基本包装类型

为了便于操作基本类型值,ECMAScript提供了三个特殊的引用类型:Boolean,Number和String。这些类型与本章介绍的其他引用类型相似。

虽然字符串,布尔值和数值是基本类型值,但是为了方便操作,ECMAScript给他们加上了方法,实际上,为了实现这些方法,后台会在调用时自动完成一系列的处理。

// 比如在读取一个字符串时,后台做了以下三步:
var s1 = new String("some text")
var s2 = s1.subString(2)
s1 = null
// 经过这种处理,基本的字符串值就变得和对象一样了
复制代码

自动创建的基本包装的类型,只存在于一行代码的执行瞬间,然后立即被销毁,这意味着我们不能在运行时为基本类型值添加属性和方法,如下:

var s1 = "some text"
s1.color = "red"
alert(s1.color) // undefined
复制代码

PS: 要注意的是,使用new调用基本包装类型的构造函数,与直接调用同名的转型函数时不一样的。

// 示例
var value = "25"
var number = Number(value) // 转型函数
typeof number // "number" number: 25

var obj = new Number(value) // 构造函数
typeof obj // "object"
/** 
    console.log(obj)
    
	Number {30}
    __proto__: Number
    [[PrimitiveValue]]: 30
**/

复制代码
  • Number 类型中的 toFixed() 方法可以用于指定显示几位小数,并且会自动舍入。
  • Number 类型中的toPrecision() 方法可用于返回固定大小格式,接收一个一共有几位的参数。
  • String 类型中的 CharAt()CharCodeAt() 这两个方法接收一个参数,返回字符串中对应位置的字符或是字符编码。
  • String 类型中也有concat()slice()indexOf()lastIndexOf() 方法,用法基本相同。
  • String 类型中的 trim() 方法用于去除文本中所有的空格。
  • String 类型可以使用match()search()replace() 方法去使用正则匹配来过滤字符串。

5.7 单体内置对象

由ECMAScript实现提供的,不依赖宿主环境对象的对象。像Object,Array,String,undefined等前面介绍的,都是此类对象。

5.7.1 Global 对象

全局对象,所有在环境中已经存在的,不属于其他任何对象的方法,都属于Global对象的方法。如encodeURI()parseInt() 等等。

eval方法

狂拽酷炫吊炸天360度冰天雪地满分无死角之代码混淆全靠它不管你跪不跪反正我跪了我牛逼的函数。啥都能干,就是不太扛揍。

window对象

在浏览器中,global对象其实就是window对象,所有你在全局环境中定义的对象,都会出现在window对象中。

5.7.2 Math对象

Math对象常用的变量和方法有:

  • Math.E PS:自然对数的底数,即常量e的值
  • Math.PI
  • Math.max()
  • Math.min()
  • Math.ceil() 向上舍入,往大了舍
  • Math.floor() 向下舍入,往小了舍
  • Math.round() 标准舍入,四舍五入
  • Math.random() 返回一个介于0和1之间的一个随机数

本章完~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值