> ## **构造函数** ### **原型导读** ECMAScript中的构造函数可用来创建特定类型的对象。像 Object 和 Array 这样的原生构造函数,在运行时会自动出现在执行环境中。此外,也可以创建自定义的构造函数,从而定义自定义对象类型的属性和方法。例如: ~~~ function Person(name, age) { this.name = name; this.age = age; this.sayName = function () { alert(this.name); }; } var person1 = new Person("小四", 24); var person2 = new Person("王云飞", 27); ~~~ - 按照惯例,构造函数始终都应该以一个 大写字母开头,而非构造函数则应该以一个小写字母开头。主要是为了 区别于 ECMAScript中的其他函数;因为构造函数本身也是函数,只不过可以用来创建对象而已 - 当new去调用一个函数:这个时候函数中的this就是创建出来的对象,而且函数返回值直接就是this(隐式返回) - new 后面调用的函数:叫做构造函数 - person1 和 person2 分别保存着 Person 的一个不同的实例。这两个对象都 有一个 constructor(构造函数)属性,该属性指向 Person,如下所示: ~~~ console.log(person1.constructor === Person); //true console.log(person2.constructor === Person); //true ~~~ ### **将构造函数当作函数** 构造函数与其他函数的唯一区别,就在于调用它们的方式不同。不过,构造函数毕竟也是函数,不 存在定义构造函数的特殊语法。任何函数,只要通过 new 操作符来调用,那它就可以作为构造函数;而 任何函数,如果不通过 new 操作符来调用,那它跟普通函数也不会有什么两样。例如,前面例子中定义 的 Person()函数可以通过下列任何一种方式来调用,如下所示: ~~~ // 当作构造函数使用 var person = new Person("小四", 24); person.sayName(); //"小四" // 作为普通函数调用 Person("王云飞", 24); // 添加到window sayName(); //"王云飞" ~~~ ### **构造函数的问题** ~~~ function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = function() { alert(this.name) } } var person1 = new Person("小四", 24); var person2 = new Person("王云飞", 27); person1.sayName() person2.sayName() console.log(person1.sayName === person2.sayName); // false ~~~ 构造函数模式虽然好用,但也并非没有缺点。使用构造函数的主要问题,就是每个方法都要在每个 实例上重新创建一遍。在前面的例子中,person1 和 person2 都有一个名为 sayName()的方法,但那 两个方法不是同一个 Function 的实例。不要忘了——ECMAScript中的函数是对象,因此每定义一个 函数,也就是实例化了一个对象。 #### **原因就是对象引用类型的问题,看例子** ``` // 对象 var a = [1,2,3]; var b = [1,2,3]; console.log(a == b); // false //基本类型:赋值的时候只是值得复制 var a = 5; var b = a; b+=3; console.log(b); // 8 console.log(a); // 5 //对象:赋值不仅是值得复制,而且也是引用的传递 var a = [1,2,3]; var b = a; b.push(4); console.log(b); // (4) [1, 2, 3, 4] console.log(a); // (4) [1, 2, 3, 4] // 只要在程序当中出现赋值,那必然要在内存中重新生成 var a = [1,2,3]; var b = a; b = [1,2,3,4]; console.log(b); // (4) [1, 2, 3, 4] console.log(a); // (3) [1, 2, 3] // 对象类型比较:值和引用都相同才行 var a = [1,2,3]; var b = [1,2,3]; console.log(a == b); // false // 把值和引用都给了b,所以结果是true var a = [1,2,3\]; var b = a; console.log(a \== b); // true // 总结:构造函数的问题:每new一次就生成一份,如果要是有上百上千份极大的浪费内存 ``` #### **然而,创建两个完成同样任务的 Function 实例的确没有必要,因此,大可像下面这样,通过把函数定义转移到构造函数外 部来解决这个问题。** ~~~ function Person(name, age) { this.name = name; this.age = age; this.sayName = sayName; // 与声明函数在逻辑上是等价的 } function sayName(){ console.log(this.name); } var person1 = new Person("小四", 24); var person2 = new Person("王云飞", 27); person1.sayName() // 小四 person2.sayName() // 王云飞 console.log(person1.sayName === person2.sayName); // true ~~~ > 在这个例子中,我们把 sayName()函数的定义转移到了构造函数外部。而在构造函数内部,我们 将 sayName 属性设置成等于全局的 sayName 函数。这样一来,由于 sayName 包含的是一个指向函数 的指针,因此 person1 和 person2 对象就共享了在全局作用域中定义的同一个 sayName()函数。这 样做确实解决了两个函数做同一件事的问题,可是新问题又来了:在全局作用域中定义的函数实际上只 能被某个对象调用,这让全局作用域有点名不副实。而更让人无法接受的是:如果对象需要定义很多方 法,那么就要定义很多个全局函数,于是我们这个自定义的引用类型就丝毫没有封装性可言了。好在, 这些问题可以通过使用原型模式来解决。