• Javascript原理


    1.javascript创建对象

      创建新对象有两种不同的方法:

    1. 定义并创建对象的实例

      

    person=new Object();
    person.firstname="Bill";
    person.lastname="Gates";
    person.age=56;
    person.eyecolor="blue";
    等价于:
    person={firstname:"John",lastname:"Doe",age:50,eyecolor:"blue"};

      2.使用函数来定义对象,然后创建新的对象实例 

    function person(firstname,lastname,age,eyecolor)
    {
    this.firstname=firstname;
    this.lastname=lastname;
    this.age=age;
    this.eyecolor=eyecolor;
    }

    创建新实例:

    var myFather=new person("Bill","Gates",56,"blue");

      原因:这时,javascript设计者想到C++和Java使用new命令时,都会调用"类"的构造函数(constructor)。他就做了一个简化的设计,在Javascript语言中,new命令后面跟的不是类,而是构造函数。

    2 实现继承

      由于所有的实例对象共享同一个prototype对象,那么从外界看起来,prototype对象就好像是实例对象的原型,而实例对象则好像"继承"了prototype对象一样。

      实例名.__proto__=类.prototype

      2.1 new创建对象过程的分解:

    function Foo() {}
    var foo=new Foo();

    分解为三步:

    a. var foo={};
    b. foo.__proto__=Foo.prototype;
    c. Foo.call(p);

    那么什么是__proto__?每一个通过函数和new操作符生成的对象都具有一个属性__proto__, 这个属性保存了创建它的构造函数的prototype属性的引用。按照标准,__proto__是个私有属性,但是在Firefox浏览器的脚本引擎中,它成为了一个可以访问的公有属性。

    2.2原型链图

     理解如下代码:

    第一段:

    <script type="text/javascript" lang="javascript">
    var str="string";
    function Foo() {var a=123;}
    var foo=new Foo();
     
    alert(str.__proto__);//empty
    alert(str.constructor);//function String() { [native code] }
    alert(str.__proto__.constructor);//function String() { [native code] }
     
    alert(str.__proto__===String.prototype);//true
    alert(str.__proto__.__proto__===Object.prototype);//true
    alert(str.__proto__.__proto__.__proto__===null);//true
     
    alert(Foo.__proto__);//function () { }
    alert(Foo.constructor);//function Function() { [native code] }
    alert(Foo.__proto__.constructor);//function Function() { [native code] }
    alert(Foo.__proto__.__proto__);//[object Object]
     
    alert(Foo.__proto__===Function.prototype);//true
    alert(Foo.__proto__.__proto__===Object.prototype);//true
    alert(Foo.__proto__.__proto__.__proto__===null);//true
     
    alert(foo.__proto__);//[object Object]
    alert(foo.constructor);//function Foo() {var a=123;}
    alert(foo.__proto__.constructor);//function Foo() {var a=123;}
    alert(foo.__proto__.__proto__);//[object Object]
     
    alert(foo.__proto__===Foo.prototype);//true
    alert(foo.__proto__.__proto__===Object.prototype);//true
    alert(foo.__proto__.__proto__.__proto__===null);//true
    </script>

    第二段:

    <script type="text/javascript" lang="javascript">
    alert(Object.__proto__);//function () { }
    alert(Object.__proto__.__proto__);//[object Object]
    alert(Object.__proto__.__proto__.__proto__);//null
     
    alert(Object.__proto__===Function.prototype);//true
    alert(Object.__proto__.__proto__===Object.prototype);//true
    alert(Object.__proto__.__proto__.__proto__===null);//true
     
    alert(Function.__proto__);//function () { }
    alert(Function.__proto__.__proto__);//[object Object]
    alert(Function.__proto__.__proto__.__proto__);//null
     
    alert(Function.__proto__===Function.prototype);//true
    alert(Function.__proto__.__proto__===Object.prototype);//true
    alert(Function.__proto__.__proto__.__proto__===null);//true
    </script>

    结论:

    a. 在JavaScript中,一切的一切都是对象,它们全部继承自Object,或者说所有对象原型链的根节点都是Object.prototype。

    b. 透彻理解JavaScript的原型链机制是非常重要的,一旦掌握了它,不管一个对象有多么的复杂,你总能够轻而易举地的将它攻破。

    c. prototype只是一个假象,它在原型链中只是一个辅助角色,换句话说,它只在new的时候有着一定的价值,但是原型链的本质,其实在于__proto__!

     2.3揭开Javascript属性constructor/prototype的底层原理

    当我们定义一个函数时,JavaScript内部会执行如下几个动作:
    为该函数添加一个原形属性(即prototype对象).

    为prototype对象额外添加一个constructor属性,并且该属性保存指向函数F的一个引用。

    这样当我们把函数F作为自定义构造函数来创建对象的时候,对象实例内部会自动保存一个指向其构造函数(这里就是我们的自定义构造函数F)的prototype对象的一个属性__proto__,所以我们在每一个对象实例中就可以访问构造函数的prototype所有拥有的全部属性和方法,就好像它们是实例自己的一样。

    当然该实例也有一个constructor属性了(从prototype那里获得的),这时候constructor的作用就很明显了,因为在这时,每一个对象实例都可以通过constrcutor对象访问它的构造函数,请看下面代码:

    var f = new F(); 
    alert(f.constructor === F);// output true 
    alert(f.constructor === F.prototype.constructor);// output true

    原型链继承,由于constructor存在于prototype对象上,因此我们可以结合constructor沿着原型链找到最原始的构造函数,如下面代码:

    function Base() {} 
    // Sub1 inherited from Base through prototype chain 
    function Sub1(){} 
    Sub1.prototype = new Base(); 
    Sub1.prototype.constructor = Sub1;
    Sub1.superclass = Base.prototype; 
    // Sub2 inherited from Sub1 through prototype chain 
    function Sub2(){} 
    Sub2.prototype = new Sub1(); 
    Sub2.prototype.constructor = Sub2; 
    Sub2.superclass = Sub1.prototype; 
    // Test prototype chain 
    alert(Sub2.prototype.constructor);
    // function Sub2(){} 
    alert(Sub2.superclass.constructor);
    // function Sub1(){} 
    alert(Sub2.superclass.constructor.superclass.constructor);
    // function Base(){}

    constructor易变,那是因为函数的prototype属性容易被更改,我们用时下很流行的编码方式来说明问题,请看下面的示例代码: 

    function F() {} 
    F.prototype = { 
    _name: 'Eric', 
    getName: function() { 
    return this._name;
     } 
    };

    初看这种方式并无问题,但是你会发现下面的代码失效了:
    var f = new F();

    alert(f.constructor === F); // output false

    怎么回事?F不是实例对象f的构造函数了吗?

    当然是!只不过构造函数F的原型被开发者重写了,这种方式将原有的prototype对象用一个对象的字面量{}来代替。

    而新建的对象{}只是Object的一个实例,系统(或者说浏览器)在解析的时候并不会在{}上自动添加一个constructor属性,因为这是function创建时的专属操作,仅当你声明函数的时候解析器才会做此动作。

    然而你会发现constructor并不是不存在的,下面代码可以测试它的存在性:

    alert(typeof f.constructor == 'undefined');// output false

    既然存在,那这个constructor是从哪儿冒出来的呢?

    我们要回头分析这个对象字面量{}。

    因为{}是创建对象的一种简写,所以{}相当于是new Object()。

    那既然{}是Object的实例,自然而然他获得一个指向构造函数Object()的prototype属性的一个引用__proto__,又因为Object.prototype上有一个指向Object本身的constructor属性。所以可以看出这个constructor其实就是Object.prototype的constructor,下面代码可以验证其结论:

    alert(f.constructor === Object.prototype.constructor);//output true

    alert(f.constructor === Object);// also output true

    一个解决办法就是手动恢复他的constructor,下面代码非常好地解决了这个问题:

    function F() {} 
    F.prototype = { 
    constructor: F, /* reset constructor */
    _name: 'Eric', 
    getName: function() { 
    return this._name; 
    }
    };

    之后一切恢复正常,constructor重新获得的构造函数的引用,我们可以再一次测试上面的代码,这次返回true
    var f = new F();
    alert(f.constructor === F); // output true this time ^^
    解惑:构造函数上怎么还有一个constructor?它又是哪儿来的?
    细心的朋友会发现,像JavaScript内建的构造函数,如Array, RegExp, String, Number, Object, Function等等居然自己也有一个constructor:
    alert(typeof Array.constructor != 'undefined');// output true
    经过测试发现,此物非彼物它和prototype上constructor不是同一个对象,他们是共存的:
    alert(typeof Array.constructor != 'undefined');// output true
    alert(typeof Array.prototype.constructor === Array); // output true
    不过这件事情也是好理解的,因为构造函数也是函数。
    是函数说明它就是Function构造函数的实例对象,自然他内部也有一个指向Function.prototype的内部引用__proto__啦。
    因此我们很容易得出结论,这个constructor(构造函数上的constructor不是prototype上的)其实就是Function构造函数的引用:
    alert(Array.constructor === Function);// output true
    alert(Function.constructor === Function); // output true
    OK, constructor从此真相大白,你不在对它陌生了~

    3.函数和对象区别

    alert(Object instanceof Function);//true
    alert(Function instanceof Object);//true
    alert(Function instanceof Function);//still true
    alert(Object instanceof Object);//still true

    在看如下例子:

    function a(){
      this.a1 = 1;
    }
    var aa = new a();//通过函数名创建一个对象,而函数本身也是一个对象。(函数也称对象构造器,对象构造器是一个对象,但对象未必是对象构造器)
    alert(a instanceof Object);//true

    alert(a) //打印出函数的内容为:function a(){this.a1 = 1;}

    alert(a.prototype); //打印出Object,说明函数有内置对象prototype
    alert(aa.prototype);//打印出undefined,说明一般对象没有prototype

      总结:

      函数特点:

      函数也称对象构造器,对象构造器是一个对象,但对象未必是对象构造器。

     

    4.Javascript全局变量和局部变量

    在函数外部定义的为全局变量,不管加不加var;

    在函数内部,不加var的,实际为全局变量,例子如下:

    function f1(){
        n=999;
      }
      f1();
      alert(n); // 999

    删除全局变量: delete 变量名;(局部变量无法删除)

    如果只是使用变量test,那么三种方式将没有什么区别。比如:alert(test) 都将显示5。但三种方式在某些情况下还是有区别的。分别按以上三种方式声明三个变量a1,a2,a3。

    1
    2
    3
    a1 = 11;
    var a2 = 22;
    window.a3 = 33;

    1.for in window

    1
    2
    3
    4
    5
    for(a in window){
        if(a=='a1'||a=='a2'||a=='a3'){
            alert(a)
        }
    }

    IE6/7/8/9:只弹出了a3,说明通过第一,二种方式声明的全局变量通过for in window时将获取不到。
    Firefox/Chrome/Safari/Opera :a1,a2,a3都弹出了,说明三种方式声明的全局变量,通过for in window时都能获取到。

    2,delete

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    try {
        alert(delete a1);
    }catch(e){alert('无法delete a1')}
     
    try{
        alert(delete a2);
    }catch(e){alert('无法delete a2')}
     
    try{
        alert(delete a3);
    }catch(e){alert('无法delete a3')}

    结果如下

    可以看到,
    1,delete a2所有浏览器都是false。即通过var声明的变量无法删除,所有浏览器表现一致。这在犀牛书上也有提到。
    2,通过window.a3方式声明的全局变量在IE6/7/8中均无法删除,IE9/Firefox/Chrome/Safari/Opera中却可以。

    虽然有以上两点不同,但当用in运算时,都返回true。

    1
    2
    3
    alert('a1' in window);//true
    alert('a2' in window);//true
    alert('a3' in window);//true

    用with打开对象window闭包时,所有浏览器也表现一致,如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    with(window){
        if(a1){
            alert(a1);//11
        }
        if(a2){
            alert(a2);//22
        }
        if(a3){
            alert(a3);//33
        }  
    }

    5.Javascript闭包

    闭包就是能够读取其他函数内部变量的函数。

    书本上对闭包的羞涩解释:闭包(closure)是一个函数,通常也被称为闭包函数或绑定函数,该函数运行在一个特定的环境中,该环境定义了一些本地变量,当该函数被调用时,仍可以使用这些本地变量。

    其实闭包的显著特征就是当一个函数在不位于它所处环境(变量作用范围)中被调用时,仍能够使用本地变量。下面来看看JavaScript中典型的两种闭包应用。

    两大用处:个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中

    闭包创建的条件:当内部函数 在定义它的作用域 的外部 被引用时,就创建了该内部函数的闭包 ,如果内部函数引用了位于外部函数的变量,当外部函数调用完毕后,这些变量在内存不会被 释放,因为闭包需要它们.

    例子:

    function f1(){
        var n=999;
        nAdd=function(){n+=1}
        function f2(){
          alert(n);
        }
        return f2;
      }
      var result=f1();
      result(); // 999
      nAdd();
      result(); // 1000

     这段代码中另一个值得注意的地方,就是“nAdd=function(){n+=1}”这一行,首先在nAdd前面没有使用var关键字,因此 nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个

    匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。

  • 相关阅读:
    Java实现 LeetCode 402 移掉K位数字
    Java实现 LeetCode 402 移掉K位数字
    Java实现 LeetCode 401 二进制手表
    Java实现 LeetCode 401 二进制手表
    wpa_supplicant使用笔记-wpa_cli iwconfig
    Linux下的定时器:alarm()与setitimer()
    在 Windows 下远程桌面连接 Linux
    assert()函数用法总结
    linux下svn修改用户名和密码
    VirtualBox
  • 原文地址:https://www.cnblogs.com/jintianfan/p/3475304.html
Copyright © 2020-2023  润新知