红皮书笔记

第二章 在 HTML 中使用 JavaScript

scrpit加载的方式

async:可选,表示应该立即下载脚本,但不妨碍页面中的其他操作,比如下载其他资源或者或者等待加载其他脚本,只对外部脚本文件有效。
charest:表示通过src属性指定的代码的字符集。
defer:表示脚本可以延迟到文档完全被解析和显示以后再执行。只对外部脚本有效。
src:可选。路径。
type:可选,表示编写代码所使用的脚本语言的内容类型。也称为MIME类型。text/javascript属性。
js在head中与body中的加载不同。
在文档的head中包含所有js文件,意味着必须等到全部js代码被下载,解析,执行后才会显示页面的内容,
浏览器遇到body标签才开始呈现内容。这会导致浏览器呈现时候出现明显的延迟。
而延迟期间浏览器将会是一片空白,为了避免这个问题,现代web一般将全部js引用放到body中。
在head的script标签中加入defer属性defer = “defer”,脚本会被延迟到整个页面解析完毕再被执行。
head中的部分是代码在整个body前预先被加载,所以希望那些被调用的代码可以在这里写,例如按钮的点击事件等。
而body中的代码是html被加载完成之后再被执行。所以有用到body中的一些属性的,放在这里写。

第三章 js中的基本概念

label标签语句:

使用label标签语句可以在代码中添加标签,以便将来使用

1
2
3
4
5
start:for(var i = 0; i < 4;i++) {
if (i == 2)
break start;
alert(i);
}

如上所示,start标签标示的for循环语句可由break或continue引用,
break和continue可以和label联合起来使用,从而返回特定的代码,这种联合使用通常发生在循环嵌套的情况下。

1
2
3
4
5
6
7
8
9
10
11
var num = 0;
outermost:
for(var i = 0; i < 10;i++){
for(var j = 0; j < 10;j++){
if(i == 5 && j == 5)
break outermost;
num++;
}

}
console.log(num);

上述例子中,outermost标签代表外部的for语句,如果循环正常执行10次,则num最终的值时100,但内部循环中break带了一个参数,表示要返回的标签,添加这个标签的结果是break不仅会内部退出,也会外部退出循环。
这样,num的值刚好是55.

with语句

with语句的作用是将代码的作用域设置到一个特定的对象中,
with语句结构如下:
with (expression) statement;
定义with语句的主要目的是为了简化多次编写同一个对象的麻烦。

1
2
3
4
5
6
7
8
9
10
11
12
13
var dic = {
a:1,
b:2,
c:3
};
with(dic){
var out_a = a;
var out_b = b;
var out_c = c;
}
console.log(out_a);
console.log(out_b);
console.log(out_c);

使用with语句关联了dic对象,这意味着with语句的代码快中,每个变量首先被认为是一个局部变量,而如果在局部变量中找不到该对象,就会查询dic中是否有该对象,严格模式下是不允许使用with语句的。
‘use strict’;with语句会报错。with语句会导致性能的降低,同时也会给调试代码造成困难,因此
在开发大型应用程序时,不建议使用 with 语句。

switch语句中可以合并多个case.

1
2
3
4
5
6
7
8
9
10
11
switch (i) {
case 25:
/* 合并两种情形 */ case 35:
alert("25 or 35");
break;
case 45:
alert("45");
break;
default:
break;
}

case中的变量可以是一个表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var num = 25;
switch (true) {
case num < 0:
alert("Less than 0.");
break;
case num >= 0 && num <= 10:
alert("Between 0 and 10.");
break;
case num > 10 && num <= 20:
alert("Between 10 and 20.");
break;
default:
alert("More than 20.");
}

函数

