1.创建对象
我们知道,创造对象有很多种方法:
1.1 比如最原始的工厂模式:
1.2 然后是构造函数模式:
以上两种方式不同的地方在于:
1.构造函数模式没有显式地创建对象;
2.构造函数模式直接将属性和方法赋给了this对象;
3.构造函数没有return语句
那么,要创建Person的新实例,必须使用new
操作符,使用这种方式调用构造函数实际上经历了哪些步骤呢?
1.创建一个新对象;
2.将构造函数的原型对象赋给新对象的原型(继承原型的属性方法);
3.在新对象的作用域环境下执行构造函数代码(添加实例属性);
4.返回新对象
{
let obj = {};
obj._proto_ = Person.prototype;
let result = Person.call(obj,"dan",20);
return typeof result === 'object'? result : obj;
}
构造函数的问题:
每个Person的实例都会有一个同名的Function实例,虽然同名,但都是由Person的每个实例创建的,所以它们不相等,同时也没必要创建这么多具有相同功能的同名函数
1.3 原型模式:
每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性,如果在实例中找到了这个属性,则返回该属性的值;如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。
我们可以通过对象实例访问保存在原型中的值,但是不能通过对象实例重写原型中的值,比如对
person1.name = null
执行语句后,会在person1
这个实例中新建一个实例属性,值为null
,再次访问name时,由于实例中存在这个属性,它不会再往原型上查找,即使这个实例属性的值为null
检测一个属性在原型中还是实例中:
//判断一个对象中是否有某属性 in 和 obj.hasOwnProperty()
//如果obj实例中或其原型对象上有"name"属性,都会返回true
"name" in obj ;
obj.hasOwnProperty('name'); //只有obj实例中有 "name",才会返回true
//所以可以结合以上两个来确定一个属性是否只存在于原型中
!obj.hasOwnProperty('name') && ("name" in obj)
Object.keys(obj) //此方法返回obj自身所有的可枚举属性,不包括obj的原型对象上的
Object.getOwnPropertyNames(obj) //此方法返回obj自身所有的实例属性(不包括obj的原型对象上的),不论是否可枚举,如constructor
1.4 组合使用构造函数模式和原型模式:
创建对象最常见的方式,就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性
,而原型模式用于定义方法和共享的属性
function Person(name,age){
this.name = name;
this.age = age;
this.friends = ["Shelby","Court"];
}
Person.prototype.sayName = function(){
alert(this.name);
};
2.继承
继承有以下几种方式
2.1原型链:
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
}
function SubType(){
this.subproperty = false;
}
// 继承了 SuperType
SubType.prototype = new SuperType(); // 执行这一步时,相当于重写 SubType.prototype
原型链的问题:
- 最主要的问题来自包含引用类型值的原型
- 在创建子类型的实例时,不能向父类型的构造函数中传递参数
确定原型和实例的关系:
instanceof
isPrototypeOf
SubType instanceof SuperType
SuperType.prototype.isPrototypeOf(subType)
这里顺便讲一下instanceof
实现的原理
//用一个函数实现instanceof的功能
function instanceofFnc(left,right){
let prototype = right.prototype;
let proto = left.__proto__;
while(true){
if(proto === prototype)
return true;
if(proto === null)
return false;
//如果以上判断都没有结果,则继续往上查找
proto = proto.__proto__;
}
}
2.2借用构造函数(经典继承/伪造对象)
2.3 组合继承(伪经典继承)
将原型链和借用构造函数技术组合在一块,从而发挥二者之长的一种继承模式。
function SuperType(name){
this.name = name;
this.colors = ["name", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
// 继承属性
Supertype.call(this,name); // 第二次调用 SuperType()
this.age = age;
}
// 继承方法
SubType.prototype = new SuperType(); // 第一次调用 SuperType()
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
instance1.colors; // "red,black,green,black"
instance1.sayName(); // "Nicholas"
isntance1.sayAge(); // 29
var instance2 = new SubType("Greg", 27);
instance2.colors; // "red,black,green"
instance2.sayName(); // "Greg"
isntance2.sayAge(); // 27
组合继承的问题是,两次调用了父类型的构造函数,这样会造成 子类型的实例和原型上具有相同的属性 的问题
2.4 原型式继承
es5中的Object.create()
方法就是原型式继承的一种实现,在只传入了一个参数的情况下,实际上就是对其进行了一次浅复制
。
//用一个函数来实现这个方法只传一个参数的功能
function object(o){
function F(){}
F.prototype = o;
return new F();
}
//至于传两个参数的用法,可以再自行搜索一下
2.5 寄生式继承:
寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像是它做了所有工作一样返回对象。
2.6 寄生组合式继承:
寄生组合式继承即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。基本思路是:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后将结果指定给子类型的原型。
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);
};