javascript学习笔记(五)原型和原型链详解


Posted in Javascript onOctober 08, 2014

私有变量和函数

在函数内部定义的变量和函数,如果不对外提供接口,外部是无法访问到的,也就是该函数的私有的变量和函数。

<script type="text/javascript">

    function Test(){

        var color = "blue";//私有变量

        var fn = function() //私有函数

        {
        }

    }

</script>

这样在函数对象Test外部无法访问变量color和fn,他们就变成私有的了:

var obj = new Test();

    alert(obj.color);//弹出 undefined

    alert(obj.fn);//同上

静态变量和函数

当定义一个函数后通过点号 “.”为其添加的属性和函数,通过对象本身仍然可以访问得到,但是其实例却访问不到,这样的变量和函数分别被称为静态变量和静态函数。

<script type="text/javascript">

    function Obj(){
    }

    Obj.num = 72;//静态变量

    Obj.fn = function()  //静态函数

    {
    } 
    alert(Obj.num);//72

    alert(typeof Obj.fn)//function
    var t = new Obj();

    alert(t.name);//undefined

    alert(typeof t.fn);//undefined

</script>

实例变量和函数

在面向对象编程中除了一些库函数我们还是希望在对象定义的时候同时定义一些属性和方法,实例化后可以访问,JavaScript也能做到这样

<script type="text/javascript">

    function Obj(){

                this.a=[]; //实例变量

                this.fn=function(){ //实例方法
                }

            }
            console.log(typeof Obj.a); //undefined

            console.log(typeof Obj.fn); //undefined
            var o=new Obj();

            console.log(typeof o.a); //object

            console.log(typeof o.fn); //function

</script>

为实例变量和方法添加新的方法和属性

<script type="text/javascript">

function Obj(){

                this.a=[]; //实例变量

                this.fn=function(){ //实例方法
                }

            }
            var o1=new Obj();

            o1.a.push(1);

            o1.fn={};

            console.log(o1.a); //[1]

            console.log(typeof o1.fn); //object
            var o2=new Obj();

            console.log(o2.a); //[]

            console.log(typeof o2.fn); //function

</script>

在o1中修改了a和fn,而在o2中没有改变,由于数组和函数都是对象,是引用类型,这就说明o1中的属性和方法与o2中的属性与方法虽然同名但却不是一个引用,而是对Obj对象定义的属性和方法的一个复制。

这个对属性来说没有什么问题,但是对于方法来说问题就很大了,因为方法都是在做完全一样的功能,但是却又两份复制,如果一个函数对象有上千和实例方法,那么它的每个实例都要保持一份上千个方法的复制,这显然是不科学的,这可肿么办呢,prototype应运而生。

基本概念

我们创建的每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。那么,prototype就是通过调用构造函数而创建的那个对象实例的原型对象。

使用原型的好处是可以让对象实例共享它所包含的属性和方法。也就是说,不必在构造函数中添加定义对象信息,而是可以直接将这些信息添加到原型中。使用构造函数的主要问题就是每个方法都要在每个实例中创建一遍。

在JavaScript中,一共有两种类型的值,原始值和对象值。每个对象都有一个内部属性 prototype ,我们通常称之为原型。原型的值可以是一个对象,也可以是null。如果它的值是一个对象,则这个对象也一定有自己的原型。这样就形成了一条线性的链,我们称之为原型链。

含义

函数可以用来作为构造函数来使用。另外只有函数才有prototype属性并且可以访问到,但是对象实例不具有该属性,只有一个内部的不可访问的__proto__属性。__proto__是对象中一个指向相关原型的神秘链接。按照标准,__proto__是不对外公开的,也就是说是个私有属性,但是Firefox的引擎将他暴露了出来成为了一个共有的属性,我们可以对外访问和设置。

<script type="text/javascript">

    var Browser = function(){};

    Browser.prototype.run = function(){

        alert("I'm Gecko,a kernel of firefox");

    }
    var Bro = new Browser();

    Bro.run();

</script>

当我们调用Bro.run()方法时,由于Bro中没有这个方法,所以,他就会去他的__proto__中去找,也就是Browser.prototype,所以最终执行了该run()方法。(在这里,函数首字母大写的都代表构造函数,以用来区分普通函数)

当调用构造函数创建一个实例的时候,实例内部将包含一个内部指针(__proto__)指向构造函数的prototype,这个连接存在于实例和构造函数的prototype之间,而不是实例与构造函数之间。

<script type="text/javascript">

function Person(name){

                this.name=name;

            }
            Person.prototype.printName=function(){

                alert(this.name);

            }
            var person1=new Person('Byron');

            var person2=new Person('Frank');

</script>

