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); 

}; 

欢迎关注我的个人微信订阅号:前端生活
转载请注明出处!

posted @ 2016-03-11 23:35  小蚊  阅读(608)  评论(2编辑  收藏  举报