JavaScript 高级程序设计笔记整理

222 阅读32分钟

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

4.1 基本类型和引用类型的值

基本类型值指的是简单的数据段

5 种 基本数据类型:Undefined、Null、Boolean、Number 和 String。这 5 种基本数据类型是按值访问

的,因为可以操作保存在变量中的实际的值。

引用类型值指那些可能由多个值构成的对象。

引用类型的值是保存在内存中的对象。JavaScript 不允许直接访问内存中的位置, 也就是说不能直接操作对象的内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。 引用类型的值是按引用访问的。

动态的属性

对于引用类型的值,我们可以为其添加属性和方法,也可以改变和删除其属性和方法。

但是,我们不能给基本类型的值添加属性,尽管这样做不会导致任何错误

var name = "Nicholas"; 
name.age = 27; 
alert(name.age); //undefined

复制变量值

如果从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上。

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

传递参数

ECMAScript中所有函数的参数都是按值传递的。也就是说,把函数外部的值复制给函数内部的参 数,就和把值从一个变量复制到另一个变量一样。基本类型值的传递如同基本类型变量的复制一样,而 引用类型值的传递,则如同引用类型变量的复制一样。

function setName(obj) { 
 obj.name = "Nicholas"; 
} 
var person = new Object(); 
setName(person); 
alert(person.name); //"Nicholas"

即使在函数内部修改了参数的值,但原始的引用仍然保持未变。实际上,当在函数内部重写 obj 时,这 个变量引用的就是一个局部对象了。而这个局部对象会在函数执行完毕后立即被销毁。

function setName(obj) { 
 obj.name = "Nicholas"; 
 obj = new Object(); 
 obj.name = "Greg"; 
} 
var person = new Object(); 
setName(person); 
alert(person.name); //"Nicholas"

检测类型

typeof 操作符是确定一个变量是字符串、数值、布尔值,还是 undefined 的最佳工具。如果变量的值是一个对象或 null,则 typeof 操作符返回"object":

var s = "Nicholas"; 
var b = true; 
var i = 22; 
var u; 
var n = null; 
var o = new Object(); 
alert(typeof s); //string 
alert(typeof i); //number 
alert(typeof b); //boolean 
alert(typeof u); //undefined 
alert(typeof n); //object 
alert(typeof o); //object

利用instanceof操作符检测引用类型的值,如果变量是给定引用类型,instanceof 操作符就会返回 true

alert(person instanceof Object); // 变量 person 是 Object 吗?
alert(colors instanceof Array); // 变量 colors 是 Array 吗?
alert(pattern instanceof RegExp); // 变量 pattern 是 RegExp 吗?

如果使用instanceof操作符检测基本类型的 值,则该操作符始终会返回 false,因为基本类型不是对象。

4.2 小结

JavaScript 变量可以用来保存两种类型的值:基本类型值和引用类型值。基本类型的值源自以下 5 种基本数据类型:Undefined、Null、Boolean、Number 和 String。基本类型值和引用类型值具 有以下特点:

  • 基本类型值在内存中占据固定大小的空间,因此被保存在栈内存中;
  • 从一个变量向另一个变量复制基本类型的值,会创建这个值的一个副本;
  • 引用类型的值是对象,保存在堆内存中;
  • 包含引用类型值的变量实际上包含的并不是对象本身,而是一个指向该对象的指针;
  • 从一个变量向另一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终都指向同 一个对象;
  • 确定一个值是哪种基本类型可以使用 typeof 操作符,而确定一个值是哪种引用类型可以使用 instanceof 操作符。

所有变量(包括基本类型和引用类型)都存在于一个执行环境(也称为作用域)当中,这个执行环境决定了变量的生命周期,以及哪一部分代码可以访问其中的变量。以下是关于执行环境的几 点总结:

  • 执行环境有全局执行环境(也称为全局环境)和函数执行环境之分;
  • 每次进入一个新执行环境,都会创建一个用于搜索变量和函数的作用域链;
  • 函数的局部环境不仅有权访问函数作用域中的变量,而且有权访问其包含(父)环境,乃至全局环境;
  • 全局环境只能访问在全局环境中定义的变量和函数,而不能直接访问局部环境中的任何数据;
  • 变量的执行环境有助于确定应该何时释放内存。

JavaScript 是一门具有自动垃圾收集机制的编程语言,开发人员不必关心内存分配和回收问题。可 以对 JavaScript 的垃圾收集例程作如下总结。

  • 离开作用域的值将被自动标记为可以回收,因此将在垃圾收集期间被删除。
  • “标记清除”是目前主流的垃圾收集算法,这种算法的思想是给当前不使用的值加上标记,然 后再回收其内存。
  • 另一种垃圾收集算法是“引用计数”,这种算法的思想是跟踪记录所有值被引用的次数。JavaScript
  • 引擎目前都不再使用这种算法;但在 IE 中访问非原生 JavaScript 对象(如 DOM 元素)时,这种 算法仍然可能会导致问题。
  • 当代码中存在循环引用现象时,“引用计数”算法就会导致问题。
  • 解除变量的引用不仅有助于消除循环引用现象,而且对垃圾收集也有好处。为了确保有效地回 收内存,应该及时解除不再使用的全局对象、全局对象属性以及循环引用变量的引用。

