JS红皮书读书笔记-05-引用类型

提醒:本文发布于 1486 天前,文章内容可能 因技术时效性过期 或 被重新修改,请谨慎参考。

TOC
  1. 1. Object
  2. 2. Array
    1. 2.1. 检测数组
    2. 2.2. 转换方法
    3. 2.3. 栈方法
    4. 2.4. 队列方法
    5. 2.5. 重排序方法
    6. 2.6. 操作方法
    7. 2.7. 位置方法
    8. 2.8. 迭代方法
    9. 2.9. 归并方法
  3. 3. Date
  4. 4. RegExp
  5. 5. Function
    1. 5.1. 没有重载
    2. 5.2. 函数声明和函数表达式
    3. 5.3. 作为值的函数
    4. 5.4. 函数内部熟悉
    5. 5.5. 函数属性和方法
  6. 6. 基本包装类型
    1. 6.1. Boolean
    2. 6.2. Number
    3. 6.3. String
  7. 7. 单体内置对象
    1. 7.1. Global
    2. 7.2. Math

Object

创建Object对象的方法:

  • new Object
  • 对象字面量(不会调用Object构造函数)

其他知识点:

  • 通常用点运算符访问对象的熟悉
  • 如非必要, 避免[“prop”]访问对象熟悉
  • 如果函数需要大量传参, 可以用对象作为参数

Array

创建方式:

  • new Array, 也可以省略new关键字i
    • var arr = new Array(10); arr.length==10
    • var arr = new Array(‘a’,’b’); arr == [‘a’,’b’]
  • var arr = [], 不会调用Array构造函数
    • var values = [1,2,]; 不要有空项, 不同浏览器识别不一致
  • length不是只读的, 可以手动修改
    • 可以利用这个属性代替arr.push的操作

知识点:

检测数组

  • instanceof
  • Arrary.isArray( )
  • constructor属性(不过不建议用这个, 可以被修改)
  • obj.toString.call(arr), 为什么不用自身的toString要用对象的toString方法来检测, 我们看示例:
    ({}).toString();	//'[object Object]'
    ([1,2,3]).toString(); //'1,2,3'

转换方法

上面的示例也涉及到一个数组的转换方法, 我这里再列出来(不过这些都是所有对象都有的方法):

  • toLocaleString(); 数组实例调用这个方法,其中的每一项也会调用
  • toString();同上
  • valueOf()

数组特有:

  • join, 解释略

栈方法

栈是一种后进先出的数据结构,也就是最新添加的项最早被移除。而栈中项的插入(叫做推入)和移除(叫做弹出),只发生在一个位置栈的顶部。对应方法为: push和pop, 请看示例:

var colors = new Array(); // 创建一个数组
var count = colors.push("red", "green"); // 推入两项
alert(count); //2

count = colors.push("black"); // 推入另一项
alert(count); //3
var item = colors.pop(); // 取得最后一项
alert(item); //"black"
alert(colors.length); //2

队列方法

栈数据结构的访问规则是 LIFO(后进先出),而队列数据结构的访问规则是 FIFO(First-In-First-Out, 先进先出)。队列在列表的未端添加项,从列表的前端移除项。由于 push()是向数组未端添加项的方法, 因此要模拟队列只需一个从数组前端取得项的方法。实现这一操作的数组方法就是 shift(),它能够移除数组中的第一个项并返回该项,同时将数组长度减 1。结合使用 shift()和 push()方法,可以像使用队列一样使用数组。

看示例:

var colors = new Array(); //创建一个数组
var count = colors.push("red", "green"); //推入两项
alert(count); //2

count = colors.push("black"); //推入另一项
alert(count); //3
var item = colors.shift(); //取得笫一项
alert(item); //"red"
alert(colors.length); //2

另外还有一个unshift方法 , 和shift方法功能相反

重排序方法

数组中已经存在两个可以直接用来重排序的方法:reverse()和 sort(), 两个方法都会修改原数组。
sort方法会调用数组中的每一项的toString方法,得到其中的字符串之后, 在做排序对比.