Person的实例person1中包含了name属性,同时自动生成一个__proto__属性,该属性指向Person的prototype,可以访问到prototype内定义的printName方法,大概就是这个样子的:

javascript学习笔记(五)原型和原型链详解

再举个栗子:

<script type="text/javascript">

    function Animal(name)   //积累构造函数

    {

        this.name = name;//设置对象属性

    }
    Animal.prototype.behavior = function() //给基类构造函数的prototype添加behavior方法

    {  

        alert("this is a "+this.name);

    }
    var Dog = new Animal("dog");//创建Dog对象

    var Cat = new Animal("cat");//创建Cat对象
    Dog.behavior();//通过Dog对象直接调用behavior方法

    Cat.behavior();//output "this is a cat"
    alert(Dog.behavior==Cat.behavior);//output true;

</script>

可以从程序运行结果看出,构造函数的prototype上定义的方法确实可以通过对象直接调用到,而且代码是共享的。(可以试一下将Animal.prototype.behavior 中的prototype属性去掉,看看还能不能运行。)在这里,prototype属性指向Animal对象。

数组对象实例

再看个数组对象的实例。当我们创建出array1这个对象的时候,array1实际在Javascript引擎中的对象模型如下:

var array1 = [1,2,3];

javascript学习笔记(五)原型和原型链详解

array1对象具有一个length属性值为3,但是我们可以通过如下的方法来为array1增加元素:

array1.push(4);
push这个方法来自于array1的__proto__成员指向对象的一个方法(Array.prototye.push())。正是因为所有的数组对象(通过[]来创建的)都包含有一个指向同一个具有push,reverse等方法对象(Array.prototype)的__proto__成员,才使得这些数组对象可以使用push,reverse等方法。

函数对象实例

function Base() {  

    this.id = "base" 

}  

javascript学习笔记(五)原型和原型链详解

var obj = new Base();

这样代码的结果是什么,我们在Javascript引擎中看到的对象模型是:

javascript学习笔记(五)原型和原型链详解

new操作符具体干了什么呢?其实很简单,就干了三件事情。

var obj  = {};  

obj.__proto__ = Base.prototype;  

Base.call(obj);

原型链

原型链:当从一个对象那里调取属性或方法时,如果该对象自身不存在这样的属性或方法,就会去自己关联的prototype对象那里寻找,如果prototype没有,就会去prototype关联的前辈prototype那里寻找,如果再没有则继续查找Prototype.Prototype引用的对象,依次类推,直到Prototype.….Prototype为undefined(Object的Prototype就是undefined)从而形成了所谓的“原型链”。

<script type="text/javascript">

    function Shape(){

        this.name = "shape";

        this.toString = function(){

            return this.name;

        }

    }

    function TwoShape(){

        this.name = "2 shape";

    }

    function Triangle(side,height){

        this.name = "Triangle";

        this.side = side;

        this.height = height;

        this.getArea = function(){

            return this.side*this.height/2;

        }

    }
    TwoShape.prototype = new Shape();

    Triangle.prototype = new TwoShape();

</script>

这里,用构造器Shape()新建了一个实体,然后用它去覆盖该对象的原型。

<script type="text/javascript">

    function Shape(){

        this.name = "shape";

        this.toString = function(){

            return this.name;

        }

    }

    function TwoShape(){

        this.name = "2 shape";

    }

    function Triangle(side,height){

        this.name = "Triangle";

        this.side = side;

        this.height = height;

        this.getArea = function(){

            return this.side*this.height/2;

        }

    }
    TwoShape.prototype = new Shape();

    Triangle.prototype = new TwoShape();
    TwoShape.prototype.constructor = TwoShape;

    Triangle.prototype.constructor = Triangle;
    var my = new Triangle(5,10);

    my.getArea();

    my.toString();//Triangle

    my.constructor;//Triangle(side,height)

</script>

原型继承

原型继承:在原型链的末端,就是Object构造函数prototype属性指向的那个原型对象。这个原型对象是所有对象的祖先,这个老祖宗实现了诸如toString等所有对象天生就该具有的方法。其他内置构造函数,如Function,Boolean,String,Date和RegExp等的prototype都是从这个老祖宗传承下来的,但他们各自又定义了自身的属性和方法,从而他们的子孙就表现出各自宗族的那些特征。

ECMAScript中,实现继承的方法就是依靠原型链实现的。

<script type="text/javascript">

    function Box(){             //被继承的函数叫做超类型(父类,基类)

        this.name = "Jack";

    }
    function Tree(){          //继承的函数叫做子类型(子类,派生类)

        this.age = 300;

    }

    //通过原型链继承,赋值给子类型的原型属性

    //new Box()会将box构造里的信息和原型里的信息都交给Tree

    Tree.prototype = new Box();//Tree继承了Box,通过原型,形成链条
    var tree = new Tree();

    alert(tree.name);//弹出 Jack