第5章 引用类型

Object 类型

1.使用Object构造函数创建对象

var person = new Object();
person.name = "Nichodid";
person.age = 29;

2.使用对象字面量表示法创建对象

var person2 = {
            name : "Ndfjiog",
            age : 29
        };

3.使用对象字面量创建只包含默认属性的方法的对象

var person3 = {};    //与new Object()相同
person.name = "Nasdfk";
person.age = 29;

/*在使用对象字面量语法时,属性名也可以使用字符串,如下面这个例子所示,这个例子会创建一个对象,
包含三个属性:name、age 和 5。但这里的数值属性名会自动转换为字符串。*/

var person = { 
  "name" : "Nicholas", 
  "age" : 29, 
  5 : true 
};

对象属性的访问

//1.利用方括号表示法访问对象的属性
alert(person["name"]);  //"Ndfkdlf"

//2.利用点表示法访问对象的属性
alert(person.name)  //"Ndkfdog"

//使用方括号访问对象属性的优点
//1.可以通过变量来访问属性
var propertyName = "name";
alert(person[propertyName]);  //"Nfjoiaf"

​ 如果属性名中包含会导致语法错误的字符,或者属性名使用的是关键字或保留字,也可以使用方括号表示法。例如由于"first name"中包含一个空格,所以不能使用点表示法来访问非字母非数字的,这时候就可以使用方括号表示法来访问它们。

person["first name"] = "Ndkfhf";

Array类型

ECMAScript数组的每一项可以保存任何类型的数据。也就是说,可以用数组的第一个位置来保存字符串,用第二位置来保存数值,用第三个位置来保存对象,以此类推。而且,ECMAScript 数组的大小是可以动态调整的,即可以随着数据的添加自动增长以容纳新增数据

//数组的创建
//1.使用Array构造函数创建数组
var colors1 = new Array();

//2.创建长度为20的数组
var colors = new Array(20);

//3.创建包含3个字符创的数组
var colors3 = new Array("red","blue","green");

//4.给构造函数传递一个值创建数组
var colors4 = new Array(3);     //创建一个包含3个项的数组 
var names = new Array("Greg");  //创建一个包含1项,即字符串"Greg"的数组

// 使用Array构造函数时,也可以省略new操作符
// var colors4 = Array(3);     //创建一个包含3个项的数组 
// var names = Array("Greg");  //创建一个包含1项,即字符串"Greg"的数组


//使用字面量表示法创建数组
var colors = ["red","blue","green"];
var name = [];

//读取和设置数组的值,注意:如果设置某个值额索引超过了数组现有的项数,数组就会自动增加到该索引的值加1的长度
var colors = ["red","blue","green"];
alert(colors[0]);   //显示第一项
colors[2] = "black";    //修改第三项
colors[3] = "brown";    //新增第四项
console.log(colors);


//通过设置length属性来移除数组的项
var colors = ["red","blue","green"];
colors.length = 4;
alert(colors[3]);       //undefined

//通过设置length属性来给数组末尾添加新项
var colors = ["red","blue","green"];
colors[colors.length] = "black";    //在位置3添加一种颜色
colors[colors.length] = "brown";    //在位置4再添加一种颜色

colors[99] = "black"        //在位置99添加一种颜色
alert(colors.length)        //100

数组方法

调用数组的 toString()方法会返回由数组中每个值的字符串形式拼接而成的一个以逗号分隔的字符串

var colors = ["red", "blue", "green"]; 
alert(colors.toString()); // red,blue,green 

join()方法只接收一个参数,即用作分隔符的字符串,然后返回包含所有数组项的字符串

var colors = ["red", "green", "blue"]; 
alert(colors.join(",")); //red,green,blue 
alert(colors.join("||")); //red||green||blue
//如果不给 join()方法传入任何值,或者给它传入 undefined,则使用逗号作为分隔符

push()方法可以接收任意数量的参数,把它们逐个添加到数组末尾,并返回修改后数组的长度。 pop()方法则从数组末尾移除最后一项,减少数组的 length 值,然后返回移除的项。请看下面的例子:

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
//shift()方法能够移除数组中的第一项并返回该项,同时将数组长度减一
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方法能在数组前端添加任意个项并返回新数组的长度,同时使用unshift()和pop方法模拟队列
var colors = new Array();
var count = colors.unshift('red','green');  //推入两项
alert(count);//2

count = colors.unshift('black');//推入另一项
alert(count);//3

