创建对象学习 —— 《高级教程》

发布日期:2019-05-18

创建单个对象的缺点:用同一个接口创建很多对象,会产生大量的重复代码。

工厂模式就是为了解决这个问题。

工厂模式

解决了创建多个相似对象的问题

function createPerson(name age job) { var o = new Object() o.name = name o.age = age o.job = job o.sayName = function() { console.log(this.name) } return o}var person1 = createPerson("Mike" 28 "xxx")console.log(person1)person1.sayName()var person2 = createPerson("Mike" 24 "aaa")console.log(person2)person2.sayName()

 

缺点:无法解决对象识别的问题——怎样知道一个对象的类型

构造函数模式

ECMAScript中的构造函数可以用来创建特定类型的对象。

function Person(name age job) { this.name = name this.age = age this.job = job this.sayName = function() { console.log(this.name) }}var person1 = new Person("Mike" 28 "teacher")console.log(person1)person1.sayName()var person2 = new Person("Danie" 24 "doctor")console.log(person2)person2.sayName()

与工厂模式的区别:

没有显示的创建对象将属性和方法赋值给了this对象没有return语句

构造函数本身也是函数,只不过可以用来创建对象

用new操作符新建构造函数的实例,经历4个步骤:

person1 和 person2 分别保存着 Person 的两个不同实例,都有一个 constructor (构造函数) 属性,指向 Person

console.log(person1.constructor == Person) // trueconsole.log(person2.constructor == Person) // true

console.log(person1.constructor == Object) // falseconsole.log(person2.constructor == Object) // false

person1 和 person2 都是 Person 的实例,也是 Object 的实例,可以通过 instanceof 操作符来检验。

console.log(person1 instanceof Person) // trueconsole.log(person2 instanceof Person) // trueconsole.log(person1 instanceof Object) // trueconsole.log(person2 instanceof Object) // true

缺点:每个构造函数中的方法都要在新的实例上创建一遍。

console.log(person1.sayName == person2.sayName) // false

将相同的方法移到外部:

