对代码的理解全部写到了注释中。
var person = { //使用字面量方法创建对象。
name: "Steve",
toString: function(){
return this.name;
}
}
console.log(person); //Steve
Object.defineProperty(person, "name", { //修改对象的属性:Object.definePreperty
value: "Erison",
writable: false, //writable:定义对象是否可写。
configurable: false //configurable:定义对象是否可删除。
});
person.name = "Steve";
delete person.name;
console.log(person); //输出:Erison。分析:1.由于writable属性是false,所以属性没有被修改。
//2.由于configurable属性是false,所以属性没有被删除
Object.defineProperty(person, "name",{
configurable: true //抛出错误,因为configurable属性已经变成false(即对象不可配置),不能再修改。
}); //也就是说将configurable特性设置为false以后,再去修改属性就会有限制。
var person = {
_name: "Steve", //加下划线代表一个只能被函数中方法访问的值,用以和name作区分。
fullname: "SteveJobs"
}
Object.defineProperty(person, "name", {
get: function(){
return "name:"+this._name;
},
set: function(value){
this._name = value;
this.fullname = value+"Jobs";
}
});
console.log(person.name); //name:Steve——通过get方法输出。
person.name = "Erison";
console.log(person.fullname); //ErisonJobs——修改name属性用了访问器属性中的set方法,所以将fullname也修改了。
Object.defineProperty(person, "name", {
get: function(){
return "name:"+this._name;
}
});
person.name = "Steve"; //报错:因为在修改访问器属性时没有添加set方法。
//Object.defineProperties():定义多个属性。
var person = {
_name: "Steve", //_表示只能通过对象方法访问的属性。
fullname: "SteveJobs"
}
//定义访问器的旧有方法: 对象.__defineSetter__和 对象.__defineGetter__
//(传入值:第一个字符串,表示属性名,第二个函数,表示get或者set方法)
person.__defineSetter__("name",function(value){ //使用访问器的旧有方法定义getter和setter
this._name = value;
this.fullname = value+"Jobs";
});
person.__defineGetter__("name", function(){
return "name:"+this._name
});
console.log(person.name); //SteveJobs
person.name = "Erison";
console.log(person.fullname); //ErisonJobs
//Object.defineProperties方法:定义多个属性
var person = {};
Object.defineProperties(person,{ //定义多个属性。属性之间用逗号分隔。
_name: { //数据属性//不写get方法就输出,是undefined
value: "Steve",
writable: true //将writable设置为true:可以修改数值。
},
fullname: {
value: "SteveJobs",
writable: true
},
name: { //访问器属性。
get: function() {
return this._name;
},
set: function(value) {
console.log(value);
this._name = value;
console.log(this._name);
this.fullname = value+"Jobs";
}
}
}
);
console.log(person.name+"::"+person.fullname); //"Steve::SteveJobs" 用defineProperties定义的属性
person.name = "Erison";
console.log(person.name+"::"+person.fullname); //"Erison::ErisonJobs" 使用defineProperties定义了的set构造了fullname。
//属性描述器
var person = {};
person.name = "Steve";
var descriptor =
Object.getOwnPropertyDescriptor(person, "name"); //取得给定属性的描述器,内含属性。
console.log(descriptor.value); //Steve
console.log(descriptor.configurable); //true
console.log(descriptor.get); //undefined——是一个指向get函数的指针。
//工厂模式:
function createPerson(name,age,job){ //创建一个可以创造person对象的工厂。
var person = {
name: name,
age: age,
job: job,
introduce: function(){
return name+"..."+age+"..."+job;
}
};
return person;
}
var p1 = createPerson("zhangsan",20,"cleaner");
console.log(p1.introduce()); //zhangsan...20...cleaner
//问题:没有解决对象识别的问题(不知道p1是什么类型的对象)
//构造函数模式:
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.introduce = function(){
return name+"..."+age+"..."+job;
};
}
var p2 = new Person("Steve",57,"Aritist"); //用构造函数新建Person实例
console.log(p2.introduce()); //Steve...57...Manager
console.log(p2 instanceof Object); //true
console.log(p2 instanceof Person); //true 因为p2既是Object实例又是Person实例。
Person("Erison",40,"Manager"); //直接调用相当于将Person添加到全局对象中(因为在全局中调用,构造函数中的this就相当于window对象。)
console.log(introduce()); //Erison...40...Manage
var o = new Object();
Person.call(o,"Bill",60,"CEO"); //用call方法,将o设为环境对象。与上一个方法相比,将Person添加到了o中。
console.log(o.introduce()); //"Bill...60...CEO"
console.log(introduce() == o.introduce());
//false。因为用构造函数创建的不同实例上同名函数是不相等的,无法实现方法的复用,等于创建了多个introduce实例
//解决方法:将构造函数内的方法定义在函数之外。
function introduce(){
return name+"..."+age+"..."+job;
}
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.introduce = introduce;
}
var p1 = new Person("Steve",57,"Artist");
var p2 = new Person("Erison",50,"Manager");
console.log(p1.introduce === p2.introduce); //true:只创建了一个introduce实例。
//仍然存在的问题:将只能给某个对象使用的函数添加到全局对象中,封装性太差(容易犯代码洁癖)。
//原型模式
function Person(){
Person.prototype.name = "Steve"; //通过修改原型的属性来创建构造函数
Person.prototype.job = "Artist"; //每个构造函数自带一个原型
Person.prototype.age = 57;
Person.prototype.introduce =
function(){
return this.name+"..."+this.age+"..."+this.job;
}
}
var p1 = new Person();
var p2 = new Person();
console.log(p1.introduce()+":::"+p2.introduce()); //"Steve...57...Artist:::Steve...57...Artist",调用了原型里的属性
console.log(p1.introduce == p2.introduce); //true,都是原型中的introduce方法,解决了复用问题。
p1.name = "Erison";
p1.age = "50";
p1.job = "Manager";
console.log(p1.introduce()+":::"+p2.introduce()); // Erison...50...Manager:::Steve...57...Artist
// 来自实例 来自原型
// 定义p1中的属性,将属性添加到了p1实例中并屏蔽了原型中的属性,但是原型 //中的属性并没有被修改。
// 执行方法过程:查找p1中是否存在introduce方法→否
// 查找p1中是否存在指向原型的指针→是,进入原型
// 查找p1的原型中是否存在introduce方法→是,调用
// 查找p1中是否存在需要的属性→是(多次)
console.log(Object.getPrototypeOf(p1) == Person.prototype); //true——Object.getPrototypeOf() :查找实例的原型。判断出p1的原型是Person.prototype
console.log(Person.prototype.isPrototypeOf(p1)); //true——obj.isPrototypeOf(obj) :判断参数的原型是不是指定对象。
delete p1.name;
console.log(p1.name); //Steve:因为删除了实例中的属性,所以恢复了对原型中"Steve"的链接
console.log(p1.hasOwnProperty("age")); //true 判断该实例中是否含有自己的实例属性age。
var person = {
name: "Steve"
};
console.log("name" in person); //true:in表示实例内是否有指定属性
function Person(){
Person.prototype.name = "Steve";
Person.prototype.job = "Artist";
Person.prototype.age = 57;
}
var p1 = new Person();
console.log("name" in p1); //true:如果指定属性在原型中,也返回true
console.log(p1.hasOwnProperty("name")); //false:说明属性在原型中(中使用hasOwnProperty和in可以确定属性的位置)。
//函数:
function hasPrototypeProperty(obj,name){ //判断属性是否在原型中的函数。
return (name in obj) && !(obj.hasOwnProperty(name)); //如果能查询到name属性(表示在原型中或在实例中)而且不在实例中,说明属性在原型中。
}
console.log(hasPrototypeProperty(p1,"name")); //true
p1.name = "Erison";
console.log(hasPrototypeProperty(p1,"name")); //false,原型中的属性被实例中的屏蔽。
//IE中的BUG:屏蔽原型中不可枚举的实例属性不会出现在for-in循环中。
// 该BUG会影响默认不可枚举的所有属性和方法。
// 包括hasOwnProperty(),propertyIsEnumerable(),toLocaleString(),toString(),valueOf()
var o ={
toString: function(){ //复写(屏蔽)了原型中的toString属性(原型中的不可枚举,但复写的可枚举)
return "toString";
}
}
for(var prop in o){
if(prop == "toString"){ //表示找到了toString方法
console.log("toString founded"); //如果是在IE中则不会显示(因为上面所说的BUG)
}
}
//取得对象上所有可枚举属性:Object.keys();
function Person(){
Person.prototype.name = "Steve";
Person.prototype.age = 50;
Person.prototype.job = "Artist";
}
var keys = Object.keys(Person.prototype);
console.log(keys); //name,age,job
var p1 = new Person();
p1.name = "Erison";
keys = Object.keys(p1);
console.log(keys); //"name" 通过实例调用,则只有实例的属性。
//无论可否枚举,获取所有属性:object.getOwnPropertyNames();
var names = Object.getOwnPropertyNames(Person.prototype);
console.log(names); //"constructor,name,age,job" 不可枚举的constructor也被列出。
//更简单的原型语法。
function Person(name){
this.name = name;
}
var p = new Person();
console.log(p.constructor); //指向了Person
//使用对象字面量重写原型:简便。
function Person(){
}
Person.prototype = {
name: "Steve",
age: 57,
job: "Artist"
}
p = new Person();
console.log(p.constructor); //指向了Object的构造函数:因为在使用对象字面量时没有指定。
//如果constructor非常重要,就在使用对象字面量新建对象时指定。
Person.prototype = {
constructor: Person,
name: "Steve",
age: 57,
job: "Artist"
}
p = new Person();
console.log(p.constructor); //指向了Person,因为在对象字面量中指定过了。
//这样做的另一个问题:constructor属性成为了Enumerable。
//将constructo的Enumerable设置为false。
Object.defineProperty(Person.prototype,"constructor",{ //手动将enumerable设置为false
enumerable: false,
value: Person
});
//原型的动态性
function Person(){
}
var p = new Person(); //即使在定义原型方法之前创建实例,实例中还是会指向原型中新建的方法(因为实例中保存的是指针)。
Person.prototype.sayHi =
function(){
console.log("hi!");
}
p.sayHi();
//特殊情况:修改原型本身时
Person.prototype = {
name: "Steve",
age: 57,
sayHi: function(){
console.log("hi!")
}
}
console.log(p.name); //undefined,因为修改原型本身创建了一个新的原型实例。但p的指针没有改变,仍旧指向旧的原型。
p.sayHi(); //"hi!",因为p指向的旧原型中还有sayHi方法。
var p1 = new Person();
console.log(p1.name); //新创建的p1指向了新的原型实例。
//理解:重写原型对象切断了现有原型与任何已经存在之间的对象实例之间的联系。他们引用的仍然是最初的原型。
//原生对象模型。(Array,String,Object等)
console.log(Array.prototype.sort); //function
console.log(String.prototype.substring()); //function
//还可以像修改自己的原型那样修改原生对象的原型。
String.prototype.startWith = function(text){
return this.indexOf(text) == 0; //向String的原型中添加方法
}
console.log("HelloWorld".startWith("Hello")); //true 为String原型添加了startWith方法。新的字符串对象会搜寻原型中的方法。
//原型对象的最大问题:共享引用类型
function Person(){
}
Person.prototype = {
friends: ["Steve","Erison"]
}
var p1 = new Person();
var p2 = new Person();
p1.friends.push("Bill");
console.log(p2.friends); //"Steve,Erison,Bill"——原型模式最大的问题。修改实例中的引用属性时,连原型对象也一并修改。从而影响了其他实例的属性!
//最常见方式:组合使用构造函数模式和原型模式。构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性。
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["Bill","Jun"];
}
Person.prototype = {
constructor: Person,
introduce: function() {
console.log(this.name+"..."+this.age+"..."+this.job);
}
}
var p1 = new Person("Steve",57,"Artist");
var p2 = new Person("Erison",50,"Maneger");
p1.friends.push("Jack");
console.log(p1.friends); //"Bill,Jun,Jack"
console.log(p2.friends); //"Bill,Jun"
console.log(p1.introduce == p2.introduce); //"true"
//共享了对方法的引用,同时有着自己的实例属性。
//动态原型模式
function Person(name,age,job) {
this.name = name;
this.age = age;
this.job = job;
if(typeof this.introduce != "function") { //判断是否已经存在方法
Person.prototype.introduce = function() {
console.log(name+"..."+age+"..."+job);
};
}
}
var p1 = new Person("Steve",57,"Artist");
var p2 = new Person("Erison",50,"Manager");
p1.introduce();
p2.introduce();
console.log(p1.introduce == p2.introduce);
//true:因为在创建方法之前添加了条件。所以即使调用了两次Person,也只生成了一个构造方法。
//寄生构造函数模式:封装函数的代码,然后返回新创建的对象。
function Person(name,age,job) {
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.introduce= function() {
console.log(this.name+"..."+this.age+"..."+this.job);
}
return o;
}
var p1 = new Person("Steve",57,"Artist");
p1.introduce(); //"Steve...57...Artist"
console.log(p1 instanceof Person); //false:用此种方式创建的对象与构造函数之间没有关系,所以返回对象不是Person类型。
//用途例子:想要创建一个具有额外方法的特殊数组。
function SpecialArray (){
var values = new Array();
values.push.apply(values,arguments);
values.toPipedString = function() {
return values.join("||"); //join方法:用参数分割数组并传出
};
return values;
}
var sa = new SpecialArray("Steve","Erison","Bill");
console.log(sa.toPipedString()); //"Steve||Erison||Bill" 由于不能直接修改Array的构造函数。
//因此可以使用这个模式创建需要的数组(在函数内已经进行了必要的修饰)。
//因为不能用instanceof操作符来确定对象类型,所以最好不要使用这种模式。
//稳妥构造函数模式:新创建的实例对象不引用this,不适用new操作符调用构造函数。
function Person(name,age,job) {
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function() {
console.log(name); //不添加this:即使修改了name属性,调用sayName方法时输出的还是初始传入的name。
}
return o;
}
var friend = Person("Steve", 57, "Artist");
friend.sayName();
friend.name = "Erison";
friend.sayName();
console.log(friend.name);
/*
* 理解:工厂模式和寄生构造函数模式的函数结构一样。
* 但是工厂模式是将函数当作一个普通函数使用,寄生模式将函数当作一个构造函数,要用new才能新建一个对象。
* 稳妥构造函数模式,则是寄生模式在新建方法实例时不用this关键字,创建新对象时不用new关键字,用以保存初始的实例属性。
*/
//继承:
//用原型链实现继承
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
};
function SubType() {
this.subProperty = false;
}
SubType.prototype = new SuperType(); //将父类的实例作为子类的prototype,实现继承。
SubType.prototype.getSubValue = function() {
return this.subProperty;
}
var instance = new SubType();
console.log(instance.getSubValue()); //false:调用了子类中的getSubValue方法。方法在SuperType.prototype中。
console.log(instance.getSuperValue()); //true:调用了父类中的getSuperValue方法。方法在SubType.prototype中。
//但property属性在instance实例中(组合模式)。
console.log(instance.constructor); //SuperType。因为SubType.prototype中的constructor被重写
//搜索方法过程:1.搜索实例
// 2.搜索Subtype.property
// 3.搜索SuperType.property
//找不到就按原型链向上查找,直到找到为止(继承的思想)
//确定原型和实例的关系。
//1.使用instanceof操作符
console.log(instance instanceof Object); //true
console.log(instance instanceof SuperType); //true
console.log(instance instanceof SubType); //true
//2.使用原型对象的.isProtoTypeOf(instance)方法。
console.log(Object.prototype.isPrototypeOf(instance)); //true
console.log(SuperType.prototype.isPrototypeOf(instance)); //true
console.log(SubType.prototype.isPrototypeOf(instance)); //true
//原型链:SubType.prototype→SuperType.prototype→Object.prototype
//重写超类方法
function SuperType() {
superValue = true;
}
SuperType.prototype.getSuperValue = function() {
return this.superValue;
}
function SubType() {
subValue = false;
}
SubType.prototype = new SuperType();
SubType.prototype.getSuperValue = function() { //重写SubType原型中的getSuperValue方法,会导致原型链中的getSuperType方法被屏蔽。
return false;
};
var sub = new SubType(); //继承了SuperType
console.log(sub.getSuperValue()); //false:使用了重写的getSuperValue方法
//用原型链实现继承时,不能用字面量方法创建原型方法。因为会重写原型链。
function SuperType() {
superValue = true;
}
SuperType.prototype.getSuperValue = function() {
return this.superValue;
}
function SubType() {
subValue = false;
}
SubType.prototype = new SuperType(); //继承了SuperType
SubType.prototype = {
getSubValue: function() {
return this.subValue;
},
someOtherMethod: function() {
return false;
}
};
var instance = new SubType();
console.log(instance.getSuperValue()); //Error:instance.prototype被重写,不再指向SuperType.prototype。原型链被切断,所以找不到超类中的方法。
//原型链的问题:1.创建子类型的实例时,不能向超类型构造函数提供参数。 2.引用类型值会被共享。
function SuperType() {
this.colors = ["red","blue","green"];
};
function SubType() {};
SubType.prototype = new SuperType();
var sub1 = new SubType();
var sub2 = new SubType();
sub1.colors.push("yellow");
console.log(sub2.colors); //"red,blue,green,yellow" 因为colors是原型(原来的实例)中引用对象属性。
//为colors添加的方法可以通过subType的所有实例反映出来(强行被共享)。
//借用构造函数(经典继承):在子类型构造函数的内部调用超类型构造函数。
function SuperType() {
this.colors = ["red","blue","green"];
}
function SubType() {
SuperType.call(this); //运用call函数在SubType环境内调用SuperType的构造函数。
}
var sub1 = new SubType();
var sub2 = new SubType();
sub1.colors.push("yellow");
console.log(sub1.colors); //"red,blue,green,yellow"
console.log(sub2.colors); //"red,blue,green"
//由于是在新建实例的时候在实例中调用超类型的函数,所以每个实例都有自己的colors副本
//借用构造函数的问题:方法都在构造函数中定义,每个函数都是一个新实例,所以无法实现函数的复用。
//组合继承(伪经典继承):JavaScript中最常用的继承模式。通过原型链实现对原型属性和方法的继承,通过借用构造函数实现对实例的继承。
function SuperType(name) {
this.name = name;
this.colors = ["red","blue","green"];
}
SuperType.prototype.sayName = function() { //用SuperType的原型定义sayName方法。
console.log(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() {
console.log(this.age);
};
var ins1 = new SubType();
var ins2 = new SubType();
console.log(ins1.colors == ins2.colors); //false
console.log(ins1.sayAge ==ins2.sayAge); //true
console.log(ins1.sayName == ins2.sayName); //true
//既拥有了自己的实例属性,又拥有了共享的方法。避免了两种方法的缺陷,融合了他们的优点。
//原型式继承:基于已有对象创建新对象。当想让一个对象与另一个对象保持类似时使用。
function object(obj) { //由函数object负责创建一个和传入的对象obj相同的对象的对象。
function F(){}
F.prototype = obj;
return new F();
}
var p1 = {
name: "Steve",
friends: ["Erison","Bill","Bob"]
};
var p2 = object(p1);
console.log(p1.friends);
p2.friends.push("Dell");
console.log(p1.friends); //Erison,Bill,Bob,Dell
//ECMAScript5通过新增Object.create()方法规范化了原型式继承。
var p3 = Object.create(p1);
console.log(p3.friends); //Erison,Bil,Bob,Dell(相当于通过p1创建了新实例)
//可以传入第二个参数:为新对象定义额外属性。
var p4 = Object.create(p1,{
job: {
value: "Artist" //声明属性的方式与defineProperties一样。
}
});
console.log(p4.job); //"Artist"
//好处:快捷。只想让一个对象与另一个保持类似的时候,原型式继承完全可以胜任。不过像原型模式一样,所有实例的引用值类型都会共享。
//寄生式继承。与工厂模式类似:封装创建函数的方法并传入一个实例并返回另一个实例。
function createAnother(original) {
var clone = object(original);
clone.sayHi = function() {
console.log("hi!");
};
return clone;
}
var person = {
name: "Steve",
job: "Artist"
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //hi!返回了一个被方法修饰过的,与person相似的对象。
//使用寄生式继承,不能复用函数,和构造函数模式类似。
//寄生式组合继承:不为子类型的原型调用超类型的构造函数,而是使用寄生式继承来继承超类型的原型,再将结果指定给子类型的原型。
//优点:避免了两次调用超类型的构造函数。
function object(obj) { //原型式继承函数
function F(){}
F.prototype = obj;
return new F();
}
function inheritPrototype(subType,superType) { //寄生组合式继承函数,用此函数替代组合继承中新建实例过程。
var prototype = object(superType.prototype); //创建对象
prototype.constructor = subType; //增强对象
subType.prototype = prototype; //指定对象(其实是修改指针的过程)
}
function SuperType(name) {
this.name = name;
this.colors = ["red","blue","green"];
}
SuperType.prototype.sayName = function() {
console.log(this.name);
}
function SubType(name,age) {
SuperType.call(this,name,age);
this.age = age;
}
inheritPrototype(SubType,SuperType); //用寄生式继承的方法,替代了组合继承中新建实例过程。
SubType.prototype.sayAge = function() { //为子类新添加方法。
console.log(this.age);
};
var sub = new SubType("Steve",57);
sub.sayName(); //Steve:继承了超类的方法。
*/
//高效率:只调用了一次SuperType构造函数,避免了在SubType.prototype上创建不必要的属性。
/*
* 对于继承方法的理解:
* 最基本的两种继承方法:
* 1.原型链,通过将子类的原型指向父类的实例,实现原型共享
* 缺陷:无法做到方法独享,修改了一个子类的引用方法以后,其他所有子类的引用方法也被修改。
* 2.引用构造函数:通过在子类中调用父类的构造函数,实现继承。
* 缺陷:无法实现函数复用。(例:在一个子类实例中增加一个函数,要想要其他子类也实现,还要在其他子类的构造函数中依次添加函数。)
*
* 由此,推出了组合继承:将独享的属性用构造函数实现,共享的方法用原型链实现。
* 这是一种几乎完美的继承!但存在一个小bug,就是继承过程中调用了两次父类的构造函数。
* 由此追(you)求(qiang)完(po)美(zheng)的程序员们又推出了“完美”的继承:
* 使用一个函数返回一个父类原型的复制体,将复制体的constructor指向子类,再将这个复制体作为子类的原型后,就无瑕疵地实现了继承。
*/