JavaScript核心基础(上)

JavaScript是一种动态的、基于对象的浏览器端脚本语言,具有JAVA、C++静态语言所没有的灵活程度。JavaScript的写法变化多端,且各种写法带来的执行性能也会有所不同,所以在此进行总结。


核心功能#

JavaScript早期分为服务端和浏览器端两种脚本,服务器端已基本被jsp和asp两大阵营淘汰。现在JavaScript遵循ECMA-262标准(也称为ECMAScript3),ECMAScript4正在激烈讨论中。

组件#

JavaScript认可两类对象:原生(Native)对象和宿主(Host)对象,其中宿主对象包含一个被称为内置对象的原生对象的子类。原生对象属于语言,而宿主对象由环境提供,比如说可能是文档对象、DOM 等类似的对象。

具体来看,Javascript主要由二块构成:Core(核心部分)+DOM实现部分。(DOM是w3c定义的针对HTML、XML文档编程的一系列接口。通过这些接口,我们可以改变文档结点的结构、样式、内容。实现这些接口的语言可以是java、Python,javascript等。)

 

Core Dom
定义了语法规则,及内置的全局对象(Date、Function等),全局方法(parseFloat),全局属性(NaN,undefined等)
针对HTML文档与XML文档操作的API

语言的特点#

面向对象语言包括几个主要特性:抽象,继承,封装和多态。Javascript本质上不是面向对象的,而是基于对象的。基于对象的语言对上面四个特性支持很弱。

JavaScript只有类,没有接口和抽象类;继承则通过prototype实现,Function的apply或call方法间接实现;封装访问特性仅有private和public两个级别;不支持多态的特性。但是,JavaScript通过闭包和函数式编程以及弱类型来克服这些缺点,使得其具有很大的灵活性。

变量和词法作用域#

JavaScript的变量分为全局变量(global variable)和局部变量(local variable)。局部变量的声明需要var,如果存在同名的全局变量而不用var,则解释器不会认为在声明局部变量。另外,一旦声明了局部变量以后,它将在整个作用域都有定义(无论位置在作用域的哪里),在此作用域内不会调用同名的全局变量。

每段JavaScript的代码都运行在与之对应的执行环境中,作用域(scope)是组成执行环境的一部分,它表示了某段代码所在的上下文环境(对象列表或对象链),通常它是以链表的形式存在,也称为作用域链。在JavaScript解释器启动的时候,它会创建一个全局对象(global object不属于任何函数的顶层),全局变量就是这个全局对象的一个属性,所有的预定义函数和属性也都是这个全局对象的属性。接着解释器会创建一个执行环境,这个执行环境包含了作用域链的定义;在这个环境下,解释器会为唤醒的function创建call object(也称为active object),并将它放置在作用域链的前面。call object持有该function的Arguments object的引用以及在此function里定义的Argument和局部变量的引用。

对于非嵌套的function来说,它的作用域链通常就是call object--global object,解释器会沿着作用域链查找变量的定义。

参考scope in javascript

闭包#

许多动态语言特别强调闭包的重要性。简言之,闭包就是函数的作用域,一个函数的主体(可执行的代码段)和它的定义作用域的组合称为闭包。闭包产生的原因是JavaScript允许创建内部函数,当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包。也就是说,内部函数会在外部函数返回后被执行。而当这个内部函数执行时,它仍然必需访问其外部函数的局部变量、参数以及其他内部函数。这些局部变量、参数和函数声明(最初时)的值是外部函数返回时的值,但也会受到内部函数的影响。

闭包有两个特点:

1、作为一个函数变量的一个引用,当函数返回时,其处于激活状态。

2、一个闭包就是当一个函数返回时,一个没有释放资源的栈区。

这两个特点也就是一个意思:如果存在闭包,JavaScript解释器的GC不会销毁已返回的外部函数占用的堆栈(因为内部函数持有了外部函数中变量的引用)。这样便可以继续使用内部函数,即使内部函数持有外部函数中变量的引用。

浏览器的内存泄露#

内存泄露的根本原因是因为浏览器的GC机制不够完善,当JavaScript和DOM对象互相引用的时候,内存泄露就会发生。GC是由对象引用计数器来控制的,当解释器发现对象的引用计数为零的时候,GC就发生了。但是,当发生DOM和JavaScript循环引用的时候,他们的引用计数都不为零,所以GC也就无法进行。一个解决内存泄露的方法就是在循环对象不再使用的时候,将null赋给它,这样它们的引用都会为零。

另外,使用闭包也要注意,因为内部函数持有外部函数的引用,所以如果DOM对象引用了内部函数,而外部函数恰巧也引用这个DOM对象,内存泄露也会发生。参考内存泄露的类型会有更多信息。

