浅谈js中的原型(ECMAScript标准层面)

在网上面搜了一下“什么是js的原型,以及什么是_proto_”啥的。。。。,然后出来的全是一堆什么

alert(xxx.prototype===yyy._proto_)//true//false啥的,有毒吧!!还有原型链就更加不用说了,看着就头疼,反正我是看不懂,全靠死记,就算当时记住了,过一会就忘了,完全起不到作用,所以这篇博客从另外一个方面(ECMAScript标准层面)谈一下js中的原型

js的原型:(prototype),每一个对象都有一个prototype属性,注意不是对象的实例,你或许听过js的面向对象是基于原型的,但是你可能没有仔细琢磨这句话,这句话的意思就是:一个对象的原型的方法和属性会被其子类继承,而非原型的方法和属性不会被子类继承!!,换句话说:某对象原型的方法和属性会传递给该对象的所有实例。如果不理解,请看完整篇博客

 

前提:

  •     了解js中的构造函数
  •     了解js中this关键字
  •     了解js中的call(),apply()函数

这个前提条件有点多哈!下面就简单的介绍一下先

    1)、js中的构造函数

        这个就不用多说了,js很灵活,一个函数你可以把它当成一个普通只用来调用的函数,也可以将它看做用来创建它自己的实例的构造函数,如果把它当成构造函数,就不得不牵扯到this关键字了,请接着看

    2)、js中的this关键字

        this关键字,简单理解就是实例本身!!这么说可能有点抽象,请看下面代码!

function F1(arg1, arg2) {
        this.name = arg1;
        this.id = arg2;
        this.nicknameArray = new Array("xiaoze", "huahua", "zehua");
    }

        这是一个构造函数,使用了this关键字定义了三个属性,这样接着看下面代码

var ze1 = new F1("ze1", "01038");
    ze1.name;//ze1
    ze1.id;//01038
    ze1.nicknameArray;//xiaoze,huahua,zehua

        这样我们可以通过new一个F1的实例:ze1,然后就可以通过 . 的方式得到相应的属性,前面说过,this关键字指的是实例本身,那么,这里的实例本身就是ze1!!!所以实际上上面两端代码等价于下面这段代码:

var ze1 = {};
    function F1(arg1, arg2) {
        ze1.name = arg1;
        ze1.id = arg2;
        ze1.nicknameArray = new Array("xiaoze", "huahua", "zehua");
    }
    F1("ze1", "01038");
    ze1.name;//ze1
    ze1.id;//01038
    ze1.nicknameArray;//xiaoze,huahua,zehua

        说完了this,call(), apply()函数也不得不说了!,因为他们不仅可以加深对于this的理解,还有很多其他的用途,本篇博客所讲的原型就会用到它,所以请继续往下看!!!

    3)、js中的call(), apply()函数

        网上说他们是加深、中函数调用的一种方式,的确,他们函数调用的一种方式,当时,本篇博客谈到的他们两的用途并不是作为函数调用,它配合this关键字,实现js中的继承!!这些在本片博客中都会讲到,先来看下这两个函数的基本用法:

        <1>、cal()函数

function F1(arg1, arg2) {
        this.name = arg1;
        this.id = arg2;
        this.nicknameArray = new Array("xiaoze", "huahua", "zehua");
    }

    var ze1 = {};
    //call函数的第一个参数(某对象的实例)将会取代对象(F1)构造函数中的this关键字
    //而其后的参数将是与构造函数的参数相对应
    F1.call(ze1, "ha", "01038");

    ze1.name;//ze1
    ze1.id;//01038
    alert(ze1.nicknameArray);//xiaoze,huahua,zehua*/

            其实可以看到,和前面的this的讲解。本质上是一样的,而后面,我们将会看到call()函数的第一个参数是this关键字的用法

        <2>、apply()函数

function F1(arg1, arg2) {
        this.name = arg1;
        this.id = arg2;
        this.nicknameArray = new Array("xiaoze", "huahua", "zehua");
    }

    var ze1 = {};
    //apply函数的第一个参数(某对象的实例)将会取代对象(F1)构造函数中的this关键字
    //而其后的参数是一个数组,是与构造函数的参数相对应
    F1.apply(ze1, ["ha", "01038"]);

    ze1.name;//ze1
    ze1.id;//01038
    alert(ze1.nicknameArray);//xiaoze,huahua,zehua*/

            可以发现,apply和call函数极其的相似,第一个入参的含义相同,后面的参数在本质上面还是一样的

 

