你可以不知道的JS三

2016-05-09 08:08
分类:编程  ASP.NET  
作者:jerrylsxu

原型

开头我还不想单刀直入的向您介绍什么是原型,因为这样无法让您享受原型带来的快感,我们从"最初的梦想"谈起
在Js中创建对象我们最初是直接创建,再到后来有了工厂模式,代码大致如下:

 
 
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title >工厂模式创建对象</title >
    <script type="text/javascript">
        //----------直接创建对象-------------
        var per = new Object();
        per.name = "piziyimao";
        per.say = function () {
            alert( this.name);
        }
        //每次这样创建一个"类"的对象,多麻烦丫!?有木有构造函数哇?!
        //于是有了工厂模式的一个变体
        //-----------”工厂模式“--------------
        function createPer(name) {
            var per = new Object();
            per.name = name;
            per.say = function () { alert(this .name); }
            return per;
        }
        var p1 = createPer("pizi" );
        var p2 = createPer("yimao" );
        alert(p1.say == p2.say); //false
    </script>
</head>
<body>
</body>
</html>

但是工厂模式存在三个缺点
    1、每创建一个类型的对象都要单独写一个构造方法
    2、同一个构造函数createPer产生的两个对象不共享实例,消耗内存资源 p1.say == p2.say返回false就是证明
    3、构造函数createPer内定义匿名函数运行时有闭包的开销【不明白参见文章末尾闭包】

针对第一个问题采用构造函数模式解决
定义一个person"类",内部通过this点的方式赋值,再通过new person的方式创建对象
大致如下:

 
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title ></title>
    <script type="text/javascript">
        function person(name,age) {
            this.name = name;
            this.say = function () { alert(this.name); }
        }
        var p1 = new person("pizi");
        var p2 = new person("yimao");
        p1.say();
    </script>
</head>
<body>
</body>
</html>

关于第二三问题就需要使用到原型来解决,
Q:什么是原型?
A:每个函数都有一个prototype属性,这个属性就是一个对象。它可以让所有的对象实例共享它所包含的属性和方法。
下面使用原型与构造函数共同生产对象
代码如下:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title ></title>
    <script type="text/javascript">
        function person() {
        }
        person.prototype.name = "pizi";
        person.prototype.say = function () {
            alert( this.name);
        }
        var p1 = new person();
        var p2 = new person();
        p2.name = "yimao";
        alert(p1.say == p2.say); //true
    </script>
</head>
<body>
</body>
</html>

尽管如此,并不是说在构造函数内创建属性不好【就像上述代码中name可以直接在构造函数中声明】,而是两者各有适合的范围。

Q:原型与构造函数如何选择?

A: 构造函数中成员产生闭包时,尽量用原型定义,这样可以减少开销。

尽量在构造函数内定义一般成员,尤其是对象或数组,因为用原型定义的成员是多个实例共享的

原型链

JavaScript中有两个特殊的对象: Object与Function,它们都是构造函数,用于生成对象。
    Object.prototype 是所有对象的祖先,
    Function.prototype是所有函数的原型,包括构造函数。
 
JavaScript中的对象通常分为三类,
    一类是用户创建的对象--即一般意义上用new语句显式构造的对象
    一类是构造函数对象--通过new调用生成普通对象的函数
    一类是原型对象--特指构造函数prototype属性指向的对象
 
这三类对象中每一类都有一个__proto__属性,它指向该对象的原型,从任何对象沿着它开始遍历都可以追溯到Object.prototype。
构造函数对象有prototype属性,指向一个原型对象,通过该构造函数创建对象时,被创建对象的__proto__属性将会指向构造函数的prototype属性。
原型对象有constructor属性,指向它对应的构造函数。
代码演示:

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title ></title>
    <script type="text/javascript">
        function F() {
        }
        Object.prototype.name = 'Obj';
        F.prototype.name = 'piziyimao';
        var obj = new Object();
        var f = new F();
        alert(f.name); //显示 piziyimao
        //f.__proto__等价于f
        alert(f.__proto__.name); //显示 piziyimao
        alert(f.__proto__.constructor.name); //F 等价于alert(f.constructor.name);
        alert(f.__proto__.constructor.prototype.name); //显示 piziyimao
        alert(f.__proto__.constructor.prototype == f.__proto__); //true
 
        alert(obj.name); //显示 Obj
        alert(f.__proto__.__proto__.name); //显示 Obj
        alert(f.__proto__.__proto__==obj.__proto__); //显示true
    </script>
</head>
<body>
</body>
</html>
 
 

定义了一个叫做F()的构造函数,并生产一个对象f。同时分别给Object和F添加原型对象

他们之间错综复杂的关系如图:

 
 