var item = colors.pop();
alert(item);    //'green';
alert(colors.length);//2

重排序方法

reverse()方法会反转数组项的顺序。请看下面这个例子var values = [1, 2, 3, 4, 5];

values.reverse(); 
alert(values); //5,4,3,2,1

利用sort

//利用sort方法从小到大排序
function f(a, b){ 
 return a - b; 
}
var values = [0, 1, 5, 10, 15]; 
values.sort(f);
alert(values); //0,1,5,10,15

//利用sort方法从大到小排序
function f(a, b){ 
 return b - a ; 
}
var values = [0, 1, 5, 10, 15]; 
values.sort(f);
alert(values); //15, 10, 5, 1, 0

操作方法

concat() 方法用于连接两个或多个数组。该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本 ,如果传递给 concat()方法的是一或多个数组,则该方法会将这些数组中的每一项都添加到结果数组中。如果传递的值不是数组,这些值就会被简单地添加到结果数组的末尾。

var colors = ["red", "green", "blue"]; 
var colors2 = colors.concat("yellow", ["black", "brown"]); 

alert(colors); //red,green,blue 
alert(colors2); //red,green,blue,yellow,black,brown

​ **slice()**方法可以接受一或两个参数,即要返回项的起始和结束位置。在只有一个参数的情况下,slice()方法返回从该参数指定位置开始到当前数组末尾的所有项。如果有两个参数,该方法返回起始和结束位置之间的项— —但不包括结束位置的项注意,slice()方法不会影响原始数组。请看下面的例子 :

var colors = ["red", "green", "blue", "yellow", "purple"]; 
var colors2 = colors.slice(1); 
var colors3 = colors.slice(1,4); 

alert(colors2); //green,blue,yellow,purple 
alert(colors3); //green,blue,yellow

splice() 方法用于插入、删除或替换数组的元素。

var aarrayObject = ["red", "green", "blue", "yellow", "purple"];
arrayObject.splice(index,howmany,ele1,.....,eleX)
参数 描述
index 必需。规定从何处添加/删除元素。
该参数是开始插入和(或)删除的数组元素的下标,必须是数字。
howmany 必需。规定应该删除多少元素。必须是数字,但可以是 "0"。
如果未规定此参数,则删除从 index 开始到原数组结尾的所有元素。
ele1 可选。规定要添加到数组的新元素。从 index 所指的下标处开始插入。
eleX 可选。可向数组添加若干元素。
var colors = ["red", "green", "blue"]; 
var removed = colors.splice(0,1); // 删除第一项
alert(colors); // green,blue 
alert(removed); // red,返回的数组中只包含一项

removed = colors.splice(1, 0, "yellow", "orange"); // 从位置 1 开始插入两项
alert(colors); // green,yellow,orange,blue 
alert(removed); // 返回的是一个空数组

removed = colors.splice(1, 1, "red", "purple"); // 插入两项,删除一项
alert(colors); // green,red,purple,orange,blue 
alert(removed); // yellow,返回的数组中只包含一项

位置方法

​ indexOf()和 lastIndexOf()。这两个方法都接收两个参数:要查找的项和(可选的)表示查找起点位置的索引。其中,indexOf()方法从数组的开头(位置 0)开始向后查找,lastIndexOf()方法则从数组的末尾开始向前查找。这两个方法都返回要查找的项在数组中的位置,或者在没找到的情况下返回-1。在比较第一个参数与数组中的每一项时,会使用全等操作符;也就是说,要求查找的项必须严格相等

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

alert(numbers.indexOf(4)); //3 
alert(numbers.lastIndexOf(4)); //5 

alert(numbers.indexOf(4, 4)); //5 
alert(numbers.lastIndexOf(4, 4)); //3 

var person = { name: "Nicholas" }; 
var people = [{ name: "Nicholas" }]; 

var morePeople = [person]; 
alert(people.indexOf(person)); //-1 
alert(morePeople.indexOf(person)); //0

迭代方法

​ ECMAScript 5 为数组定义了 5 个迭代方法。每个方法都接收两个参数:要在每一项上运行的函数和 (可选的)运行该函数的作用域对象——影响 this 的值。传入这些方法中的函数会接收三个参数:数 组项的值、该项在数组中的位置和数组对象本身。根据使用的方法不同,这个函数执行后的返回值可能 会也可能不会影响方法的返回值。以下是这 5 个迭代方法的作用 :

every():对数组中的每一项运行给定函数,如果该函数对每一项都返回 true,则返回 true filter():对数组中的		 每一项运行给定函数,返回该函数会返回 true 的项组成的数组。
forEach():对数组中的每一项运行给定函数。这个方法没有返回值
map():对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组
ome():对数组中的每一项运行给定函数如果该函数对任一项返回 true则返回 true
//every()方法
var num = [1,2,3,4,5,4,3,2,1];