</script>

原型链的问题:原型链虽然很强大,可以用它来实现继承,但它也存在一些问题。其中最主要的问题来自包含引用类型的值原型。包含引用类型的原型属性会被所有实例共享;而这也正是为什么要在构造函数中,而不是在原型对象中定义属性的原因。在通过原型来实现继承时,原型实际上回变成另一个类型的实例。于是,原先的实例属性也就变成了原型的属性。

在创建子类型的实例时,不能向超类型的构造函数中传递参数。实际上,应该说是没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。再加上刚刚讨论的由于原型中包含引用类型值所带来的问题,实践中很少会单独使用原型链。

再举个栗子:

<script type="text/javascript">

    function Person(name)

    {

        this.name = name;//设置对象属性

    };
    Person.prototype.company = "Microsoft";//设置原型的属性

    Person.prototype.SayHello = function() //原型的方法

    {  

        alert("Hello,I'm "+ this.name+ " of " + this.company);

    };
    var BillGates = new Person("BillGates");//创建person对象

    BillGates.SayHello();//继承了原型的内容,输出"Hello,I'm BillGates of Microsoft"
    var Jobs = new Person("Jobs");

    Jobs.company = "Apple";//设置自己的company属性,掩盖了原型的company属性

    Jobs.SayHello = function()

    {

        alert("Hi,"+this.name + " like " + this.company);

    };

    Jobs.SayHello();//自己覆盖的属性和方法,输出"Hi,Jobs like Apple"

    BillGates.SayHello();//Jobs的覆盖没有影响原型,BillGates还是照样输出

</script>

看下面一个原型链例子:

<script type="text/javascript">

    function Year(){

        this.value = 21;

    }

    Year.prototype = {

        method:function(){
        }

    };
    function Hi(){
    };

    //设置Hi的prototype属性为Year的实例对象

    Hi.prototype = new Year();

    Hi.prototype.year = 'Hello World';
    Hi.prototype.constructor = Hi;
    var test = new Hi();//创建一个Hi的新实例
    //原型链

    test [Hi的实例]

        Hi.prototype [Year的实例]

            {year:'Hello World'}

            Year.prototype

                {method:……};

                object.prototype

                    {toString:...};
</script>

从上面例子中,test对象从Hi.prototype和Year.prototype中继承下来;因此他能访问Year的原型方法method,同时他能访问实例属性value

__ptoto__属性

__ptoto__属性(IE浏览器不支持)是实例指向原型对象的一个指针,它的作用就是指向构造函数的原型属性constructor,通过这两个属性,就可以访问原型里的属性和方法了。

Javascript中的对象实例本质上是由一系列的属性组成的,在这些属性中,有一个内部的不可见的特殊属性——__proto__,该属性的值指向该对象实例的原型,一个对象实例只拥有一个唯一的原型。

<script type="text/javascript">

    function Box(){        //大写,代表构造函数

        Box.prototype.name = "trigkit4";//原型属性

        Box.prototype.age = "21";

        Box.prototype.run = function()//原型方法

        {  

            return this.name + this.age + 'studying';

        }

    }
    var box1 = new Box();

    var box2 = new Box();

    alert(box1.constructor);//构造属性,可以获取构造函数本身,

                            //作用是被原型指针定位,然后得到构造函数本身

</script>  

__proto__属性和prototype属性的区别

prototype是function对象中专有的属性。
__proto__是普通对象的隐式属性,在new的时候,会指向prototype所指的对象;
__ptoto__实际上是某个实体对象的属性,而prototype则是属于构造函数的属性。__ptoto__只能在学习或调试的环境下使用。

原型模式的执行流程

1.先查找构造函数实例里的属性或方法,如果有,就立即返回。
2.如果构造函数的实例没有,就去它的原型对象里找,如果有,就立即返回

原型对象的

<script type="text/javascript">

    function Box(){        //大写,代表构造函数

        Box.prototype.name = "trigkit4";//原型属性

        Box.prototype.age = "21";

        Box.prototype.run = function()//原型方法

        {  

            return this.name + this.age + 'studying';

        }

    }
    var box1 = new Box();

    alert(box1.name);//trigkit4,原型里的值

    box1.name = "Lee";

    alert(box1.name);//Lee,就进原则
    var box2 = new Box();

    alert(box2.name);//trigkit4,原型的值,没有被box1修改

</script>

构造函数的

