吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2917|回复: 7
收起左侧

[其他转载] 【笔记】深入理解Javascript面向对象编程-三

[复制链接]
海军 发表于 2016-1-8 23:58
本帖最后由 奋斗丶小Z 于 2016-1-9 00:10 编辑

// 先看下面的代码:

// 定义构造函数A,定义特权属性和特权方法

function A(x) {

    this.x1 = x;

    this.getX1 = function(){

        return this.x1;

    }

}

// 定义构造函数B,定义特权属性和特权方法

function B(x) {

    this.x2 = x;

    this.getX2 = function(){

        return this.x1 + this.x2;

    }

}

B.prototype = new A(1);

B.prototype = new A(1);这句代码执行的时候,B的原型继承于A,因此B.prototype也有A的属性和方法,即:B.prototype.x1 = 1; B.prototype.getX1 方法;但是B也有自己的特权属性x2和特权方法getX2; 如下代码:


function C(x) {

    this.x3 = x;

    this.getX3 = function(){

        return this.x3 + this.x2;

    }

}

C.prototype = new B(2);

C.prototype = new B(2);这句代码执行的时候,C的原型继承于B,因此C.prototype.x2 = 2; C.prototype.getX2方法且C也有自己的特权属性x3和特权方法getX3,

var b = new B(2);

var c = new C(3);

console.log(b.x1);  // 1

console.log(c.x1);  // 1

console.log(c.getX3()); // 5

console.log(c.getX2()); // 3

var b = new B(2);

实列化B的时候 b.x1 首先会在构造函数内查找x1属性,没有找到,由于B的原型继承于A,因此A有x1属性,因此B.prototype.x1 = 1找到了;var c = new C(3); 实列化C的时候,从上面的代码可以看到C继承于B,B继承于A,因此在C函数中没有找到x1属性,会往原型继续查找,直到找到父元素A有x1属性,因此c.x1 = 1;c.getX3()方法; 返回this.x3+this.x2 this.x3 = 3;this.x2 是B的属性,因此this.x2 = 2;c.getX2(); 查找的方法也一样,不再解释


prototype的缺点与优点如下:


优点是:能够允许多个对象实列共享原型对象的成员及方法,


缺点是:1. 每个构造函数只有一个原型,因此不直接支持多重继承;


2. 不能很好地支持多参数或动态参数的父类。在原型继承阶段,用户还不能决定以


什么参数来实列化构造函数。


四:理解使用类继承(继承的更好的方案)


类继承也叫做构造函数继承,在子类中执行父类的构造函数;实现原理是:可以将一个构造函数A的方法赋值给另一个构造函数B,然后调用该方法,使构造函数A在构造函数B内部被执行,这时候构造函数B就拥有了构造函数A中的属性和方法,这就是使用类继承实现B继承与A的基本原理;


如下代码实现demo:


function A(x) {

    this.x = x;

    this.say = function(){

        return this.x;

    }

}

function B(x,y) {

    this.m = A; // 把构造函数A作为一个普通函数引用给临时方法m

    this.m(x);  // 执行构造函数A;

    delete this.m; // 清除临时方法this.m

    this.y = y;

    this.method = function(){

        return this.y;

    }

}

var a = new A(1);

var b = new B(2,3);

console.log(a.say()); //输出1, 执行构造函数A中的say方法

console.log(b.say()); //输出2, 能执行该方法说明被继承了A中的方法

console.log(b.method()); // 输出3, 构造函数也拥有自己的方法

上面的代码实现了简单的类继承的基础,但是在复杂的编程中是不会使用上面的方法的,因为上面的代码不够严谨;代码的耦合性高;我们可以使用更好的方法如下:


function A(x) {

    this.x = x;

}

A.prototype.getX = function(){

    return this.x;

}

// 实例化A

var a = new A(1);

console.log(a.x); // 1

console.log(a.getX()); // 输出1

// 现在我们来创建构造函数B,让其B继承与A,如下代码:

function B(x,y) {

    this.y = y;

    A.call(this,x);

}

B.prototype = new A();  // 原型继承

console.log(B.prototype.constructor); // 输出构造函数A,指针指向与构造函数A

B.prototype.constructor = B;          // 重新设置构造函数,使之指向B

console.log(B.prototype.constructor); // 指向构造函数B

B.prototype.getY = function(){

    return this.y;

}

var b = new B(1,2);

console.log(b.x); // 1

console.log(b.getX()); // 1

console.log(b.getY()); // 2


// 下面是演示对构造函数getX进行重写的方法如下:

B.prototype.getX = function(){

    return this.x;

}

var b2 = new B(10,20);

console.log(b2.getX());  // 输出10

下面我们来分析上面的代码:


