js自学笔记--面向对象

Posted 吕松松

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了js自学笔记--面向对象相关的知识,希望对你有一定的参考价值。

前言:ECMA-262将对象定义为:"无序属性的集合,其属性可以包含基本值,对象或者函数"。这就相当于说对象是一组没有特定顺序的值。对象的每个属性或方法都有一个名字,而每个名字都映射到一个值。正因为这样,我们可以把ECMAScript的对象看成散列表:一组名值对,值可以是数据或函数。(每个对象都是基于一个引用类型创建的)

对象理解

1.属性类型

ECMAScript中有两种属性:数据属性和访问器属性。
1.数据属性
数据属性包含一个数据值的位置,在这个位置可以读取和写入值。数据属性有4个描述其行为的特性
  • [[Configurable]]:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。(直接在对象上定义的属性,这个特性为true)
  • [[Enumerable]]:表示能否通过for-in循环返回属性。(直接在对象上定义的属性,这个特性为true)
  • [[Writeable]]:能否修改属性的值。(直接在对象上定义的属性,默认true)
  • [[Value]]:包含这个属性的数据值。读取属性值时,从这个位置读;写入属性值时,把新值保存在这个位置。默认值为undefinde。
	var person ={
	 	name:"zhangsan"
	 };

创建了一个名为name的属性,值是zhangsan,[[Value]]特性被设置为"zhangsan",对这个属性的修改都会反映在这个位置

使用Object.defineProperty()修改属性的特性:接收三个参数:属性所在的对象,属性名,描述符对象(对象属性为:configurable,enumerable,writable,value)

	var person ={
	 	name:"zhangsan"
	 };
	 Object.defineProperty(person,"name",{
	 	writable:false,   //设置name属性不可修改
	 	value:"lisi"      //设置name属性值为 lisi
	 });	
	 alert(person.name);// lisi
	 person.name = "wangwu";
	 alert(person.name); // lisi

注意:一旦将属性定义为不可配置,就不能再把它变回可配置了。(再调用Object.defineProperty()方法修改除writable之外的特性,都会报错)

	var person ={
	 	name:"zhangsan"
	 };
	 Object.defineProperty(person,"name",{
	 	configurable:false,  //设置name属性不可修改
	 	value:"lisi"      //设置name属性值为 lisi
	 });	
	Object.defineProperty(person,"name",{
		configurable:true,   //报错  Cannot redefine property: name
		value:"wangwu"
	});

2.访问器属性

访问器属性不包含数据值;它们包含一对getter和setter函数(都不是必需的):4个特征

  • [[Configurable]]:能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性。(直接在对象上定义的属性,默认true)。
  • [[Enumerable]]:能否通过for-in循环返回属性。(直接在对象上定义的属性,默认true)
  • [[Get]]:读取属性时调用的函数。默认值为undefined。
  • [[Set]]:写入属性时调用的函数。默认值为undefined。

访问器属性不能直接定义,必须使用Object.defineProperty()来定义

	var book = {
		_year:2016,
		edition:1
	};

	Object.defineProperty(book,"year",{//定义一个访问器属性,<strong><span style="color:#ff0000;">没指定configurable,enumerable特性,默认值都是false</span></strong>
		get:function(){
			return this._year;
		},
		set:function(newValue){
			if(newValue>2016){
				this._year = newValue;
				this.edition += newValue - 2016;
			}
		}
	});
	book.year = 2018;     //通过访问器属性设置_year的值
	alert(book.edition); // 3
	alert(book.year);    // 2018,通过访问器属性访问_year属性
	alert(book._year);   //2018,直接访问_year属性

创建了一个book对象,并给它定义了两个默认的属性_year和edition。_year前面的下划线是一种常用记号,用于表示只能通过对象方法访问的属性。访问器属性是year,包含一个getter函数和setter函数。

注意:调用Object.defineProperty()方法时,如果没指定configurable,enumerable,writable特性,默认值都是false。

不一定非要同时指定getter和setter。只指定getter意味着属性是不能写,尝试写入属性会被忽略。

2.定义多个属性