我们来看一个示例:

var values = [0, 1, 5, 10, 15]; 
values.sort();
alert(values); //0,1,10,15,5, 这里并没有按我们想要的来排序

我们给sort加一个比较函数作为参数:

function compare(value1, value2) { 
if (value1 < value2) {
return 1;
} else if (value1 > value2) {
return -1;
} else {
return 0;
}
}

var values = [0, 1, 5, 10, 15];
values.sort(compare);
alert(values); // 15,10,5,1,0

操作方法

  • concat, 返回被修改的副本
  • slice(start,end), 返回被删除的数组, 一般用于克隆数组(浅复制) .通常用[].slice.call(arrayLike)来转换成数组
  • splice(start [, deleteCount, insretItem1, insretItem2, ….] ) // 返回被删除的数组:
    var x = [1,2,3,4,5,6];
    x.splice(1,1,'x')//[2]
    alert(x) //[1,'x',3,4,5,6]

位置方法

  • indexOf()
  • lastIndexOf()

迭代方法

  • every():对数组中的每一项运行给定函数,如果该函数对每一项都返回 true,则返回 true。
  • filter():顾名思义, 过滤所需要的数组元素, 对数组中的每一项运行给定函数,返回该函数会返回 true 的项组成的数组。
  • some():对数组中的每一项运行给定函数,如果该函数对任一项返回 true,则返回 true。
  • forEach():对数组中的每一项运行给定函数。这个方法没有返回值
  • map():对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。
    以上5种方法都不会修改数组中的包含的值。

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

我们看一个map的示例:

var numbers = [1,2,3,4,5,4,3,2,1];

var mapResult = numbers.map(function(item, index, array){
// 所以map的回调函数有三个参数, 数组项, 索引, 数组本身
return item * 2;
});

alert(mapResult); //[2,4,6,8,10,8,6,4,2]

我们在看一个面试题:

["1","2","3"].map(parseInt);//?

你可能一下子想不出答案, 那我直接告诉你好了, 答案是: [1,NaN,NaN]

如果你不明白, 可以看下面的解析, 否则直接跳过

function cb(item,index,arr){
return item+'-'+index; //注意map回调方法里面要有返回值
}
["1","2","3"].map(cb); //["1-0", "2-1", "3-2"];

//我们换一种写法帮助理解
function _parseInt(string, radix){
return parseInt(string,radix) // parseInt(string,radix), 这个方法本身是有返回值的
}
["1","2","3"].map(_parseInt); //[1,NaN,NaN]

//所以看得出item,和index传到了parseInt对应的参数string, radix中, 而string中的数字必须小于radix, 否则转换十进制失败, 即NaN

归并方法

  • reduce
  • reduceRight

解析: 略

Date

只讲一个奇技淫巧, 求某个月有多少天:

var getDays = function(m){
return new Date((new Date).getFullYear(),m,0).getDate()
}

RegExp

请查看正则表达式教程

Function

每个函数都是 Function 类型的实例,而且都与其他引用类型一样具有属性和方法。由于函数是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。

三种使用方式:
函数声明:

function sum (num1, num2) { 
return num1 + num2;
}

函数表达式:

var sum = function (num1, num2) { 
return num1 + num2;
}; //这个分号别漏了, 养成习惯

构造函数(不推荐):

var sum = new Function("num1", "num2", "return num1 + num2");

从技术角度讲,这是一个函数表达式。但是,我们不推荐读者使用这种方法定义函数,因为这种语法会导致解析两次代码(第一次是解析常规 ECMAScript 代码,第二次是解析传入构造函数中的字符串),从而影响性能。不过,这种语法对于理解“函数是对象,函数名是指针"的概念倒是非常直观的。

没有重载

ES函数中, 是没有重载的,如果你非得声明多个一样的函数, 那么最后声明的那个函数会覆盖之前的声明.