<script type="text/javascript">

    function Box(){                 

        this.name = "Bill";

    }
    Box.prototype.name = "trigkit4";//原型属性

    Box.prototype.age = "21";

    Box.prototype.run = function()//原型方法

    {  

            return this.name + this.age + 'studying';

    }
    var box1 = new Box();

    alert(box1.name);//Bill,原型里的值

    box1.name = "Lee";

    alert(box1.name);//Lee,就进原则

</script>
Javascript 相关文章推荐
原生Js与jquery的多组处理, 仅展开一个区块的折叠效果
Jan 09 Javascript
extjs_02_grid显示本地数据、显示跨域数据
Jun 23 Javascript
用Jquery.load载入页面后样式没了页面混乱的解决方法
Oct 20 Javascript
基于jQuery+PHP+Mysql实现在线拍照和在线浏览照片
Sep 06 Javascript
Javascript编程中几种继承方式比较分析
Nov 28 Javascript
js滚动条平滑移动示例代码
Mar 29 Javascript
下雪了 javascript实现雪花飞舞
Aug 02 Javascript
微信QQ的二维码登录原理js代码解析
Jun 23 Javascript
使用jQuery Ajax 请求webservice来实现更简练的Ajax
Aug 04 Javascript
js判断所有表单项不为空则提交表单的实现方法
Sep 09 Javascript
Angular5中调用第三方库及jQuery的添加的方法
Jun 07 jQuery
优化Vue项目编译文件大小的方法步骤
May 27 Javascript
JavaScript中获取高度和宽度函数总结
Oct 08 #Javascript
5个可以帮你理解JavaScript核心闭包和作用域的小例子
Oct 08 #Javascript
JavaScript中获取样式的原生方法小结
Oct 08 #Javascript
吐槽一下我所了解的Node.js
Oct 08 #Javascript
Node.js 的异步 IO 性能探讨
Oct 08 #Javascript
JS中的form.submit()不能提交表单的错误原因
Oct 08 #Javascript
js数值计算时使用parseInt进行数据类型转换(jquery)
Oct 07 #Javascript
You might like
在php中取得image按钮传递的name值
2006/10/09 PHP
PHP图片处理之使用imagecopy函数添加图片水印实例
2014/11/19 PHP
php上传大文件设置方法
2016/04/14 PHP
phpmailer绑定邮箱的实现方法
2016/12/01 PHP
PHP设计模式之工厂模式详解
2017/10/24 PHP
详解PHP的抽象类和抽象方法以及接口总结
2019/03/15 PHP
jQuery 扩展对input的一些操作方法
2009/10/30 Javascript
百度Popup.js弹出框进化版 拖拽小框架发布 兼容IE6/7/8,Firefox,Chrome
2010/04/13 Javascript
javascript常用代码段搜集
2014/12/04 Javascript
JQuery记住用户名密码实现下次自动登录功能
2015/04/27 Javascript
cocos2dx骨骼动画Armature源码剖析(一)
2015/09/08 Javascript
js实现的页面加载完毕之前loading提示效果完整示例【附demo源码下载】
2016/08/02 Javascript
HTML中setCapture、releaseCapture 使用方法浅析
2016/09/25 Javascript
javascript 单例模式详解及简单实例
2017/02/14 Javascript
Angular2库初探
2017/03/01 Javascript
jQuery设计思想
2017/03/07 Javascript
bootstrap treeview 扩展addNode方法动态添加子节点的方法
2017/11/21 Javascript
Vue2.0 给Tab标签页和页面切换过渡添加样式的方法
2018/03/13 Javascript
vue实现点击选中,其他的不选中方法
2018/09/05 Javascript
基于vue实现一个禅道主页拖拽效果
2019/05/27 Javascript
原生js实现抽奖小游戏
2019/06/27 Javascript
Element-UI中关于table表格的那些骚操作(小结)
2019/08/15 Javascript
Openlayers绘制聚合标注
2020/09/28 Javascript
JavaScript代码实现简单计算器
2020/12/27 Javascript
Python中处理时间的几种方法小结
2015/04/09 Python
使用C语言来扩展Python程序和Zope服务器的教程
2015/04/14 Python
Linux下通过python访问MySQL、Oracle、SQL Server数据库的方法
2016/04/23 Python
通过shell+python实现企业微信预警
2019/03/07 Python
python 实现线程之间的通信示例
2020/02/14 Python
详解使用postMessage解决iframe跨域通信问题
2019/11/01 HTML / CSS
仓库管理专业个人自我评价范文
2013/11/11 职场文书
面试求职的个人自我评价
2013/11/16 职场文书
商场中秋节活动方案
2014/02/07 职场文书
五年级学生评语大全
2014/12/26 职场文书
岁月神偷观后感
2015/06/11 职场文书
python通过函数名调用函数的几种方法总结
2021/06/07 Python