通过Object.defineProperties()方法,可通过描述符一次定义多个属性。这个方法接收两个对象参数:第一个对象是要添加和修改属性的对象,第二个对象的属性就是第一个对象中要添加或修改的属性,如:

	var book = {_year:null,edition:null};
	Object.defineProperties(book,{
		_year:{
			value:2016
		},
		edition:{
			value:1
		},
		year:{
			get:function(){
				return this._year;
			},
			set:function(newValue){
				if(newValue > 2016){
					this._year = newValue;
					this.edition += newValue -2016;
				}
			}
		}
	});
	book.year = 2018;  //set方法设置_year值
	alert(book.year);  //get方法获取_year值
	alert(book.edition);

3.读取属性的特性

使用Object.getOwnPropertyDescriptor()方法,可以取得给定属性的描述符。接收两个参数:属性所在的对象,要读取描述符的属性名称。返回一个对象,包含访问器属性或是数据属性对应的特性。

	var book = {};
	Object.defineProperties(book,{
		_year:{
			value:2016
		},
		edition:{
			value:1
		},
		year:{
			get:function(){
				return this._year;
			},
			set:function(newValue){
				if(newValue > 2016){
					this._year = newValue;
					this.edition += newValue -2016;
				}
			}
		}
	});
	book.year = 2018;  //set方法设置_year值
	
	var descriptor = Object.getOwnPropertyDescriptor(book,"_year");
	console.log(descriptor.value);  //2016
	console.log(descriptor.configurable); //false  所以输出的是2016,前面修改值为2018无效
	
	var descriptor1 = Object.getOwnPropertyDescriptor(book,"year");
	console.log(descriptor1.value);//undefined
	console.log(descriptor1.enumerable);//false
	console.log(typeof descriptor1.get);//function

创建对象

1.工厂模式

通过函数来封装以特定接口创建对象的细节,如:

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("zhangsan",22,"Software Engineer");
var person2 = createPerson("lisi",24,"Doctor");
person1.sayName();
person2.sayName();

2.构造函数模式

function Person(name,age,job){ //构造函数Constructor
	this.name = name;
	this.age = age;
	this.job = job;
	this.sayName = function(){
		alert(this.name);
	}
}
var person1 = new Person("zhangsan",23,"dc");
var person2 = new Person("lisi",23,"se");
person1.sayName();
console.log(typeof(person1)); // object,构造函数Constructor是Person();
console.log(typeof(person1.constructor)); //function
console.log(person1.constructor == Person);  //true
Person()函数取代了createPerson()函数。不同之处:
  • 没有显示的创建对象;(没有 var o = new Object())
  • 直接将属性和方法赋给了this对象;
  • 没有return语句
首字母P大写,与 其他函数区分开,以为构造函数本身也是函数,只不过可以用来创建对象。
要创建Person的新实例,必须使用new操作符。实际上经历下面4个步骤:
  1. 创建一个新对象;
  2. 将构造函数的作用域赋给新对象(因此this就指向了这个新的对象);
  3. 执行构造函数中的代码(为这个新对象添加属性);
  4. 返回新对象。
person1和person2分别保存这Person的不同实例,都有一个constructor(构造函数)属性,该属性指向Person()函数
对象的constructor属性最初是用来标识对象类型的。但是检测对象类型还是用instanceof操作符靠谱。
console.log(person1 instanceof Person); //true
创建自定义的构造函数意味着可以将它的实例标识为一种特定的类型;这正是构造函数胜过工厂模式的 地方。
这种方式定义的构造函数是定义在Global对象中(window)的
1.将构造函数当作函数
构造函数与其他函数的唯一区别,就在于调用它们的方式不同。任何函数,只要通过new操作符来调用,那它就可以作为构造函数;
//当构造函数使用
var person  = new Person("zhangsan",24,"teacher");
//当普通函数使用
Person("lisi",25,"driver");
window.sayName(); // lisi
//在另一个对象的作用域中调用
var o = new Object();
Person.call(o,"wangwu",24,"cooker");
o.sayName();  // wangwu
2.构造函数的问题
使用构造函数的问题:每个方法都要在每个实例上创建一遍。
function Person(name,age,job){ //构造函数Constructor
	this.name = name;
	this.age = age;
	this.job = job;
	this.sayName = new Function("alert(this.name)");//与函数申明方式一样,函数也是对象
}