函数声明和函数表达式

函数声明和函数表达式虽然功能是一致的, 但是解析器对这两种方式的优先级并不一致. 解析器会率先读取函数声明, 并使其在执行任何代码之前可用(可以访问);至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。

看示例:

alert(sum(10,10)); // 会正常执行
function sum(num1, num2){
return num1 + num2;
}

作为值的函数

函数内部熟悉

在函数内部,有两个特殊的对象:argumentsthis。其中,arguments 在第 3 章曾经介绍过, 它是一个类数组对象,包含若传入函数中的所有参数。虽然 arguments 的主要用途是保存函数参数, 但这个对象还有一个名叫 callee 的属性,该属性是一个指针,指向拥有这个 arguments对象的函数。

请看下面这个非常经典的阶乘函数:

function factorial(num){ 
if (num <=1) {
return 1;
} else {
return num * factorial(num-1) ;//看这里, 如果外部的factorial被人改变的话, 此处的factorial也需要同时改变
}
}

于是我们可以:

function factorial(num){ 
if (num <=1) {
return 1;
} else {
return num * arguments.callee(num-1) ;//降低耦合, 严格模式会报错
}
}

this: 函数上下文对象, 当前执行环境对象.
caller: 表示当前函数被调用, ES5下也有arguments.caller,值为undefined, 它的存在主要用于区别函数的caller

ps: 严格模式下, 函数.caller不能赋值, arguments.caller访问报错

函数属性和方法

属性: 前面通过caller讲了一个, 接下来还有lengthprototype:
length: 你可能比较少见函数的length熟悉, 它表示函数的形参长度.
prototype: 我们下一章重点讲这个

方法: 每个函数都包含两个非继承而来的方法, 这两个方法的功能类似, 区别在于第二个参数的格式不同而已
call(contextObj [,arg1,arg2…])
apply(contextObj, arr): arr可以是数组示例, 也可以是arguments对象

那么这两个方法是用来干啥? 主要是用来改变指向的(但它们的基本功能还是用于改变传参方式):

window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
sayColor(); //red
sayColor.call(this); //red
sayColor.call(window); //red
sayColor.call(o); //blue

你要是不好理解, 可以这么想象:

函数fn打了个电话(call), 等会我要到你(环境对象)那里去玩哦, 顺便给你带点东西(参数), 既然玩完了, 那自然fn任务也完成了(说明fn会被执行完毕).

使用 call()(或 apply())来扩充作用域的最大好处,就是对象不需要与方法有任何耦合关系(例如不需要o.sayColor的存在)。可提高代码重用性。

此外还有另一个方法: bind, 这个方法和call,apply的功能是类似的:

window.color = "red";
var o = { color: "blue" };

function sayColor(){
alert(this.color);
}
var objectSayColor = sayColor.bind(o);
objectSayColor(); //blue

另外还有:

  • toLocaleString
  • toString
  • valueOf

详解略

基本包装类型

为了便于操作基本类型值,ECMAScript 还提供了 3 个特殊的引用类型:Boolean、Number 和
String。这些类型与本章介绍的其他引用类型相似,但同时也具有与各自的基本类型相应的特殊行为。 实际上,每当读取一个基本类型值的时候,后台就会创建一个对应的基本包装类型的对象,从而让我们能够调用一些方法来操作这些数据。

var s1 = "some text";
var s2 = s1.substring(2);

这个例子中的变量 s1 包含一个字符串,字符串当然是基本类型值。而下一行调用了 s1 的
substring()方法,并将返回的结果保存在了 s2 中。我们知道,基本类型值不是对象,因而从逻辑上讲它们不应该有方法(但是它们确实有方法)

当第二行代码访问 s1 时,访问过程处于一种读取模式,也就是要从内存中读取这个字符串的值。而在读取模式中访问字符串时,后台都会自动完成下列处理。

  • 创建 String 类型的一个实例
  • 在实例上调用指定的方法
  • 销毁这个实例