var res = num.every(function(item,index,array){
    return (item > 2);
})

console.log(res);   //false


//some方法
var num = [1,2,3,4,5,4,3,2,1];

var res = num.some(function(item,index,array){
    return (item > 2);
})

console.log(res);   //true

//filter方法
var num = [1,2,3,4,5,4,3,2,1];

var res = num.filter(function(item,index,array){
    return (item > 2);
})

console.log(res);   //[3, 4, 5, 4, 3]

//map方法
var numbers = [1,2,3,4,5,4,3,2,1]; 
var mapResult = numbers.map(function(item, index, array){ 
return item * 2; 
}); 
alert(mapResult); //[2,4,6,8,10,8,6,4,2]


//forEach方法
var numbers = [1,2,3,4,5,4,3,2,1]; 
numbers.forEach(function(item, index, array){ 
//执行某些操作 
console.log(item)
});

归并方法

reduce()

总共执行4次回调,第一次执行回调函数,prev 是 1,cur 是 2

var values = [1,2,3,4,5]; 
var sum = values.reduce(function(prev, cur, index, array){ 
 return prev + cur; 
}); 
alert(sum); //15

reduceRight()的作用类似,只不过方向相反而已

var values = [1,2,3,4,5]; 
var sum = values.reduceRight(function(prev, cur, index, array){ 
 return prev + cur; 
}); 
alert(sum); //15

Date类型

getTime() 返回表示日期的毫秒数;与valueOf()方法返回的值相同
getFullYear() 取得4位数的年份(如2007而非仅07)
getMonth() 返回日期中的月份,其中0表示一月,11表示十二月
getDate() 返回日期月份中的天数(1到31)
getDay() 返回日期中星期的星期几(其中0表示星期日,6表示星期六)

RegExp类型

创建正则表达对象 :

//pattern 正则表达式
//flags   标志
var expression =  /pattern/ flags;

g:表示全局(global)模式,即模式将被应用于所有字符串,而非在发现第一个匹配项时立即停止;

i:表示不区分大小写(case-insensitive)模式,即在确定匹配项时忽略模式与字符串的大小写;

m:表示多行(multiline)模式,即在到达一行文本末尾时还会继续查找下一行中是否存在与模式匹配的项
/* 
* 匹配字符串中所有"at"的实例
*/ 
var pattern1 = /at/g; 

/* 
* 匹配第一个"bat"或"cat",不区分*/ 
var pattern2 = /[bc]at/i; 

/* 
* 匹配所有以"at"结尾的 3 个字符的*/ 
var pattern3 = /.at/gi;

与其他语言中的正则表达式类似,模式中使用的所有元字符都必须转义。正则表达式中的元字符包括:

( [ { \ ^ $ | ) ? * + .]}
/* 
* 匹配第一个" [bc]at",不区分大小写
*/ 
var pattern2 = /\[bc\]at/i;

/* 
* 匹配所有".at",不区分大小写
*/ 
var pattern4 = /\.at/gi;

使用RegExp构造函数创建正则对象

/* 
* 匹配第一个"bat"或"cat",不区分大小
*/ 
var pattern1 = /[bc]at/i; 

/* 
* 与 pattern1 相同,只不过是使用构造函数创建的
*/ 
var pattern2 = new RegExp("[bc]at", "i");

RegExp实例方法

exec()方法

exec() 方法在一个指定字符串中执行一个搜索匹配。返回一个结果数组或 null。

语法: regObj.exec(str) str: 要匹配正则表达式的字符串

返回值: 如果匹配成功,exec() 方法返回一个数组,并更新正则表达式对象的属性。返回的数组将完全匹配成功的文本作为第一项,将正则括号里匹配成功的作为数组填充到后面。如果匹配失败,exec() 方法返回 null。

var re = /quick\s(brown).+?(jumps)/ig;
var result = re.exec('The Quick Brown Fox Jumps Over The Lazy Dog');

执行结果如下:

对象 属性/索引 描述 例子
result [0] 匹配的全部字符串 Quick Brown Fox Jumps
result [1] , ... [n] 括号中的分组捕获 [1] = Brown [2] = Jumps
result index 匹配到的字符位于原始字符串的基于0的索引值 4
result input 原始字符串 The Quick Brown Fox Jumps Over The Lazy Dog
re lastIndex 下一个匹配开始的位置 25

​ 当正则表达式使用 "g" 标志时,可以多次执行 exec 方法来查找同一个字符串中的成功匹配。当你这样做时,查找将从正则表达式的 lastIndex 属性指定的位置开始。

var myRe = /ab*/g;
var str = 'abbcdefabh';
var myArray;
while ((myArray = myRe.exec(str)) !== null) {
  var msg = 'Found ' + myArray[0] + '. ';
  msg += 'Next match starts at ' + myRe.lastIndex;
  console.log(msg);
}