var person1 = new Person("zhangsan",24,"hehe");
var person2 = new Person("lisi",25,"heihei");
alert(person1.sayName == person2.sayName);//  false,创建了两个函数实例,不同实例上的同名函数是不相等的

解决方法:将函数定义转移到构造函数外

function Person(name,age,job){ //构造函数Constructor
	this.name = name;
	this.age = age;
	this.job = job;
	this.sayName = sayName;
}
function sayName(){ //函数方法移到构造函数外面
	alert(this.name);
}

这样一来,构造函数中的sayName属性指向一个全局的sayName()函数。创建的多个实例sayName都会指向同一个全局函数。但是这样做的问题在于,这个sayName()全局函数只能被指定的对象调用,另一方面,多个方法就需要定义多个全局函数,对这个自定义的引用类型而言就没有封装行了。可通过原型模式来解决

3.原型模式

我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法,所以我们不必在构造函数中定义对象实例信息,可以将这些信息直接添加到原型对象中。

	function Person(){
	}
	Person.prototype.name = "zhangsan";
	Person.prototype.age = 25;
	Person.prototype.job = "doctor";
	Person.prototype.sayName = function(){
		alert(this.name);
	}
	var person1 = new Person();
	var person2 = new Person();
	alert(person1.sayName==person2.sayName);// true

1.理解原型对象

无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。所有原型对象都会自动获得一个consturctor属性,这个属性指向prototype属性所在函数的指针。Person.prototype.constructor 指向Person。通过这个构造函数可以继续为原型对象添加其他属性和方法。

当创建一个新的实例,该实例内部包含一个指针(  [[Prototype]]   ),指向构造函数的原型对象。这个连接存在于实例与构造函数的原型对象,而不是实例与构造函数之间。

技术分享技术分享

Person的每一个实例person1和person2都包含一个内部属性[[Prototype]],指向Person.prototype原型对象。实际与构造函数并没有直接关系

虽然我们在所有实现中都无法访问到[[Prototype]],但可以通过isPrototypeOf()方法来确定对象之间是否存在这种关系。如果[[Prototype]]指向调用isPrototypeOf()方法的对象(Person.prototype),就返回true。

alert(Person.prototype.isPrototypeOf(person1));  //true
使用原型对象的isPrototypeOf()方法测试person1,因为person1内部有一个指向Person.prototype的指针,所以返回true。
ECMAScript5增加了一个新方法,叫Object.getPrototypeOf(),返回[[Prototype]]的值。如:

	alert(Object.getPrototypeOf(person1) == Person.prototype); //true
	alert(Object.getPrototypeOf(person2).name);  // zhangsan

注意:每当代码读取某个对象的某个属性时,都会执行一次搜索(目标是具有给定名字的属性)。搜索首先从对象实例本身开始。

当为对象实例添加一个属性时,这个属性会屏蔽原型对象中的同名属性。不过添加这个属性只会阻止我们访问原型中的属性,并不会改变原型中的属性。即使将实例中的这个属性设置为null,也不会恢复其指向原型的连接。但是使用delete可完全删除实例属性,重新去访问原型中的属性,如:

	function Person(){
	}
	Person.prototype.name = "zhangsan";
	Person.prototype.age = 25;
	Person.prototype.job = "doctor";
	Person.prototype.sayName = function(){
		alert(this.name);
	}
	var person1 = new Person();
	var person2 = new Person();

	person1.name = "lisi";
	alert(person1.name); //  lisi 来自实例属性
	delete person1.name; // 删除实例属性
	alert(person1.name); //  zhangsan 访问的是原型属性	
hasOwnProperty()方法可检测一个属性是存在于实例中还是原型中(从Object中继承而来):
	person1.name = "lisi";
	alert(person1.name); //  lisi 来自实例属性
	alert(person1.hasOwnProperty("name"));//true
	delete person1.name; // 删除实例属性
	alert(person1.name); //  zhangsan 访问的是原型属性	
	alert(person1.hasOwnProperty("name"));//false

2.原型与in操作符

