对象创建模式:
- 工厂模式
- 由于javascript中没有类的概念,所以用开发人员发明了一种函数,通过函数来封装特定接口创建对象的,见例子:
function createObj (name, age) { return { name: name, age: age } }
- 构造函数模式
- 该模式和上述工厂模式的不同点:
- 没有显示地创建对象
- 直接将属性和方法赋给了this对象
- 没有return语句
function Person (name, age){ this.name = name; this.age = age; }
- 构造函数名首字母约定大写
- 创建对象的过程大概分为以下三个步骤:
- 创建一个对象
- 将构造函数的作用域赋给新对象
- 执行构造函数内的代码,为这个对象添加属性
- 返回对象
var obj = Object.create(Person.prototype); // 创建一个对象,将这个对象的原型指向构造函数的的原型; Person.call(obj); // 执行步骤2和3, 将构造函数的this指向创建的obj对象,并运行构造函数内的代码,为obj对象添加属性
- 需要注意的是: 如果构造函数中有return语句,返回的是引用类型的数据,则返回该引用类型数据
- 如果做普通函数,在全局作用域中执行,则this指向window
- 构造函数存在的问题:每个方法都要在每个实例上创建一遍,对此有一个简单的方法解决,将方法函数的定义放在外面,但是如果有很多个方法,那就需要在外面定义很多个方法,好在可以通过原型模式来解决这一问题
- 该模式和上述工厂模式的不同点:
- 原型模式
- 将共同的方法属性添加到函数的原型中,好处也体现在此处,让所有的对象实例共享它包含的属性和方法
function Person () {}; Person.prototype.name = 'zhanhui'; Person.prototype.job = 'programmer'; Person.prototype.sayName = function () { console.log(this.name); };
- 每一个函数都有一个prototype原型属性,指向一个对象
- prototype原型对象上有一个constructor属性,指向构造函数
- Object.getPrototypeOf() 获取对象的原型属性
- isPrototypeOf()方法确定对象之间是否存在原型关系
- 虽然可以通过对象实例访问保存在原型中的值,但是不能通过对象实例重写原型中的值,并且为对象实例添加一个原型上重名的属性时,这个属性会屏蔽原型对象中保存的同名属性,如果要回复对原型对象该属性的访问,可以用delete操作符删除实例属性,见下例子:
var person1 = new Person(); var person2 = new Person(); person1.name = 'wawa'; console.log(person1.name); // 'wawa'来自实例对象 console.log(person2.name); // 'zhanhui'来自原型对象
- hasOwnProperty()获取实例对象上的属性,不能获取原型对象的属性
- in 操作符 单独使用或者配合for-in循环中使用
- 单独使用时,in操作符会在通过对象是否能访问到给定的属性时返回true,无论该属性是否存在于实例中还是原型中
- for-in循环时,可以通过对象访问到、可枚举的属性,该属性既可以在实例中,也可以是原型中,并且即使将该属性的Enumerable标记为false,也是可以遍历到的
person1.age = 26; Object.defineProperty(Object.getPrototypeOf(person1), 'age', { enumerable: false }); for (var key in person1) { console.log(key); // name job sayName }
- for-in获取key
var obj = { x: 1, y: 2 }; var props = []; var i = 0; for (props[i++] in obj); props // ['x', 'y']
- hasPrototypeProperty() 判断读取到的某属性是在实例中读取到还时原型中读取到
hasPrototypeProperty(person1, 'name'); // false person1中的name是从实例中读取的 hasPrototypeProperty(person2, 'name'); // true person2中的name是从原型中读取的
- Object.keys()返回所有可枚举属性的字符串数组
- Object.getOwnPropertyNames()可以获取实例的所有属性,不管是否可枚举
- 原型对象:
- 默认原型对象的constructor是不可枚举的,当我们重写原型时,constructor指向object构造函数,所以在重写构造函数的原型对象时,如果constructor很重要,会用的到,就需要特别指定一下,见例子:
function Person (){} Person.prototype = { constructor: Person, // 特别指定 name: 'zhanhui', say: function() { console.log(this.name); } } // 将constructor属性设置为不可枚举 Object.defineProperty(Person.prototype, 'constructor', { enumberable: false })
- 重写原型后,调用构造函数时会为实例添加一个指向最初的原型,而重写原型则将构造函数和其最初的原型断开了联系,实例化时又将实例对象的__proto__指向最初的原型,所以就存在问题,实例不能获取重写后的原型上的属性和方法,见例子:
var boo = new Person(); boo.say() // error // 但是经过验证并不是,新的浏览器内核已经不存在这样的问题了
- 原型对象存在的问题:主要是其共享性所带来的,例如原型对象上挂载了一个引用类型的属性,其他一个实例中更改这个引用属性都会将影响扩散到其他的所有实例上,见例子:
function Person (){} Person.prototype = { constructor: Person, // 特别指定 name: 'zhanhui', say: function() { console.log(this.name); }, friends: ['boo', 'mike'] } var person1 = new Person(); var person2 = new Person(); person1.friends.push('lili'); console.log(person1.friends); // ['boo', 'mike', 'lili'] console.log(person2.friends); // ['boo', 'mike', 'lili']
- 将共同的方法属性添加到函数的原型中,好处也体现在此处,让所有的对象实例共享它包含的属性和方法
- 组合构造函数模式和原型模式
- 构造函数模式用来定义实例属性,而原型模式用于定义方法和共享的属性
- 见例子:
function Person (name, age) { this.name = name; this.age = age; this.friends = ['boo', 'mike']; } Person.prototype = { constructor: Person, say: function () { console.log(this.name); } }
- 寄生构造函数模式
- 其实和工厂模式类似,构造函数用来封装创建对象的代码,然后在返回新创建的对象,见例子:
function Person (name, age) { var obj = {}; obj.name = name; obj.age = age; obj.say = function () { console.log(this.name); } } var person1 = new Person('zhanhui', 26);
- 应用场景: 例如我们要创建一个额外方法的数组
- 特殊说明:构造函数返回的对象和构造函数以及构造函数的原型没有关系,因此不能用instanceof来确定对象类型
- 稳妥构造函数
- 所谓稳妥就是没有公共属性,其方法不使用new调用函数,也不引用this
- 见例子:
function Person (name,age){ var o = {}; // 定义私有变量和方法; function howOld () { console.log(age); } // 添加共有方法,这也是闭包的应用场景,共有方法访问私有变量 o.say = function () { console.log(this.name); }; return o; } var person1 = Person('zh', 26); // 只能通过say方法访问私有变量name