JS详细教程(下)
五、数组
数组是数据的有序列表,每个元素在数组中都有数字位置编号,也就是索引。JS中的数组是弱类型,每一项都可以保存任何类型的数据。
创建数组
①使用Array构造函数
var arr=new Array();
var array=new Array("red","green","blue"); //传入数组应该包含的项
var a=new Array(100); //直接指定数组长度
注意:数组的长度最大为4294967295
②使用数组字面量表示法
var Person=['winty','lzh','LuckyWinty'] //创建一个3个字符串的数组
var test=[1,2,] //不要这样,这样会创建一个包含2或3项的数组
数组元素读写
数组元素操作
①栈方法
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
②队列方法
由于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
ECMAScript还为数组提供了一个unshift()方法。顾名思义,unshift()与shift()的用途相反:它能在数组前端添加任意个项并返回新数组的长度。因此,同时使用 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()和 sort()。
var values = [1, 2, 3, 4, 5];
values.reverse();
alert(values); //5,4,3,2,1
但是sort()方法是比较字符串以确定如何排序的,即使数组中的每一项都是数值,如:
var values = [0, 1, 5, 10, 15];
values.sort();
alert(values); //0,1,10,15,5
不用说,这种排序方式在很多情况下都不是最佳方案。因此sort()方法可以接收一个比较函数作为参数,以便我们指定哪个值位于哪个值的前面。 比较函数接收两个参数,如果第一个参数应该位于第二个之前则返回一个负数,如果两个参数相等则返回0,如果第一个参数应该位于第二个之后则返回一个正数。以下就是一个简单的比较函数:
function compare(value1, value2) {
if (value1 < value2) {
return -1;
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
}
这个比较函数可以适用于大多数数据类型,只要将其作为参数传递给sort()方法即可,如下面这个例子所示。
var values = [0, 1, 5, 10, 15];
values.sort(compare);
alert(values); //0,1,5,10,15
操作方法
合并数组:concat()
slice()方法,有三种用法。
删除:可以删除任意数量的项,只需指定 2个参数:要删除的第一项的位置和要删除的项数。
插入:可以向指定位置插入任意数量的项,只需提供3个参数:起始位置、0(要删除的项数)和要插入的项。如果要插入多个项,可以再传入第四、第五,以至任意多个项。
替换:可以向指定位置插入任意数量的项,且同时删除任意数量的项,只需指定 3个参数:起始位置、要删除的项数和要插入的任意数量的项。插入的项数不必与删除的项数相等。
例如:
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,返回的数组中只包含一项
join()方法将数组转为字符串:
var arr=[1,2,3]
arr.join("-"); // "1_2_3"
位置方法
迭代方法
every():对数组中的每一项运行给定函数,如果该函数对每一项都返回true,则返回true。
filter():对数组中的每一项运行给定函数,返回该函数会返回true的项组成的数组。
forEach():对数组中的每一项运行给定函数。这个方法没有返回值。
map():对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。
some():对数组中的每一项运行给定函数,如果该函数对任一项返回true,则返回true。
以上方法都不会修改数组中的包含的值。
例子:
var arr=[1,2,3,4,5];
arr.forEach(function(x,index,a){
console.log(x+"---"+index+"----"+(a===arr));
});
//1---0----true
//2---1----true
//3---2----true
//4---3----true
//5---4----true
arr.map(function(x){
return x+10;
}); //[11,12,13,14,15]
arr; //[1,2,3,4,5] 原数组不会被修改
归并方法
reduce()和 reduceRight()。这两个方法都会迭代数组的所有项,然后构建一个最终返回的值。其中,reduce()方法从数组的第一项开始,逐个遍历到最后。而reduceRight()则从数组的最后一项开始,向前遍历到第一项。
给reduce()和 reduceRight()的函数接收 4个参数:前一个值、当前值、项的索引和数组对象。
这个函数返回的任何值都会作为第一个参数自动传给下一项。第一次迭代发生在数组的第二项上,因此第一个参数是数组的第一项,第二个参数就是数组的第二项。 使用reduce()方法可以执行求数组中所有值之和的操作,比如:
var values = [1,2,3,4,5];
var sum = values.reduce(function(prev, cur, index, array){
return prev + cur;
});
alert(sum); //15
var sum1=arr.reduce(function(x,y){
return x+y
},100);
alert(sum1)// 115,有参数传入,所以会把参数也加上。
数组和一般对象的比较:
相同:两者都可以继承,数组是对象,对象不一定是数组,都可以当作对象添加删除属性。
不同:数组自动更新length,按索引访问数组常常比访问一般对象属性明显迅速。数组对象继承Array.prototype上的大量数组操作方法。
六、函数
函数是一块JavaScript代码,被定义一次,但可执行和调用多次。JS中的函数也是对象。所以JS函数可以像其它对象那样操作和传递。所以我们也常叫JS中的函数为函数对象。
调用方式
直接调用:foo();
对象方法:o.method();
构造器:new Foo();
call/apply/bian:func.call(o);
函数声明、表达式、构造器比较:
this
①全局作用域上的this指向的就是this
②一般函数的this
③对象原型链上的this
④构造器上的this
⑤call/apply方法与this
⑥bind方法与this(IE9+才会支持该方法)
函数属性&arguments
函数闭包
闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。
闭包与变量
作用域链的这种配置机制引出了一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最后一个值。别忘了闭包所保存的是整个变量对象,而不是某个特殊的变量。
下面这个例子可以清晰地说明这个问题。
function createFunctions(){
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(){
return i;
};
}
return result;
}
这个函数会返回一个函数数组。表面上看,似乎每个函数都应该返自己的索引值,即位置0的函数返回0,位置1的函数返回1,以此类推。但实际上,每个函数都返回10。因为每个函数的作用域链中都保存着 createFunctions()函数的活动对象,所以它们引用的都是同一个变量 i。当createFunctions()函数返回后,变量i 的值是10,此时每个函数都引用着保存变量i的同一个变量对象,所以在每个函数内部i的值都是10。
但是,我们可以通过创建另一个匿名函数强制让闭包的行为符合预期,如下所示。
function createFunctions(){
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(num){
return function(){
return num;
};
}(i);
}
return result;
}
在这个版本中,我们没有直接把闭包赋值给数组,而是定义了一个匿名函数,并将立即执行该匿名函数的结果赋给数组。这里的匿名函数有一个参数num,也就是最终的函数要返回的值。在调用每个匿名函数时,我们传入了变量i。由于函数参数是按值传递的,所以就会将变量i的当前值复制给参数num。而在这个匿名函数内部,又创建并返回了一个访问num的闭包。这样一来,result数组中的每个函数都有自己num 变量的一个副本,因此就可以返回各自不同的数值了。
可以利用闭包的这个特性,封装函数,模仿块级作用域。
function(){
//这里是块级作用域
})();
无论在什么地方,只要临时需要一些变量,就可以使用私有作用域,例如:
function outputNumbers(count){
(function () {
for (var i=0; i < count; i++){
alert(i);
}
})();
alert(i); //导致一个错误!
}
在这个重写后的outputNumbers()函数中,我们在for循环外部插入了一个私有作用域。在匿名函数中定义的任何变量,都会在执行结束时被销毁。因此,变量i只能在循环中使用,使用后即被销毁。而在私有作用域中能够访问变量count,是因为这个匿名函数是一个闭包,它能够访问包含作用域中的所有变量。 这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。一般来说,我们都应该尽量少向全局作用域中添加变量和函数。在一个由很多开发人员共同参与的大型应用程序中,过多的全局变量和函数很容易导致命名冲突。而通过创建私有作用域,每个开发人员既可以使用自己的变量,又不必担心搞乱全局作用域。例如:
(function(){
var now = new Date();
if (now.getMonth() == 0 && now.getDate() == 1){
alert("Happy new year!");
}
})();
把上面这段代码放在全局作用域中,可以用来确定哪一天是1月1日;如果到了这一天,就会向用户显示一条祝贺新年的消息。其中的变量now现在是匿名函数中的局部变量,而我们不必在全局作用域中创建它。
七、oop
JS解释器按照如下顺序找到我们定义的函数和变量:
1.函数参数(若未传入,初始化该参数为undefined)
2.函数声明(若发生命名冲突,会覆盖)——函数声明提升的原因
3.变量声明(初始化变量值undefined,若发生命名冲突,会忽略)
在全局作用域下,函数声明和变量声明会被前置到全局执行上下文(执行环境)中。
在浏览器环境下,当this表示全局对象时,this就指window对象
匿名函数,加上括号就变成了函数表达式,再加个括号,就变成了立即执行函数,再在前面加个!号可以将函数声明变为函数表达式,防止函数被前置执行,留下最后那个括号...
同一个函数,被调用多次的话,每次调用函数时都会有独立的执行上下文,每个执行上下文环境都会记录各自的变量参数等信息。
继承
ECMAScript只支持实现继承,而且其实现继承主要是依靠原型链来实现的。
实现原型链有一种基本模式,其代码大致如下。
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
//继承了SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); //true
借用构造函数
在解决原型中包含引用类型值所带来问题的过程中,开发人员开始使用一种叫做借用构造函数(constructor stealing)的技术(有时候也叫做伪造对象或经典继承)。这种技术的基本思想相当简单,即在子类型构造函数的内部调用超类型构造函数。如下所示:
function SuperType(){
this.colors = ["red", "blue", "green"];
}
function SubType(){
//继承了 SuperType
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"
组合继承
既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。下面来看一个例子。
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
//继承属性
SuperType.call(this, name);
this.age = age;
}
//继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
原型式继承
var person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"] };
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
克罗克福德主张的这种原型式继承,要求你必须有一个对象可以作为另一个对象的基础。如果有这么一个对象的话,可以把它传递给object()函数,然后再根据具体需求对得到的对象加以修改即可。在这个例子中,可以作为另一个对象基础的是person对象,于是我们把它传入到object()函数中,然后该函数就会返回一个新对象。这个新对象将person作为原型,所以它的原型中就包含一个基本类型值属性和一个引用类型值属性。这意味着person.friends不仅属于person所有,而且也会被anotherPerson以及yetAnotherPerson 共享。实际上,这就相当于又创建了person对象的两个副本。
ECMAScript 5 通过新增Object.create()方法规范化了原型式继承。这个方法接收两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。在传入一个参数的情况下,Object.create()与object()方法的行为相同。
var person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"] };
var anotherPerson = Object.create(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
Object.create()方法的第二个参数与Object.defineProperties()方法的第二个参数格式相同:每个属性都是通过自己的描述符定义的。以这种方式指定的任何属性都会覆盖原型对象上的同名属性。
寄生式继承
即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。以下代码示范了寄生式继承模式。
function createAnother(original){
var clone = object(original); //通过调用函数创建一个新对象
clone.sayHi = function(){ //以某种方式来增强这个对象
alert("hi");
};
return clone;
} / /返回这个对象
在这个例子中,createAnother()函数接收了一个参数,也就是将要作为新对象基础的对象。然后,把这个对象(original)传递给object()函数,将返回的结果赋值给clone。再为clone对象添加一个新方法sayHi(),最后返回clone对象。
可以像下面这样来使用createAnother()函数:
var person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"] };
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"
这个例子中的代码基于person返回了一个新对象——anotherPerson。新对象不仅具有person的所有属性和方法,而且还有自己的sayHi()方法。
寄生组合式继承
寄生组合式继承的基本模式如下所示。
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); //创建对象
prototype.constructor = subType; //增强对象
subType.prototype = prototype; //指定对象
}
这个示例中的 inheritPrototype()函数实现了寄生组合式继承的最简单形式。这个函数接收两个参数:子类型构造函数和超类型构造函数。在函数内部,第一步是创建超类型原型的一个副本。第二步是为创建的副本添加constructor属性,从而弥补因重写原型而失去的默认的constructor属性。最后一步,将新创建的对象(即副本)赋值给子类型的原型。这样,我们就可以用调用 inherit- Prototype()函数的语句,去替换前面例子中为子类型原型赋值的语句了。
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function(){
alert(this.age);
};
欢迎关注我的个人微信订阅号:前端生活
转载请注明出处!