在使用in操作符时,如果对象能够访问给定属性就返回true(无论是实例属性还是原型属性)。
	alert("name" in person1); //访问的原型中的name属性   true
	person1.name = "lisi";
	alert("name" in person1); //访问的实例中的name属性    true
for-in,返回的是所有能够通过对象访问的,可枚举(enumerated)属性(包括实例中的属性和原型中的属性)。屏蔽了原型中不可枚举属性([[Enumerable]]标记的属性])的实例属性也会在for-in循环中返回。因为根据 规定,所有开发人员定义的属性都是可枚举的。

要取得对象上所有可枚举的实例属性,可以使用Object.keys()方法,接收一个对象作为参数,返回包含所有可枚举属性的字符串数组。

	var keys = Object.keys(Person.prototype);
	alert(keys); //name,age,job,sayName
	var person1 = new Person();
	person1.name="zhangsan";
	person1.age= 25;
	var keys1 = Object.keys(person1);
	alert(keys1); // name,age
获取所有实例属性(无论是否可枚举)Object.getOwnPropertyNames()
	var keys2 = Object.getOwnPropertyNames(Person.prototype);
	alert(keys2); // constructor,name,age,job,sayName   不可枚举的constructor属性也显示出来了

3.更简单的原型语法

避免每次添加属性都写一遍Person.prototype,使用包含所有属性和方法的字面量来重写整个原型对象。

	function Person(){
	}
	Person.prototype = {
		constructor:Person,  //如果不显示指定构造函数指向Person,constructor属性默认会指向Object构造函数(对原来构造函数的引用就因为重写断开了)
		name:"zhangsan",
		age:25,
		job:"Software Engineer",
		sayName:function(){
			alert(this.name);
		}
	}
	//像上面给constructor属性重新设置值会导致它的[[Enumerable]]特性被设置为true(默认情况下是false,不可枚举的)。
	//可使用Object.defineProperty()。来设置constructor属性。
	Object.defineProperty(Perosn.prototype,"constructor",{
		enumerable:false,
		value:Person
	});

4.原型的动态性

由于在原型中查找值的过程是一次搜索,因此对原型对象做任何修改都能立刻从实例中体现出来。(即使是先创建实例后修改原型)
	function Person(){
	}
	var person = new Person();//先创建实例
	Person.prototype.sayHi = function(){//后修改原型也可以访问
		alert("hi");
	};
	person.sayHi();

注意:调用构造函数创建对象时,会给实例添加一个属性[[Prototype]],指向最初的原型对象。如果将原型修改成另一个对象后,就切断了构造函数和最初的原型间的联系。(原来创建的实例还是指向原来的原型,在新的原型对象中添加属性,老的实例并不会有变化)

	function Person(){
	}
	var person = new Person();//先创建实例
	Person.prototype = { //构造函数prototype属性指向新原型对象
		constructor:Person,
		name:"张三",
		sayHi:function(){
			alert(this.name);
		}
	};
	person.sayHi();//实例还是指向的最初的原型对象,所以会报错,提示没有sayHi这个函数
技术分享

5.原生对象的原型

原生引用类型(Object,Array,String,等等)都在其构造函数的原型上定义了方法。如:Array.prototype中可以找到sort()
与自定以的引用类型一样,也可以给原生的引用类型添加方法(通过原生对象的prototype属性)。
但是:不推荐这么做,最好不要修改或添加原生对象的方法,可能命名冲突,或意外重写原生方法。

6.原型对象的问题

由于原型本身的共享特性,对于包含引用类型值的属性,会出现下面的问题:
	function Person(){
	}
	Person.prototype = { //构造函数prototype属性指向新原型对象
		constructor:Person,
		name:"张三",
		friends:["wangwu","zhaoliu"],//原型中定义引用类型
		sayHi:function(){
			alert(this.name);
		}
	};
	var person1 = new Person();
	var person2 = new Person();
	person1.friends.push("sunqi");  //给person1实例的friends属性添加值后,person2实例也跟着改变了
	alert(person1.friends);  // wangwu,zhaoliu,sunqi
	alert(person2.friends);  // wangwu,zhaoliu,sunqi
	alert(person1.friends == person2.friends); //ture

4.组合使用构造函数和原型模式(最常用的方式,默认模式)

