第四章.变量、作用域和内存问题
4.1基本类型和引用类型的值
- 基本类型值指的是简单的数据段,而引用类型值指那些可能有多个值构成的对象,注意是对象;
- 以下两段代码说明了只能给引用类型值动态地添加属性,以便将来使用。
var person = new Object();
person.name = "Chaney";
alert(person.name); //Chaney
//错误的对比演示
var name = "Chaney";
name.age = 27 ;
alert(name.age); //undefined
- 1.基本类型值的赋值,从一个变量向另一个变量复制基本类型的值,会在变量对象上创建新值,再把新值复制到新变量分配的位置上,两变量是完全独立的,相互之间不受影响;
2.引用类型值得赋值,复制操作结束后,两个变量是引用了同一个对象,因此改变其中一个变量,就会影响到另一个变量; - ECMAScript中所有函数的参数都是按值传递的,
//换句话说,即使person这个变量是按值传递的,obj也会按引用来访问同一个对象,因此函数内部为obj添加name属性后,函数外部的person也会有反映。
function setName(obj){
obj.name="Chaney";
}
var person= new Object();
setName(person);
alert(person.name); //"Chaney"
//即使函数内部修改了参数的值,但原始的引用仍然保持不变。实际上当在函数内部重写obj时,这个变量引用的就是一个局部对象了,这个局部对象会在函数执行完毕后会立即被销毁;
function setName(obj){
obj.name="Chaney";
obj=new object();
obj.name ="Bobo";
}
var person= new Object();
setName(person);
alert(person.name); //"Chaney"
- 如果变量的值是一个对象或者null,则typeof操作符会返回”object”;
- 想知道某对象是什么类型的对象,就需要用到instanceof
alert(person instanceof Object); //变量person是Object吗?
alert(colors instanceof Array); //变量colors 是Array吗?
alert(pattern instanceof RegExp); //变量pattern 是RegExp吗?
4.2执行环境及作用域
- 没有块级作用域,对于JavaScript来说,有for语句创建的变量i即使在for循环执行结束后,也依旧会存在于循环外部的执行环境中。(if语句同理)
- 变量声明,如果在函数内部的变量声明中没有用var,也会变成全局变量,添加到全局环境(不推荐使用)。但,在严格模式下,初始化未经声明的变量会导致错误
- 查询标识符
//首先,搜索getColor()的变量对象中是否包含名为color的标识符,没有找到的情况下,搜索到下一个变量对象(即全局环境的变量对象),最后找到了名为color的标识符
//变量声明提升:如果在函数内部访问了一个在他后面定义的变量,会把这个变量的声明提升,提升到函数最前面不会提升赋值
var color="blue";
function getColor(){
return color;
}
alert(getColor()); //"blue"
第五章.引用类型
5.2Array类型
- 如果设置某个值的索引超过了数组现有项数,数组就会自动增加到该索引值加1的长度
- 数组的length属性很有特点,它不是只读的,通过设置这个属性,可以从数组的末尾移除项或想数组中添加新项
//移除最后一项
var colors=["red","blue","green"]; //length为3的数组
colors.length=2;
alert(colors[2]); //undefined
//在最后增加一项
var colors=["red","blue","green"]; //length为3的数组
colors.length=4;
alert(colors[3]); //undefined
- 检测数组,Array.isArray( )方法,就是确定某个值到底是不是数组,而不管它是在哪个全局执行环境中创建的
if(Array.isArray(value) ){
//对于数组执行某些操作
}
- 所有对象都具有toLocaleString( )、toString( )和valueOf( ),默认情况下都会以逗号分隔的字符串形式返回数组项,如果使用join( )则可以使用不同的分隔符来创建这个字符串
- 栈方法:
1.push()方法可以接收任意数量的参数,把它们逐个添加到数组末尾,并返回修改后的数组的长度
2.pop()方法则从数组末尾移除最后一项,减少数组的length值,然后返回删除项
var colors = ["red","blue"];
colors.push("brown"); //push在最后推入一项
colors[3]="black"; //length不是只读的,可在数组最后添加一项
alert(colors.length); // 4
var item = colors.pop( ); //pop删除最后一项
alert(item); //"black"
- 队列方法(先进先出):
1.结合使用shift( )和push( )方法就可以像使用队列一样使用数组了
2.同时使用unshift( )和pop( )方法可以从相反的方向模仿队列 - 重排序方法:reverse( )和sort( ),但都有缺陷,系统会把数组中的所有的数字转化为ASCII字符,然后按照字符来排序,因此sort( )方法可以接收一个比较函数作为参数,以便我们指定哪个值位于哪一个值的前面
function compare(value1,value2){
return value1>value2?1:-1;
}
var values=[0,5,15,10,1];
values.sort(conpare);
alert(values); //0,1,5,10,15
- 操作方法
- 1.concat()方法可以基于当前数组中的所有项创建一个新数组,最后的原数组保持不变
- 2.slice()方法能够基于当前数组中的一或多个项创建一个新数组,接受一到两个参数,即要返回项的起始和结束位置,返回项里不包括结束位置的想。注意,该方法不会影响原数组,如果参数中有负数,则用数组长度加上该参数确定相应的位置。
var colors=["red","green","blue","yellow","purple"];
var colors2=colors.slice(1);
var colors3=colors.slice(1,4);
console.log(colors2); //red,green,blue,yellow,purple
console.log(colors3); //red,green,blue,yellow
- 3.splice()方法的主要用途是向数组的中部插入项,该方法会始终返回一个数组,该数组中包含从原始数组中删除的项(如果没有删除任何项,则返回一个空数组),有三种方式
a.删除,可以删除任意数量的项,只需指定2个参数:要删除的第一项的位置和要删除的项数。例如,splice(0,2)会删除数组中的前两项
b.插入,可以向指定位置插入任意数量的项,只需提供3个参数:起始位置、0(要删除的项数)、和要插入的项。如果要插入多个项,可以再传入第四、第五以至任意多个项。例如,splice(2,0,”red”,”green”)会删除当前数组位置为2的项,然后再从位置2开始插入字符串”red”和”green”
c.替换,可以向指定位置插入任意数量的项,且同时删除任意数量的项,只需指定3个参数:起始位置、要删除的项数和要插入的任意数量的项。插入的项数不必与删除的项数相等。
var colors=["red","green","blue"];
var removed=colors.splice(0,1); //删除第一项
console.log(colors); //green,blue
console.log(removed); //red,返回的数组中只包含一项
removed=colors.splice(1,0,"yellow","orange"); //从位置1开始插入两项
console.log(colors); //green,yellow,orange,blue
console.log(removed); //返回的是一个空数组
removed=colors.splice(1,1,"red","purple"); //删除一项,插入两项
console.log(colors); //green,red,purple,orange,blue
console.log(removed); //yellow,返回的数组中只包含一项
- 位置方法:indexOf()和lastIndexOf()。这两个方法接收两个参数:要查找的项和(可选的)表示查找起点位置的索引,都返回要查找的项在数组中的位置,或者没有找到就返回-1,要求查找的项必须严格相等(就像使用===一样)。indexOf()方法从数组的开头(位置0)开始向后查找,而lastIndexOf()方法则从数组的末尾向前查找。
- 迭代方法,以下方法都不会修改数组中的包含的值
1.every():对数组中的每一项运行给定函数,如果该函数对每一项都返回true,则返回true。
2.filter():对数组中的每一项运行给定的函数,返回该函数会返回true的项组成的数组。
3.forEach():对数组中的每一项运行给定函数。这个方法没有返回值。
4.map():对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。
5.some():对数组中的每一项运行给定函数,如果该函数对任一项返回true,则返回true。
//every
var numbers = [1,2,3,4,5,4,3,2,1];
var everyResult = numbers.every(function(item,index,array){
return(item>2);
});
console.log(everyResult ); //false
//some
var someResult = numbers.some(function(item,index,array){
return(item>2);
});
console.log(someResult); //true
//filter
var numbers = [1,2,3,4,5,4,3,2,1];
var filterResult = numbers.filter(function(item,index,array){
return(item>2);
});
console.log(filterResult); //[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);
});
console.log(mapResult); //[2,4,6,8,10,8,6,4,2]
//forEach
//forEach()方法,并没有返回值,本质上与使用for循环迭代数组一样
var numbers = [1,2,3,4,5,4,3,2,1];
var forEachResult = numbers.forEach(function(item,index,array){
});
- 归并方法:reduce()和reduceRight(),两个方法都会迭代数组的所有项,然后构建一个最终返回的值。reduce()方法从数组的第一项开始,逐个遍历到最后。reduceRight则相反。这两个方法接收两个参数:一个在每一项上调用的函数和(可选的)作为归并基础的初始值。而传给两个方法的函数参数又接收4个参数:前一个值,当前值,项的索引和数组的对象
//使用reduce()方法可以执行求数组中所有值之和的操作
//第一次执行回调函数,prev是1,cur是2。第二次,prev是3(1加2的结果),cur是3(数组的第三项)。这个过程会持续到数组中的每一项都访问一遍,最后返回结果
var values=[1,2,3,4,5];
var sum=values.reduce(function(prev,cur,index,array){}
return prev+cur;
);
console.log(sum); //15
5.5Function类型
- 函数是对象,函数名是指针。注意,使用不带圆括号的函数名是访问函数指针,并非调用函数
- 一个函数可能会有多个名字
function sum(num1,num2){
return num1+num2;
}
console.log('第一次sum'+sum(10,10)); //20
var anotherSum=sum;
console.log("第一次another"+anotherSum(10,10)); //20
sum=null;
console.log("第二次another"+anotherSum(10,10)); //20
console.log('第二次sum'+sum(10,10)); //Uncaught TypeError: sum is not a function
- 没有重载
//通过观察重写后的代码,可以知道,在创建第二个函数时,实际上覆盖了引用第一个函数的变量addSomeNumber。
function addSomeNumber(num){
return num+100;
}
function addSomeNumber(num){
return num+200;
}
var result = addSomeNumber(100); //300
- 函数声明与函数表达式的区别,解析器会率先读取函数声明,并使其在实行任何代码之前可用(可以访问);至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。
//以下代码完全可以正常运行,代码开始执行前,解析器就已经通过函数声明提升的过程,读取并将函数声明添加到执行环境中(运用了函数声明)
alert(sum(10,10));
function sum(num1,num2){
return num1+num2;
}
//但是以下代码就会在运行期间报错了,(运用了函数表达式)
alert(sum(10,10));
var sum = function(num1,num2){
return num1+num2;
}
- 作为值的函数,以下是根据一个对象数组里某一个对象属性来进行排序的需求例子
function createComparisonFunction(propertyName) {
return function(object1, object2) {
var value1 = object1[propertyName];
var value2 = object2[propertyName];
if (value1 < value2) {
return -1;
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
};
}
var data = [{name:"chaney",age:23},{name:"Bobo",age:24}];
data.sort(createComparisonFunction("name"));
alert(data[0].name); //"Bobo"
data.sort(createComparisonFunction("age"));
alert(data[0].name); //"Chaney"
- 函数内部属性,在函数内部有两个特殊的对象:
arguments
,this
。arguments
是一个类数组对象,包含着传入函数中的所有参数。arguments
对象还有一个名叫callee
的属性,该属性是一个指针,指向拥有这个arguments
对象的函数。
//经典的阶乘函数
//使用了arguments.callee,消除了紧密耦合的现象
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * arguments.callee(num - 1);
}
}
- 函数内部的另一个特殊对象是this。this引用的是函数执行的环境对象(当在网页的全局作用域中调用函数式,this对象引用的就是window)
- 函数属性和方法—(属性)。每个函数都包含两个属性:
length
和prototype
。
1.length
属性表示函数希望接收的命名参数的个数;
2.prototype
。对于ECMAScript中的引用类型而言,prototype
是保存它们所有实例方法的真正所在。prototype
属性是不可枚举的,因此使用for-in无法发现。 - 函数属性和方法—(方法)。每个函数都包含两个非继承而来的方法:
apply( )
和call( )
。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内this对象的值。
1.apply()
方法接受两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中第二个参数可以是Array
的实例,也可以是arguments
对象
function sum(num1,num2){
return num1+num2;
}
function applySum1(num1,num2){
return sum.apply(this,arguments); //传入arguments对象
}
function applySum2(num1,num2){
return sum.apply(this,[num1,num2]); //传入数组
}
alert(applySum1(10,10)); //20
alert(applySum2(10,10)); //20
2.call()
方法与apply()
方法的作用相同,区别仅在与接收参数的方式不同。第一个参数是this值没有变化,变化的是其余参数都是直接传递给函数,换句话说就是传递给函数的参数必须逐个列举出来
//函数结构与apply的例子基本相同,以下是不同之处
return sum.call(this,num1,num2);
事实上,传递
apply()
和call()
它们真正强大的地方是能够扩充函数赖以运行的作用域
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;
5.7.2Math对象
1.1 min()和max()
这两个方法经常用于避免多余的循环和在if语句中确定一组数的最大值
- 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,9,99 ];
var max=Math.max.apply(Math,values);
1.2 舍入方法
- Math.ceil( ) 执行向上舍入;
- Math.floor( ) 执行向下舍入;
- Math.round( ) 执行标准舍入,即四舍五入;
1.3 random( )方法
- Math.random( )方法返回大于等于0小于1的一个随机数(0<=x<1);
//利用这个方法,可以从某个整数范围内随机选择一个值
//值 = Math.floor(Math.random( ) * 可能值得总数 + 第一个可能的值)
//例如:想选择一个1到10之间的数值
var num = Math.flooor(Math.random() * 10 + 1);
1.4 其他方法
- Math.abs(num); 返回num的绝对值
- Math.exp(num); 返回Math.E的num次幂
- Math.log(num); 返回num的自然对数
- Math.pow(num,power); 返回num的power次幂
- Math.sqrt(num); 返回num的平方根