在构造函数B内,使用A.call(this,x);这句代码的含义是:我们都知道使用call或者apply方法可以改变this指针指向,从而可以实现类的继承,因此在B构造函数内,把x的参数传递给A构造函数,并且继承于构造函数A中的属性和方法;


使用这句代码:B.prototype = new A();  可以实现原型继承,也就是B可以继承A中的原型所有的方法;console.log(B.prototype.constructor); 打印出输出构造函数A,指针指向与构造函数A;我们明白的是,当定义构造函数时候,其原型对象默认是一个Object类型的一个实例,其构造器默认会被设置为构造函数本身,如果改动构造函数prototype属性值,使其指向于另一个对象的话,那么新对象就不会拥有原来的constructor的值,比如第一次打印console.log(B.prototype.constructor); 指向于被实例化后的构造函数A,重写设置B的constructor的属性值的时候,第二次打印就指向于本身B;因此B继承与构造A及其原型的所有属性和方法,当然我们也可以对构造函数B重写构造函数A中的方法,如上面最后几句代码是对构造函数A中的getX方法进行重写,来实现自己的业务~;


五:建议使用封装类实现继承


封装类实现继承的基本原理:先定义一个封装函数extend;该函数有2个参数,Sub代表子类,Sup代表超类;在函数内,先定义一个空函数F, 用来实现功能中转,先设置F的原型为超类的原型,然后把空函数的实例传递给子类的原型,使用一个空函数的好处是:避免直接实例化超类可能会带来系统性能问题,比如超类的实例很大的话,实例化会占用很多内存;


如下代码:

function extend(Sub,Sup) {

    //Sub表示子类,Sup表示超类

    // 首先定义一个空函数

    var F = function(){};


    // 设置空函数的原型为超类的原型

    F.prototype = Sup.prototype;


// 实例化空函数,并把超类原型引用传递给子类

    Sub.prototype = new F();


    // 重置子类原型的构造器为子类自身

    Sub.prototype.constructor = Sub;


    // 在子类中保存超类的原型,避免子类与超类耦合

    Sub.sup = Sup.prototype;


    if(Sup.prototype.constructor === Object.prototype.constructor) {

        // 检测超类原型的构造器是否为原型自身

        Sup.prototype.constructor = Sup;

    }


}

测试代码如下:

// 下面我们定义2个类A和类B,我们目的是实现B继承于A

function A(x) {

    this.x = x;

    this.getX = function(){

        return this.x;

    }

}

A.prototype.add = function(){

    return this.x + this.x;

}

A.prototype.mul = function(){

    return this.x * this.x;

}

// 构造函数B

function B(x){

    A.call(this,x); // 继承构造函数A中的所有属性及方法

}

extend(B,A);  // B继承于A

var b = new B(11);

console.log(b.getX()); // 11

console.log(b.add());  // 22

console.log(b.mul());  // 121

注意:在封装函数中,有这么一句代码:Sub.sup = Sup.prototype; 我们现在可以来理解下它的含义:


比如在B继承与A后,我给B函数的原型再定义一个与A相同的原型相同的方法add();


如下代码

extend(B,A);  // B继承于A

var b = new B(11);

B.prototype.add = function(){

    return this.x + "" + this.x;

}

console.log(b.add()); // 1111

那么B函数中的add方法会覆盖A函数中的add方法;因此为了不覆盖A类中的add()方法,且调用A函数中的add方法;可以如下编写代码:


B.prototype.add = function(){

    //return this.x + "" + this.x;

    return B.sup.add.call(this);

}

console.log(b.add()); // 22

B.sup.add.call(this); 中的B.sup就包含了构造函数A函数的指针,因此包含A函数的所有属性和方法;因此可以调用A函数中的add方法;


如上是实现继承的几种方式,类继承和原型继承,但是这些继承无法继承DOM对象,也不支持继承系统静态对象,静态方法等;比如Date对象如下:


// 使用类继承Date对象

function D(){

    Date.apply(this,arguments); // 调用Date对象,对其引用,实现继承

}

var d = new D();

console.log(d.toLocaleString()); // [object object]

如上代码运行打印出object,我们可以看到使用类继承无法实现系统静态方法date对象的继承,因为他不是简单的函数结构,对声明,赋值和初始化都进行了封装,因此无法继承;


下面我们再来看看使用原型继承date对象;


function D(){}

D.prototype = new D();

var d = new D();

console.log(d.toLocaleString());//[object object]

我们从代码中看到,使用原型继承也无法继承Date静态方法;但是我们可以如下封装代码继承:


function D(){

    var d = new Date();  // 实例化Date对象

    d.get = function(){ // 定义本地方法,间接调用Date对象的方法

        console.log(d.toLocaleString());

    }

    return d;

}

var d = new D();

d.get(); // 2015/12/21 上午12:08:38

六:理解使用复制继承