ecmascript函数不介意传递进去多少个参数,也不在乎传递进来的参数是什么类型的,也就是说,即使你定义的函数接收2个参数,但实际传进来3个,1个都是可以的。之所以这样, 是因为js中参数在内部是以数组形式表示的,函数接收到的始终是一个数组,而不关心数组中包含哪些参数(如果有参数的话)。如果这个数组中不包含任何元素,无所谓;如果包含多个元素,也没有问题。实际上,在函数体内可以通过arguments对象访问这个参数数组,从而获取传递给函数的每一个参数。
arguments对象只是与数组类似(并不是array的实例),因为可以使用方括号语法访 问它的每一个元素(即第一个元素是 arguments[0],第二个元素是 argumetns[1],以此类推),使 用 length 属性来确定传递进来多少个参数。

1
2
3
4
function add(num1,num2) {
return num1 + num2;
}
console.log(add(1));

这样是不会报错的。只是会返回NaN,因为num2没有传递进去。

1
2
3
4
function add(num1,num2) {
return arguments[0] * arguments[1];
}
console.log(add(1,2));

可以用argument来访问其中的参数。

1
2
3
4
5
function doAdd(num1, num2) {
arguments[1] = 10;
alert(arguments[0] + num2);
}
console.log(doAdd(1,2));

在以上代码中,doAdd函数每次都会重写第二个参数,将第二个参数的值改为10,因为argument对象中的值会自动反映到对应的命名参数中,所以修改arguments[1],也就修改了num2,结果它们的值都会变成10,不过,这并不是说读取这两个值会访问相同的内存空间;它们的内存空间是独立的,但它们的值会同步。另外需记住,如果只传入了一个参数,那么arguments[1]的值修改不会反映到对应的num2中,,这是因为argument对象的长度是由传入的参数个数决定的,不是由定义时候的命名参数决定的。
**没有传递值得命名参数将自动被赋予undefined,这就跟定义了变量但是没有初始化时一样的。例如,如果只给 doAdd()函数传递了一个参数,则 num2 中就会保存 undefined 值。
严格模式对如何使用 arguments 对象做出了一些限制。首先,像前面例子中那样的赋值会变得无效。也就是说,即使把 arguments[1]设置为 10,num2 的值仍然还是 undefined。其次,重写 arguments 的值会导致语法错误(代码将不会执行)。

js中函数是没有重载的概念的。像其他语言中,定义了函数名字,即签名,但只要接收的参数不同,就会被重载,注意,js中是没有这个的。js中函数没有签名,因为其参数是由包含零或多个值的数组来表示的,而如果没有函数签名,重载也是做不到的。
相同名字的第二个函数定义会覆盖第一个。

第四章 变量、作用域和内存问题

基本类型和引用类型

js包含两种不同数据类型的值,基本类型值和引用类型值,基本类型值有string,boolean,number,undefined,null,这5种数据类型是按照值访问的。因为可以操作保存在变量中的实际的值。引用类型的值是保存在内存中的对象,js不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。 为此,引用类型的值是按引用访问的。当复制保存对象的某个变量时,操作的是对象的引用,但是在为对象添加属性时,操作的是实际的对象。
如下代码

1
2
3
var person = new Object();
person.name = "jack";
alert(person.name);

注意:我们不能给基本数据类型值添加属性,尽管不会导致任何错误。

1
2
3
var name = "jack";
name.age = 27;
alert(name.age);

输出为undefined.
复制的问题:从一个变量向另一个变量复制基本类型的值时,会在变量对象上创建一个新值,然后把该值复制 到为新变量分配的位置上。来看一个例子:

当复制引用类型的时候,同样也会将存储在变量对象中的值复制一份放到 为新变量分配的空间中。不同的是,这个副本实质上是一个指针,而这个指针指向存储在堆中的一个对象,复制操作结束以后,两个变量实际上将引用同一个对象,因此,改变其中的一个变量值,会影响另外一个变量的值。

1
2
3
4
var obj1 = new Object();
var obj2 = obj1;
obj1.name = "ckq";
alert(obj2.name);//ckq

输出的第二个obj2的name也为ckq。
如下图所示:

传递参数

js中所有函数都是按照值传递的,把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样。
基本数据类型是值传递容易理解。引用数据类型传值时,会把这个值在内存中的地址复制给一个局部变量,因此这个局部变量的变化会反映在函数的外部。

