首页 > 编程语言 >再读《悟透javascript》之三、甘露模型

再读《悟透javascript》之三、甘露模型

时间:2022-12-08 10:32:37浏览次数:39  
标签:function name 之三 javascript var base 甘露 prototype age


前言

      甘露模型是用于开发基于javascript的类库的,通过它,我们可以以类似C#等面向对象的语言式的模式来开发javascript类库,这将使你的javascript代码变得清晰有条理也便于维护。

 

 

一、创建对象三部曲

前面有说过,使用new来创建一个对象,可以分为三个步骤,比如:var x = new obj();

1.       创建一个空对象,var x = {};,注意,即使x指针原来指向某个对象,此时也会被新创建的空对象替换,也就是改变了x的指向。

2.       将新创建对象的隐式链指向obj对象的prototype对象,x.__proto__=obj.prototype;,此时,新创建的对象就可以使用obj.prototype上的属性和方法了。

3.       将新创建的对象作为obj函数的this来调用,obj.call(this);,这样,新创建的对象就拥有了obj函数中的所有this属性。

 

 

二、继承

我们再来回顾一下,javascript类的继承,如下:

function person(name, age){
this.name = name;
this.age = age;
}
person.prototype.say = function(){alert(this.name+" is "+this.age+" years old");}

function
person.apply(this, [name, age]);
this.salary = salary;
}
worker.prototype = new
worker.prototype.show = function(){alert(this.name+"'s salary is "+this.salary);}

这里worker类继承了person类,我们来仔细分析worker是如何继承person的。我发现这里也可以分为三步:

1.       是构造函数初始化this,这里的初始化还包括调用基类的构造函数来初始化基类的this

2.       将worker的prototype的隐式链指向person的prototype对象

3.       将worker类的方法都挂在worker的prototype对象上

通过这三步我们就实现了worker类继承person类。那么我们可否在一个函数中,封装这三个步骤呢?当然可以,这里需要使用到前面提到的语法甘露。

 

 

三、初步的甘露模型

要封装继承的实现,我们必须先找出它的要素,这是继承,所以必须要有一个基类,其次是要定义的类。这样我们就可以得到如下代码:

function
var
if(!defineClass) return;

var
if(!baseClass) return;
}

      基类和要定义的类都是必须有的,否则无法继续,但是基类可以空缺,空缺的话就用Function对象替代,Function是所有函数的基类。

 

 

      然后我们按上面分析的显示继承的三步来走。

 

 

      一、我们要初始化this,这必须要有一个构造函数,我们假定每个对象都有一个同名的构造函数create,这个create其实就是我们所说的类。所有又有了如下代码:

 

function
var
if(!defineClass) return;

var
if(!baseClass) return;

//声明的类即是构造函数,如果缺省的话使用默认构造函数
var _class = defineClass.create ? defineClass.create : function(){}
}

_class就是类的构造函数,如果缺省的话,会默认为一个空的函数。按上面的说法,构造函数中应该有调用基类的构造函数的语句才对,但是此处的基类是Function,又没有参数,所以就为空了。

 

 

      二、我们要将定义类的prototype的隐式链指向基类的prototype,结果如下:

function
var
if(!defineClass) return;

var
if(!baseClass) return;

//声明的类既是构造函数,如果缺省的话使用默认构造函数
var _class = defineClass.create ? defineClass.create : function(){}

function
_prototype.prototype = baseClass.prototype;
var prototype = new _prototype(); //构造声明类的prototype

_class.prototype = prototype;
}

这里使用了一个_prototype壳函数来避免直接new baseClass,因为直接new baseClass中可能出现很多的程序副本,这这些副本是不必要的累赘。

 

 

      三、将定义类的方法挂到定义类的prototype上,代码如下:

function
var
if(!defineClass) return;

var
if(!baseClass) return;

//声明的类既是构造函数,如果缺省的话使用默认构造函数
var _class = defineClass.create ? defineClass.create : function(){}

function
_prototype.prototype = baseClass.prototype;
var prototype = new _prototype(); //构造声明类的prototype

for(var member in defineClass) //将定义的类的元素复制到声明类的prototype上
if(member!="create")
prototype[member] = defineClass[member];

_class.prototype = prototype;

return
}