prototype#

每个JavaScript对象都有一个prototype(原型)属性,该属性引用另外一个对象,且可以编程赋予。每个原型对象有个constructor属性反过来引用函数本身。



 

 

原型属性也是一个对象,因此其本身也有prototype属性引用另一个对象,这样就可能形成一个原型链。这个原型链会终止于链中原型为 null 的对象,而Object 构造函数的默认原型正好有一个 null 原型。

更为重要的是,JavaScript解释器会自动顺着原型链查找属性。下图说明了在Dog中查找toString(),实际该方法在Object.prototype中。

 



 

 

eval(String)#

将字符串作为javascript脚本执行

setTimeout#

setTimeout用来推迟一个函数的执行,其特殊之处在于将所推迟的函数脱离现有的调用栈(call stack),例如:

function a () {   //入第一个栈(编号1)
setTimeout
( function (){ alert ( 1 )}, 0 );     //如第二个栈(即一个新栈)(编号1)
alert
( 2 );     //入第一个栈(编号2)
}
a
();     //执行结果为 2 1
 
 

 

这样的setTimeout用法在实际项目中还是会时常遇到。比如浏览器会聪明的等到一个函数堆栈结束后才改变DOM,如果再这个函数堆栈中把页面背景先从白色设为红色,再设回白色,那么浏览器会认为DOM没有发生任何改变而忽略这两句话,因此我们可以通过setTimeout把“设回白色”函数加入下一个堆栈,那么就可以确保背景颜色发生过改变了。

类似的,浏览器还有个onkeyup事件也可以实现我们的需求

对Javascript调用堆栈和setTimeout用法的深入研究


基本语法的一些问题#

匿名函数与直接函数量#

本质上,匿名函数和直接函数量是同样的事物,匿名函数是无法被外部调用的。使用匿名函数的优势是可以直接进行值调用(函数式编程的特点),只将匿名函数作为中间过程而不用担心会与其他外部变量冲突。

1、在

  var f = function ( x ) { return x * x ;}; f ();
 
去掉了f后,再在函数体上加上括号(优先级的缘故),得到匿名函数第一种方式
  ( function ( x ){})();
 

2、

( function ( x ){ return x * x ;}( x )); //Eg. x=10,返回100
 

最后的括号中的x作为匿名函数的参数

几种对象声明的代码#

var MyObject = {};   //one

function MyObject ( pram )   //two
{
...
}

var MyObject = function ( pram ) //three
{ blablabla };
 

声明成员函数/属性#

var MyObject = {
     
FirstName : "Mary" ,   //使用冒号
     
LastName = "Cook" , //使用=号

     
"Age" : 18 ,                       //使用字符串声明
     
"ShowFullName" : function (){
      alert
( this . FirstName + ' ' + this . LastName );
       
}
 
}

MyObject . ShowFullName ();     //直接访问成员函数,无需构造
 

用字符串做变量名可以使程序更灵活

var MyObject = new Function ( "msg" , "msg1" , "alert(msg+msg1);" );
// Function可以有多个入口参数,最后一个参数作为方法体。

var o = new MyObject ( 'Hello world!' , ' I am here!' );
//显示"Hello world! I am here!"
 

可以利用.和[]]来引用成员

var DateTime = { Now : new Date (), "Show" : function () { alert ( new Date ());} };

alert
( DateTime . Now );
// 等价于:
alert
( DateTime [ "Now" ]);

DateTime . Show ()
// 等价于:
DateTime [ "Show" ]();
 

call#

call 方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。

member1 = 'test' ;
function abc ()
{
    alert
( this . member1 );
}
var obj = { member1 : "Hello world" , show : abc };
var obj2 = { member1 : "Hello world again" , show : abc };

obj
. show ();
//也可以使用
abc
. call ( obj );
abc
. call ( obj2 );

abc
(); // 此时abc中的this指向了当前上下文
 
 

 

apply#

apply是与call类似的用于切换上下文的语句,与call不同的是apply能把参数包裹进一个数组再传递给呼叫函数。

 

命名空间#

var System = {};   //声明命名空间

//命名空间的一个级别可能不是唯一的,因此可以创建嵌套的命名空间:
System . lang = {};
System . lang . out = function (...){...}
System . lang . out . writer = function (...){...}

//还可以指定别名来定义命名空间
var Eg = System . lang . out ;
writer
= Eg . writer ()
;
if ( typeof System == 'undefined' ) var System = {}; //判断代码来防止重复声明
 

你可能感兴趣的:(JavaScript,编程,浏览器,脚本,prototype)