1
2
3
4
5
6
7
8
function addTen(num) {
num += 10;
return num;
}
var count = 20;
var result = addTen(count);
alert(count);//20
alert(result);//30

传递给addTen中的num和count是独立的,没有关系。
如果传递的是引用类型:则函数内部产生的变化会影响到外部

1
2
3
4
5
6
function set(obj) {
obj.name = "cpp";
}
var obj = new Object();
set(obj);
alert(obj.name);//cpp

复制给set中的obj是外部的一个拷贝。实际上是一个指针,所以内部引起的变化会影响到外部。
下面是一个特殊的例子:

1
2
3
4
5
6
7
8
function set(obj) {
obj.name = "cpp";
obj = new Object();
obj.name = "ckq";
}
var obj = new Object();
set(obj);
alert(obj.name);//cpp

在上述例子中,首先将obj复制给函数,随后obj.name设置成cpp.当obj = new Object()的时候,在这时候obj已经指向了新的内存空间,与前面的指向无关,此时将obj设置成了ckq,所以后面的弹出obj依然是cpp。

检测类型

typeof只能检测基本的数据类型,例如字符串,数值,布尔值,还是undefined的最佳工具,如果变量的值是一个obj或者null的时候,则会返回object类型。可以采用instance of类型来检测更加具体的类型。

1
2
3
4
5
6
7
8
9
10
11
12
var a = "abc";
var b = true;
var c = 22;
var d;
var e = null;
var f = new Object();
console.log(typeof a);//string
console.log(typeof b);//boolean
console.log(typeof c);//number
console.log(typeof d);//undefined
console.log(typeof e);//object
console.log(typeof f);//object

根据规定,所有引用类型的值都是object的实例,因此,在检测一个基本数据类型的时候,会返回false,因为不是对象,检测对象的时候,会返回true.

1
2
3
4
5
var person = new Object();
console.log(person instanceof Object);//true
console.log(person instanceof Array); //false
var a = "11";
console.log(a instanceof String);//false

因为基本数据类型不是引用类型。

1
2
3
var a = new Array(1,2,3);
console.log(a);//1,2,3
console.log(a instanceof Array);//true

执行环境和作用域