因为create是用来作为构造函数的,所以,在将定义类的方法挂到定义类的prototype上时,不需要将create挂上去。完成了这第三步,我们也就完成了对继承的三步的封装,最后将_class构造函数返回。

 

 

      四、实际应用一下,例:

    var

        create:function(name, age){

            this.name = name;

            this.age = age;

        },

        say:function(){

            alert(this.name+" is "+this.age+" years old");

        }

    });

   

    var p = new person("soldierluo", 23);

    p.say();

   

    var

        create:function(name, age, salary){

            person.apply(this, [name, age]);

            this.salary = salary;

        },

        show:function(){

            alert(this.name+"'s salary is "+this.salary+"$");

        }

    });

   

    var w = new worker("luo", 33, 33333);

    w.say();

w.show();

这里,我们成功的进行了类的声明、继承、实例化和调用,并且结构清晰。这已经很好了吧,确实,看起来是没什么问题了——————但是,还可以更好。上面,在worker的构造函数中需要调用基类的构造函数来初始化this,使用person.apply(this, [name, age]);的方式并非很直接,加入我们可以这样this.base(…);,不是更好了吗?(base是基类,也就是基类构造函数)

 

 

四、完善后的甘露模型

上面说到的使用基类名称来初始化this,并不是很直接。如果改成this.base(…)的法师会好很多,如果这样的话,我们就需要为构造函数添加一个base的基类属性,如下:

    function

        var

        if(!defineClass) return;

       

        var

        if(!baseClass) return;  

       

        var _class = defineClass.create ? defineClass.create : function(){}

       

        function

        _prototype.prototype = baseClass.prototype;

        var prototype = new _prototype();    //构造声明类的prototype

       

        for(var member in defineClass)  //将定义的类的元素复制到声明类的prototype上

            if(member!="create")

                prototype[member] = defineClass[member];

       

        _class.prototype = prototype;

        _class.base = baseClass;

       

        return

}

然后,我们将应用的代码改成

    var

        create:function(name, age){

            this.name = name;

            this.age = age;

        },

        say:function(){

            alert(this.name+" is "+this.age+" years old");

        }

    });

   

    var

        create:function(name, age, salary){

            this.base(name, age);

            this.salary = salary;

        },

        show:function(){

            alert(this.name+"'s salary is "+this.salary+"$");

        }

    });

   

    var w = new worker("luo", 33, 33333);

    w.say();

    w.show();

执行后发现,居然报错了,错误是“对象不支持此属性或方法”。哪个对象不支持哪个属性或方法?我们刚才仅仅是将person.apply(this, [name, age]);改成了this.base(name, age);,难道问题出在这?改回来再试试,果然又可以了。

 

 

      看来问题的确出在this.base这里,那我们检查一下这个this.base吧!用alert(this.base);后发现,this.base居然是undefined。这怎么可能,上面我们不是已经为_class增加了base属性吗?

 

 

这是为什么,我试来试去后发现,“对于javascript中的类,其实例化对象只能调用该类中this和prototype上的属性及方法,而通过类名直接增加的属性和方法是与该类的实例化对象无关的,也就是说,实例化对象无法调用通过类名直接增加的属性和方法”,这个说起来好像很复杂,下面做个测试:

    <script type="text/javascript">

    function

        this.name="ddd";

    }

    test.age = 23;

   

    var t = new

   

    alert(t.name+":"+t.age+":"+test.age);

</script>

结果发现,t.age是undefined,而test.age则是上面所赋值的23。正和我上面归纳的一样,而原因是什么呢?在我苦思冥想后终于开朗,原因是这样的:test作为一个函数对象,分为内外两部分,内的就是{}中间的部分,其余的都是其外边的部分包括prototype也是外边的部分。当我们使用 var t = new test();时,首先是创建了一个新的空的对象,然后将这个空的对象作为test的this调用,这样t就拥有了test内的所有东东,然后将t的隐式链指向test的prototype,这样又拥有了test.prototype上的东东,但是,test外边的东西,除了test.prototype外,其余的东东都没有付给这个t对象,所以,t无法访问到test外的除test.prototype外的东东。

 

 

好了,插了这么大一段“废话”,目的就是要说明。想通过this.base来直接调用baseClass——没门,那是否要放弃这种方式呢?可以明确的告诉你,不用。有一个方法是可以解决,但这个方法在逻辑上是相当的绕,各位先理理脑筋,免得等下打结。

 

 

this,也就代表当前的实例化出来的对象,而上面说得很清楚了,这个对象是访问不到我们所添加的base的,但是this访问不到,他会根据继承关系一层层的往下找,先找person然后找Function再找Object,如果还没有那就真是没有了。