正式:

1、ECMAScript标准

    既然涉及到了ECMAScript这个词,就不得不说一下它是个什么东西了:我想,能搜到这篇博客的读者都是听说过javascript的,那么,javascript和ECMAScript是什么关系呢?简单的说就说:ECMAScript是javascript这类脚本语言的标准,而javascript是这个标准的实现和扩展(是先有javascript,后又ECMAScript的,想详细了解的可以其ECMAScript官网查看官方文档),标准的魅力这是让人愉悦,马士兵(编者的java的启蒙老师,必须赞一波)说过,一流公司买标准,二流公司买服务,三流公司卖产品(题外话)。

2、原型

    一、以对象中属性和方法的定义为例需要特别注意的是,这并不是继承,这和继承一点关系都没有,创建对象的实例并不是继承

        在不使用原型(prototype)定义对象的属性和方法:也就是只使用构造函数的方式来定义属性和方法

function F1(arg1, arg2) {
        this.name = arg1;
        this.id = arg2;
        this.nicknameArray = new Array("xiaoze", "huahua", "zehua");
        this.func = function () {
            alert(this.nicknameArray);
        }
    }
var ze1 = new F1("ze1", "01038");
    var ze2 = new F1("ze2", "01038");
    ze1.func();//xiaoze,huahua,zehua
    ze2.func();//xiaoze,huahua,zehua

            可以发现,我们new了两个F1的实例,但是,这样就会带来一个问题:func()这个函数会因为我们new了两个实例而被创建两次!这样不合理,而js的原型可以解决这个问题!下面看只利用js的原型来定义对象的方法和属性

        只利用js的原型来定义对象的方法和属性

function F1(arg1, arg2) {

    }
    F1.prototype.name = "ze1";
    F1.prototype.id = "01038";
    F1.prototype.nicknameArray = new Array("xiaoze", "huahua", "zehua");
    F1.prototype.func = function () {
        alert(this.nicknameArray);
    };

    var ze1 = new F1("ze1", "01038");
    var ze2 = new F1("ze2", "01038");
    ze1.nicknameArray[0] = "zeze";
    ze1.func();//zeze,huahua,zehua
    ze2.func();//zeze,huahua,zehua

            可以发现两个问题:

            1)、现在不能通过构造函数来传递参数,可以发现,这里的属性是写死的!

            2)、使用prototype的方式传递给实例的是指针,所以正如上面的例子,某个实例的属性改变了,其他的该对象的实例的属性都会发生相应的变化

            所以,我们通常使用构造函数/原型的混合模式来定义对象的属性和方法,也就是使用构造函数定义对象的非函数属性,而使用原型定义对象的函数属性,看到这里,有木有对js中的原型有更好的理解呢?如果嫌不够,那就继续往下看——基于原型的js继承机制

        使用构造函数定义对象的非函数属性,而使用原型定义对象的函数属性

function F1(arg1, arg2) {
        this.name = arg1;
        this.id = arg2;
        this.nicknameArray = new Array("xiaoze", "huahua", "zehua");
    }
    F1.prototype.func = function () {
        alert(this.nicknameArray);
    };

    var ze1 = new F1("ze1", "01038");
    var ze2 = new F1("ze2", "01038");
    ze1.nicknameArray[0] = "zeze";
    ze1.func();//zeze,huahua,zehua
    ze2.func();//xiaoze,huahua,zehua

            可以发现,通过这种方式,完美的解决了上面的所有问题

            1、使用原型定义对象的函数属性,该函数只会被创建一遍!

            2、当改变某实例的属性值的时候,并不会影响到该对象的其他实例

            3、可以通过构造函数来传递参数

   

    二、基于原型的js继承机制

        最基本是:将某函数对象(父类)直接赋予给某对象的属性(子类)

