js继承——从创建对象开始
从创建对象开始
创建对象的简单方法就是:使用Object构造函数或者对象字面量。但这两个方法有个缺点:创建相同对象,要编写大量重复代码。
为了避免这个问题——>工厂模式:用函数封装以特定接口创建对象
function createPerson(name, age){ var o = new Object(); o.name = name; o.age = age; o.sayName = function(){ return this.name; } return o; } var p1 = createPerson("Tom", 23); var p2 = createPerson("Joe", 20) p1 instanceof createPerson //false
工厂模式:优点: 解决了创建多个相似对象问题 缺点:没有解决对象识别问题(不知道对象的类型)
为了解决对象识别问题——>构造函数模式:创建特定类型的对象
function Person(name, age){ this.name = name; this.age = age; this.sayName = function(){ return this.name; } } var p1 = new Person("Tom", 23); var p2 = new Person("Joe", 20); p1 instanceof Person //true 检测对象类型 p1.constructor === Person //true 识别对象类型
p1.sayName === p2.sayName //false
构造函数模式:Person()与createPerson() 不同之处:没有显示创建对象、将属性方法值赋值给this、没有return 语句,还有就是函数名一个字母大写。
构造函数也是函数,那么函数用new执行会发生什么呢(重点):
1.创建一个空对象。
2.将构造函数的作用域赋值给新对象(this指向该对象, 同时还继承了该函数的原型)。
3.执行构造函数中的代码(添加属性和方法)
4.隐式的返回this(返回该对象, 如果没有显示的返回其他对象的话)
上面说了构造函数也是函数,只是使用了new 调用,如果不使用new就和普通函数一样
var wp = Person("Marry", 18); window.sayName(); //"Marry" wp.sayName(); //TypeError: Cannot read property 'sayName' of undefined sayName() //"Marry"
构造函数解决了对象识别问题,但是缺点:正如前面(p1.sayName === p2.sayName //false )会导致不同作用域链和标识符解析
//构造函数中的 this.sayName = function(){ return this.name; } //等价于 this.sayName = new Function("return this.name;")
为了解决这个问题,我们可以把sayName函数放外面作为全局函数,但这方法缺点颇多,很不合适。于是...
出现了——>原型模式:函数有个prototype属性,指向一个对象,该对象可以包含由特定类型的所有实例共享的属性和方法,简称之prototype是p1,p2的原型对象。这个原型对象可以让所有实例共享它包含的方法和属性。
function Person(){} Person.prototype.name = "Tom"; Person.prototype.age = 20; Person.prototype.friends = ["A", "B", "C"]; Person.prototype.sayName = function(){ return this.name; } var p1 = new Person(); var p2 = new Person(); p1.sayName === p2.sayName; //true p1.name = "other"; p2.friends.push("D"); p1.sayName(); // "other" ---来自实例 p2.sayName(); // "Tom"; ---来自原型 p1.friends; //["A", "B", "C", "D"] ----引用类型 p2.friends; //["A", "B", "C", "D"] Person.prototype.isPrototypeOf(p1) //true Object.getPrototypeOf(p1) === Person.prototype //true //in 通过对象能访问给定属性就返回true,不管实例还是原型 "name" in p1; //true "name" in p2; //true
// hasOwnProperty() 只返回实例中的属性 p1.hasOwnProperty("name"); //true p2.hasOwnProperty("name"); //false //Object.keys() 返回对象上所有可枚举实例属性 Object.keys(Person.prototype); //["name", "age", "friends", "sayName"] Object.keys(p1); //["name"]
//Object.getOwnPropertyNames() 返回所有实例属性,无论是否可枚举 Object.getOwnPropertyNames(Person.prototype) //["constructor", "name", "age", "friends", "sayName"] Object.getOwnPropertyNames(p1) //["name"]
说明:实例,构造函数,原型的关系:实例的[[Prototype]](__proto__)只指向原型、构造函数的prototype属性指向原型、原型的constructor指向构造函数。
in操作符:在<= IE8中 in不会枚举[[Enumerate]]为false的同名属性 ;在safari3中 in会枚举被隐藏的属性。 所以in慎用。
其中上述代码
Person.prototype.name = "Tom"; Person.prototype.age = 20; Person.prototype.friends = ["A", "B", "C"]; Person.prototype.sayName = function(){ return this.name; } //可写成: ---相当于重写原型 Person.prototype = { constructor: Person, name = "Tom", age = 20, friends = ["A", "B", "C"], sayName = function(){ return this.name; } } //支持ES5中,可去掉 上句 constructor: Person,添上如下代码: Object.defineProperty(Person.prototype, "constructor", { enumerate: false, value: Person });
原型模式:虽然解决了构造函数的缺点问题,但缺点: 1.默认情况下取相同的值;2.引用类型的实例被很多实例共享
为了避免这些问题——〉组合使用构造函数和原型模式:构造函数模式用于定义实例属性,而原型模式用于定义方法和共享属性, 最大限度的节省了内存
function Person(name, age){ this.name = name; this.age = age; this.friends = ["A", "B", "C"]; } Person.prototype = { constructor: Person, sayName: function(){ return this.name; } } var p1 = new Person("Tom", 34); var p2 = new Person("Joe", 21); p1.friends.push("D"); p1.friends //["A", "B", "C", "D"] p2.friends //["A", "B", "C"] p1.sayName === p2.sayName; //true
组合模式:优点:集两模式优点于一身,极好!因此该模式使用最广泛、认知度最高。
东西一好,就有人挑骨头,其他oo语言开发经验者看到独立的构造函数与原型会觉得很困惑,于是...
出现了——>动态原型模式:将所有信息封装在构造函数中
function Person(name, age){ this.name = name; this.age = age; this.friends = ["A", "B", "C"]; if (typeof this.sayName != "function") { Person.prototype.sayName = function(){ return this.name; } } } //创建第一个实例才会执行,此后,原型完成初始化 var p1 = new Person("Tom", 23); p1.sayName(); //"Tom"
至此,如果都不适用的话...
出现了——〉寄生构造函数模式:创建一个函数,作用仅仅是封装创建对象的代码,然后再返回新创建的对象
function Person(name, age){ var o = new Object(); o.name = name; o.age = age; o.sayName = function(){ return this.name; } return o; } var p1 = new Person("Tom", 23); p1.sayName();
该模式准确的说与工厂模式一模一样。只是把该函数当构造函数调用(使用new)而与构造函数模式相似,又有点不同,这不同之处就是:显示的创建对象,重写了返回值。
既然如此,这个模式的优势在哪里呢?对于构造函数模式而言,它可以new一个除object类型之外的其他类型——在不扩展原生构造函数的情况下自定义一个扩展型的构造函数。
function SpecialArray(){ var values = new Array(); values.push.apply(values, arguments); values.toPipedString = function(){ return this.join("|"); }; return values; } var friends = new SpecialArray("A", "B", "C"); friends.toPipedString(); //"A|B|C" friends instanceof SpecialArray //false friends instanceof Array //true
说明:构造函数返回的对象与构造函数或者构造函数的原型之间没有任何关系,也就是说构造函数外面创建或者里面创建是没有区别的,(只是寄生在构造函数中)因此无法用instanceof来识别对象。
还有一种创建对象的方法:——>稳妥构造函数模式:没有公共属性,不使用this、new,适合安全环境下使用
function Person(name, age){ } function Person(name){ var o = new Object(); o.sayName = function(){ return name; } return o; } var p1 = Person("Tom"); var p2 = Person("Joe"); p1.name //undefined p2.name //undefined p1.sayName() //"Tom" p2.sayName() //"Joe" 传入构造函数的name之恩那个通过sayName()访问
稳妥构造函数模式与寄生构造函数模式类似:创建的对象与构造函数之间没有任何关系。所以无法用instanceof来识别对象。
该博客与js高级程序设计内容相似,加之自己的理解与总结,不对之处欢迎指出。详情可看js高级程序设计。