这样的话,我们可以在Function或Object的prototype上加个base函数,在这个base函数中,我们可以通过caller知道谁调用了这个base,而这个caller恰好就是create函数,也就是Class中的_class,这样我们绕了个圈又找回了create函数对象,现在我们就可以放心的调用_class.base(…)了,代码如下:

Function.prototype.base = function(){   //调用基类的构造函数
var
Caller&&Caller.base&Caller.base.apply(this, arguments);
}

 

五、最终的代码及示例如下

<script type="text/javascript">
function
var
if(!defineClass) return;

var
if(!baseClass) return;

var _class = defineClass.create ? defineClass.create : function(){}

function
_prototype.prototype = baseClass.prototype;
var prototype = new _prototype(); //构造声明类的prototype

for(var member in defineClass) //将定义的类的元素复制到声明类的prototype上
if(member!="create")
prototype[member] = defineClass[member];

_class.prototype = prototype;
_class.base = baseClass;

return
}

Function.prototype.base = function(){ //调用基类的构造函数
var
Caller&&Caller.base&Caller.base.apply(this, arguments);
}

var
create:function(name, age){alert(this.base);
this.base();
this.name = name;
this.age = age;
},
say:function(){
alert(this.name+" is "+this.age+" years old");
}
});

var p = new person("soldierluo", 23);
p.say();

var
create:function(name, age, salary){alert(this.base);
this.base(name, age);
this.salary = salary;
},
show:function(){
alert(this.name+"'s salary is "+this.salary+"$");
}
});

var w = new worker("luo", 33, 33333);
alert(w.constructor);
w.say();
w.show();
</script>

至此,我们基本完成了甘露模型的构建,现在体会一下,准备用它大展用途吧!

 

 

 

标签:function,name,之三,javascript,var,base,甘露,prototype,age
From: https://blog.51cto.com/u_15906220/5920662

相关文章

  • JavaScript:代码细节和良好编码习惯
    这些细节,与语法无关,仅仅是编写代码时需要注意的最最基本的细节和一些良好编码习惯。注释代码注释代码分为单行注释和多行注释,如下所示:严格区分大小写JS的代码时严格区......
  • JavaScript:严格模式"use strict"
    因为历史遗留问题,JS其实存在很多feature,以及兼容性问题;所以JS在ES5之后,新增了一个严格模式,以区别于普通模式,用来激活新的特性,使得某些代码的执行准确无误;如何开启严格模......
  • JavaScript:是一种什么样的编程语言?
    有关JavaScript的发展历程,百度百科上已经说得很清楚了,这里不赘述,只是想谈一下我刚刚接触JS的一些感触。作为后端java开发者,初次学习JS的时候,真的觉得JS非常的不严谨,很混乱......
  • JavaScript:代码应该编写在哪里?
    我们可以将JS的代码,编写在三个地方。但是无论编写在哪里,最后它都会嵌入进网页代码中,被浏览器执行。编写在script标签中我们可以直接在HTML的script标签中,编写大段JS代码......
  • JavaScript:jQuery类库
    目录jQuery类库一、jQuery简介1.特点2.使用jQuery的不同方式3.jQuery底层本质4.标签对象与jQuery对象二、jQuery查找标签1.基本选择器2.组合选择器3.层级选择器4.属性选择......
  • JavaScript(三)
    ❤️‍JavaScriptjQuery查找标签jQuery节点操作jQuery事件绑定bootstrap页面框架❤️‍jQuery查找标签......
  • 算法练习:两指针之三数之和为0
    问题描述给出一个整型数组,找出所有三个元素的组合,其组合之和等于0。要求在结果集里不含有重复的组合。举例:输入{-2, 1, -1, 2, 1}输出{-2, 1, 1 } 问题分析最容易想到的是......
  • 瀑布流布局 不到30行代码实现(JavaScript + absolute)支持懒加载
    @目录前言一、使用css实现瀑布流布局1.flex布局2.column-count多栏布局3.grid网格布局二、结合JavaScript的瀑布流布局实现1.推荐原因2.实现步骤a.初步实现:结合JavaSc......
  • Javascript-极速入门指南-3-jQuery使用教程
    内容概要jQuery类库类库jQuery简介jQuery的宗旨:Writeless,domore写的更少做的更多jQuery的特点为: 1.加载速度快 2.选择器更多更好用 3.一行代码走天下......
  • 现代javascript教程 数组
    array字面量或者构造函数声明数组newArray(100),长度100的空获取数组长度,是一个属性,arr.length获得元素,通过索引值,arr[0]修改数组,arr[0]=0用alert方法打印数组,会......