function F1(arg1, arg2) {
        this.name = arg1;
        this.id = arg2;
        this.nicknameArray = new Array("xiaoze", "huahua", "zehua");
        this.alertName = function () {
            alert(this.name);
        };
    }

    function F2(arg1,arg2, arg3) {
        //注意,这里的F1是被当做普通的函数进行传递,而不是构造函数
        this.f1 = F1;
        this.f1(arg1, arg2);
        this.school = arg3;
        this.alertSchool = function () {
            alert(this.school);
        };
    }

    var f1 = new F1("ze1", "01038");
    var f2 = new F2("ze2", "01038", "SDU");
    f1.alertName();//ze1
    f2.alertName();//ze2
    f2.alertSchool();//SDU

            可以看到,这里大量用到this关键字,注意上面的那行注释!!所有,这行代码:this.f1 = F1等价于下面的代码:

this.f1 = function (arg1, arg2) {
            this.name = arg1;
            this.id = arg2;
            this.nicknameArray = new Array("xiaoze", "huahua", "zehua");
            this.alertName = function () {
                alert(this.name);
            };
        };

            可以发现,这个就是F1的源码!!而当new出F2时,这些this将全部被f2这个实例给替换,成为f2的属性和方法!,

        基于call()函数的继承

function F1(arg1, arg2) {
        this.name = arg1;
        this.id = arg2;
        this.nicknameArray = new Array("xiaoze", "huahua", "zehua");
        this.alertName = function () {
            alert(this.name);
        };
    }

    function F2(arg1,arg2, arg3) {
        //注意,这里传入了this作为call函数的第一个参数
        F1.call(this, arg1, arg2);
        this.school = arg3;
        this.alertSchool = function () {
            alert(this.school);
        };
    }

    var f1 = new F1("ze1", "01038");
    var f2 = new F2("ze2", "01038", "SDU");
    f1.alertName();//ze1
    f2.alertName();//ze2
    f2.alertSchool();//SDU

            注意到上面的那行注释,以this关键字作为call函数的第一个参数,想必,现在读应该知道这个this在F2实例化的时候会被替换成什么对象了吧!没错,在这个例子中,this被f2替换,这样,是不是更好的理解了call函数呢!

        基于apply()函数的继承

            因为这个函数和call函数极其的相似,在这里就请读者参照上面的例子自行完成吧!

        基于原型的继承

            终于进入正题了,看下面例子

function F1(arg1, arg2) {
    this.name = arg1;
    this.id = arg2;
    this.nicknameArray = new Array("xiaoze", "huahua", "zehua");
    }
    F1.prototype.alertName = function () {
        alert(this.name);
    };

    function F2(arg1,arg2, arg3) {
        //利用这种方式(有些地方成为“对象冒充”),继承非函数属性
        F1.call(this, arg1, arg2);
        this.school = arg3;
        this.alertSchool = function () {
            alert(this.school);
        };
    }
    //注意,这里讲F1的实例传递给F2的原型对象
    //注意,这里的F1是不用带参数的
    //我们使用这种方式(原型),继承函数属性
    F2.prototype = new F1();

    var f1 = new F1("ze1", "01038");
    var f2 = new F2("ze2", "01038", "SDU");
    f1.alertName();//ze1
    f2.alertName();//ze2
    f2.alertSchool();//SDU

            注意到上面的那些注释!,以及前面内容的铺垫,这些代码是不是很容易理解!通过这行代码可以看出(F2.prototype = new F1();),F2的原型对象(也就是F2.prototype)是F1的实例,这样,F1的原型对象的所有的方法和属性都将传递给F2.prototype,这样,上面的那句代码就等价于下面这句代码:

//在这个例子中F2.prototype = new F1();等价于下面代码
//因为F1中只有一个alertName是原型方法(这里只算自己定义的,其实还有很多是继承而来的)

F2.prototype.alertName = function () {
        alert(this.name);
    };

            这样写,是不是就好理解了,就理解了为什么F2的实例f2可以使用alertName方法了

这里有一个小坑,就是:因为我们是将F2的原型对象重新赋予为F1的实例,这样在F2.prototype = new F1()这句代码之前的所有为F2.prototype赋值的代码(例如F2.prototype.xxx=yyy)都将失效,所以一个解决方法是:将这些新的赋值代码写在F2.prototype = new F1()这句代码之后就行了。

 

说了这么多,是不是对js中的原型在应用层面有了较好的了解了呢!

注意:这篇博客中的实例是基于ECMAScript规范来的,并不是javascript

 

 

版权声明:本博客只许被链接,拒绝任何形式的copy

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值