构造函数模式

使用构造函数可以创建特定类型的对象,类似于Array、Date等原生JavaScript的对象。其实现方法如下:

function Person(name, age){ 
 this.name = name; 
 this.age = age; 
 this.speak = function(){ 
   alert(this.name + "is " + this.age + "years old"); 
 } 
}
var person1 = new Person("Lily ", 19); 
var person2 = new Person("Jack", 18);
console.log(person2 instanceof Person); // true, 判断person2是否是Person的实例 

在创建对象的构造函数、方法成员中,this指向为实例对象本身。当我们创建了一个Person的对象时,this即是指这个创建的对象。现在,我们可以识别出对象person1和person2的具体类型了。使用console.log(person2 instanceOf Person)后可以发现,输出的值为true。

可以看出这种方式解决了工厂模式中不能识别对象的类型的问题,这个例子与工厂模式中除了函数名不同以外,还有许多不同之处:

函数名首写字母为大写(按照惯例,构造函数的首写字母用大写);

没有显示的创建对象;

直接将属性和方法赋值给了this对象;

没有return语句;

使用new创建对象;

能够识别对象(这正是构造函数模式胜于工厂模式的地方)。

构造函数虽然好用,但也并非没有缺点,使用构造函数的最大的问题在于每次创建实例的时候都要重新创建一次方法(理论上每次创建对象的时候对象的属性均不同,而对象的方法是相同的),也就是说,构造函数内的方法是与对象绑定的。下面代码的输出可以验证我们的推断:alert(person1.speak == person2.speak); // false

然而创建两次完全相同的方法是没有必要的,解决这个缺点的一种比较简单的方法就是将函数的声明放到构造函数的外面,如下:

function Person(name, age){
    this.name = name; 
    this.age = age; 
    this.speak = speak; 
} 
function speak(){ 
	alert(this.name + "is " + this.age + "years old"); 
} 
var person1 = new Person("Lily", 19); 
var person2 = new Person("Jack", 18); 
alert(person1.speak == person2.speak);  // true  

问题解决了,但这种方法又带来了新的问题。首先,函数speak是在全局作用域中声明的,但它却只能被用于Person构造函数,放在全局作用域中有被误用的风险;其次,如果一个自定义类型有很多的方法,则需要声明很多的全局函数,这既将导致全局作用域的污染,也不利于代码的封装。因此可以通过原型来解决此问题。

4. 原型模式

创建的每个函数都有prototype(原型)属性,这个属性会被对象副本所继承,这样创建新对象时不用重复已有的属性、方法,节省了内存空间。使用原型对象的好处就是可以让所有对象实例共享它所包含的属性及方法。

function Person(){} 
Person.prototype.name="Lily"; 
Person.prototype.age=19; 
Person.prototype.speak = function(){ 
  alert(this.name + "is " + this.age + "years old"); 
} 
var person1 = new Person(); 
person1.speak(); 
var person2 = new Person(); 
alert(person1.speak == person2.speak); // true

可以看到,虽然构造函数内没有声明speak方法,但我们创建的对象person1还是能调用speak方法,这是因为JavaScript有一个搜索规则,先搜索实例属性和方法,找到则返回;如果没找到,则再到prototype中去搜索。因此没有污染全局作用域。

原型模式省略了构造函数传递初始化参数这一环节,结果所有实例在默认情况下都取得了相同的属性值,这样非常不方便,但这还不是原型的最大问题,原型模式的最大问题在于共享的本性所导致的,由于共享,因此一个实例修改了引用,另一个也随之更改了引用。因此通常不单独使用原型,而是结合原型模式与构造函数模式。

5. 混合模式(原型模式 + 构造函数模式)

function Person(name, age){ 
    this.name = name; 
    this.age = age; 
} 
Person.prototype.speak = function(){ 
    alert(this.name + "is " + this.age + "years old"); 
} 
var person1 = new Person(); 
person1.speak();
var person2 = new Person(); 
alert(person1.speak == person2.speak); // true  

混合模式中构造函数模式用于定义实例属性,而原型模式用于定义方法和共享属性。每个实例都会有自己的一份实例属性,但同时又共享着方法,最大限度的节省了内存。示例中因为只创建speak()函数的一个实例,所以没有内存浪费。另外这种模式还支持传递初始参数。优点甚多。这种模式在ECMAScript中是使用最广泛、认同度最高的一种创建自定义对象的方法。所有的非函数属性都由构造函数创建,意味着又可用构造函数的参数赋予属性默认值了。

6. 动态原型模式

有面向对象编程经验的开发人员可能会觉得将prototype的声明放在构造函数外面有点别扭,动态原型模式可以实现方法放到构造函数里去。

动态原型方法的基本想法与混合的构造函数/原型方式相同,即在构造函数内定义非函数属性,而函数属性则利用原型属性定义。唯一的区别是赋予对象方法的位置。

动态原型模式将所有信息封装在了构造函数中,而通过构造函数中初始化原型(仅第一个对象实例化时初始化原型),这个可以通过判断该方法是否有效而选择是否需要初始化原型。即先判断原型中的某个属性或方法是不是已经声明过,如果没有声明,则声明整个原型;否则,什么也不用做。示例代码如下:

function Person(e, n) {
    this.name = e, this.age = n, "undefined" == Person.prototype.speak && (alert("exe time"), Person.prototype.speak =
        function () {
            alert(this.name + "is " + this.age + "years old")
        })
}
var person1 = new Person,
    person2 = new Person;

可以看到上面的例子中只弹出一次窗,’exe time’,即当person1初始化后,person2就不在需要初始化原型。

如前所述,目前使用最广泛的是混合的构造函数/原型方式。些外,动态原型方法也很流行,在功能上与前者等价,可以采用这两种方式中的任何一种。

动态插入行和单元格

Javascript可以控制table,动态的插入行和单元格。rows保存着<tbody>元素中行的HTMLCollection。

语法:tableObject.insertRow(index)

该方法创建一个新的 TableRow 对象,表示一个新的<tr> 标记,用于在表格中的指定位置插入一个新行,并返回这个新插入的行 TableRow,新行将被插入 index 所在行之前。若 index 等于表中的行数,则新行将被附加到表的末尾。如果表是空的,则新行将被插入到一个新的 <tbody> 段,该段自身会被插入表中。若参数 index 小于 0 或大于表中的行数,该方法将抛出代码为 INDEX_SIZE_ERR 的 DOMException 异常。

table.insertRow(),默认添加到最后一行,统计行数:table.rows.length

cells保存着<tr>元素中单元格的HTMLCollectioin集合;insertCell(pos) 向cells集合的指定位置插入一个单元格,并返回引用;table.insertCell(),默认添加到最后一列,还可以根据需要动态改变单元格的属性,统计列数:table.rows.item(0).cells.length

动态删除某行

语法:table.deleteRow(index) 用来删除指定位置的行。

row.deleteCell(index) 用来/删除指定位置的单元格;

DOM方法:removeChild(node)用来删除子节点(元素);

parentNode 用来获取节点(元素)的父节点。