构造函数用来定义实例属性,原型用来定义方法和共享的属性。这样,每个实例都会有自己的实例属性,同时也有共享的方法
	function Person(name,age){
		this.name = name;
		this.age = age;
		this.friends = ["sunliu"];
	}
	Person.prototype = { //构造函数prototype属性指向新原型对象
		constructor:Person,
		sayHi:function(){
			alert(this.name);
		}
	};
	var person1 = new Person("zhaosi",34);
	var person2 = new Person("zhangsan",24);
	person1.friends.push("sunqi");  //给person1实例的friends属性添加值
	alert(person1.friends);  // sunliu,sunqi
	alert(person2.friends);  // sunliu
	alert(person1.friends == person2.friends); //false

5.动态原型模式

在构造函数中动态去构造并初始化原型。
	function Person(name,age){
		this.name = name;
		this.age = age;
		if(typeof this.sayHi != 'function'){  //只在初次调用构造函数sayHi没有的时候才执行
			Person.prototype.sayHi = function(){
				alert(this.name);
			}
		}
	}
	var person  = new Person("zhangsan",24);
	person.sayHi()

6.寄生构造函数模式

创建一个函数(作用只是封装创建对象的代码),返回新创建的对象。乍一看很像构造函数
	function Person(name,age){
		var o = new Object();//新建一个对象
		o.name = name;
		o.age = age;
		o.sayHi = function(){
			alert(this.name);
		};
		return o;
	}
	var person = new Person("zhangsan",23);
	alert(typeof person);  // Object
	alert(person instanceof Person);// false
	person.sayHi(); // zhangsan

返回的对象与构造函数或者与构造函数的原型属性没有关系。所以不能依赖instanceof来确定对象类型。

如果构造函数不返回值,默认会返回新的对象实例。通过在构造函数末尾添加return语句,可以重写调用构造函数时返回的值。

	function SpecialArray(){
		var values = new Array(); //创建数组

		//添加值
		values.push.apply(values,arguments);// push方法初始化数组值

		//添加方法
		values.toPipedString = function(){  //给数组实例添加方法
			return this.join(" | ");
		}

		//返回数组
		return values;
	}
	var colors = new SpecialArray("red","blue","black");
	alert(colors.toPipedString()); // red | blue | black

7.稳妥构造函数模式

稳妥对象即没有公共属性,而且其方法也不引用this的对象。适合在安全环境中(禁止使用this和new),或者在防止数据被其他应用程序改动时使用。与寄生构造函数不同点:1.新创建对象的实例方法不引用this;2.不使用new操作符调用构造函数。
	function Person(name,age){
		//创建要返回的对象
		var o = new Object();

		o.sayHi = function(){
			alert(name); // 不使用this
		}
		return o;
	}
	var person = Person("zhangsan",23);
	person.sayHi(); // zhangsan
像上面的方式,除了sayHi()函数外,没有其他办法访问name值。即使有其他代码给这个对象添加方法或数据,但也不可能有别的办法访问传入构造函数中的原始数据。稳妥构造函数模式提供的这种安全性,使得它非常适合在安全执行环境下使用。

继承

1.原型链

利用原型让一个引用类型继承另一个引用类型的属性和方法。

基本模式

	function Father(){
		this.fatherName = "father";
	}
	Father.prototype.getFatherName = function(){
		return this.fatherName;
	}

	function Child(){
		this.childName = "child";
	}
	//子继承父(通过重写原型对象,指向父实例)
	Child.prototype = new Father(); //注意:先继承,后给子原型添加方法(原型动态性)

	Child.prototype.getChildName = function(){
		return this.childName;
	}
	var child = new Child();
	alert(child.getChildName()); //child
	alert(child.getFatherName()); //father
	alert(child.fatherName);  //father

技术分享

技术分享

如上图所示:

通过创建Father父的实例,然后让子构造函数的Child.prototype指向该实例。因此,father实例中的属性和方法就存在与Child.prototype原型对象中了。并且在新的原型对象(father instance)中还有一个内部指针([[Prototype]])指向 Father.prototype。

