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,可以在函数外部对函数内部的局部变量进行操作。

posted @ 2013-12-15 15:17  金天凡  阅读(346)  评论(0编辑  收藏  举报