function sayName() { // 将相同的方法移到构造函数的外部 console.log(this.name)}function Person(name age job) { this.name = name this.age = age this.job = job this.sayName = sayName}var person1 = new Person("Mike" 28 "teacher")console.log(person1)person1.sayName()var person2 = new Person("Danie" 24 "doctor")console.log(person2)person2.sayName()

// 此时不同实例上的方法就相等了console.log(person1.sayName == person2.sayName) // true

缺点:需要在全局作用域定义很多函数,没有封装性可言

原型模式

好处: 所有对象实例可共享它所包含的属性和方法

function Person() {}Person.prototype.name = "Mike"Person.prototype.age = 28Person.prototype.job = "teacher"Person.prototype.sayName = function() { console.log(this.name)}var person1 = new Person()var person2 = new Person()person1.sayName()person2.sayName()console.log(person1.sayName == person2.sayName) // true

下图以上面代码为例,展示了Person构造函数、Person的原型属性,及两个实例之间的关系。

 

在实现中,无法访问 [[Prototype]],可以用 isPrototypeOf() 方法来确定对象之间是否有这种关系。

console.log(Person.prototype.isPrototypeOf(person1)) // trueconsole.log(Person.prototype.isPrototypeOf(person2)) // true

通过 Object.getPrototypeOf() 方法,访问原型对象上的属性

console.log(Object.getPrototypeOf(person1) == Person.prototype) // trueconsole.log(Object.getPrototypeOf(person1).name) // Mike

修改实例属性 

// 原型模式 修改实例属性function Person() {}Person.prototype.name = "Mike"Person.prototype.age = 28Person.prototype.job = "teacher"Person.prototype.sayName = function() { console.log(this.name)}var person1 = new Person()person1.name = "Gray"person1.job = "doctor"var person2 = new Person()person1.sayName() // Grayperson2.sayName() // Mikeconsole.log(person1) // Person {name: "Gray" job: "doctor" __proto__: Object}console.log(person2) // Person {__proto__: Object}

修改实例属性为 null 不会恢复指向原型的链接

function Person() {}Person.prototype.name = "Mike"Person.prototype.age = 28Person.prototype.job = "teacher"Person.prototype.sayName = function() { console.log(this.name)}var person1 = new Person()person1.name = "Gray"var person2 = new Person()person1.sayName() // Grayperson2.sayName() // Mikeperson1.name = null // 不会恢复指向原型的链接person1.sayName() // null

删除实例属性 会重新恢复指向原型对象的链接

// 原型模式 删除实例属性 会重新恢复指向原型对象的链接function Person() {}Person.prototype.name = "Mike"Person.prototype.age = 28Person.prototype.job = "teacher"Person.prototype.sayName = function() { console.log(this.name)}var person1 = new Person()person1.name = "Gray"var person2 = new Person()person1.sayName() // Grayperson2.sayName() // Mikedelete person1.name // 会重新恢复指向原型对象的链接person1.sayName() // Mike

hasOwnProperty() 检测一个属性存在于实例中还是存在于原型中。

// 原型模式 hasOwnProperty()function Person() {}Person.prototype.name = "Mike"Person.prototype.age = 28Person.prototype.job = "teacher"Person.prototype.sayName = function() { console.log(this.name)}var person1 = new Person()console.log(person1.hasOwnProperty("name")) // falseperson1.name = "Gray"console.log(person1.hasOwnProperty("name")) // truevar person2 = new Person()console.log(person2.hasOwnProperty("name")) // falsedelete person1.nameconsole.log(person1.hasOwnProperty("name")) // false

Object.keys() 获得对象上所有可枚举的实例属性

// Object.keys()function Person() {}Person.prototype.name = "Mike"Person.prototype.age = 28Person.prototype.job = "teacher"Person.prototype.sayName = function() { console.log(this.name)}console.log(Object.keys(Person.prototype)) // ["name" "age" "job" "sayName"]var person1 = new Person()console.log(Object.keys(person1)) // []person1.name = "Gray"console.log(Object.keys(person1)) // ["name"]

更简单的原型语法

function Person() {}Person.prototype = { // prototype 的 constructor 属性不再指向 Person name: "Mike" age: 28 job: "Teacher" sayName: function() { console.log(this.name) }}var person1 = new Person()console.log(person1)

以上方式,Person.prototype 的 constructor 属性将不再指向 Person。

对比下面两张图:

Person.prototype.name = "Mike" // 方式创建的

constructor 会指向 Person

Person.prototype = {} // 对象字面量方式创建的

constructor 不会指向 Person

通过 instanceof 还能返回正确的结果,但是 constructor 已经不能确定对象的类型了。

console.log(person1 instanceof Object) // trueconsole.log(person1 instanceof Person) // trueconsole.log(person1.constructor == Object) // trueconsole.log(person1.constructor == Person) // false

如果 constructor 很重要,可以显示指定

// 显示指定 constructorfunction Person() {}Person.prototype = { // 通过指定 constructor属性,指向 Person constructor: Person // 以这种方式重设,会使它的[[Enumerable]]特性被设置为true name: "Mike" age: 28 job: "Teacher" sayName: function() { console.log(this.name) }}var person1 = new Person()console.log(person1) // 打印出的结果见下图

兼容 ECMAScript5的浏览器引擎

Object.defineProperty()

function Person() {}Person.prototype = { name: "Mike" age: 28 job: "Teacher" sayName: function() { console.log(this.name) }}Object.defineProperty(Person.prototype "constructor" { enumerable: false value: Person})

原型的动态性

先创建实例,再在Person的原型对象上加方法,实例也可以调用。

// 原型的动态性function Person() {}var person1 = new Person()Person.prototype.sayHi = function() { console.log("Hi")}person1.sayHi() // Hi

重写原型对象

function Person() {}var person1 = new Person()Person.prototype = { // 通过指定 constructor属性,指向 Person constructor: Person name: "Mike" age: 28 job: "Teacher" sayName: function() { console.log(this.name) }}console.log(person1)person1.sayName() // person1.sayName is not a function

可以看到,person1的原型对象上没有Person的新原型对象上的属性,因为他们是两个不同的对象。

下图展示了重写原型之前和重写原型之后各个对象之间的关系。

 

 未完待续...

 

注:以上所有的文字、代码都是本人一个字一个字敲上去的,图片也是一张一张画出来的,转载请注明出处,谢谢!