继承的实现方式
在前面,我们的结论就是可以通过继承来让一个对象可以使用另一个对象的属性和方法,那怎么实现继承呢?
- 最简单的继承实现
混入式继承
<script type="text/javascript">
var obj = {
name:"布莱德皮特",
age:12,
sayHello: function () {
console.log("Hello World");
}
}
var o = {};
//混入式继承
for(var k in obj){
o[k] = obj[k];
}
console.log(o);
</script>
- 原型继承
<script type="text/javascript">
//原型继承
//利用原型中的成员可以被其他相关的对象共享这一特性,可以实现继承
//这种实现继承的方式,就叫做原型继承
// 1,给原型对象中添加成员(通过对象的动态特性) 不是严格意义上的继承
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.sayHello = function () {
console.log("Hi,nihao");
}
var p = new Person("迪丽热巴",24);
p.sayHello();
//此方式属于对象继承了原型对象
</script>
- 直接替换原型对象的方式继承
<script type="text/javascript">
// 2,直接替换原型对象
function Son(name, age){
this.name = name;
this.age = age;
}
var parent = {
sayHello: function () {
console.log("Hi,Wife");
}
}
Son.prototype = parent;
var s = new Son("蜡笔小新",12);//一定要在设置原型后面
console.log(Son.prototype);
s.sayHello();
//s对象继承了原型对象(parent对象)
</script>
注意:使用体会原型的方式实现继承的时候,原有的原型中的成员会消失
- 利用混入的方式给原型对象添加成员
<script type="text/javascript">
// 3,利用混入的方式给原型对象添加成员
function Son(name, age){
this.name = name;
this.age = age;
}
var s = new Son("蜡笔小新",12);//可以在设置原型之前
var parent = {
sayWife: function () {
console.log("Hi,My Wife!");
}
}
for(var k in parent){
Son.prototype[k] = parent[k];
}
s.sayWife();
</script>
- 经典继承方式的实现
<script type="text/javascript">
//《JavaScript语言精粹》作者提出了一个方式来实现继承
function inherit(obj){
var o = {};
o.__proto__ = obj;
return o;
}
var o = inherit({name:"张三"});
console.log(o);
//经典继承的语法
//Object.create(obj)
//返回值为一个对象,继承自参数中的obj
//这个方法是ES5中出来的,所以存在兼容性问题
// var o = {
// name:"李四"
// }
// var obj = Object.create(o);
// console.log(obj);
//如何处理Object.create的兼容性问题
var o = {
name:"李四"
}
//检测浏览器的能力,如果没有Object.create方法就给他添加一个(不推荐使用)
if(Object.create){
var obj= Object.create(o)
}else{
Object.create = function (o) {
function F(){}
F.prototype = o;
var obj = new F;
return obj;
}
var obj = Object.create()
}
var o1 = Object.create(o);
console.log(o1);
//自己定义个函数
function create(obj){
if(Object.create){
return Object.create(obj)
}else{
Object.create = function (obj) {
function F(){}
F.prototype = obj;
return new F;;
}
}
}
var o = {
age:12,
}
var obj = create(o);
console.log(obj);
</script>
原型继承
每一个构造函数都有prototype原型属性,通过构造函数创建出来的对象都继承自该原型属性。所以可以通过更改构造函数的原型属性来实现继承。
function Dog(){
this.type = "yellow Dog";
}
function extend(obj1, obj2){
for (var k in obj2){
obj1[k] = obj2[k];
}
};
//使用混入的方式,将属性和方法添加到构造函数的原型属性上,构造函数所创建出来的实例就都有了这些属性和方法。
extend(Dog.prototype, {
name:"",
age:"",
sex:"",
bark:function(){}
})
//使用面向对象的思想把extend方法重新封装
//extend是扩展的意思,谁要扩展就主动调用extend这个方法
//所以extend应该是对象的方法,那现在我们要扩展的是构造函数的原型对象
//所以给构造函数的原型对象添加一个extend方法
//如下:
Dog.prototype.extend = function(obj){
for (var k in obj){
this[k]=obj[k];
}
}
//调用方式就变成了下面这种形式
Dog.prototype.extend({
name:"",
age:"",
sex:"",
bark:function(){}
});
继承的应用
<script type="text/javascript">
var arr = [1,2,5,3,8,1];
//给数组添加一个sayHello方法
Array.prototype.sayHello = function () {
console.log("您好,我告诉你数组的长度是:"+this.length);
}
arr.sayHello();
//但是这样属于扩展内置对象,就是给内置对象新增成员
//在多人开发时,大家都扩展内置对象,如果名称一样会有问题
//内置对象中原来有的成员,可能会被替换,那内置对象的功能会丧失
//在ECMScript升级时,扩展内置对象的新增成员可能有被覆盖的危险
//所以在项目中应避免扩展内置对象
//避免扩展内置对象,使用继承的方式
function MArray(){
}
var arr = new Array();
MArray.prototype = arr;
//mArr这个对象就继承自arr
var mArr = new MArray();
mArr.push(1);
mArr.push(2,3,4,5);
console.log(mArr);//打印:(5) [1, 2, 3, 4, 5]
</script>
属性搜索原则
访问一个对象的成员的时候,首先是在实例中找,没有找到, 就去原型中找, 但是原型中没有怎么办?
属性搜索原则
所谓的属性搜索原则,也就是属性的查找顺序,在访问对象的成员的时候,会遵循如下的原则:
- 首先在当前对象中查找,如果找到,停止查找,直接使用,如果没有找到,继续下一步
- 在该对象的原型中查找,如果找到,停止查找,直接使用,如果没有找到,继续下一步
- 在该对象的原型的原型中查找,如果找到,停止查找,直接使用,如果没有找到,继续下一步。
- 继续往上查找,直到查找到
Object.prototype
还没有, 那么是属性就返回undefied
,是方法,就报错xxx is not a function
。
原型链
每一个对象都有原型属性,那么对象的原型属性也会有原型属性,所以这样就形成了一个链式结构,我们称之为原型链。
原型链结构
凡是对象就有原型, 原型又是对象, 因此凡是给定义一个对象, 那么就可以找到他的原型, 原型还有原型. 那么如此下去, 就构成一个对象的序列. 称该结构为原型链.
使用构造函数创建出对象, 并且没有利用赋值的方式修改原型, 就说该对象保留默认的原型链.
默认原型链结构是什么样子呢?
function Person() {
}
var p = new Person();
// p 具有默认的原型链
默认的原型链结构就是:当前对象 -> 构造函数.prototype -> Object.prototype -> null
在实现继承的时候, 有时会利用替换原型链结构的方式实现原型继承, 那么原型链结构就会发生改变
<script type="text/javascript">
//1,什么是原型链
// 每个构造函数都有原型对象
// 每个对象都有构造函数
// 每个构造函数的原型对象都是一个对象
// 那么这个原型对象也会有构造函数
// 那么这个原型对象的构造函数也会有原型对象
// 这样就会形成一个链式的结构,称为原型链
function Person(){
}
var p = new Person();
//p的原型是一个Object对象
console.log(p.__proto__);
// p---->Person.prototype ----> Object.prototype ----> null
//属性的搜索规则
// 1.当访问一个对象的成员的时候,会现在自身找有没有,如果找到直接使用,
// 2.如果没有找到,则去当前对象的原型对象中去查找,如果找到了直接使用,
// 3.如果没有找到,继续找原型对象的原型对象,如果找到了,直接使用
// 4.如果没有找到,则继续向上查找,直到Object.prototype,如果还是没有,就报错
//原型继承
//通过修改原型链结构实现的继承,就叫做原型继承
</script>
复杂的原型链
<script type="text/javascript">
function Animal(){
this.age = 8;
}
Human.prototype = new Animal;
Human.prototype.constructor = Human;
function Human(){
this.name = "杨迪";
}
Teacher.prototype = new Human;
Teacher.prototype.constructor = Teacher;
function Teacher(){
this.work = "教书";
}
BadTeacher.prototype = new Teacher;
BadTeacher.prototype.constructor = BadTeacher;
function BadTeacher(){
this.teaching = "打人";
}
var bt = new BadTeacher;
// console.log(bt);
//Teacher {work: "教书", constructor: function}
console.log(bt.__proto__);
//Human {name: "杨迪", constructor: function}
console.log(bt.__proto__.__proto__);
//Animal {age: 8, constructor: function}
console.log(bt.__proto__.__proto__.__proto__);
//Object {constructor: function}
console.log(bt.__proto__.__proto__.__proto__.__proto__);
//Object {__defineGetter__: function, __defineSetter__: function, hasOwnProperty: function, __lookupGetter__: function, __lookupSetter__: function…}
console.log(bt.__proto__.__proto__.__proto__.__proto__.__proto__);
//null
console.log(bt.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__);
var arr = [];
console.log(arr);
//[constructor: function, toString: function, toLocaleString: function, join: function, pop: function…]
console.log(arr.__proto__);
</script>
Object.prototype的成员
- constructor
- hasOwnProperty
- propertyIsEnumerable
- Object.defineProperty();
- toString 和 toLocaleString
- valueOf
- __proto__
<script type="text/javascript">
//1.constructor
//原型对象内的一个属性,指向该原型对象相关联的构造函数
//2.hasOwnProperty
//一个方法,用来判断对象本身(不包含原型)是否拥有某个属性
function People(){
this.name = "王久";
}
People.prototype.name = "张三";
var pp = new People();
console.log(pp.name);
console.log(pp.hasOwnProperty("name"));//true
console.log(pp.hasOwnProperty("__proto__"));//false
//3.propertyIsEnumerable
// 1. 判断属性是否属于对象本身
// 2. 判断属性是否可以被遍历
console.log(pp.propertyIsEnumerable("name"));//true
// Object.defineProperty();
// 使用以上方法添加属性的时候,可以附加一些信息,
// 例如这个属性是否可写 可读 可遍历
//4.toString 和 toLocaleString
var o = {};
console.log(o.toString());//[object Object]
console.log(o.toLocaleString());//[object Object]
var now = new Date();
console.log(now.toString());//Wed Jul 19 2017 23:57:53 GMT+0800 (China Standard Time)
console.log(now.toLocaleString());//2017/7/19 下午11:57:53
function Person(){}
var p = new Person();
console.log(1+p);//1[object Object]
//5.valueOf
function Person1(){
this.valueOf = function () {
return 123;
}
}
var p1 = new Person1();
console.log(1 + p1);//124
//在对象参与运算的时候
// 1.默认的会先去调用对象的valueOf方法,
// 2.如果valueOf获取到的值,无法进行运算 ,就去去调用p的toString方法 最终做的就是字符串拼接的工作
//6. __proto__
//原型对象对象中的属性
//可以使用 对象.__proto__ 去访问原型对象
</script>