可以将以上三个步骤想象成是执行了下列 ECMAScript 代码。

var s1 = new String("some text"); 
var s2 = s1.substring(2);
s1 = null;

经过此番处理,基本的字符串值就变得跟对象一样了。而且,上面这三个步骤也分别适用于 Boolean
和 Number 类型对应的布尔值和数字值。

引用类型基本包装类型的主要区别就是对象的生存期。使用 new 操作符创建的引用类型的实例, 在执行流离开当前作用域之前都一直保存在内存中。

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

我们来看这个示例:

var s1 = "some text"; 
s1.color = "red";
alert(s1.color); //undefined

在此,第二行代码试图为字符串 s1 添加一个 color 属性。但是,当第三行代码再次访问 s1 时, 其 color 属性不见了。问题的原因就是第二行创建的 String 对象在执行第三行代码时已经被销毁了。第三行代码又创建自已的 String 对象,而该对象没有 color 属性。

当然,可以显式地调用 Boolean、Number 和 String 来创建基本包装类型的对象。不过,如非必要, 请不要这做,因为这种做法很容易让人分不清自已是在处理基本类型还是引用类型的值。

基本类型基本包装类型区别在于new产生的对象, 还能继续拥有属性和方法.

Object 构造函数也会像工厂方法一样,根据传入值的类型返回相应基本包装类型的实例。例如:(同理传入number和boolean一样)

var obj = new Object("some text"); 
alert(obj instanceof String); //true

Boolean

只有一个建议: 不要用它的基本包装类型

Number

String

有下列的一些方法:

  • 继承的方法:
    • valueOf
    • toLocaleString
    • toString
  • 字符方法:
    • charAt
    • charCodeAt
    • String.fromCharCode, 此为静态方法. 与charCodeAt功能相反
  • 字符串操作方法:
    • concat
    • slice
    • substr
    • substring
    • localeCompare, 略
  • 字符串位置方法:
    • indexOf
    • lastIndexOf
  • 去除空格:
    • trim, 返回副本
  • 格式:
    • toLocaleUpperCase
    • toLocaleLowerCase
    • toUpperCase
    • toLowerCase
  • 字符串模式匹配方法:
    • match, text.match(pattern)作用和pattern.exec(text) 相同
    • search
    • replace, 常用语替换HTML字符串模板, 转义HTML符号
    • split, 转数组
  • HTML方法: 实现标签的嵌套:string
    • anchor
    • big
    • bold
    • fixed
    • fontcolor
    • fontsize
    • italics
    • link
    • small
    • strike
    • sup
    • sub

单体内置对象

由ECMAScript实现提供的、不依赖于宿主环境的对象,这些对象在程序执行之前就已经存在了。

Global

在浏览器中即window

- URI编码
    - encodeURI
    - encodeURIComponent
    - decodeURI
    - decodeURIComponent
- Eval: 在 eval()中创建的任何变量或函数都不会被提升,因为在解析代码的时候,它们被包含在一个字符串中;它们只在 eval()执行的时候创建。严格模式下不能赋值, 也不能访问到eval里面创建的函数或者变量
- global对象的属性: 略

Math

  • Math的属性: 略
  • Math.max和Math.min
    var max = Math.max(3, 54, 32, 16); 
    alert(max); //54

    var min = Math.min(3, 54, 32, 16);
    alert(min); //3

    //如果要求一个数组的最大值要怎么办呢?
    var values = [1, 2, 3, 4, 5, 6, 7, 8];
    Math.max.apply(null,values); //8, 前面函数方法那里, 讲了call/apply, 这里就是apply的基本功能

PS: 基于这个, 我们可以出一个题目, 有多少种方法求数组的最大值?(自行思考)

  • 舍入方法
    • Math.ceil, 向上取舍
    • Math.floor, 向下取舍
    • Math.round, 四舍五入
  • Math.random: 最常用的方法之一
  • 其他方法: 看图
    Math其他方法

访客评论