参考《JavaScript高级程序设计3版》
关键词: 对象,创建对象,原型,原型链, 继承
怎么创建对象?
1. 就是最基本的方式 使用Object 如下
2. 使用对象字面量方式
var person = new Object();
person.name = 'xiaoming';
person.age = 29;
//对象字面量表示方式 创建对象
var person = {
name : "xiaoming",
age : 29
}
Object和字面量创建对象的方式有个缺点:会产生大量的重复代码并且占用内存。
3. 工厂模式(实际上用的不多)
工厂方式虽然解决了,会产生大量重复代码的问题,但是还有一个问题没解决。就是怎样识别对象,怎样知道一个对象的类型。实际上用的不多
//p162工厂模式
//缺点没有解决对象识别的问题
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('zhao',29,'software');
var person2 = createPerson('liang',27,'teacher');
4. 构造函数模式
构造函数也是普通函数, 不new 也可以使用;不过this指向的就是外面的(外层的)执行环境。
以new 构造函数,实际上会经历以下4个步骤
- 创建一个新对象;
- 将新对象赋值给this
- 执行构造函数中的代码,为这个新对象添加属性
- 返回新对象
//6.2.2p162 构造函数模式
//这种调用构造函数实际上会经历四个步骤
//1,创建一个新对象2,将构造函数的作用域赋给新对象(因此this就指向了这个新对象);3,将执行构造函数中的代码,4,返回新对象
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
}
//this.sayName = new Function(“alert(this.name)”);
}
var person1 = new Person('zhao',29,'software');
var person2 = new Person('liang',27,'Doctor');
alert(person2.constructor == Person);//true
//构造函数的模式意味着可以将他的实例标示为一种特定的类型,而这正是构造函数模式胜过工厂模式的地方
//alert(person2 instanceof Object);//true
//alert(person2 instanceof Person);//true
//任何函数,只要通过new操作符来调用,那他就可以作为构造函数。
//person1.sayName();//zhao
//作为普通函数调用
Person('zhao2',27,'actor');
//当在全局作用域中调用一个函数时,this对象指向global对象
//window.sayName();//zhao2
//在另一个对象的作用域中调用,this指向o
var o = new Object();
Person.call(o,'liangge',24,'cook');
o.sayName();
var self = this;
function test() {
console.log('self == this : ');
console.log(self == this);
}
test();
构造函数的问题
缺点: 每个实例的方法都要创建一遍。 属性不一样是应该的,方法每个实例都有就浪费内存了。只要不是大量创建对象这个也不算大问题了。
//问题
console.log('问题:');
console.log(person1.sayName == person2.sayName); //false
console.log(person1.name == person2.name); //false
稍微改进方案,就是把函数移出去放到全局环境中。这么着也不好没有封装性了。
(实际中用构造函数模式的的改进方案的不多)
//改进版
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('zhao',26,'software');
var person2 = new Person('liang',21,"doctor");
person1.sayName();
person2.sayName();
var name = 'san';
sayName();
alert(person1.sayName == person2.sayName);//true
//结,新的问题:如果对象需要定义很多方法,那么就要定义很多个全局函数,这个自定义的引用类型没有封装性可言了。
5. 原型模式
每个函数都有一个prototype (原型)属性。这个属性,指向一个对象;这个对象可以包含了属性和方法。这个对象是被共享的。
来说说原型对象是怎么一回事
只要创建了一个函数,就会为该函数创建一个prototype属性,这个属性指向函数的原型对象。
原型对象默认只会有constructor 属性(它指向函数名);至于其他方法则都是从Object继承而来的。
function Person() {
}
Person.prototype.name = "xiaoming";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function () {
alert(this.name);
}
var person1 = new Person();
person1.sayName();
var person2 = new Person();
person2.sayName();
console.log(Person.prototype);
console.log(Person);
console.log(person1);
console.log(person1.constructor);
console.log(person1.sayName === person2.sayName);
Person.prototype.isPrototypeOf(person1);
console.log(Object.getPrototypeOf(person1));
console.log(person1.__proto__);
for( var i in person1){
console.log(i);
}
var keys = Object.keys(person1);
console.log('keys:',keys); //[] 空的数组
var names = Object.getOwnPropertyNames(person1);
console.log(names); // [] 空数组
console.log('person1->constructor:',person1.constructor);
console.log(keys.__proto__);
构造函数和实例,原型,及Object 的关系图:
查找属性时怎么搜索?给定属性的名字怎么搜索?。
首先解析器会问:“实例person1有sayName()属性吗? 答没有,然后,他继续搜索,再问person1的原型有sayName属性吗? 答“有”。
实例可以访问原型中的值,但是实例能重写原型中的值吗? 答“不能”。
我在实例中添加一个属性,和原型中的属性同名,他不会覆盖原型中的属性。
再次访问实例的这个名字的属性,他访问的是实例中的这个名字的属性,不去访问原型中那个名字的属性,因为已经找到了,这两个同名属性是可以同时存在的。
换句话说: 当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性(如果有的话)。
实例中和它的原型中都有这个名字的属性;那么实例怎么才能通过实例(person1.)访问到原型中的属性呢? 使用delete删除实例的这个属性。改为null是不行的。
怎么检测,判断一个属性是存在于实例中,还是存在于原型中?用什么方法
hasOwnProperty()
使用实例对象的hasOwnProperty()方法,(它是从Object继承来的)。
这个方法功能就是 从这个实例中找不去从它的原型中找,如果找到这个属性,就说明有这个属性,就返回true.
获取实例的属性的详细信息?方法?
Object.getOwnPropertyDescriptor(obj, propertyname);
注意: 这个方法中propertyname 只能是实例自己的,不能写原型中的属性。
原型与in for in 操作符。
无论是在实例中还是在原型中有指定名字的属性存在时返回true.
for in 遍历实例和原型中的属性。enumerable 是false的属性不显示略过。
constructor 和prototype的enumerable特性为false
Object.keys() 方法只返回实例属性;不返回它原型上的属性。但必须是可枚举的!!
我想得到所有的实例属性(不包括原型上的属性),不管它是否可枚举?
Object.getOwnPropertyNames();
function Person() {
}
Person.prototype.name = "xiaoming";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function () {
alert(this.name);
}
var person1 = new Person();
person1.name = "xiaowang";
for( var i in person1){
console.log(i, person1[i]);
}
Object.defineProperty(person1, 'name',{
//Object.defineProperty(person1, 'love',{
enumerable : false,
//enumerable : true,
value : 'xiaoli'
});
for( var i in person1){
console.log(i,person1[i]);
}
//2.
var person2 = {};
person2.name='zhangsan';
Object.defineProperty(person2,'name',{
enumerable : false,
value:'liang'
});
for( var i in person2){
console.log(i, person2[i]);
}
Object.getOwnPropertyNames() 例子
function Person() {
}
Person.prototype.name = "xiaoming";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function () {
alert(this.name);
}
var person1 = new Person();
var keys1 = Object.getOwnPropertyNames(person1);
console.log(person1.constructor);
console.log('keys1:',keys1); //[]
//2.
var person2 = {};
person2.name='zhangsan';
Object.defineProperty(person2,'name',{
enumerable : false,
value:'liang'
});
for( var i in person2){
console.log(i, person2[i]);
}
var keys = Object.keys(person2);
console.log('keys:', keys); //[]
var names = Object.getOwnPropertyNames(person2);
console.log('names:',names); //["name"]
console.log(person2.constructor); //ƒ Object() { [native code] }
在定义构造函数时,我想少写一些prototype 该怎么做? 重写原型对象{} 注意一些陷阱,容易错的地方。
//p172更简单的原型语法
function Person(){
}
Person.prototype = {
name : 'zhao',
age : 29,
job : 'software',
sayName : function(){
alert(this.name);
}
};
var friend = new Person();
alert(friend instanceof Object); //true
alert(friend instanceof Person); //true
alert(friend.constructor == Person); //false
alert(friend.constructor == Object); //true 这个constructor 搜索的是friend的原型的原型(Object)的constructor
console.log(friend);
分析1 为什么friend.constructor 指向Object构造函数。因为这个constructor 搜索到的是frend的原型的原型(Object)的constructor属性。
2 为什么friend 使用instance of 依然可以判断是 Person 的实例,猜测原因是: 是从new的词法分析出来的不是从属性出来的
严谨的写法如下:在原型上补上constructor属性
Object.defineProperty(Person.prototype, "constructor",{
enumerable : false,
value : Person
});
注意不要在实例过对象后,完全重写构造函数的原型,你可以补充一些属性和方法这都没有问题!!!。完全重写后之前创建的对象和新原型之前没有必然联系了!!!避免这种不好的做法
原生对象(Object, Array, String,等等)的原型,Object(); Array();和自己写的构造函数 new Person() 套路一样。
Array.prototype.sort; String.prototype.substring; 尽管可以给原生构造函数的原型添加新方法或属性但是不建议这么做!!!没有理由非要这么做,总有其他更好的办法的。
构造函数原型方式创建对象有什么问题?
答:共享!共享是一把双刃剑。现实的情况,或大多的需求是,函数可以共享,属性全部是非引用类型也没问题(如String Number Undefined Null);但如果属性是引用类型如(Object , Array, 或者其他对象)。 问题就来了,因为大多数情况下属性都是私有的,各个实例都是互不影响的。所以不能把这样的属性定义在原型上没什么意义,还会出错。那么那些非引用类型的属性定义在原型上也没啥意义。如果是共享就有意义(各个实例不能修改)否则,各个实例能修改,虽然不报错,不出逻辑错误,但这时属性相当于给一个默认值。 总结:共享的属性和 函数放在原型上。 私有的属性(即便是非引用类型的属性//写了也不会出现逻辑错误)的也不要放在原型上!!!养成好的习惯。
说了这么多合理的使用原型来创建对象的方式如下: 结合了构造函数内部写私有属性,和原型上写共享的属性和函数。组合使用构造函数和原型模式才是最常用的方式!!!!和适用大多数的需求。 单纯写构造函数和原型方式创建对象都不常见。
按说我写一个对象里边有属性有方法,和写一个构造函数有属性有方法?有什么差别呢?其中一条区别是。构造函数可以创建大量对象。而字面量方式只是一个对象。而且通过构造函数创建的对象是有类型的有组织的!
6. 组合使用构造函数和原型模式 (最常用的模式)
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ['xiaomi','xiaoli'];
}
Person.prototype = {
constructor:Person,
sayName:function(){
alert(this.name);
}
}
var person1 = new Person('zhao',28,'software');
var person2 = new Person('li',27,'doctor');
person1.friends.push('xiaow');
alert(person1.friends);
alert(person2.friends);
alert(person1.friends === person2.friends);//false
alert(person1.sayName === person2.sayName);//true
//目前是使用最广泛,认同最高的一种创建自定义类型的方法。可以说,这是用来定义引用类型的一种默认模式!!!
是 new Person() 才创建原型的吗? 还是把构造函数定义读完就创建了?
事实上通过下面例子,可以看出把构造函数执行完,原型就创建了!
另一个问题在 new Person创建实例的时候它的__proto__属性是什么时候被赋值上的? 事实证明在执行代码时,就被赋值了,也可能刚new就有了。
//p172更简单的原型语法
function Person(){
console.log('in Person');
}
console.log(Person.prototype); //{constructor: ƒ}
Person.prototype = {
name : 'zhao',
age : 29,
job : 'software',
sayName : function(){
alert(this.name);
}
};
console.log(Person.prototype)
//
//
Object.defineProperty(Person.prototype, "constructor",{
enumerable : false,
value : Person
});
console.log(Person.prototype); //{name: "zhao", age: 29, job: "software", sayName: ƒ, //constructor: ƒ}
7. 动态原型模式:
对组合构造函数和原型这样创建对象模式的美化。
实际上这样已经能满足很多需求了,但是又强迫证的人觉得两坨代码感觉不够“封装”,怎么搞到一个大括号里就好了。
//p172更简单的原型语法
function Person(name,age,job){
console.log('in:',this.__proto__);
//属性
this.name = name;
this.age = age;
this.job = job;
if(typeof this.sayName != 'function'){
// Person.prototype.sayName = function(){
// alert(this.name);
// }
Person.prototype = {
sayName : function () {
console.log('hi')
}
}
}
}
var friend = new Person('zhao',28,'software');
//friend.sayName(); //未定义这时候,原型还没有这个方法。从侧面说明,在开始执行代码前实例的__proto__已经先被赋值了。
console.log(friend.name);
var friend2 = new Person('lisi',17,'teacher');
console.log(friend2.name); //第一个实例执行完后。原型就有sayName方法了
friend2.sayName();
分析例子代码: 任何时候都不要重写原型!!!没有充分理由啊,违反这个原则简直自己给自己找事!。
8. 寄生构造函数模式 和工厂模式一样的。不常用
和工厂方式唯一形式上的不同是使用寄生构造函数使用new。
函数内部使用原生 构造函数 如 Object() Array()来创建。
//p178 寄生构造函数模式
function Person(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 friend = new Person('zhao',28,'software');
friend.sayName(); //zhao
//这个模式其实和工厂模式是一模一样的。
//首先,返回的对象与构造函数或者与构造函数的原型属性之间没有关系。这种模式不建议!!!
9. 稳妥构造函数模式
不使用this ,不new 。和工厂模式一样。概念上它强调安全。
继承:
只支持实现继承,而且实现继承主要是依靠原型链。
构造函数, 原型 , 和 实例的关系?
每个构造函数都有一个原型;
原型对象都包含一个指向构造函数的指针;
实例都包含一个指向原型对象的内部指针;
演化为原型链,怎么演化为原型链?
如果让原型对象等于另一个类型(new 构造函数)的实例;
更甚至: 另一个类型的原型又是另另一个类型的实例;
等等。。。。。
如此形成了原型链
//6.3继承
//原型链的概念
function SuperType(){
this.property = true;
}
console.log(SuperType.prototype);
SuperType.prototype.getSuperValue = function(){
return this.property;
}
function SubType(){
this.subproperty = false;
}
console.log(SubType.prototype);
//继承了SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function(){
return this.subproperty;
}
console.log(SubType.prototype);
var instance = new SubType();
//alert(instance.getSuperValue());//true;
console.log(instance);
//理解图:p181
//别忘了默认的原型
//所有函数的默认原型都是Object的实例。
//完整的原型链的图:p182
//有两种方法来确定原型和实例之间的关系。一是用instanceof ,二是用:isPrototypeOf()
console.log(Object.prototype.isPrototypeOf(instance));
console.log(SuperType.prototype.isPrototypeOf(instance));
console.log(SubType.prototype.isPrototypeOf(instance));
console.log(instance instanceof Object);
console.log(instance instanceof SuperType);
console.log(instance instanceof SubType);
还是那句话:不要重写原型没有理由必须这样做的。
原型链的问题? new 一个类型 的实例 作为子类的原型 的问题? 最原生自然的原型链存在的问题?
就是因为 new 父类,所以在实例中有属性,其实这个属性我们不想放在这里,不想共享,并且如果有引用类型的属性(并且我们不想共享公用)就会出现矛盾,这个矛盾必须解决,需求要求必须解决。
第二个问题:在创建子类型实例,new 时不能向超类的构造函数中传递参数。当然我们可以在new 后 重写实例的属性,来改变。这么着不雅观。
实践中是不会用这种原始,最自然的原型链来 作为继承解决方案的。
技巧:
借用构造函数:constructor stealing
一句话: 在子类中构造函数内部调用超类型构造函数。
//借用构造函数,很少单独使用!!!
function SuperType(name){
this.colors = ["red", "blue", "green"];
this.name = name;
}
function SubType(){
//继承了SuperType,同时还传递了参数
SuperType.call(this,'zhao');
//实例属性
this.age = 29;
}
var instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.name);
console.log(instance1.age);
console.log(instance1.colors);
var instance2 = new SubType();
console.log(instance2.colors);
通过这种方式把把子类和超类定义的属性全部集中到 子类型的实例中。
但是他的问题是它从属性出发,没有考虑函数公用的问题。很少单独使用!
组合继承:
将原型链和借用构造函数组合到一块。思路是手段是: 使用原型链实现对属性和方法的继承,使用借用构造函数来实现把属性都集中到实例的属性上。实现私有。
//p186 组合继承,也称为经典继承,值的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。使用原型链实现对原型属性和方法的继承,而通过借用构造构造函数来实现对实例属性的继承。
function SuperType(name){
this.name = name;
this.colors = ['red','blue','green'];
}
SuperType.prototype.sayName = function(){
alert(this.name);
}
function SubType(name,age){
SuperType.call(this,name);
this.age = age;
}
SubType.prototype = new SuperType();
//SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
}
var instance1 = new SubType('zhao',28);
instance1.colors.push('black');
console.log(instance1.colors);
instance1.sayName();
instance1.sayAge();
var instance2 = new SubType('liang',27);
console.log(instance2.colors);
instance2.sayName();
instance2.sayAge();
//JavaScript中最常用的继承模式。而且instanceof和isPrototypeOf()也能够识别基于组合继承创建的对象
组合继承看起来比较完善,子类实例完全拥有属性。方法公用。但是还是有问题,就是实例的原型(超类的实例)还是有属性的。如果能去除就更好了。
原型式继承:/对象式继承/改变实例的原型指向的 这种继承
基于已有的对象创建新对象,同时还不必因此创建自定义类型。
没有自定义类型,或说不关注它的类型。
使用系统Object.create() 函数
//p189.较为简洁的继承---原型式继承,功能不如组合继承强大
function object(o){
function F(){}
F.prototype = o;
return new F();
}
var person = {
name:'zhao',
friends:['xiaom','xiaoli','xiaow']
};
var anotherPerson = object(person);
anotherPerson.name = 'zhao1';
anotherPerson.friends.push('rob');
var yetAnotherPerson = object(person);
yetAnotherPerson.name = 'zhao2';
yetAnotherPerson.friends.push('bar');
console.log(person.friends);
console.log(person.name);
console.log(yetAnotherPerson.name);
console.log(yetAnotherPerson.__proto__);
var sub = new Object();
console.log(sub.__proto__);
sub.__proto__ = person; //这么做,也可以但是兼容性肯定不好,非主流做法。
console.log(sub.__proto__);
console.log(sub);
//Object.create()方法
var person = {
name:"zhao",
friends:['xiaom','xiaoli','xiaow']
};
var son = Object.create(person,{
name:{
value:'liang2'
}
});
console.log(son.name);
console.log(person.name);
console.log(son.friends);
//在没有必要兴师动众的创建构造函数,而只是想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的。不过。包含引用类型的值属性始终都会共享相应的值,就像使用原型模式一样。
原型继承/对象式继承适用范围:
在没有必要兴师动众地创建构造函数,而是想让一个对象有另一个对象的属性,还可以另外增加自己的属性或者覆盖父对象的属性。
寄生式继承。
与原型模式相关,是对它的一种加强和封装,用来批量创建子类对象。
寄生式继承与 寄生式构造函数, 工厂模式模式 类似。不 new.
//寄生式继承,与原型式继承紧密相关的一种思路
function object(o){
function F(){}
F.prototype = o;
return new F();
}
function createAnother(original){
var clone = object(original); //通过调用函数创建一个新对象
clone.sayHi = function(){//以某种方式增强这个对象
alert('hi');
};
return clone;
}
var person = {
name:'zhao',
friends:['xiaom','xiaoli','xiaow']
}
var anotherPerson = createAnother(person);
console.log(anotherPerson.friends);
anotherPerson.sayHi();
//在主要考虑对象而不是自定义类型的和构造函数的情况下,寄生模式继承也是一种有用的模式。
//使用寄生模式来为对象添加函数,会由于不能做到函数复用而降低效率,这一点与构造函数类似
问题:子类无类型,如果给子类添加函数,这样的函数不能公用。实际中用的不多。
寄生组合式继承
组合继承的问题是: 子类的原型中还是会出现属性,没有就好了,使用寄生式模式来创建子类的原型,就可以解决这个问题。
//寄生组合式继承
//组合继承的问题,无论什么情况都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。
function inheritPrototype(subType,superType){
var prototype = Object.create(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
function SuperType(name){
this.name = name;
this.colors = ['red','blue','green'];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name,age){
SuperType.call(this,name);
this.age = age;
}
inheritPrototype(SubType,SuperType);
SubType.prototype.sayAge = function(){
alert(this.age);
};
var person = new SubType('zhao',25);
console.log(person.name);
console.log(person.age);
person.sayName();
person.sayAge();
console.log(person);
//这个例子的高效性,体现在他只调用了一次SuperType构造函数,因此避免了在SubType.prototype上面创建不必要的,多余的属性。还能使用instanceof和isPrototypeOf();
//开发人员普遍认为寄生组合继承是引用类型最理想的继承范式。
寄生组合继承是最理想最常用的继承模式!!!