结果就是: 子实例(child  instance)的 原型([[Prototype]])指向  父实例(father instance),父实例的原型([[Prototype]])指向Father.prototype。getFatherName()是原型方法,还是存在与Father.prototype中。fatherName是实例属性,在(father instance)中,因此就在Child.prototype中(子构造函数继承了父构造函数的实例属性)

注意:child instance的 constructor指向的是  Father构造函数

child.getFatherName() 搜索过程:1.搜索child实例。2.搜索Child.prototype子原型。3.搜索Father.prototype父原型

  • 确定原型和实例的关系
	console.log(child instanceof Child);  //true
	console.log(child instanceof Father);  //true
	console.log(child instanceof Object);  //true

	console.log(Child.prototype.isPrototypeOf(child));  //true
	console.log(Father.prototype.isPrototypeOf(child));  //true
	console.log(Object.prototype.isPrototypeOf(child));  //true
  • 谨慎地定义方法
给原型添加方法的语句一定要在替换原型语句的后面。
在通过原型链实现继承时,不能使用对象字面量创建原型方法,这样会重写原型链,如:
	function Father(){
		this.fatherName = "father";
	}
	Father.prototype.getFatherName = function(){
		return this.fatherName;
	}

	function Child(){
		this.childName = "child";
	}
	//子继承父(通过重写原型对象,指向父实例)
	Child.prototype = new Father(); //注意:先继承,后给子原型添加方法(原型动态性)

	Child.prototype = {  //原来的继承的原型链就断了
		getChildName:function(){
			return this.childName;
		}
	}
	var child = new Child();
	child.getFatherName();// 报错child.getFatherName is not a function  找不好这个函数
  • 原型链的问题

1.Father中定义的实例属性,被Child继承后成为了  Child.prototype中的原型属性。引用类型的原型属性会被所有实例共享

2.在创建子类型的实例时,不能向父类构造函数传递参数

所以实践中很少单独使用原型链。

2.借用构造函数

通过借用构造函数(constructor stealing)的技术(伪造对象或经典继承)。即在子类型构造函数的内部调用父类型构造函数。

	function Father(){
		this.colors = ["red","blue"];
	}
	function Child(){
		//子对象调用父构造函数
		Father.call(this);
	}
	var child = new Child();
	child.colors.push("black");
	var child2 = new Child();
	child2.colors.push("yellow");
	console.log(child);
	console.log(child2);
上面代码通过使用call()方法(或apply()方法),在(未来将要)新创建的Child实例的环境下调用了Father构造函数。如此就可以在新的Child实例上执行Father()函数中定义的所有对象初始化代码
1.传递参数

可以在子类型构造函数中向父类型构造函数传递参数

	function Father(name){
		this.name = name;
	}
	function Child(name,age){
		Father.call(this,name); //防止Father构造函数不会重写Child的属性,先调用父类构造函数,后给子类添加属性
		this.age = age;
	}
	var child = new Child("zhangsan",23);
	console.log(child.name);

问题:方法都在构造函数中定义,函数无法复用。很少单独使用。

3.组合继承(常用的继承方式)

使用原型链实现对原型属性和方法的继承,通过借用构造函数类实现对实例属性的继承。
	function Father(name){
		this.name = name;
		this.colors = ["red","blue"];
	}
	Father.prototype.sayName  = function(){
		alert(this.name);
	}
	function Child(name,age){
		Father.call(this,name);//继承父类的实例属性
		this.age = age;
	}
	Child.prototype = new Father();//继承父类的原型方法
	Child.prototype.sayAge = function(){
		alert(this.age);
	}

	var child = new Child("zhangsan",23);
	child.colors.push("black");
	child.sayName();    //zhangsan
	child.sayAge();     //23
	alert(child.colors);  //red,blue,blace

	var child1 = new Child("lisi",24);
	child1.sayName();   //lisi
	child1.sayAge();    //24
	alert(child1.colors);  //red,blue

4.原型式继承

function object(o){ //借助原型基于已有的对象创建新对象
	function F(){};
	F.prototype = o;
	return new F();
}

var person  = {
	name:"zhangsan",
	friends:["lisi","wangwu"]   //引用数据类型,被多个实例共享
};

var person1 = object(person);
person1.name = "ethan";
person1.friends.push("zhaoliu");

