javascript面向对象编程(二)

1,原型介绍

要介绍原型,要就必须要从js的函数开始介绍了,在JavaScript中,函数本身也是一个包含了方法和属性的对象,而函数对象中一个属性就是prototype。下面我们逐步来了解它。

现有构造函数如下:

function Person(name, age){
    this.name = name;
    this.age = age;
    this.sayHi = function(){
        console.log("你好");
    }
}

调用该构造函数创建对象,并对比创建出来的对象的sayHi方法:


var p = new Person("张三", 18);
var p1 = new Person("李四", 19);
console.log(p.sayHi == p1.sayHi); //输出结果为false

由于每个对象都是由new Person创建出来的,因此每创建一个对象,函数sayHi都会被重新创建一次,这个时候,每个对象都拥有一个独立的,但是功能完全相同的方法。


但其实功能相同的方法,我们没必要让他存在那么份,因为毕竟每一份,都会占用内存,这样就造成了资源浪费,一般我们可能会

想到使用外部的函数,然后大家都引用他,这样就都是一份,虽然解决了,但是这样造成了全局变量增多,代码的结构混乱,所以

也不是很好的方法,我们看下面的代码

  • 每一个函数在定义的时候,都会有跟它关联的一个对象被创建出来
  • 每一个由构造函数创建出来的对象,都会默认的和构造函数的神秘对象关联
  • 当使用一个方法进行属性或者方法访问的时候,会先在当前对象内查找该属性和方法
  • 如果当前对象内未找到,就回去跟它关联的神秘对象内进行查找
function Person(name, age){
    this.name = name;
    this.age = age;
    this.sayHi = function(){
        console.log("Hello!");
    };
}


var p = new Person("张三", 18);
p.sayHi(); //当前对象内有这个方法,所以不会去神秘对象内进行查找
var p1 = new Person("李四", 19);
p1.sayHello(); //当前对象没没有找到这个方法,所以去神秘对象内进行查找

问题来了,如何访问到这个神秘对象呢?


//可以通过 构造函数.prototype 访问这个神秘对象
console.log(Person.prototype);

当尝试给这个对象新增一个方法之后:

Person.prototype.sayHello = function(){
    console.log("我是神秘对象中的方法");
};

使用p,p1都可以访问这个方法:

p.sayHello();
p1.sayHello();

总结:

所有对象共享神秘对象(构造函数.prototype)内的属性和方法。

既然所有对象共享神秘对象(构造函数.prototype)内的属性和方法。我们只需要将需要共享的东西,也就是重复占用内存的东西,

全部都放到 神秘对象(构造函数.prototype)中,那么所有对象就都可以使用,并且内存里面也只有一份了。



  • 构造函数创建的对象 继承自 构造函数的原型属性
  • 构造函数创建的对象 继承自 该对象的原型对象
    • 原型中的成员, 可以直接被实例对象所使用
    • 实例对象直接 "含有" 原型中的成员
    • 因此实例对象 继承自 原型
    • 这样的继承就是 "原型继承"

2.继承的实现方式

说完原型,就来看看面向对象的主要特性之一,继承,与原型的关系也是千丝万缕

2.1. 最简单的继承实现

直接遍历父对象的属性,将所有的属性加到当前对象上


var animal = {
    name:"Animal",
    sex:"male",
    age:5,
    bark:function(){
        console.log("Animal bark");
    }
};

var dog = {};

for (var k in animal){

    dog[k]= animal[k];

}

2.2. 原型继承

每一个构造函数都有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(){}
});

3.属性搜索机制


属性搜索原则

访问一个对象的成员的时候,首先是在实例中找,没有找到, 就去原型中找, 但是原型中没有怎么办?

原型链

每一个对象都有原型属性,那么对象的原型属性也会有原型属性,所以这样就形成了一个链式结构,我们称之为原型链。

属性搜索原则

所谓的属性搜索原则,也就是属性的查找顺序,在访问对象的成员的时候,会遵循如下的原则:

  1. 首先在当前对象中查找,如果找到,停止查找,直接使用,如果没有找到,继续下一步

  2. 在该对象的原型中查找,如果找到,停止查找,直接使用,如果没有找到,继续下一步

  3. 在该对象的原型的原型中查找,如果找到,停止查找,直接使用,如果没有找到,继续下一步。

  4. 继续往上查找,直到查找到Object.prototype还没有, 那么是属性就返回 undefied,是方法,就报错xxx is not a function

4.原型链

4.1原型链结构

凡是对象就有原型, 原型又是对象, 因此凡是给定义一个对象, 那么就可以找到他的原型, 原型还有原型. 那么如此下去, 就构成一个对象的序列. 称该结构为原型链.

使用构造函数创建出对象, 并且没有利用赋值的方式修改原型, 就说该对象保留默认的原型链.

默认原型链结构是什么样子呢?

function Person() {
}

var p = new Person();
// p 具有默认的原型链

默认的原型链结构就是:

当前对象 -> 构造函数.prototype -> Object.prototype -> null

在实现继承的时候, 有时会利用替换原型链结构的方式实现原型继承, 那么原型链结构就会发生改变


function ItcastCollection () {
}
ItcastCollection.prototype = [];
var arr = new ItcastCollection();
// arr -> [] -> Array.prototype -> Object.prototype -> null
// var arr = new Array();

4.2原型式继承

观察:DOM对象的原型链

原型式继承就是利用修改原型链的结构( 增加一个节点, 删除一个节点, 修改节点中的成员 ), 来使得实例对象可以使用整条链中的所有成员.

绘制原型链结构

注意:函数也有__proto__属性,暂时不考虑这个!

观察如下代码,绘制相应的原型链结构图:

function Person(){};
var p = new Person();

对应的原型链结构图为:


猜你喜欢

转载自blog.csdn.net/s8460049/article/details/52814160