一、最简单的创建对象的两种方法
1. 创建一个 Object 的实例,然后在为他添加属性和方法:
var person = new Object(); person.name = "Jhon"; person.age = 23; person.sayHi = function() { console.log("hello,", person.name); }
// 调用对象的方法 person.sayHi() // hello,Jhon
2. 对象字面量的方法创建对象:目前比较常用的是这种对象字面量的方法
var person = { name: "Jhon", age:"23", sayhello : function () { console.log("Hello,", person.name) } } person.sayhello();
上面两种方法的缺点:虽然能用来创建单个对象,但是,使用一个接口创建很多对象,会产生大量重复的代码。为解决这个问题,产生了下面的各种创建对象的方法:
二、工厂模式,用函数来封装以特定接口创建对象的细节:
function createPerson(name, age, job) { var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayhi = function() { console.log("hello,", o.name) }; return o; } var p1 = createPerson("Jack", "23", "doctor");
var p2 = createPerson("Mary", "11", "student");
工厂模式的缺点:虽然解决了创建多个相似对象的问题,但是没有解决对象识别问题。例如上面,只能判断实例的类型是 Object,但判断不出来是 createPerson。
三、构造函数模式:
function Person(name, age, obj) { this.name = name; this.age = age; this.obj = obj; this.sayHi = function () { console.log(`hello, ${this.name}`); } } var p3 = new Person("Jhon", "22", "doctor") p3.sayHi();
var p4 = new Person("Tom", "22", "doctor")
console.log(p3.sayHi() === p4.sayHi()) // false
对比和工厂模式的不同:1. 没有显示的创建对象;2. 直接将属性和方法赋给了 this 对象;3. 没有 return 语句;4. 使用的时候要用 new 来创建一个新的实例。5. 解决了对象识别问题:constructor 属性指向 Person;或者用 p1 instanceof Person 进行检测。解决对象标识问题上,构造函数模式,优于工厂模式。
new 的过程经过四个步骤:1. 创建一个新的对象; 2. 将构造函数的作用域赋给新的对象(因此,this 就指向了这个新的对象。注意总结 this 的情况);3. 执行函数中的代码(为这个新的对象添加属性和方法); 4. 返回新的对象。或者这样理解:先把参数传进去,或者不传参数。函数执行的时候,函数里面的 this 先变成一个空对象,再在函数里面对 this. name this.age 进行赋值,赋值之后,返回这个 this。最后,再把存好地址赋值给 doctor 这个变量。
构造函数缺点:每个方法都要在每个实例上重新创建一遍。这样,不同实例上的同名函数式不相等的。解决方法1. 把所有实例共用的方法放在构造函数的外面 --- 全局作用域中。这样,所有的实例,都可以调用这个方法了。但随之而来的问题是:全局作用域中定义的方法实际上只对某几个实例调用,让全局作用域有点名不副实。而且,对象需要定义很多方法,那么就需要定义很多全局作用域的函数,那么我们自定义的这个引用类型就毫无封装性可言了。所以可以用原型模式来解决这个问题。
四、原型模式:每个函数都有 prototype(原型)属性,这个属性是一个指针,指向原型对象,这个原型对象里面包含了实例共享的属性和方法。prototype 就是调用构造函数而创建的实例的原型对象。使用原型对象的好处是,让所有对象实例共享原型对象里面的方法和属性。换句话说就是,不必在构造函数中定义实例的信息,而是将这些信息直接添加到原型对象中。
function Person() { } Person.prototype.name = "Jhon"; Person.prototype.age = "12"; Person.prototype.obj = "doctor"; Person.prototype.sayName = function() { console.log(this.name); } var p5 = new Person(); p5.sayName() // "Jhon" var p6 = new Person(); p6.sayName() // "Jhon" console.log(p5.sayName() === p6.sayName()) // true
原型模式缺点:修改一个实例的引用类型(array,function)的属性,也会影响到另一个实例的引用类型属性。例如下面例子中的 friends 属性,修改了 p7 的,会影响到 p8。注意对于基本类型的属性,修改后不会有影响。在实例中添加一个同名的基本类型的属性,可以隐藏原型中对应的属性。解决方式,结合使用构造函数模式和原型模式。
function Person() { } Person.prototype = { name : "Jack", age : '23', friends : ["a", 'b'] } var p7 = new Person(); var p8 = new Person(); p7.friends.push("Tom"); console.log(p7.friends); // [ "a", "b", "Tom" ] console.log(p8.friends); // [ "a", "b", "Tom" ]
五、组合使用构造函数模式和原型模式
每个实例都会有一份自己的实例属性副本,但同时又共享着对方法的引用,最大限度的节省了内存。把每个实例不同的写入构造函数中,相同的写入 prototype 中。
function Person(name, age) { this.name = name; this.age = age; this.friends = ['a', 'b'] } Person.prototype = { sayhi : function() { console.log("hi,", this.name) } } var p9 = new Person('Jhon', "23") var p10 = new Person('Tom', "12") p9.friends.push("Jack"); console.log(p9.friends) // [ "a", "b", "Jack" ] console.log(p10.friends) // [ "a", "b"]