var person2 = object(person);
person2.name = "king";
person2.friends.push("sunqi");

console.log(person1.name);     //ethan
console.log(person1.friends);  //lisi,wangwu,zhaoliu,sunqi 
console.log(person2.name);     //king
console.log(person2.friends);  //lisi,wangwu,zhaoliu,sunqi
ECMAScript5通过新增Object.create()方法规范化了原型式继承。接收两个参数:一,当作新对象原型的对象和(可选)为新对象定义额外属性的对象。

第二个参数与Object.defineProperties()方法的第二个参数格式相同:每个属性都是通过自己的描述定义的。

var person  = {
	name:"zhangsan",
	friends:["lisi","wangwu"]   //引用数据类型,被多个实例共享
};

var person1 = Object.create(person,{
	name:{
		value:"ethan"  //覆盖原来对象中的name属性
	}
});
console.log(person1.name);  // ethan
在没有必要创建构造函数,只想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的。


5.寄生式继承

创建一个用于封装集成过程的函数,在函数内部增强对象,然后返回对象
function object(o){
	function F(){};
	F.prototype = o;
	return new F();
}
function createObject(original){  //寄生式继承
	var clone = object(original);
	clone.sayHi = function(){ //每个实例对象都会创建一个sayHi函数,无法复用
		alert(this.name);
	};
	return clone;  //返回这个对象
}
var person  = {
	name:"zhangsan",
	friends:["lisi","wangwu"]   //引用数据类型,被多个实例共享
};

var person1 = createObject(person);
person1.sayHi();
使用寄生式继承来为对象添加函数,与构造函数模式有点类似,都由于不能做到函数复用而效率地下。

6.寄生组合式继承

组合继承无论在什么情况下都会调用两次父类的构造函数:一是在创建子类原型的时候。二是在子类型构造函数内部。子类型最终会包含超类型对象的全部实例属性,但我们不得不在调用子类型构造函数时重写这些属性。
function Father(name){
	this.name = name;
	this.colors = ["red","blue","green"];
}

Father.prototype.sayName = function(){
	alert(this.name);
}

function Child(name,age){  
	Father.call(this,name);//第二次调用Father父类构造函数,在新的子类实例对象上添加name,和colors属性,覆盖第一次(子原型中的属性)
	this.age = age;
}
Child.prototype = new Father();//第一次调用Father父类构造函数,继承父的实例属性,name,colors。原型链指向父类
Child.prototype.constructor = Child;//原型构造函数指向Child
Child.prototype.sayAge = function(){
	alert(this.age);
}
寄生组合式继承:通过借用构造函数来继承属性,通过原型链混成形式来继承方法。
思路:不必为了指定子类型的原型而调用父类的构造函数,只需获得父类型的一个副本而已。使用寄生式继承来继承父类的原型,然后将结果指给之类的原型。

function object(o){
	function F(){};
	F.prototype = o;
	return new F();
}
//继承父类的原型方法,保持原型链不变
function inheritPrototype(child,father){
	var prototype = object(father.prototype);//创建父类原型的副本
	prototype.constructor = child;//防止重写原型失去默认的constructor属性
	child.prototype = prototype;//重写子类原型
}
function Father(name){
	this.name = name;
	this.colors = ["red","blue"];
}

Father.prototype.sayName = function(){
	alert(this.name);
}
function Child(name,age){
	Father.call(this,name);
	this.age = age;
}
inheritPrototype(Child,Father);
//会把父类的原型直接暴露给子类,那么子类可以任意的修改父类的属性和方法
//Child.prototype = Father.prototype;//在子类原型上添加方法实际上添加到父类上了
Child.prototype.sayAge  = function(){
	alert(this.age);
}

var child = new Child("zhangsan",23);
console.log(child);
child.sayName(); //zhangsan
console.log(child.colors); //red,blue



































































































以上是关于js自学笔记--面向对象的主要内容,如果未能解决你的问题,请参考以下文章

VSCode自定义代码片段9——JS中的面向对象编程

jQuery 自学笔记

jQuery 自学笔记

js自学笔记—— 基础部分一

js自学笔记---基础部分二

JS笔记加强版3