/*运行结果如下:
Found abb. Next match starts at 3
Found ab. Next match starts at 9

*/

test方法

​ 正则表达式的第二个方法是 test(),它接受一个字符串参数。在模式与该参数匹配的情况下返回 true;否则,返回 false。

var text = "000-00-0000"; 
var pattern = /\d{3}-\d{2}-\d{4}/; 
if (pattern.test(text)){ 
 alert("The pattern was matched."); 
}

Function类型

apply()call()。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内 this 对象的值。首先,apply()方法接收两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是 Array 的实例,也可以是arguments 对象。

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

function callSum1(num1, num2){ 
  return sum.apply(this, arguments); // 传入 arguments 对
} 

function callSum2(num1, num2){ 
  return sum.apply(this, [num1, num2]); // 传入数组
} 

alert(callSum1(10,10)); //20 
alert(callSum2(10,10)); //20

call()方法与 apply()方法的作用相同,它们的区别仅在于接收参数的方式不同。对于call()方法而言,第一个参数是 this 值没有变化,变化的是其余参数都直接传递给函数

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

function callSum(num1, num2){ 
  return sum.call(this, num1, } 
                  
alert(callSum(1010)) //20

​ 传递参数并非 apply()call()真正的用武之地;它们真正强大的地方是能够扩充函数赖以运行的作用域(改变函数体内this的指向)。下面来看一个例子

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

bind()。这个方法会创建一个函数的实例,其 this 值会被绑定到传给 bind()函数的值。例如:

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

function sayColor(){ 
  alert(this.color); 
} 

var objectSayColor = sayColor.bind(o); 
objectSayColor(); //blue

单体内置对象

ECMA-262 对内置对象的定义是:“由 ECMAScript 实现提供的、不依赖于宿主环境的对象,这些对 象在 ECMAScript 程序执行之前就已经存在了。

Math对象

min()和 max()方法用于确定一组数值中的最小值和最大值。这两个方法都可以接收任意多个数值参数

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

要找到数组中的最大或最小值,可以像下面这样使用 apply()方法

var values = [1, 2, 3, 4, 5, 6, 7, 8]; 
var max = Math.max.apply(Math, values);

Math.ceil()执行向上舍入,即它总是将数值向上舍入为最接近的整数; Math.floor()执行向下舍入,即它总是将数值向下舍入为最接近的整数; Math.round()执行标准舍入,即它总是将数值四舍五入为最接近的整数

alert(Math.ceil(25.9)); //26 
alert(Math.ceil(25.5)); //26 
alert(Math.ceil(25.1)); //26 

alert(Math.round(25.9)); //26 
alert(Math.round(25.5)); //26 
alert(Math.round(25.1)); //25 
  
alert(Math.floor(25.9)); //25 
alert(Math.floor(25.5)); //25 
alert(Math.floor(25.1)); //25

Math.random()方法返回大于等于 0 小于 1 的一个随机数

//如果你想选择一个 1到 10 之间的数值
var num = Math.floor(Math.random() * 10 + 1);

基本包装类型

三个基本包装类型 : Boolean, Number, String

​ 每当读取一个基本类型值的时候,后台就会创建一个对应的基本包装类型的对象,从而让我们能够调用一些方法来操作这些数据。来看下面的例子:

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

/*
在执行到第二行代码时,后台偷偷帮我们做了三件事 :
(1) 创建String类型的一个实例
(2) 在实例上调用指定的方法
(3) 销毁这个实例
可以将以上三个步骤想象成执行了下面的js代码
*/

var s1 = new Sring('some text');
var s2 = s1.substring(2);
s1 = null;

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

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

//结果是undefined的原因就是第二行创建的 String 对象在执行第三行代码时已经被销毁了

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

/*把字符串传给 Object 构造函数,就会创建 String 的实例;而传入数值参数会得到 Number 的实
例,传入布尔值参数就会得到 Boolean 的实例。*/

var obj = new Object('some text');
console.log(obj instanceof String);		//true

Boolean类型

​ 基本类型与引用类型的布尔值还有两个区别。首先,typeof 操作符对基本类型返回"boolean",而对引用类型返回"object"。其次,由于 Boolean 对象是 Boolean 类型的实例,所以使用 instanceof操作符测试 Boolean 对象会返回 true,而测试基本类型的布尔值则返回 false。例如:

var booleanObject = new Boolean(true);
var falseValue = false;

alert(typeof falseObject); //object 
alert(typeof falseValue); //boolean 
alert(falseObject instanceof Boolean); //true 
alert(falseValue instanceof Boolean); //false

Number类型

Number是与数字值对应的引用类型,要创建Number对象,可以在调用Number构造函数时向其传递相应的数值

var numberObject = new Number(10);

可以为 toString()方法传递一个表示基数的参数,告诉它返回几进制数值的字符串形式

var num = 10; 
alert(num.toString()); //"10" 
alert(num.toString(2)); //"1010" 
alert(num.toString(8)); //"12" 
alert(num.toString(10)); //"10" 
alert(num.toString(16)); //"a"

toFixed()方法会按照指定的小数位返回数值的字符串表示,例如:

var num = 10;
alert(num.toFixed(2));	//"10.00"

//如果数值本身包含的小数位比指定的还多,那么接近指定的最大小数位的值就会四舍五入,如下面的例子所示:
var num = 10.005;
alert(num.toFixed(2));		//"10.01"

toExponential()也接收一个参数,而且该参数同样也是指定输出结果中的小数位数。看下面的例子

var num = 10;
alert (num.toExponential(1));	////"1.0e+1"

toPrecision()方法可能会返回固定大小(fixed)格式,也可能返回指数(exponential)格式;具体规则是看哪种格式最合适。这个方法接收一个参数,即表示数值的所有数字的位数(不包括指数部分)。请看下面的例子

var num = 99; 
alert(num.toPrecision(1)); //"1e+2" 
alert(num.toPrecision(2)); //"99" 
alert(num.toPrecision(3)); //"99.0"

String类型

//String 类型的每个实例都有一个 length 属性,表示字符串中包含多个字符。来看下面的例子。
var stringValue = "hello world"; 
alert(stringValue.length); //"11"

//charAt()方法以单字符字符串的形式返回给定位置的那个字符,例如:
var str = 'hello world';
alert(str.charAt(1));		//"e"

//可以使用方括号加数字索引来访问字符串中的特定字符,如下面的例子所示。
var str = 'hello world';
alert(str[1]);		//"e"

//substr():获取指定范围字符串
//第一个参数:from:开始的位置  第二个参数:length:从开始位置之后截取几个字符
console.log(str.substr(10,3));//从第十个下标开始往后截取三个字符

//slice(from, to);
//from:开始位置的下标    to:结束位置的下标
var str = 'hello world';
alert(str.slice(3, 7));		//"lo w"


indexOf()方法从字符串的开头向后搜索子字符串,而 lastIndexOf()方法是从字符串的末尾向前搜索子字符串。

var str = "hello world";
alert(str.indexOf('0'));		//4
alert(str.lastIndexOf('0'))		//7

/*这两个方法都可以接收可选的第二个参数,表示从字符串中的哪个位置开始搜索。换句话说,indexOf()会从该参数指定的位置向后搜索,忽略该位置之前的所有字符;而 lastIndexOf()则会从指定的位置向前搜索,忽略该位置之后的所有字符*/
var stringValue = "hello world"; 
alert(stringValue.indexOf("o", 6)); //7 
alert(stringValue.lastIndexOf("o", 6)); //4
//使用indexOf方法的案例
var str =  "Lorem ipsum dolor sit amet, consectetur adipisicing elit";
var positions = [];
var pos = str.indexOf('e');

while(pos > -1) {
    positions.push(pos);
    pos = str.indexOf('e',pos + 1);
};

alert(positions);   

trim()方法。这个方法会创建一个字符串的副本,删除前置及后缀的所有空格,然后返回结果。例如:

var stringValue = " hello world "; 
var trimmedStringValue = stringValue.trim(); 
alert(stringValue); 			//" hello world " 
alert(trimmedStringValue); 		//"hello world"

大小写转换

var stringValue = "hello world"; 
alert(stringValue.toLocaleUpperCase()); //"HELLO WORLD" 
alert(stringValue.toUpperCase()); //"HELLO WORLD" 
alert(stringValue.toLocaleLowerCase()); //"hello world" 
alert(stringValue.toLowerCase()); //"hello world"

fromCharCode()。这个方法的任务是接收一或多个字符编码,然后将它们转换成一个字符串

alert(String.fromCharCode(104, 101, 108, 108, 111)); //"hello"

第6章 面向对象的程序设设计

理解对象

属性类型

对象的属性在创建时都带有一些特征值,JavaScript 通过这些特征值来定义它们的行为,

这些特性是为了实现 JavaScript 引擎用的,因此在 JavaScript 中不能直接访问它们。为了 表示特性是内部值,该规范把它们放在了两对儿方括号中,例如[[Enumerable]]

var person = new Object(); 
person.name = "Nicholas"; 
person.sayName = function(){ 
 alert(this.name); 
};

person对象包含一个name属性和sayName方法

对象的属性主要分为数据属性访问器属性

1、数据属性

数据属性有 4 个描述其行为的特性。

  • [[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为 true。
  • [[Enumerable]]:表示能否通过 for-in 循环返回属性。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为 true。
  • [[Writable]]:表示能否修改属性的值。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为 true。
  • [[Value]]:包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候, 把新值保存在这个位置。这个特性的默认值为 undefined。
var person = { 
 name: "Nicholas" 
};

这里创建了一个名为 name 的属性,为它指定的值是"Nicholas"。也就是说,[[Value]]特性将被设置为"Nicholas",而对这个值的任何修改都将反映在这个位置

要修改属性默认的特性,必须使用 Object.defineProperty()方法

var person = {}; 
Object.defineProperty(person, "name", { 
 writable: false, 
 value: "Nicholas" 
}); 
alert(person.name); //"Nicholas" 
person.name = "Greg"; 
alert(person.name); //"Nicholas"

把 configurable 设置为 false,表示不能从对象中删除属性。如果对这个属性调用 delete,则在非严格模式下什么也不会发生,而在严格模式下会导致错误。而且,一旦把属性定义为不可配置的,就不能再把它变回可配置了。此时,再调用 Object.defineProperty()方法修改除 writable 之外 的特性,都会导致错误:

var person = {}; 
Object.defineProperty(person, "name", { 
 configurable: false, 
 value: "Nicholas" 
}); 

//抛出错误
Object.defineProperty(person, "name", { 
 configurable: true, 
 value: "Nicholas" 
});

在调用 Object.defineProperty()方法时,如果不指定,configurable、enumerable 和 writable 特性的默认值都是 false。

2、访问器属性

访问器属性的特性:

  • [[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特 性,或者能否把属性修改为数据属性。对于直接在对象上定义的属性,这个特性的默认值为 true。
  • [[Enumerable]]:表示能否通过 for-in 循环返回属性。对于直接在对象上定义的属性,这 个特性的默认true。
  • [[Get]]:在读取属性时调用的函数。默认值为 undefined。
  • [[Set]]:在写入属性时调用的函数。默认值为 undefined。

在读取访问器属性时,会调用 getter 函数,这个函数负责返回有效的值;在写入访问器属性时,会调用 setter 函数并传入新值,这个函数负责决定如何处理数据

访问器属性不能直接定义,必须使用 Object.defineProperty()来定义

var book = { 
 _year: 2004, 
 edition: 1 
}; 


Object.defineProperty(book, "year", { 
     get: function(){ 
     	return this._year; 
     }, 
     set: function(newValue){ 
         if (newValue > 2004) { 
         this._year = newValue; 
         this.edition += newValue - 2004; 
         } 
     } 
}); 
book.year = 2005; 
alert(book.edition); //2

这是使用访问器属性的常见方式,即设置一个属性的值会导致其他属性发生变化。

不一定非要同时指定 getter 和 setter。只指定 getter 意味着属性是不能写,尝试写入属性会被忽略。 在严格模式下,尝试写入只指定了 getter 函数的属性会抛出错误。类似地,只指定 setter 函数的属性也 不能读,否则在非严格模式下会返回 undefined,而在严格模式下会抛出错误。

定义多个属性

利用Object.defineProperties()方法为对象定义多个属性

var book = {}; 

Object.defineProperties(book, { 
     _year: { 
     	value: 2004 
     }, 

     edition: { 
     	value: 1 
     }, 
     year: { 
         get: function(){ 
         return this._year; 
     }, 
     set: function(newValue){ 
         if (newValue > 2004) { 
             this._year = newValue; 
             this.edition += newValue - 2004; 
             } 
         } 
     } 
});

上定义了两个数据属性(_year 和 edition)和一个访问器属性(year)

读取属性的特性

Object.getOwnPropertyDescriptor()方法,返回一个对象,包含了属性的特性

第7章 函数表达式

第15章 使用canvas绘图

canvas的基本使用

​ 与其他元素一样,元素对应的 DOM 元素对象也有 width 和 height 属性,可以随意修 改。而且,也能通过 CSS 为该元素添加样式,如果不添加任何样式或者不绘制任何图形,在页面中是看 不到该元素的

<body>
    <!-- 创建canvas标签 -->
    <canvas id="drawing" width="200" height="200">这是一个寂寞的天</canvas>

    <script>
        var drawing = document.getElementById('drawing');

        //要在canvas上绘图,需要调用getContext()方法获取绘图上下文对象
        //确定浏览器支持<canvas>元素
        
        if (drawing.getContext) {
            var context = drawing.getContext('2d');
            //更多代码
        };
    </script>
</body>

2D上下文

​ 使用 2D 绘图上下文提供的方法,可以绘制简单的 2D 图形,比如矩形、弧线和路径。2D 上下文的 坐标开始于元素的左上角,原点坐标是(0,0)。所有坐标值都基于这个原点计算,x 值越大表示 越靠右,y 值越大表示越靠下。默认情况下,width 和 height 表示水平和垂直两个方向上可用的像素 数目。

填充和描边

2D上下文的两种基本绘图操作: 填充---fillStyle属性 : 用指定的样式(颜色,渐变或者图像)填充图形 描边---strokeStyle属性 : 就是只在图形的边缘划线

fillStyle和strokeStyle属性的默认值都是"#000000" 可以使用 CSS 中指定颜色值的任何格式,包括颜色名、十六进制码、rgb、rgba、hsl 或 hsla

var drawing = document.getElementById('drawing');

//要在canvas上绘图,需要调用getContext()方法获取绘图上下文对象
//确定浏览器支持<canvas>元素
if (drawing.getContext) {
    
    var context = drawing.getContext('2d');
    context.strokeStyle = 'red';
    context.fillStyle = '#0000ff';
}

绘制矩形 fillRect()方法

fillRect()方法接收3个参数, 在画布上绘制的矩形会填充指定的颜色,填充的颜色通过fillStyle属性指定

参数1 : 矩形的x坐标 参数2 : 矩形的y坐标 参数3 : 矩形的宽度 参数4 : 矩形的高度

var drawing = document.getElementById('drawing');

//要在canvas上绘图,需要调用getContext()方法获取绘图上下文对象
//确定浏览器支持<canvas>元素
if (drawing.getContext) {
    var context = drawing.getContext('2d');

    //绘制红色矩形
    context.fillStyle = '#ff0000';
    context.fillRect(10,10,50,50);

    //绘制半透明的蓝色矩形
    context.fillStyle = 'rgba(0,0,255,0.5)';
    context.fillRect(30,30,50,50);
}

绘制矩形 strokeRect()方法

strokeRect()方法在画布上绘制的矩形会使用指定的颜色描边。描边颜色通过 strokeStyle 属性指定。

strokeRect()方法接收4个参数

参数1 : 矩形的x坐标 参数2 : 矩形的y坐标 参数3 : 矩形的宽度 参数4 : 矩形的高度

var drawing = document.getElementById('drawing');

//要在canvas上绘图,需要调用getContext()方法获取绘图上下文对象
//确定浏览器支持<canvas>元素
if (drawing.getContext) {
var context = drawing.getContext('2d');

//绘制红色矩形
context.strokeStyle = '#ff0000';
context.strokeRect(10,10,50,50);

//绘制半透明的蓝色矩形
context.strokeStyle = 'rgba(0,0,255,0.5)';
context.strokeRect(30,30,50,50);
}

下面是在画布上的效果 :

矩形绘制 clearRect()方法

clearRect()属性用于清除画布上的矩形区域的一部分内容

clearRect()方法也是接收4个参数:

参数1 : 矩形的x坐标 参数2 : 矩形的y坐标 参数3 : 矩形的宽度 参数4 : 矩形的高度

var drawing = document.getElementById("drawing"); 
//确定浏览器支持<canvas>元素
if (drawing.getContext){ 
var context = drawing.getContext("2d"); 

//绘制红色矩形
context.fillStyle = "#ff0000"; 
context.fillRect(10, 10, 50, 50); 
    
//绘制半透明的蓝色矩形
context.fillStyle = "rgba(0,0,255,0.5)"; 
context.fillRect(30, 30, 50, 50); 
    
//在两个矩形重叠的地方清除一个小矩形
context.clearRect(40, 40, 10, 10); 
}

在画布上的效果如下 :

绘制路径

要绘制路径,首先必须调用 beginPath()方法,表示要开始绘制新路径。然后,再通过调用下列方法来实际地绘制路径。

beginPath() : 表示要开始绘制新路径

moveTo(x, y):将绘图游标移动到(x,y),不画线

lineTo(x, y):从上一点开始绘制一条直线,到(x,y)为止

arc(x, y, radius, startAngle, endAngle, counterclockwise):以(x,y)为圆心绘
制一条弧线,弧线半径为 radius,起始和结束角度(用弧度表示)分别为 startAngle 和
endAngle。最后一个参数表示 startAngle 和 endAngle 是否按逆时针方向计算,值为 false
表示按顺时针方向计算

stroke() : 对路径描边,简单来是就是把绘制的线显示出来
//下面看一个例子,即绘制一个不带数字的时钟

var drawing = document.getElementById('drawing');

//确定浏览器支持<canvas>元素
if (drawing.getContext){ 
  var context = drawing.getContext('2d');
  
  //要绘制路径,首先必须调用 beginPath()方法,表示要开始
  //绘制新路径。然后,再通过调用context对象的其它方法来实际地绘制路径
  context.beginPath(); 
    
//绘制外圆
 context.arc(100, 100, 99, 0, 2 * Math.PI, false); 
    
 //绘制内圆
 context.moveTo(194, 100); 
 context.arc(100, 100, 94, 0, 2 * Math.PI, false); 
    
 //绘制分针
 context.moveTo(100, 100); 
 context.lineTo(100, 15); 
    
 //绘制时针
 context.moveTo(100, 100); 
 context.lineTo(35, 100); 
    
 //描边路径
 context.stroke(); 
}

画布上的效果如下 :