复制继承的基本原理是:先设计一个空对象,然后使用for-in循环来遍历对象的成员,将该对象的成员一个一个复制给新的空对象里面;这样就实现了复制继承了;如下代码:


function A(x,y) {

    this.x = x;

    this.y = y;

    this.add = function(){

        return this.x + this.y;

    }

}

A.prototype.mul = function(){

    return this.x * this.y;

}

var a = new A(2,3);

var obj = {};

for(var i in a) {

    obj = a;

}

console.log(obj); // object

console.log(obj.x); // 2

console.log(obj.y); // 3

console.log(obj.add()); // 5

console.log(obj.mul()); // 6

如上代码:先定义一个构造函数A,函数里面有2个属性x,y,还有一个add方法,该构造函数原型有一个mul方法,首先实列化下A后,再创建一个空对象obj,遍历对象一个个复制给空对象obj,从上面的打印效果来看,我们可以看到已经实现了复制继承了;对于复制继承,我们可以封装成如下方法来调用:


// 为Function扩展复制继承方法

Function.prototype.extend = function(o) {

    for(var i in o) {

        //把参数对象的成员复制给当前对象的构造函数原型对象

        this.constructor.prototype = o;

    }

}

// 测试代码如下:

var o = function(){};

o.extend(new A(1,2));

console.log(o.x);  // 1

console.log(o.y);  // 2

console.log(o.add()); // 3

console.log(o.mul()); // 2

上面封装的扩展继承方法中的this对象指向于当前实列化后的对象,而不是指向于构造函数本身,因此要使用原型扩展成员的话,就需要使用constructor属性来指向它的构造器,然后通过prototype属性指向构造函数的原型;


复制继承有如下优点:


1. 它不能继承系统核心对象的只读方法和属性


2. 如果对象数据非常多的话,这样一个个复制的话,性能是非常低的;


3. 只有对象被实列化后,才能给遍历对象的成员和属性,相对来说不够灵活;


4. 复制继承只是简单的赋值,所以如果赋值的对象是引用类型的对象的话,可能会存在一些副作用;如上我们看到有如上一些缺点,下面我们可以使用clone(克隆的方式)来优化下:


基本思路是:为Function扩展一个方法,该方法能够把参数对象赋值赋值一个空构造函数的原型对象,然后实列化构造函数并返回实列对象,这样该对象就拥有了该对象的所有成员;代码如下:


Function.prototype.clone = function(o){

    function Temp(){};

    Temp.prototype = o;

    return Temp();

}

// 测试代码如下:

Function.clone(new A(1,2));

console.log(o.x);  // 1

console.log(o.y);  // 2

console.log(o.add()); // 3

console.log(o.mul()); // 2


免费评分

参与人数 1热心值 +1 收起 理由
BY丶显示 + 1 鼓励转贴优秀软件安全工具和文档!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

Cizel 发表于 2016-1-9 00:12
2点建议:

第一:图文并茂 并用颜色标出重点
第二:注意版规,加上标题前缀
BY丶显示 发表于 2016-1-9 01:31
2909094965 发表于 2016-1-9 10:59
cydib 发表于 2016-8-23 18:59
都是大牛啊
a7816995 发表于 2016-8-30 18:51
留下印记,~慢慢看。

1292822554 发表于 2016-8-31 00:05 来自手机
唉,我还是入门阶段
小泽松岛枫 发表于 2017-7-20 23:03
大神,跟你请教下,我这儿有段代码,写的是一个简单定时器,可以开始、暂停和复位,想问下,怎么将他改成面向对象的形式呢,不动HTML元素,只改js代码,将它写成对象封装的形式,怎么写呢,一直搞不明白

<body>

    <p id="show">0</p>
    <button id="start">开始</button>
    <button id="pause">暂停</button>
    <button id="reset">复位</button>

<script>

    var showBox=document.getElementById("show");
    var startBtn=document.getElementById("start");
    var pauseBtn=document.getElementById("pause");
    var resetBtn=document.getElementById("reset");
    var timer1;
    pauseBtn.disabled=true;
    resetBtn.disabled=true;   
    startBtn.onclick=function(){
        this.disabled=true;
        pauseBtn.disabled=false;
        resetBtn.disabled=false;
        timer1=setInterval(function(){
            var num=parseInt(showBox.innerHTML);
            num++;
            showBox.innerHTML=num;
        },1000)
    }
    pauseBtn.onclick=function(){
        startBtn.disabled=false;        
        this.disabled=true;
        resetBtn.disabled=false;
        clearInterval(timer1);
    }
    resetBtn.onclick=function(){
        startBtn.disabled=false;
        pauseBtn.disabled=true;
        this.disabled=true;      
        clearInterval(timer1);
        showBox.innerHTML=0;
    }

</script>

</body>
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-14 20:39

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表