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维吉尼亚密码算法实现代码
Nov 09 Javascript
JavaScript中为元素加上name属性的方法
May 09 Javascript
jQuery ajax 路由和过滤器使用说明
Aug 02 Javascript
JQuery+JS实现仿百度搜索结果中关键字变色效果
Aug 02 Javascript
JavaScript返回网页中超链接数量的方法
Apr 03 Javascript
jQuery基于函数重载实现自定义Alert函数样式的方法
Jul 27 Javascript
js获取浏览器的各种属性
Apr 27 Javascript
package.json配置文件构成详解
Aug 27 Javascript
layui自己添加图片按钮并点击跳转页面的例子
Sep 14 Javascript
axios解决高并发的方法:axios.all()与axios.spread()的操作
Nov 09 Javascript
JS数据类型分类及常用判断方法
Nov 19 Javascript
JS前端轻量fabric.js系列之画布初始化
Aug 05 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对字符串的递增运算分析
2010/08/08 PHP
PHP中使用GD库绘制折线图 折线统计图的绘制方法
2015/11/09 PHP
php实现的生成迷宫与迷宫寻址算法完整实例
2017/11/06 PHP
PHP使用Http Post请求发送Json对象数据代码解析
2020/07/16 PHP
Valerio 发布了 Mootools
2006/09/23 Javascript
JavaScript 学习笔记(十四) 正则表达式
2010/01/22 Javascript
Js获取事件对象代码
2010/08/05 Javascript
让JavaScript拥有类似Lambda表达式编程能力的方法
2010/09/12 Javascript
基于jquery点击自以外任意处,关闭自身的代码
2012/02/10 Javascript
解读JavaScript中 For, While与递归的用法
2013/05/07 Javascript
使用node.js 获取客户端信息代码分享
2014/11/26 Javascript
jQuery 处理页面的事件详解
2015/01/20 Javascript
非常实用的12个jquery代码片段
2015/11/02 Javascript
JS基于HTML5的canvas标签实现炫目的色相球动画效果实例
2016/08/24 Javascript
nodejs前端自动化构建环境的搭建
2017/07/26 NodeJs
JS实现按钮添加背景音乐示例代码
2017/10/17 Javascript
[01:00:10]完美世界DOTA2联赛PWL S2 FTD vs Inki 第二场 11.21
2020/11/24 DOTA
跟老齐学Python之有容乃大的list(1)
2014/09/14 Python
Python进程间通信之共享内存详解
2017/10/30 Python
python绘制铅球的运行轨迹代码分享
2017/11/14 Python
python用BeautifulSoup库简单爬虫实例分析
2018/07/30 Python
Python实现简单的用户交互方法详解
2018/09/25 Python
python调用接口的4种方式代码实例
2019/11/19 Python
Python3读写Excel文件(使用xlrd,xlsxwriter,openpyxl3种方式读写实例与优劣)
2020/02/13 Python
专门经营化妆刷的美国彩妆品牌:Sigma Beauty
2017/09/11 全球购物
《商鞅南门立木》教学反思
2014/02/16 职场文书
亲子读书活动方案
2014/02/22 职场文书
教师新年寄语
2014/04/03 职场文书
政法干警核心价值观心得体会
2014/09/11 职场文书
2014年个人债务授权委托书范本
2014/09/22 职场文书
公安交警中队队长个人对照检查材料思想汇报
2014/10/05 职场文书
2015年禁毒宣传活动总结
2015/03/25 职场文书
寻找最美乡村教师观后感
2015/06/18 职场文书
银行大堂经理培训心得体会
2016/01/09 职场文书
2016年小学教师政治学习心得体会
2016/01/23 职场文书
领导激励员工的演讲稿,各种会上用得到,建议收藏
2019/08/13 职场文书