上图基本算做原型链的一个雏形。继承就是依靠原型链的机制实现的。【继承的本质就是一个对象可以访问到它的原型链上任何一个原型对象的属性或方法。】
例如
    上例的f对象,它拥有F.prototype和Object.prototype所有属性的浅拷贝(只复制基本数据类型,不复制对象)。所以可以直接访问f.constructor,f.toString

 

继承

基于上述原型链的思想实现继承
 
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title ></title>
    <script type="text/javascript">
        //--------------Beta版------------------------
        function A(name) {
            this.name = name;
        }
        A.prototype.Say = function () {
            alert( this.name);
        }
        function B() {
            //通过apply()修改A上下文对象实现属性的继承
            A.apply( this, ["痞子一毛" ]);
        }
        B.prototype.Say = A.prototype.Say; //实现方法的继承
        var b = new B();
        b.Say();
        //貌似实现了,但这并不理想,如果A原型不止Say一个方法呢?,
        //比如再来一个Play,那岂不要手动为B添加对应方法:B.prototype.Play = A.prototype.Play;
        //我们能不能将B原型指针直接指向A原型地址 B.prototype=A.prototype;答案是可以的,但会产生新的问题
 
        //--------------升级版------------------------
        B.prototype = A.prototype; //实现方法的继承
        var b = new B();
        b.Say(); //痞子一毛
 
        //看上去"风平浪静",实质"暗流涌动"
        B.prototype.play = function () {
            alert( "我修改了A原型方法哦!!侬信不信?!" );
        }
        var a = new A();
        a.play(); //"我修改了A原型方法哦!!侬信不信?!"  如果你说这没有违反继承!那我就再也不相信爱情了
        //Q:这是谁的错呢?
        //A:A的错,B只想继承A的属性与方法,而A却是"投怀送抱以身相许"
 
        //--------------旗舰版------------------------
        function A(name) {
            this.name = name;
        }
        A.prototype.Say = function () {
            alert( this.name);
        }
        function B() {
        }
        B.prototype.play = function () {
            alert( "侬调戏我?!" );
        }
        var b = new A("痞子一毛");//通过new出一个A类对象赋给B对象,即能轻轻松松把钱赚,又不"湿"身,不是一般人能想出来的 
        b.Say();
        //var a = new A();
        //a.play();//错误 A对象是访问不到的
    </script>
</head>
<body>
</body>
</html>
 
图解如下:Object和Function未画,粗体线为继承方法Say()的调用链

闭包

闭包:内部函数可以访问到外部函数/内部函数包含了外部函数的指针
在理解上一章节有关作用域的基础上在来理解闭包比较容易
上述原型中的工厂模式生产对象有三个缺点,这里通过第三个缺点来认识闭包
工厂模式代码简化如下:

 
 
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title ></title>
    <script type="text/javascript">
        //createPer(name)方法返回一个匿名函数
        function createPer(name) {
            return function () {
                alert(name);
            }
        }
        createPer( "piziyimao")(); //piziyimao 结果说明:匿名函数内部可以访问到外部函数name
        //补充:
        var b = createPer("piziyimao" );
        b();
        var c = createPer("痞子一毛" );
        c(); //痞子一毛 b与c互不影响,说明生成了两个闭包实例,内部引用的name变量分属各自的运行环境
    </script>
</head>
<body>
</body>
</html>

 

闭包的用途

上述图中阐述了使用闭包的危害,但我还没告诉你什么情况下使用闭包
闭包的用途主要有两个:1.实现嵌套的回调函数 2.实现私有成员【Js对象中是没有私有属性的】

回调函数代码示例:

    <script type="text/javascript">
        function getPicByPost() {
            var xhr = createXhr();
            xhr.open( "POST", "getPic.ashx" , true);
            xhr.setRequestHeader( "Content-Type", "application/x-www-form-urlencoded" );
            xhr.onreadystatechange = function () {//onreadystatechange回调函数
                if (xhr.readyState == 4) {
                    if (xhr.status == 200) {
                        var res = xhr.responseText;
                        alert( "res=" + res);
                    } else {
                        alert( "对不起,服务器出错" );
                    }
                }
            }
            xhr.send( "txtName=piziyimao" );
        }
    </script>
私有成员示例
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title ></title>
    <script type="text/javascript">
        var addtimes = function () {
            var count = 0;
            return function () {
                count++;
                return count;
            }
        }
        var test = addtimes();
        alert(test()); //1 只有调用test才能访问到闭包内的count变量,别妄想"暴力破解"哈哈
        alert(test()); //2
        alert(test()); //3
    </script>
</head>
<body>
</body>
</html>
好文要顶 收藏该文 我要举报
如果您觉得本文对你有用,不妨帮忙点个赞,或者在评论里给我一句赞美,小小成就都是今后继续为大家编写优质文章的动力!
上一篇:国外干货!6个方法助你设计出优秀的APP
下一篇:无限分级和tree结构数据增删改【提供Demo下载】

一起悦读网    Copyright © 2016    浙ICP备11048508号