虽然Object构造函数与对象字面量都能建立单个对象, 但这些方式都有明显的缺点: 使用同一个接口建立不少对象, 会产生大量重复代码。javascript
var obj = {}; //对象字面量 var obj = new Object(); //对象构造函数(对象构造器)
这种模式抽象了对象具体建立的过程(相似其它语言的类)html
function createPerson (name, age, job) { var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function () { alert (this.name); } return o; } var person1 = createPerson("汪淼", 50, "纳米"); var person2 = createPerson("杨冬", 20, "基础物理学");
工厂模式解决了建立多个对象的问题, 确没有解决对象识别问题(如何知道一个对象的类型)由于使用该模式并无给出对象的类型java
建立自定义构造函数意味着未来能够将它的实列类型标识为一种特定的类型。(更优势)
这种方式定义的函数是定义在global中的编程
function Person (name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = function () { alert (this.name); } } var person1 = new Person("汪淼", 50, "纳米"); var person2 = new Person("杨冬", 20, "基础物理学");
与工厂模式区别安全
高级编程对象处的说法 1.建立(或者说构造)一个全新的对象。 2.将构造函数的做用域赋值给新对象(所以this指向了新对象) 3.执行构造函数的代码(为这个新对象添加属性) 4.返回对象
person1.constructor == Person; person2.constructor == Person;
constructor 最初是用来标识对象类型的。但提到检测对象类型,仍是使用 instanceofapp
person1 instanceof Person // true person2 instanceof Person // true
构造函数与其余函数惟一区别。调用方式的不一样。 用new 操做符调用就是做为构造函数,不用则为普通函数函数
// 构造函数 var person = new Person('罗辑', 20, '宇宙社会学'); person.sayName(); //罗辑 // 普通函数 Person('叶文洁', 20, '基础物理学'); window.sayName(); //叶文洁 // 在另外一个对象做用域中调用 var o = new Object(); Person.call(o, '泰勒', 25, '面壁者'); //call() apply() 是会当即调用函数的 而bind() 则不会 o.sayName(); // 泰勒
使用构造函数的主要问题是每一个方法都要在每一个实例上从新建立一遍,建立多个完成相同任务的方法彻底没有必要,浪费内存空间this
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = new Function("alert(this.name)"); // 等价于 // this.sayName = function () { // alert (this.name); // } }
function Person (name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = sayName; } function sayName() { alert (this.name); } var person1 = new Person("汪淼", 50, "纳米"); var person2 = new Person("杨冬", 20, "基础物理");
sayName 放到了构造函数外部, 构造函数内部经过sayName指针指向sayName函数;
问题: 建立了全局函数sayNameprototype
因而这个自定义引用类型就没有丝毫封装性可言指针
咱们建立的每一个函数都有一个 prototype(原型属性),该属性是一个指针,指向一个对象,而这个对象的用途是包含能够由特定类型全部实例共享的属性和方法。
用原型对象,可让全部实例共享它的属性和方法。换句话说,没必要在构造函数中定义对象实例的信息,而是能够将这些信息直接添加到原型对象中
function Person() { } Person.prototype.name = '维德'; Person.prototype.age = '24'; Person.prototype.sayName = function() { alert(this.name); } var person1 = new Person(); person1.sayName(); // 维德 var person2 = new Person(); person2.sayName(); // 维德 alert(person1.sayName == person2.sayName); //true // @todo 原型链图
function Person(){}; Person.prototype = { name: "丁怡", age: 50, job: '物流', sayName : function(){ console.log(this.name); } }; var person1 = new Person(); person1.sayName();// '丁怡' console.log(person1.constructor === Person);// false console.log(person1.constructor === Object);// true
这种语法 constructor 再也不指向Person,(每建立一个函数,就会同时建立prototype, 同时这个对象也会自动获取constructor),而该方法本质上至关于彻底重写了prototype的默认属性,constructor 也变成了新对象的constructor属性(指向Object构造函数),再也不指向Person函数。此时尽管instanceof操做符还能返回正确的结果,但经过constructor已经没法肯定对象的类型了
var friend = new Person(); friend instanceof Object; // true friend instanceof Person; // true friend constructor Person; // false friend constructor Object; // true
function Person(){}; Person.prototype = { // 方法1:添加 constructor constructor: 'Person', name: "丁怡", age: 50, job: '物流', sayName : function(){ console.log(this.name); } }; // 方法2:经过defineProperty() 添加 Object.defineProperty(Person.prototype,'constructor',{ enumerable: false, value: Person });
不管何时,只要建立了一个新函数,就会根据一组特定的的规则为该函数建立一个prototype属性,这个属性指向函数的原型对象。在默认状况下,全部的原型对象都会自动获取constructor(构造函数)属性,这个属性包含了一个指向prototype属性所在函数数的指针
in 会查找 原型链 hasOwnProperty() 则只查找实例自己
function Person(){}; Person.prototype = { name: "丁怡", age: 50, job: '物流', sayName : function(){ console.log(this.name); } }; var person1 = new Person(); person1.sayName();// '丁怡' console.log(person1.constructor === Person);// false console.log(person1.constructor === Object);// true
var friend = new Person(); Person.prototype.sayHi = function() { alert('hi'); } friend.sayHi(); // hi (ok!)
重写function原型以后
function Person() {} var friend = new Person(); Person.prototype = { constructor: Person, name: '程心', age: 21, job: '天体物理', sayName: fucntion() { alert(this.name); } }; friend.sayName(); //error 原型链重写 @todo 原型链图
js 全部原生的引用类型,都是采用这种模式(Object, Array, String)
原型模式问题在于引用类型值属性会被全部的实例对象共享并修改,这也是不多有人单独使用原型模式的缘由
function Person(){} Person.prototype = { constructor: Person, name: "bai", age: 29, job: "Software Engineer", friend : ["shelby","Court"], sayName: function(){ console.log(this.name); } }; var person1 = new Person(); var person2 = new Person(); person1.friends.push("Van"); alert(person1.friends);//["shelby","Court","Van"]; alert(person2.friends);//["shelby","Court","Van"]; alert(person1.friends === person2.friends);//true
组合使用构造函数模式和原型模式是建立自定义类型的最多见方式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性,这种组合模式还支持向构造函数传递参数。实例对象都有本身的一份实例属性的副本,同时又共享对方法的引用,最大限度地节省了内存。该模式是目前使用最普遍、认同度最高的一种建立自定义对象的模式
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.friends = ['丁怡', '汪淼']; } Person.prototype = { constructor: Person, sayName: function() { alert(this.name); } } var person1 = new Person("bai",29,"Software Engineer"); var person2 = new Person("hu",25,"Software Engineer"); person1.friends.push('杨冬'); alert(person1.friends);// ['丁怡', '汪淼', '杨冬']; alert(person2.friends);// ['丁怡', '汪淼']; alert(person1.friends === person2.friends);//false alert(person1.sayName === person2.sayName);//true
动态原型模式将组合模式中分开使用的构造函数和原型对象都封装到了构造函数中,而后经过检查方法是否被建立,来决定是否初始化原型对象
function Person(name, age, job) { // 属性 this.name = name; this.age = age; this.job = job; // 方法 if(typeof this.sayName != 'function') { Person.prototype.sayName = function(){ console.log(this.name); }; } } var friend = new Person("bai",29,"Software Engineer"); friend.sayName();//'bai'
该模式的基本思想是建立一个函数,该函数的做用仅仅是封装建立对象的代码,而后再返回新建立的对象。该模式是工厂模式和构造函数模式的结合
寄生构造函数模式与构造函数模式有相同的问题,每一个方法都要在每一个实例上从新建立一遍,建立多个完成相同任务的方法彻底没有必要,浪费内存空间
function Person(name,age,job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ console.log(this.name); }; // o.sayName = new Function('console.log(this.name);'); return o; } var person1 = new Person("bai", 29, "software Engineer"); var person2 = new Person("hu", 25, "software Engineer"); //具备相同做用的sayName()方法在person1和person2这两个实例中却占用了不一样的内存空间 console.log(person1.sayName === person2.sayName);//false
使用new 操做符会默认返回新对象实例,这里return 重写了调用构造函数的时返回的值
所谓稳妥对象指没有公共属性,并且其方法也不引用this的对象。稳妥对象最适合在一些安全环境中(这些环境会禁止使用this和new)或者在防止数据被其余应用程序改动时使用
稳妥构造函数与寄生构造函数模式类似,但有两点不一样:一是新建立对象的实例方法不引用this;二是不使用new操做符调用构造函数
function Person(name,age,job){ //建立要返回的对象 var o = new Object(); //能够在这里定义私有变量和函数 //添加方法 o.sayName = function(){ console.log(name); }; //返回对象 return o; } //在稳妥模式建立的对象中,除了使用sayName()方法以外,没有其余方法访问name的值 var friend = Person("bai",29,"Software Engineer"); friend.sayName();//"bai"
与寄生构造函数模式类似,使用稳妥构造函数模式建立的对象与构造函数之间也没有什么关系,所以instanceof操做符对这种对象也没有什么意义
本文从使用Object构造函数与对象字面量建立一个对象开始提及,建立多个对象会形成代码冗余;使用工厂模式能够解决该问题,但存在对象识别的问题;接着介绍了构造函数模式,该模式解决了对象识别的问题,但存在关于方法的重复建立问题;接着介绍了原型模式,该模式的特色就在于共享,但引出了引用类型值属性会被全部的实例对象共享并修改的问题;最后,提出了构造函数和原型组合模式,构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性,这种组合模式还支持向构造函数传递参数,该模式是目前使用最普遍的一种模式。此外,一些模式下面还有一些解决特殊需求的拓展模式(寄生构造函数模式, 稳妥寄生模式)