执行环境是js中重要的一个概念,执行环境定义了变量或函数有权访问的其他数据,决定了他们各自的行为,每个执行环境都有一个与之关联的变量对象,环境中所有的变量和函数都保存在这里,虽然我们编写的代码无法访问,但解析器会在后台处理。
每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。 而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。ECMAScript 程序中的执行流 正是由这个方便的机制控制着。
当代码在一个环境中执行时,会创建变量对象的一个作用域链,作用域链的用途,是保证对执行环节有权访问的所有变量和函数的有序访问,作用域链的前端,始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象(activation object)作为变量对象。活动对 象在最开始时只包含一个变量,即 arguments 对象(这个对象在全局环境中是不存在的)。作用域链中 的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境。这样,一直延 续到全局执行环境;全局执行环境的变量对象始终都是作用域链中的最后一个对象。
以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
var color = "blue";
function changeColor(){
var anotherColor = "red";
function swapColors(){
var tempColor = anotherColor;
anotherColor = color;
color = tempColor;
// 这里可以访问color、anotherColor和tempColor }
// 这里可以访问color和anotherColor,但不能访问tempColor
swapColors();
}
// 这里只能访问color changeColor();

有如下作用域链:

内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何函数和变量,这些环境的联系是有序的,线性的。

延长作用域链

虽然执行环境总共只有2种,全局和局部的,但还是有其他办法来延长作用域链。有些语句可以在作用域的前端临时增加一个变量对象,该变量对象会在代码执行后自动销毁,在两种情况下会发生这种现象。具体来说,就是当执行流进入下列任何一个语句时,作用域链就会 得到加长:
**try-catch模块和with语句。
这两个语句都会在作用域链的前端添加一个变量对象。对 with 语句来说,会将指定的对象添加到
作用域链中。对 catch 语句来说,会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明。 下面看一个例子。

1
2
3
4
5
6
7
function buildUrl() {
var qs = "?debug=true";
with(location){
var url = href + qs;
}
return url;
}

在此,with 语句接收的是 location 对象,因此其变量对象中就包含了 location 对象的所有属 性和方法,而这个变量对象被添加到了作用域链的前端。

没有块级作用域

js中是没有块级作用域的 例如如下会输出true

1
2
3
4
if (true) {
var color = "blue";

alert(color); //"blue"

在c或者java中,花括号后,color便会自动销毁,但在js中,if语句中的变量声明会将变量添加到当前的执行环境(在这里是全局环境),在用for语句时要尤其记住这一点。

1
2
3
4
for (var i=0; i < 10; i++){
doSomething(i);
}
alert(i); //10

对于js来说,由for语句创建的变量i即使在for循环后,也会存在于循环外部的执行环境中。
声明变量。
使用var声明的变量会被自动添加到最接近的环境中,在函数内部,最接近的环境就是函数的局部环境;在 with语句中,最接近的环境是函数环境,**如果初始化变量时候没有使用var,该变量会被自动添加到全局环境中。

1
2
3
4
5
6
 function add(num1, num2) {
sum = num1 + num2;
return sum;
}
var result = add(10, 20); //30
alert(sum); //sum是全局变量,因此会弹出sum.

js中,不声明而直接初始化是一个非常严重的错误,所以,我们建议在初始化之前,一定要先声明,这样会避免许多错误,在严格模式下,初始化未经声明的变量会导致错误。

1
2
3
'use strict'
sum = 4;
console.log(sum);

上述代码会报错。

第五章 引用类型

object类型

直接创建

1
2
var person = new Object();
person.name = "nichoload";

使用对象字面量的方法

1
2
3
4
5
var p = {
name:'load',
age:28,
5:true
}

推荐使用对象字面量。
用方括号或者点来进行访问

1
2
console.log(p['name']); //load
console.log(p.name); //load

array类型

创建 ecmscript的每一项都可以存储任何数据类型

1
2
3
4
5
6
7
var color = new Array();
//长度为20的
var colors = new Array(20);
//包含3个字符串的
var colors = new Array("red","blue","green");
//或者直接创建
var colors = ["red","blue","green"];

如下创建最后一个可能会包含undefined

1
2
var values = [1,2,];
console.log(values[2]);//undefined

可以直接修改数组的length,修改后末尾会自动添加上undefined

1
2
3
var values = [1,2];
values.length = 3;
console.log(values[2]); //undefined

检测数组

可以使用isArray方法来检测数组 或者使用 isstanceof方法

1
2
3
4
5
6
var values = [1,2];
if(values instanceof Array){
console.log(true);
}
if(Array.isArray(values))
console.log(true);

转换方法

toString() 方法返回拼接后的字符串,逗号分隔

1
2
3
var values = [1,2];
console.log(values.toString()); //1,2
console.log(values.valuesOf());//[1,2]

join方法

join方法表示使用不同的字符进行拼接数组

1
console.log(values.join("||"));//1||2

如果不给 join()方法传入任何值,或者给它传入 undefined,则使用逗号作为分隔 符。IE7 及更早版本会错误的使用字符串”undefined”作为分隔符。
类似栈的方法

1
2
3
var values = [1,2];
values.push(3);
console.log(values); //1,2,3

在末尾添加
获得末尾的最后一项

1
2
var item = values.pop();
console.log(item);

类似队列的方法

shift方法表示从最前面移除一个数组

1
2
var item = values.shift();
console.log(item); //1

重排序方法

reverse表示翻转,sort表示排序,默认升序

1
2
3
4
var values = [1,4,3];
values.reverse();
console.log(values); //3,4,1
console.log(values.sort()); //1,3,4

sort方法调用每个数组项toString()方法,然后进行比较

1
2
var values = [1,10,5];
console.log(values.sort()); //1,10,5

典型例子
所以需要自己传入一个compare函数进行比较。

1
2
3
4
5
6
7
8
9
10
11
function compare(value1,value2) {
if(value1 < value2)
return -1;
else if(value1 > value2)
return 1;
else
return 0;
}

var values = [1,10,5];
console.log(values.sort(compare)); //1,5,10

表示升序排列。
第二个表示降序排列

1
2
3
4
5
6
7
8
9
10
11
function compare(value1,value2) {
if(value1 < value2)
return 1;
else if(value1 > value2)
return -1;
else
return 0;
}

var values = [1,10,5];
console.log(values.sort(compare)); //10,5,1

Array的基本方法

concat方法,基于当前数组创建一个副本,随后再将新的参数添加到这个副本的末尾,最后返回新的构建的数组

1
2
3
4
var color = [1,2];
var color1 = color.concat("yellow");
console.log(color); //1,2
console.log(color1);//1,2,'yellow'

slice方法,接收一个或两个参数,返回项的起始和结束为止,只有一个参数时,方法返回从该参数指定位置开始到当前数组末尾的所有项。如果有两个参数,该方法返回起始和结束位置之间的项。slice不会影响原来的数组

1
2
3
4
var color = [1,2,3,4,5];
var color1 = color.slice(2);
console.log(color); //1,2,3,4,5
console.log(color1);//3,4,5

1
2
3
4
var color = [1,2,3,4,5];
var color1 = color.slice(1,3);
console.log(color); //1,2,3,4,5
console.log(color1);//2,3 不包含末尾

splice方法是最强大的,
删除方法 只需指定2个参数,要删除的第一个位置和要删除的项数。

1
2
3
4
var color = [1,2,3,4,5];
var color1 = color.splice(0,2);
console.log(color); //3,4,5
console.log(color1);//1,2

返回的不是新数组,而是在原数组的基础上修改。
插入方法 起始位置,要删除的项数,和插入的项。

1
2
3
4
var color = [1,2,3,4,5];
color1 = color.splice(0,2,7,5);
console.log(color);//1,2 表示删除的2项
console.log(color1); //7,5,3,4,5

位置方法 indexOf表示从开始找起,lastIndexof表示从末尾找起
indexof有2个参数可选,array.indexOf(item,start),item必须查找的元素,start规定开始查找的元素位置
可选的整数参数。规定在数组中开始检索的位置。它的合法取值是 0 到 stringObject.length - 1。如省略该参数,则将从字符串的首字符开始检索。

1
2
3
4
5
6
var color = [1,2,3,3,5];
console.log(color.indexOf(3));//2
console.log(color.indexOf(3,4));//-1
console.log(color.lastIndexOf(3));//3
console.log(color.lastIndexOf(3));//3
console.log(color.lastIndexOf(3,4));//表示从数组下标4,也就是5开始找,随后找到的是数组下标3.

表示从前向后,和从后向前,但是返回的index依然是从左往右的

迭代方法
every,对数组中的每一项都运行给定的函数,如果该函数每一项都返回true,则返回true.
filter,最数组的每一项运行给定函数,返回函数true的项形成的数组
forEach,每一项运行函数,但是没有返回值。
map,每一项运行函数,返回每次函数调用的结果形成的数组
some,对数组每一项运行函数,如果函数对任一一项返回true,则返回true.
以上的方法都不会修改数组找那个的包含的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var color = [1,2,3,3,5];
var every = color.every(function (item,index,array) {
return item > 2;
})
console.log(every);//false

var color = [1,2,3,3,5];
var some = color.some(function (item,index,array) {
return item > 2;
})
console.log(some);//true

var color = [1,2,3,3,5];
var filters = color.filter(function (item,index,array) {
return item > 2;
})
console.log(filters);//3,3,5

var color = [1,2,3,3,5];
var filters = color.map(function (item,index,array) {
return item * 2;
})
console.log(filters);//2,4,6,6,10

var color = [1,2,3,3,5];
var filters = color.forEach(function (item,index,array) {
console.log(item * 2);
})
console.log(filters);//undefined forEach没有返回值,因此没有filter