zoukankan      html  css  js  c++  java
  • 原型链

    函数

      在Javascript中,function才是Javascript的第一型。当我们写下一段函数时,其实不过是建立了一个function类型的实体。就像我们可以写成这样的形式一样:

      function Hello() {

                alert("Hello");

            }

            Hello();

            var Hello = function () {

                alert("Hello");

            }

            Hello();

      其实都是一样的。但是当我们对其中的函数进行修改时,会发现很奇怪的问题。

        <script type="text/javascript">

            function Hello() {

                alert("Hello");

            }

            Hello();

            function Hello() {

                alert("Hello World");

            }

            Hello();

        script>

      我们会看到这样的结果:连续输出了两次Hello World。而非我们想象中的HelloHello World

      这是因为Javascript并非完全的按顺序解释执行,而是在解释之前会对Javascript进行一次预编译,在预编译的过程中,会把定义式的函数优先执行,也会把所有var变量创建,默认值为undefined,以提高程序的执行效率。也就是说上面的一段代码其实被JS引擎预编译为这样的形式:

        <script type="text/javascript">

            var Hello = function() {

                alert("Hello");

            }

            Hello = function() {

                alert("Hello World");

            }

            Hello();

            Hello();

        script>

      我们可以通过上面的代码很清晰地看到,其实函数也是数据,也是变量,我们也可以对函数进行赋值(重赋值)。当然,我们为了防止这样的情况,也可以这样:

        <script type="text/javascript">

            function Hello() {

                alert("Hello");

            }

            Hello();

        script>

        <script type="text/javascript">

            function Hello() {

                alert("Hello World");

            }

            Hello();

        script>

      这样,程序被分成了两段,JS引擎也就不会把他们放到一起了。

    作用域

      每个写过程序的人都不会对作用域这个概念陌生,那在这篇文章中就来谈下Javascript的作用域。

      在Javascript,全局环境本身就一个对象。在浏览器宿主中这个对象是window,而当Javascript用于其它非浏览器的宿主,如嵌入式的环境中,可能会是其它的对象。

      在这里也纠正一个观念,有很多人都认为Javascript只在浏览器中使用,其实Javascript也能在很多非Web情况下使用,据介绍Javascript在一些基于嵌入式的应用领域表现得也很出色,当然这些我也只是听过传说而已。

      言归正传,当我们写下:var i=1时,其实就是声明了一个window作用域的一个变量。

      而当我们写下i=1时,是声明了一个window的属性。

      看这样一段代码:

        <script type="text/javascript">

            var a = "hello";

            b = "world";

            Test();

            function Test() {

                alert(a + " " + b);

                var a = "welcome";

                b = "china";

                alert(a + " " + b);

            }

            alert(a + " " + b);

        script>

      这段代码分别输出的结果是:undefined worldwelcome china, hello china.

      我们来分别解释:

      在上文中,我们说过,在Javascript预编译时,会把所有var变量创建,默认值为undefined,我们在这里可以举一个例子:

      我们可以写这样一段代码:

        <script type="text/javascript">

            alert(a);

            alert(b);

            var a = "111";

            b = "111";

        script>

      当我们执行运行这段脚本时,可以发现,首先弹出undefined,然后回提示脚本错误,提示b不存在。由此就可以表明,a在预编译的过程中就已经被创建并且初始化为undefined,而b却只能在实际运行时按顺序去解释。其实在预编译后的Javascript代码可以近乎理解如下:

        <script type="text/javascript">

            var a = undefined;

            alert(a);

            alert(b);

            a = "111";

            b = "111";

        script>

     

      接下来我们可以谈一下函数的作用域问题,每当代码运行进入一个函数时,Javascript引擎就会自动创建一个新的作用域,然后把这个新作用域作为当前作用域的子作用域,然后把当前的代码作用域切换到这个新作用域。当代码退出函数时,这个作用域销毁,把代码作用域交还给他的父作用域。

      好,准备工作差不多了,接下来我们就来解释第一个问题:问什么会输出undefined world

      首先代码进行在预编译,当进入 Test方法时,开启一个新作用域,然后把全局作用域作为他的父作用域。然后对Test内的方法进行预编译,和上面的代码一样,Test方法被预编译后方法体大致如下:

    function Test() {

        var a = undefined;

        alert(a + " " + b);

        var a = "welcome";

        b = "china";

        alert(a + " " + b);

    }

      当然,在当前作用域下无法找到b,于是他就会到他的父作用域下,也就是全局作用域找到了b=“world”。于是也就产生了这样的结果。

      第二次弹出welcome china,没什么好说的。

      第三次,弹出hello china。我们可以这样理解,var a 只是 方法Test的一个局部变量,而b由于事先未声明,因此他会去父作用域中去找到对应的定义。

      好,接下来,我们再看一下这个方法的若干个变体。

        <script type="text/javascript">

            var a = "hello";

            b = "world";

            Test();

            function Test() {

                alert(a + " " + b);

                a = "welcome";

                b = "china";

                alert(a + " " + b);

            }

            alert(a + " " + b);

        script>

      首先,我们将方法体内的var a改成a,我们先不看答案,直接来分析,首先,在预编译阶段,方法体内几乎没有任何改变,因此此时ab一样,都要去他们的父作用域中去寻找,因此第一次出的结果应该是hello world,第二次没什么说的:welcome china,第三次由于ab在本作用域内都没有事先定义,因此都是再改变父作用域内的值,因此应该输出welcome china.

      我们继续:

        <script type="text/javascript">

            var a = "hello";

            b = "world";

            Test();

            function Test() {

                alert(a + " " + b);

                var a = "welcome";

                var b = "china";

                alert(a + " " + b);

            }

            alert(a + " " + b);

        script>

      和上面的分析一样,应该输出undefined undefinedwelcome chinahello world.

      继续:

        <script type="text/javascript">

            a = "hello";

            b = "world";

            Test();

            function Test() {

                alert(a + " " + b);

                var a = "welcome";

                b = "china";

                alert(a + " " + b);

            }

            alert(a + " " + b);

        script>

      应该是undefined world,welcome china,hello china.

      经试验,都没问题, 不知道你明白了么?

      因此我们可以得出,每个变量在找不到自己的定义时,都会沿着作用链向上寻找,这样就很可能会出现未预知的错误,给排错添加了很多困难。更麻烦的是,还可能会对父作用域上的变量值进行修改,因此我们在声明变量时应该尽量加上var,尽管Javascript并不强迫我们这样做。

    Eval函数

     eval的作用其实很简单,就是把一段字符串传递给JS解释器,由Javascript解释器将这段字符串解释成Javascript代码,并且执行他。

      举个最简单的例子:

        <script type="text/javascript">

            eval("alert(1+1)");

        script>

      很简单,把字符串解释成JS代码并执行,弹出2

      当然,上面的例子只是个玩具,在实际中没有人会傻到这么用。我想大家最基本的使用eval函数都是应该在DOM中,例如我们有div1,div2,div3,那么在document.getElementByID时我们的ID没有办法去得到,那么最简单的办法就是在for循环中,使用eval来拼接这么一段程序。例如这样:

        <script type="text/javascript">

            for (var loop = 1; loop < 10; loop++) {

                eval('document.getElementById("div"+loop).innerHTML="123"');

            }

        script>

      最基本的用法说完,相信大家还是对这个函数意犹未尽,如果这个函数只有这么点用法,那就太无聊了。那我们就一点点来剖下一下eval()函数。

      我们就先从eval的作用域说起,先看这样一段函数:

        <script type="text/javascript">

            eval("var i=3");

            alert(i);

        script>

      代码很简单,结果可以弹出3。接下来再对比这段代码:

        <script type="text/javascript">

            var test = function () {

                eval("var i=3");

                alert(i);

            }

            test();

            alert(i);

        script>

      结果是首先弹出3,然后是undefined

      那么说明:eval()函数动态执行的代码并不会创建新的作用域,其代码就是在当前的作用域执行的。因此也就是说,eval()函数也完全可以使用当前作用域的this,argument等对象。

      在IE中,支持这样一种和eval()非常类似的函数叫做:execScript()。我们可以来写段简单的代码。

        <script type="text/javascript">

            var test = function () {

                execScript("var i=3");

                alert(i);

            }

            test();

            alert(i);

        script>

      结果弹出了23,这也就看出了execScript函数的特点,首先他和eval相类似,都能将字符串解释成JS代码并且执行,但是他的作用域不是当前作用域,而是全局作用域。当我们把上面的代码放到Firefox和谷歌浏览器上去试试:发现在FirefoxexecScript上代码是无效的,那么也说明一个问题,execScript代码的浏览器兼容性是有问题的。

      那么就引申出这样一个问题,我们如何能把这两个函数的优点给汇总到一起呢,也就是说,全局+浏览器兼容性。上网搜了下,自己给汇总了一下,大概是这样:

        <script type="text/javascript">

            var StrongEval = function (code) {

                if (window.navigator.userAgent.indexOf("MSIE") >= 1) {

                    execScript(code);

                }

                if (window.navigator.userAgent.indexOf("Firefox") >= 1) {

                    window.eval(code);

                }

                else {

                    execScript(code);

                }

            };

            var Test = function () {

                StrongEval("var i=3");

            }

            Test();

            alert(i);

        script>

      这样就可以完美地兼容FFIE了,其本质代码就在于在FFevalwindow.eval并不等效,这是个很奇妙的事。

      另外,我们还可以用eval+with实现一些奇淫技巧。

      我们在一般意义上可以写出这样的代码:

    var obj = function () {

        this.a = 1;

        this.b = 2;

        this.c = 5;

        this.fun = function () {

            this.c = this.a + this.b;

        }

    };

    var o = new obj();

    o.fun();

    alert(o.c);

      或者是这样:

    var obj = {

        a: 1,

        b: 2,

        c: 5,

        fun: function () {

            this.c = this.a + this.b;

        }

    }

      再或者是这样:

    var obj = function () {

        this.a = 1;

        this.b = 2;

        this.c = 5;

    };

    obj.prototype.fun = function () {

        this.c = this.a + this.b;

    }

    var o = new obj();

    o.fun();

    alert(o.c);

      无论怎么样,你是不是对这样的this感觉厌烦了呢?那就让我们采取个很另类的办法吧,让至少在感官上可能会舒服一点。

        <script type="text/javascript">

            var funtemp = function () {

                c = a + b;

            }

            var obj = {

                a: 1,

                b: 2,

                c: 5

            };

            var fun;

            with (obj) {

                eval("fun = " + funtemp);

            }

            fun();

            alert(obj.c);

        script>

      这个很勉强,那么好,我们不讨论什么看着舒服不舒服。我们来讨论这样一种情况。

        <script>

            var DBCommon = function () {

                alert("1."); CreateConnection();

                alert("2."); OpenConnection();

                alert("3."); CreateCommand();

                alert("4."); ExcuteCommand();

                alert("5."); CloseConnection();

            }

            var SQLServerCommon = {

                CreateConnection: function () { alert("建立SQL Server连接"); },

                OpenConnection: function () { alert("打开SQL Server连接"); },

                CreateCommand: function () { alert("创建¨SQL Server命令"); },

                ExcuteCommand: function () { alert("执行DSQL Server命令"); },

                CloseConnection: function () { alert("关闭SQL Server连接"); }

            };

            var OracleCommon = {

                CreateConnection: function () { alert("建立¢Oracle连接"); },

                OpenConnection: function () { alert("打开aOracle连接"); },

                CreateCommand: function () { alert("创建¨Oracle命令"); },

                ExcuteCommand: function () { alert("执行DOracle命令"); },

                CloseConnection: function () { alert("关闭?Oracle连接"); }

            };

            with (SQLServerCommon) {

                eval("forSQLServer=" + DBCommon);

            }

            with (OracleCommon) {

                eval("forOracle=" + DBCommon);

            }

            forSQLServer();

            forOracle();

        script>

      我们又是否可以把这个看成是一个简陋的模板方法模式呢?呵呵。我们也可以把这个称为利用evalwith配合改变函数的上下文。

      不过话又说回来,Eval在一般的情况中是很少被用到的,我们是完全可以避免来使用它的。

    类和对象

      首先,不得不说,我无法达到抛开类和对象的概念来看Javascript的境界,对于Javascript是否是面向对象的说法有很多,不过我最认同的还是Javascript是一种基于prototype的面向对象语言

      面向对象语言三大特点:继承,多态,封装,这三点虽然Javascript没有提供天然的语法实现,但是我们都可以通过prototype等技巧来实现,因此这种说法似乎不过分。

      在Javascript中,构造对象有三种方式:

      1. 首先,我们要明确一个概念,Javascript是一种弱类型的语言,一方面体现在Javascript的变量,返回类型都是没有强类型约束的,另一方面,Javascript可以为对象任意添加属性和方法。根据这个,我们可以写出这样的代码:

        <script type="text/javascript">

            var person = {};

            person.name = "飞林沙";

            person.age = 21;

            person.Introduce = function () {

                alert("My name is " + this.name + ".I'm " + this.age);

            };

            person.Introduce();  

        script>

      这里的person就是我们构造出的一个对象。

      2. 我们也可以利用JSON的形式来构造一个对象。

        <script type="text/javascript">

            var person = {

                name: "飞林沙",

                age: 21,

                Introduce: function () { alert("My name is " + this.name + ".I'm " + this.age); }

            };

            person.Introduce();  

        script>

      这个是不是很像我们在C#3.0里提出的匿名对象呢?

    protected void Page_Load(object sender, EventArgs e)

    {

        var person = new

        {

            name = "飞林沙",

            age = 21

        };

        Response.Write("My name is " + person.name + ".I'm " + person.age);

    }

      不同的是在Javascript中,函数是一种类型,所以可以赋给某个变量,但是C#不可以。

      但是上面两种方法我们看到,我们都是单独定义了一个对象。接下来让我们把他们抽象出来成为一个类。

        <script type="text/javascript">

            var Person = function () {

                this.name = "飞林沙";

                this.age = 21;

                this.Introduce = function () {

                    alert("My name is " + this.name + ".I'm " + this.age);

                };

            };

            var person = new Person();

            person.Introduce();

        script>

      可是在这里,我们看到,属性都已经被写死了,我们根本没办法为每个对象单独订制,解决办法很简单:

        <script type="text/javascript">

            var Person = function (name, age) {

                this.name = name;

                this.age = age;

                this.Introduce = function () {

                    alert("My name is " + this.name + ".I'm " + this.age);

                };

            };

            var person = new Person("飞林沙", 21);

            person.Introduce();

        script>

      好,我们来对比一下第二种和第三种写法,两者是等效的。在第二种写法中,实际上是构建了一个JSON对象,而我们又知道JSON本质上其实就是一个键值对,那么我们是否也可以用同样的方式来理解一个对象呢?

      我们来写出这样的测试代码试试:

        <script type="text/javascript">

            var Person = function (name, age) {

                this.name = name;

                this.age = age;

                this.Introduce = function () {

                    alert("My name is " + name + ".I'm " + age);

                };

            };

            var person = new Person("飞林沙", 21);

            for (var p in person) {

                alert(p);

            }

            alert(person["name"]);

        script>

      这样的代码没偶任何问题,首先用遍历的方式来找到person所有的key(属性和方法名)。然后我们用索引的方式来访问person对象的name属性。

      这些都没有问题,可是我们是不是看到了一个引申的问题,从传统面向对象的语言来看,nameage应该属于私有变量,那么这样用person简简单单的访问,是不是破坏了封装性呢?

      还记得我们在前文中说过的么?var的叫变量,没有var的叫属性。那么我们如果讲代码改成这个样子。

        <script type="text/javascript">

            var Person = function (name, age) {

                var name = name;

                var age = age;

                this.GetName = function () {

                    return name;

                }

                this.GetAge = function () {

                    return age;

                }

                this.Introduce = function () {

                    alert("My name is " + name + ".I'm " + age);

                };

            };

            var person = new Person("é?3", 21);

            alert(person["name"]);

            alert(person.GetName());

        script>

      这样就可以封装得很好了,这也是在Javascript中的封装方式。

    prototype的提出

      <script type="text/javascript">

            var Person = function (name, age) {

                this.name = name;

                this.age = age;

                this.Introduce = function () {

                    alert("My name is " + this.name + ".I'm " + this.age);

                };

            };

            var person1 = new Person("飞林沙", 21);

            var person2 = new Person("kym", 26);

            alert(person1.Introduce == person2.Introduce);

        script>

     

      结果弹出false。也就是说,这两个对象的方法是不同的方法。那么我们知道,在C#中,每个对象会维护着一个方法表,可是方法表应该指向同一块地址。如果是这样的话,那当我们声明了100个对象,是不是要建立100个对象拷贝,对空间是不是一个很大的浪费呢?

      于是我们就想了这样的解决办法,用prototype

        <script type="text/javascript">

            var Person = function (name, age) {

                this.name = name;

                this.age = age;

            };

            Person.prototype.Introduce = function () {

                alert("My name is " + this.name + ".I'm " + this.age);

            }

            var person1 = new Person("飞林沙", 21);

            var person2 = new Person("kym", 26);

            alert(person1.Introduce == person2.Introduce);

        script>

     

      这样就可以了。所以你还会再说是否用prototype都是一样的么?其实我以前也是这么理解的,在这次偶然的试验中看到了这个问题。

     

    说到prototype,就不得不先说下new的过程。

    我们先看看这样一段代码:

        <script type="text/javascript">

            var Person = function () { };

            var p = new Person();

        script>

    很简单的一段代码,我们来看看这个new究竟做了什么?我们可以把new的过程拆分成以下三步:

    <1> var p={}; 也就是说,初始化一个对象p

    <2> p.__proto__=Person.prototype;

    <3> Person.call(p);也就是说构造p,也可以称之为初始化p

    关键在于第二步,我们来证明一下:

        <script type="text/javascript">

            var Person = function () { };

            var p = new Person();

            alert(p.__proto__ === Person.prototype);

        script>

    这段代码会返回true。说明我们步骤2的正确。

    那么__proto__是什么?我们在这里简单地说下。每个对象都会在其内部初始化一个属性,就是__proto__,当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么他就会去__proto__里找这个属性,这个__proto__又会有自己的__proto__,于是就这样一直找下去,也就是我们平时所说的原型链的概念。

    按照标准,__proto__是不对外公开的,也就是说是个私有属性,但是Firefox的引擎将他暴露了出来成为了一个共有的属性,我们可以对外访问和设置。

    好,概念说清了,让我们看一下下面这些代码:

        <script type="text/javascript">

            var Person = function () { };

            Person.prototype.Say = function () {

                alert("Person say");

            }

            var p = new Person();

            p.Say();

        script>

    这段代码很简单,相信每个人都这样写过,那就让我们看下为什么p可以访问PersonSay

    首先var p=new Person();可以得出p.__proto__=Person.prototype。那么当我们调用p.Say()时,首先p中没有Say这个属性,于是,他就需要到他的__proto__中去找,也就是Person.prototype,而我们在上面定义了Person.prototype.Say=function(){}; 于是,就找到了这个方法。

    好,接下来,让我们看个更复杂的。

        <script type="text/javascript">

            var Person = function () { };

            Person.prototype.Say = function () {

                alert("Person say");

            }

            Person.prototype.Salary = 50000;

            var Programmer = function () { };

            Programmer.prototype = new Person();

            Programmer.prototype.WriteCode = function () {

                alert("programmer writes code");

            };

            Programmer.prototype.Salary = 500;

            var p = new Programmer();

            p.Say();

            p.WriteCode();

            alert(p.Salary);

        script>

    我们来做这样的推导:

    var p=new Programmer()可以得出p.__proto__=Programmer.prototype;

    而在上面我们指定了Programmer.prototype=new Person();我们来这样拆分,var p1=new Person();Programmer.prototype=p1;那么:

    p1.__proto__=Person.prototype;

    Programmer.prototype.__proto__=Person.prototype;

    由根据上面得到p.__proto__=Programmer.prototype。可以得到p.__proto__.__proto__=Person.prototype

    好,算清楚了之后我们来看上面的结果,p.Say()。由于p没有Say这个属性,于是去p.__proto__,也就是Programmer.prototype,也就是p1中去找,由于p1中也没有Say,那就去p.__proto__.__proto__,也就是Person.prototype中去找,于是就找到了alert(“Person say”)的方法。

    其余的也都是同样的道理。

    这也就是原型链的实现原理。

    最后,其实prototype只是一个假象,他在实现原型链中只是起到了一个辅助作用,换句话说,他只是在new的时候有着一定的价值,而原型链的本质,其实在于__proto__

    JSON做原型

    利用JSON去构造一个对象。代码如下:

        <script type="text/javascript">
            var People = {
                name: "kym",
                age: 21,
                SayHello: function () {
                    alert("Hello,My name is " + this.name + ".I am " + this.age);
                }
            }
            alert(People.name);
            People.SayHello();
        script>

    但是我们是不能重用这个对象的,我们如何把这个对象作为原型的呢?

    首先,在一个JSON对象有一个构造方法是不可能的了,那么我们就做一个简单的工厂吧,写一个方法来专门负责创建。

        <script type="text/javascript">
            var People = {
                Create: function (name, age) {
                    this.name = name;
                    this.age = age;
                },
                SayHello: function () {
                    alert("Hello,My name is " + this.name + ".I am " + this.age);
                }
            }
            People.Create("kym", 21);
            People.SayHello();
        script>

    但是通过这个方法我们却发现,我们没有办法用People作为原型

    var p=new People();==>p.__proto__=People.prototype。于是当我们p.SayHello()的时候就会去People.prototype中去找,结果什么都找不到。

    如果可以People.prototype.SayHello=function(){}就可以解决这个问题。但是我们知道,只有function才可以有prototype

    那么我们想想之前的推导公式,怎么样能让p.SayHello()呢?如果可以p.__proto__=People就好了。那么我们想个办法:

    既然在new的时候,某个对象的__proto__只能等于某个函数的prototype,我们设置一个函数X,令p.__proto__=X.prototype,我们再令X.prototype=People。这样的关系是这样:

        <script type="text/javascript">
            var People = {
                Create: function (name, age) {
                    this.name = name;
                    this.age = age;
                },
                SayHello: function () {
                    alert("Hello,My name is " + this.name + ".I am " + this.age);
                }
            };
            
            var X = function () { };
            X.prototype = People;
            var p = new X();
            p.Create("kym", 21);
            p.SayHello();
        script>

    这样就相当于用X做了一个中间变量,使得我们可以访问JSON对象的内部属性。但是这样是不是不太优雅呢?我们每次创建一个对象时,都需要来写这样一个辅助的函数。那好,我们就把这个过程封装起来:

    var Factory = {
        CreatePeople : function (className,name,age) {
            var temp = function () {
                className.Create(name, age);
            };
            temp.prototype = className;
            var result = new temp();
            return result;
        }
    };
    var people = Factory.CreatePeople(People,"kym",21);
    people.SayHello();

    但是这样也有一个缺点,就是每次我增加一个类,就需要向Factory里注册一个新方法,这样是很麻烦的,我在很久以前的  中说过关于callapply的区别,因为这里的参数不固定,我们不可能一一列举,因此我们在这里可以用apply来改善这个方法:

        <script type="text/javascript">
            var People = {
                Create: function (name, age) {
                    this.name = name;
                    this.age = age;
                },
                SayHello: function () {
                    alert("Hello,My name is " + this.name + ".I am " + this.age);
                }
            };

            var Factory = {
                Create: function (className, params) {
                    var temp = function () {
                        className.Create.apply(this, params);
                    };
                    temp.prototype = className;
                    var result = new temp();
                    return result;
                }
            };
            var people = Factory.Create(People,["kym",21]);
            people.SayHello();
        script>

    这样,一个完整的创建类就诞生了!那么我们每次创建时就都可以用JSON来做了,然后用户每次都统一来调用Factory.Create()就可以了!

    prototype封装继承

    我利用prototype的原理做了一个封装的New,然后我就想到,我是否可以用prototype的原理进一步封装面向对象的一些基本特征呢?比如继承。

    好,那就让我们一步步打造,首先让我们来看下继承原本的写法:

        <script>

            var Person = function(name, age) {

                this.name = name;

                this.age = age;

            }

            Person.prototype.SayHello = function () {

                alert(this.name + "," + this.age);

            };

            var Programmer = function (name, age, salary) {

                Person.call(this, name, age);

                this.salary = salary;

            };

            Programmer.prototype = new Person();

            var pro = new Programmer("kym", 21, 500);

            pro.SayHello();

        script>

    我们看到,在实际上,继承的根本就在于这一步Programmer.prototype=new Person()。也就是说把Person加到原型链上。那也就是说,我们实现的关键就在于原型链的打造。

    在上文中,我们用JSON来打造了一个原型,其原型链是p.__proto__=Person。那么我们希望在这个上封装继承,那么原型链应该是p.__proto__.__proto__=SuperClass,也就是说Person.__proto__=SuperClass。但是按照我们上面代码的继承方法,原型链关系是Person.__proto__=SuperClass.prototype

    这个和我们在上文中一样,我们的办法就是借助一个辅助函数,将原来的函数内的属性赋给X,然后令X.prototype=SuperClass即可,也就是说我们将子原型进行一个封装。

    好,就按照这个思路,我们来实现利用原型链的继承关系的封装。

        <script>

            var Factory = {

                Create: function (className, params) {

                    var temp = function () {

                        className.Create.apply(this, params);

                    };

                    temp.prototype = className;

                    var result = new temp();

                    return result;

                },

                CreateBaseClass: function (baseClass, subClass) {

                    var temp = function () {

                        for (var member in subClass) {

                            this[member] = subClass[member];

                        }

                    };

                    temp.prototype = baseClass;

                    return new temp();

                }

            };

            var People = {

                Create: function (name, age) {

                    this.name = name;

                    this.age = age;

                },

                SayHello: function () {

                    alert("Hello,My name is " + this.name + ".I am " + this.age);

                }

            };

            var Temp = {

                Create: function (name, age, salary) {

                    People.Create.call(this, name, age);

                    this.salary = salary;

                },

                Introduce: function () {

                    alert(this.name + "$" + this.age + "$" + this.salary);

                }

            };

            var Programmer = Factory.CreateBaseClass(People, Temp);

            var pro = Factory.Create(Programmer, ["kym", 21, 500]);

            pro.SayHello();

        script>

     

    这样就完成了我们对继承关系的封装。当然,我们也可以不单独写一个变量:

    var Programmer = Factory.CreateBaseClass(People,

    {

        Create: function (name, age, salary) {

            People.Create.call(this, name, age);

            this.salary = salary;

        },

        Introduce: function () {

            alert(this.name + "$" + this.age + "$" + this.salary);

        }

    });

    网页运行原理

    当我们打开一个网页的时候,浏览器会首先创建一个窗口,这个窗口就是我所知道的window对象,也就是整个Javascript运行所依附的全局变量。

    为了加载网页文档,当前窗口又需要创建一个Document对象,然后把打开的网页加载到Document下。网页就是在这个加载的过程中,一边加载一边呈现,所以我们当网速非常慢的时候可以看到,网页从上到下一点点地打开。

    当我们用<script src=’’>引入其他的JS时,浏览器可能会派遣其他线程去下载,但是浏览器也会等待需要的JS文件下载完成,然后再有主线程按顺序加载JS其他的代码。在Web标准下,限制对同一个域名最多只允许使用两个线程可以同时加载内容,当然可以通过修改注册表来强迫Windows模块突破这一限制。

    同时,许多网站会把js放到不同的子域名下,这样就可以使浏览器开启更多的线程并行加载这些资源,从而更加充分地利用网络带宽。

    当整个页面都加载结束后,浏览器开始触发window对象或者body对象的onload事件,其实window对象和body对象的load事件是想通的,这也就意味着两个事件只能有一个起作用。当然,在常规意义上,也没有同时设置两者的需求。

    到此结束,然后JS引擎就暂停工作,等待着下一次的触发。因此我们可以说:“JS总是被动触发的

    包装DOM对象

    我们在日常的应用中,使用Javascript大多数时间都是在用DOM ,以致于很多人都有一种看法就是DOM==JS,虽然这种看法是错误的,但是也可以说明DOM的重要性。

    这就导致了我们在写JS的时候,首先会考虑的是这个方法在页面上会产生什么样的变化之类的问题,用架构的思想来说:我们可以说这样把用户界面和业务逻辑掺杂到了一起。我们也知道,这样对于一个稍大的项目来说,满脑袋都是DIV,都是CSS是做不好东西的。

    那么怎么办?我们还是用对象的角度,从逻辑上来考虑这个问题,让我们忘记那些DOM对象。

    我们来举一个例子吧:

     

    对于某个回复,可能是回复本贴,也可能是举报。那么暂时让我们忘记那些DOM对象,让我们想清楚逻辑:

    点击回复本贴时,隐藏举报窗口,打开回复窗口。

    点击举报本贴时,隐藏回复窗口,打开举报窗口。

    OK,也就是说整个逻辑包含两个对象,一个是举报窗口对象,一个是回复窗口对象,每个对象有两个方法,一个是打开,一个是隐藏。由于某个页面可能会有很多这样的对象,每个对象都应该是被创建的一个原型,于是就应该有这样的代码:

    <script type="text/javascript">

        var Comment = function (x, y) {

            var x = x;

            var y = y;

        };

        Comment.prototype.Create = function () {

        };

        Comment.prototype.Hide = function () {

        };

        var Report = function (x, y) {

            var x = x;

            var y = y;

        };

        Report.prototype.Create = function () {

        };

        Report.prototype.Hide = function () {

        };

    script>

    至于逻辑就是:

    buttonCommert.onclick = function () {

        GetReport(“id”).Hide();

        HideOthers();  //  关闭本页面其它的回复窗口

        var c = new Comment("1","1");

        c.Create();

    }

    举报按钮也近似如此。

    好了大致逻辑如此,我们需要的只是实现原型中的创建和隐藏方法。

    var Comment = function (x, y) {

        var x = x;

        var y = y;

        var ConfirmComment = function () {

            //Ajaxá?à?

        };

    };

    Comment.prototype.Create = function () {

        var com = document.createElement("div");

        document.getElementById("XXXX").appendChild(com);

        com.x = x;

        com.y = y;

        com.style.left = "XXpx";

        com.style.top = "YYpx";

        var txt = document.createElement("input");

        txt.nodeType = "text";

        com.appendChild(txt);

        var btn = document.createElement("input");

        btn.nodeType = "button";

        btn.onclick = ConfirmComment();

        com.appendChild(btn);

    };

    以上皆为伪代码,只是提供一种封装DOM的思路。

    在工程中,将DOM对象包装成符合我们自己业务逻辑的Javascript对象是一种非常好的做法,这也是企业JS库形成的一个过程。

    说句极端话,如果足够成熟后,也许页面中写JS看不到DOM,而皆为包装好的JS对象

    关于响应事件

    关于事件的问题。

    这里简单说下:

    <input type="button" runat="server"  value="Click Me" id="ButtonTest" />

    很普通的一个按钮,我们要为其添加点击事件,有两种方法:

    A.

        <input type="button" runat="server" onclick="Alert()" value="Click Me" id="ButtonTest" />

        <script type="text/javascript">

            function Alert() {

                alert("Click Me");

            }

        script>

    B.

        <input type="button" runat="server" value="Click Me" id="ButtonTest" />

        <script type="text/javascript">

            document.getElementById("ButtonTest").onclick = function () {

                alert("Click Me");

            }

        script>

    我们称A方法为静态绑定,B方法为动态绑定。我们来看A方法,他近乎可等于这样的效果:

        <input type="button" runat="server" onclick="Alert()" value="Click Me" id="ButtonTest" />

        <script type="text/javascript">

            document.getElementById("ButtonTest").onclick = function () {

                Alert();

            }

        script>

    也就是说,当我们静态绑定一个事件的时候,实际上是系统默认为我们做了一个匿名函数,然后把我们的方法体包于其中。由于这样,就涉及到了this的问题。

    我们来看这样一段代码,这也是我在公司初学JS的时候,公司的JS小牛抛给我的问题。

        <input type="button" runat="server" onclick="Alert1()" value="Test1" id="ButtonTest1" />

        <input type="button" runat="server" onclick="Alert2(this)" value="Test2" id="ButtonTest2" />

        <script type="text/javascript">

            function Alert1() {

                alert(this.value);

            }

            function Alert2(obj) {

                alert(obj.value);

            }

        script>

    当试验这样一段代码时,便会发现第一个按钮会弹出”undefined”,第二个按钮弹出”Test2”。原因就是如我上面所说,在按钮一中,方法等于声明了一个匿名函数,然后讲Alert1()闭包于其中,也就是说,Alert1()是无法找到其调用者的,所以这时,他会去找window对象的value属性,结果发现未定义,我们可以这样证明:

        <input type="button" runat="server" onclick="Alert1()" value="Test1" id="ButtonTest1" />

        <input type="button" runat="server" onclick="Alert2(this)" value="Test2" id="ButtonTest2" />

        <script type="text/javascript">

            value = "window";

            function Alert1() {

                alert(this.value);

            }

            function Alert2(obj) {

                alert(obj.value);

            }

        script>

    运行上面的代码,就可以很好地理解问题了!

    简单Ajax

    Ajax:Asynchronous Javascript And XML。写个简单的例子:

    <body>

        <form id="form1" runat="server">

        <div>

            <asp:Label ID="LabelTime" runat="server">asp:Label>

        div>

        form>

        <script type="text/javascript">

            if (!window.XMLHttpRequest) {

                window.XMLHttpRequest = function () {

                    return new ActiveXObject("Microsoft.XMLHTTP");

                };

            }

            function UpdateClock() {

                var request = new XMLHttpRequest();

                request.open("post", "TimeTest.aspx", false);

                request.send("");

                document.getElementById("LabelTime").innerText = request.responseText;

            }

            setInterval(UpdateClock, 1000);

        script>

    body>

    而在另一个页面写下当前时间,这样就形成了一个钟表。

    代码很简单,就是操纵一个XMLHttpRequest对象来获取服务器时间,然后更新时间。上面的代码在与服务器交互时,并没有页面整体刷新,而是局部刷新。

    但是上面的代码在request.open时,最后一个参数为false,表示发出的XMLHttpRequest是同步的,由于Javascript是单线程的,所以在等待请求的过程中,线程会被阻塞,如果请求时间过长,浏览器会停止响应。

    虽然Javascript是单线程的,但是XMLHttpRequest具备异步处理请求的能力。代码如下:

    <body>

        <form id="form1" runat="server">

        <div>

            <asp:Label ID="LabelTime" runat="server">asp:Label>

        div>

        form>

        <script type="text/javascript">

            if (!window.XMLHttpRequest) {

                window.XMLHttpRequest = function () {

                    return new ActiveXObject("Microsoft.XMLHTTP");

                };

            }

            function AsynRequest() {

                var request = new XMLHttpRequest();

                request.open("post", "TimeTest.aspx", true);

                request.onreadystatechange = function () {

                    if (request.readyState === 4) {

                        UpdateClock(request.responseText);

                    }

                };

                request.send("");

            }

            function UpdateClock(time) {

                document.getElementById("LabelTime").innerText = time;

            }

            setInterval(AsynRequest, 1000);

        script>

    body>

    Ajax与PHP数据交换

    一、前台传递字符串变量,后台返回字符串变量(非json格式)

      Javascript代码:

      这里,为了解决Ajax数据传递出现的汉字乱码,在字符串传递之前,使用javascript函数escape()对汉字字符串进行了编码,并且对返回

    的字符串使用unescape()函数进行解码,使得汉字得以正常显示。当然了,后台PHP代码也添加了头文件,以保证汉字字符串不会出现乱码。各种后台代码解决

    汉字乱码问题的方式如下:

      PHP:header('Content-Type:text/html;charset=GB2312'); 

     

     $(function(){

         var my_data="前台变量";

         my_data=escape(my_data)+"";//编码,防止汉字乱码

         $.ajax({

             url: "ajax_php.php",  

             type: "POST",

             data:{trans_data:my_data},

             //dataType: "json",

             error: function(){  

                 alert('Error loading XML document');  

             },  

             success: function(data,status){//如果调用php成功    

                 alert(unescape(data));//解码,显示汉字

             }

         });

         

     });

     

      PHP代码:

     <?php

         header('Content-Type:text/html; charset=gb2312');//使用gb2312编码,使中文不会变成乱码

         $backValue=$_POST['trans_data'];

         echo $backValue."+后台返回";

     ?>

    二、前台传递多个一维数组,后台返回字符串变量(非json格式)

      Javascript代码:

      在非json格式下,后台只能返回字符串,如果想后台返回数组,可以采用json格式,在本文的后面会详细介绍。

     $(function(){

         var my_data=new Array();

         var my_data1=new Array();

         my_data[0]=0;

         my_data[1]=1;

         my_data[2]=2;

         

         my_data1[0]=10;

         my_data1[1]=11;

         my_data1[2]=12;

         

         $.ajax({

             url: "ajax_php.php",  

             type: "POST",

             data:{trans_data:my_data,trans_data1:my_data1},

             //dataType: "json",

             error: function(){  

                 alert('Error loading XML document');  

             },  

             success: function(data,status){//如果调用php成功    

                 alert(data);

             }

         });

         

     });

      PHP代码:

      <?php

          header('Content-Type:text/html; charset=gb2312');//使用gb2312编码,使中文不会变成乱码

          

          //读取第一个数组

          $backValue="trans_data:";

          $trans=$_POST['trans_data'];

          foreach($trans as $value)

          {

              $backValue=$backValue." ".$value;

         }

         

         //读取第二个数组

         $backValue=$backValue." , trans_data1:";

         $trans=$_POST['trans_data1'];

         foreach($trans as $value)

         {

             $backValue=$backValue." ".$value;

         }

         echo $backValue;

     ?>

    三、前台传递多个一维数组,后台返回二维数组(json格式)

      Javascript代码:

      $(function(){

         var my_data=new Array();

         var my_data1=new Array();

         my_data[0]=0;

         my_data[1]=1;

         my_data[2]=2;

          

         my_data1[0]=10;

         my_data1[1]=11;

         my_data1[2]=12;

         

         $.ajax({

             url: "ajax_php.php",  

             type: "POST",

             data:{trans_data:my_data,trans_data1:my_data1},

             dataType: "json",

             error: function(){  

                 alert('Error loading XML document');  

             },  

             success: function(data){//如果调用php成功            

                 var back="";

                 for(var i=0;i<(data.length);i++){

                     for(var j=0;j<data[0].length;j++){

                         back+=" "+i+" "+j+" :"+data[i][j]+" ";

                     }

                     back+=" ";

                 }

                 alert(back);

             }

         });

     });

      PHP代码:

     <?php

         header('Content-Type:text/html; charset=gb2312');//使用gb2312编码,使中文不会变成乱码    

         $backValue=array();

         $backValue[0]=$_POST['trans_data'];    

         $backValue[1]=$_POST['trans_data1'];

         

         echo json_encode($backValue);

     ?>

    四、前台传递一维数组和二维数组,后台返回二维数组(json格式)

      Javascript代码:

     $(function(){

         var my_data=new Array();

         var my_data1=new Array();

         var my_data2=new Array();

          

         my_data[0]=0;

         my_data[1]=1;

         my_data[2]=2;

         

         my_data1[0]=10;

         my_data1[1]=11;

         my_data1[2]=12;

         

         my_data2[0]=my_data;

         my_data2[1]=my_data1;

         

         $.ajax({

             url: "ajax_php.php",  

             type: "POST",

             data:{trans_data:my_data,trans_data1:my_data1,trans_data2:my_data2},

             dataType: "json",

             error: function(){  

                 alert('Error loading XML document');  

             },  

             success: function(data){//如果调用php成功            

                 var back="";

                 for(var i=0;i<(data.length);i++){

                     for(var j=0;j<data[0].length;j++){

                         back+=" "+i+" "+j+" :"+data[i][j]+" ";

                     }

                     back+=" ";

                 }

                 alert(back);

             }

         });

        

     });

      PHP代码:

     <?php

         header('Content-Type:text/html; charset=gb2312');//使用gb2312编码,使中文不会变成乱码    

         $backValue=array();

         $backValue=$_POST['trans_data2'];    

         $backValue[2]=$_POST['trans_data'];    

         $backValue[3]=$_POST['trans_data1'];

         

         echo json_encode($backValue);

     ?>

  • 相关阅读:
    javascript、jQuery的扩展方法,扩展实例展示代码
    经典弹出层Colorbox
    在Entity Framework中使用事务
    WCF中常用的binding方式
    前端上传组件Plupload使用指南
    SQL ROW_NUMBER() OVER函数的基本用法用法
    数据库选项--触发器
    数据库选项--会刷新过程缓存的选项
    数据库选项--页面检查
    数据库选项--自动更新统计
  • 原文地址:https://www.cnblogs.com/yuqing-o605/p/6498772.html
Copyright © 2011-2022 走看看