JavaScript学习小结

通过对JavaScript高级程序设计一书的学习,我对这门脚本语言有了进一步的了解,特写下这几篇博文,总结一下自己的收获。

第一、了解了JavaScript的背景。

它最初是Netscape公司为了在客户端对表单进行简单验证,减轻对服务器的资源消耗而开发的一种客户端语言。而后出现了多个不同的版本,JavaScript的标准化问题提上了议事日程。1997年,ECMA-262标准诞生,它以JavaScript1.1为蓝本定义了一种名为ECMAScript(发音为“ek-ma-script”)的新脚本语言。

值得注意的是,ECMAScript与Web浏览器没有依赖关系,它提供了核心语言功能。Web浏览器只是ECMAScript实现可能的宿主环境之一。我们现在所说的JavaScript是ECMAScript在Web浏览器环境中扩展后的产物,扩展的内容有两部分,一部分是BOM(Browser Object Model浏览器对象模型,提供了与浏览器交互的方法和接口),另一部分是DOM(Document Object Model文档对象模型,提供了访问和操作网页内容的方法和接口)。

第二,对JavaScript中的数据类型有了比较清晰系统的认识。

常说,JavaScript是宽松数据类型的语言,这里的“宽松”,我理解的就是在预编译时不会对声明的变量进行类型检查,对变量一视同仁(使用function声明的函数对象稍有不同)。但是,在执行过程中对变量进行赋值时,就涉及到了变量的存储,这样就把数据值分成了两类,存储在栈内存中的简单数据类型(也成为“基本数据类型”)和存储在堆内存中的引用类型,简单数据类型按值访问,引用类型按引用访问,这一点和Java是一样的。

【简单数据类型】

简单数据类型有5种:undefined、null、number、boolean、string。

在变量声明但未赋值时,其类型即为undefined。null表示一个空对象指针。另外,得知undefined其实是由null类型派生出来的,在使用“==”对两者进行判断时是返回true的,但使用“===”进行逻辑判断时返回false。两者的相似点是,它们都是只有一个值的类型。区别在于,对一个变量显示赋值为undefined是没有意义的,但显示赋值为null却有意义,表示该变量准备用来存放引用类型。

只能用 === 运算来测试某个值是否是未定义的,因为 == 运算符认为 undefined 值等价于 null。 注释:null 表示无值,而 undefined 表示一个未声明的变量,或已声明但没有赋值的变量,或一个并不存在的对象属性。

number类型表示整数和浮点数。比较常用的有这样几个函数,isNaN()和Number()、parseInt()、parseFloat()。

isNaN()是用来判断是参数值是不是非数字(is Not a Number ?),如果参数值不能转换为数值,那么返回true,返回数字则表示参数值是数字或者能够转换为数值。后三个函数都是用来进行数值转换的。

创建 Boolean 对象的语法:

new Boolean(value);	//构造函数
Boolean(value);		//转换函数

参数

参数 value 由布尔对象存放的值或者要转换成布尔值的值。

返回值

当作为一个构造函数(带有运算符 new)调用时,Boolean() 将把它的参数转换成一个布尔值,并且返回一个包含该值的 Boolean 对象。

如果作为一个函数(不带有运算符 new)调用时,Boolean() 只将把它的参数转换成一个原始的布尔值,并且返回这个值。

注释:如果省略 value 参数,或者设置为 0、-0、null、""、false、undefined 或 NaN,则该对象设置为 false。否则设置为 true(即使 value 参数是字符串 "false")。

 

string类型用于表示由零或多个16为Unicode字符组成的字符序列,即字符串。有三点需要注意:字符串类型采用单引号、双引号表示都行,但前后需要一致;调用字符串的length属性得到的值可能不准确(如果包含转义序列时);比较常用的是两个字符串转换函数toString()和String()。

【引用类型】

引用类型就是指对象,它是一组无序属性的集合,其属性可以包含简单数据类型、对象或者函数。该类型类似于Java中的类。Object类型是引用类型的基础类型,其他所有类型都从Object继承了基本的行为。就连我们熟悉的函数,在JavaScript中也是继承了Object类型。

①对象的属性

对象的属性可以分为两类:数据属性和访问器属性(我们常用的是数据属性)。属性本身的各种特征是由属性的特性的决定的。

下边是一个例子,可以用来更好地理解数据属性和访问器的特性。

[javascript] view plain copy print ?
  1. //name、_age、sayHi三个属性都是数据属性
  2. var person = {
  3. //name和sayHi属性的configurable、enumerable、writable三个特性值为true
  4. name:"wang",
  5. sayHi:function(){
  6. alert("hi");
  7. }
  8. };
  9. Object.defineProperty(person,"_age",{
  10. //该属性的configurable和enumerable两特性值为false
  11. writable:true,
  12. value:12
  13. });
  14. //age属性是访问器属性
  15. Object.defineProperty(person,"age",{
  16. //该属性的configurable和enumerable两特性值为false,set特性值为undefined
  17. get:function(){
  18. return this._age+1;
  19. }
  20. });
  21. alert(person._age);//在读取数据属性时,会返回其value特性值,结果是12
  22. alert(person.age);//在读取访问器属性时,会调用其get特性指向的函数,结果返回13
  23. //另外,对数据属性进行赋值时,改变的是该数据属性的value特性值;和对访问器属性进行赋值时,会调用其set特性指向的函数
  24. //访问属性的特性调用此方法
  25. var descriptor = Object.getOwnPropertyDescriptor(person,"age");
  26. alert(descriptor.writable);


上边例子的Object.defineProperty可以定义为一个Object.defineProperties 。

[javascript] view plain copy print ?
  1. var person = {};
  2. Object.defineProperties(person,{
  3. name:{
  4. value:"wang"
  5. },
  6. sayHi:{
  7. value:new Function("alert('hi');")
  8. },
  9. _age:{
  10. writable:true,
  11. value:12
  12. },
  13. age:{
  14. get:function(){
  15. return this._age+1;
  16. }
  17. }
  18. });


我们可以从例子中看出调用Object.defineProperties 函数时,需要两个参数,第一个参数是一个表示对象的变量,第二个参数是一个采用对象字面量形式的对象。

②JavaScript面向对象的设计

 

特别需要注意的是,其实现方式与Java中的类不同。在没有类的情况下,JavaScript主要是通过构造函数(还有一种创建对象的方式是对象字面量)来创建对象的,是通过原型链来实现类型的继承的。

我觉得,原型链在面向对象的设计中最重要,以下边的代码为例来说明原型链的概念。

        function Person(){
               this.name = "zhang";
               this.age = 1;
        }
 
        Person.prototype.sayHi = function(){
               alert("hi~");
        };
 
        var p1 = new Person();
        p1.name = "wang";
        var p2 = new Person();
        p2.job = "IT";
 
        alert(p1.name);//wang
        alert(p2.name);//zhang
 
        p1.sayHi();//hi~
        p2.sayHi();//hi~
 


通过下图可以了解原型链,虚线串连起来的红线就是一条原型链。之所以用虚线,是因为不能通过代码直接访问。

JavaScript学习小结_第1张图片

在访问对象属性时既然会其原型链进行搜索,那么如何判断一个对象的属性是存在与本地还是存在于原型链中呢?

原来在基础类型Object的原型对象中存在有hasOwnProperty属性(该属性是一个函数),其他对象都共享了该方法。调用对象的hasOwnProperty方法,将要判断的属性名作为参数传入,就能返回属性是否存在与对象本地。那么怎么判断属性是否存在于原型中呢?in操作符能判断出属性在原型链中是否能找到,无论该属性存在于实例中还是原型中。可以将in操作符与hasOwnProperty函数结合使用,即可判断出属性是否存在于原型中了。

        //判断属性是否存在于原型中
        function hasPrototypeProperty(object, name){
               return (name in object) && (!object.hasOwnProperty(name));   
        }

理解了原型链,我们就容易理解new操作符的作用了。在例子中有var p1 = new Person();这样的语句使用了new操作符,结合指针关系图,我们可以认为,在执行这行代码时,是先隐式创建了一个空对象this,然后对this对象添加属性,最后再隐式地返回这个this对象给p1。

另外,如果构造函数的代码中已经显式地返回了对象这时用new操作符来调用改构造函数,那么返回的将不是隐式创建的新对象(这个新对象还存在于内存中),而是这个显式的对象。如果构造函数中的代码中显式地返回了简单类型的数据,则没有影响,即还是会将隐式创建的this对象返回的。如将Person()的代码改为

        
        function Person(){
               var o = {};
               this.name = "zhang";
               this.age = 1;
               o.name = "li";
               return o;
        }

再执行var p1 = new Person(); 那么p1.name的值将为"li",而不是"zhang"。内存中会有一个对象,name属性值为"zhang",age属性值为1的对象。特别注意的是,这个时候执行var p1 = new Person(); 与执行var p1 = Person();两者返回的对象是相同的,但后者会把name属性和age属性添加给全局对象window。

③几个典型的引用类型

继承自Object类型的,比较典型的有这么几个类型。

【Array类型】

1.首先数组有两种方式,使用构造函数和数组字面量(使用中括号)。

2.JavaScript中数组的length属性和java中不同,是可以对其进行赋值的,并且没有数组下标越界的概念。

3.Array类型提供了一系列方法

Array类型的方法

方法类别

具体的方法说明

转换方法

join()将数组转换为字符串,将传入的字符串作为分隔符,默认以逗号分割

栈方法

可将数组看成栈来使用,提供了pushpop方法

队列方法

可将数组看成队列类使用,提供了unshiftshift方法(poppush方法同上)

重排序方法

反转数组项的reverse方法、对数组项进行排序的sort方法(可传入排序策略)

操作方法

并入新数组项的concat方法、截取掉部分数组项的slice方法、可在指定位置插入新数组项的splice方法

位置方法

用于检索目标元素在数组中的索引位置的indexOf方法和lastIndexOf方法

迭代方法

起检测作用的everysome方法,起过滤作用的filter方法和map方法(map方法是将每个数组元素执行后返回的新元素组成一个新数组),起执行作用的forEach方法。

迭代方法都有一个函数作为参数,方法起的作用不同,那么函数的返回值也不同。

缩小方法

reducereduceRight方法。迭代数组中的所有项,构建一个最终返回值。

【Date类型】

1.Date类型的构造函数不传参数时,自动获得当前日期和时间;如果想根据特定的日期和时间创建日期对象,必须传入表示该日期的毫秒数。

2.Date类型有两个重要方法:Date.parse()和Date.UTC(),它们都是用来返回一个表示日期的毫秒数。需要注意的是,这两个方法是属于构造函数这个对象的,而不是Date类型的实例对象,相当于java中类的静态方法。

3.在ECMAScript5中,支持Date.now(),用来返回当前时间的毫秒数。这个方法可以用来分析前台js代码的执行效率。

4.使用toDateString()和toTimeString()能以特定于的格式显示日期和时间。

5.Date类型还有一些方法可以直接获取和设置日期值中特定的部分。

【RegExp类型】

该类型用来支持正则表达式的。

1.三种模式:g表示全局模式,i表示不区分大小写,m表示多行模式

2.两种方式用来检测正则表达式。

test:检查字符串是否与给出的正则表达式模式相匹配,如果是则返回 true,否则就返回 false

var expression = / pattern / flags;
if(expression.test(text)){...}

match:使用正则表达式模式对字符串执行查找,并将包含查找的结果作为数组返回

  function MatchDemo(){
          var s = "The rain in Spain falls mainly in the plain"; 
          var re = /(a)in/ig; // 创建正则表达式模式 
          var r = s.match(re); // 尝试去匹配搜索字符串
          document.write(r); //输出结果:ain,ain,ain,ain 
  }

【Function类型】

1.函数实际是对象,属于引用类型,函数名是指针(引用)。

2.函数可以用任意多个参数来调用,如果参数不够,那么缺少的参数就用undefined代替。

3.函数声明提升:解析器在向执行环境中加载数据时,会率先读取函数声明,并使其在执行任何代码之前可用。

4.函数的两个内部属性:this表示函数据以执行的环境对象,arguments表示函数实际参数的类数组对象。它本身又有自己的属性,length属性表示参数的数量,callee属性表示当前的函数(这一属性常用与递归)。

5.函数的属性:caller属性只能在函数内部调用,表示调用本函数的函数,在全局作用域中调用该函数会返回null;另外一个重要的属性是prototype属性,该属性指向函数的原型对象。在函数对象创建时该属性就会自动指向原型对象。

6.函数的方法:apply()、call(),这两个方法都是用来设置函数内部属性this从而扩展函数作用域的(让函数在指定的作用域中执行),只不过apply()扩展函数作用域时是以(类)数组方式接受函数的参数,而call()扩展函数作用域时需要将函数参数一一列举出来传递。另外,在ECMAScript5中还定义了bind()方法,它与前边两者的区别是它只是将函数的this值绑定到传给bind的参数,如varbindFun = fun.bind(obj); 只有在调用了绑定后返回的函数引用才会执行,即 需要执行代码bindFun();

【三个包装类型Boolean类型、Number类型、String类型】

三个包装类型是和boolean、number、string三个基本类型相对应的。<SPAN style="FONT-SIZE: 18px">就像java中的int与Integer之间的关系。不过最大的区别是,隐式地创建了相应的包装类型。只是寿命很短,只存在于一行代码的执行瞬间(这个瞬间我们可以使用简单类型的字符串、数字,直接调用其包装类型的方法),然后立即就被销毁了。Boolean类型没啥用。

String类型有哪些方法呢?有字符方法(charAt/charCodeAt)、字符串操作方法(substring/slice/concat/trim)、字符串位置方法(indexOf/lastIndexOf)、大小写转换、模式匹配方法等

Number类型有哪些方法呢?有toFixed方法用来对小数进行四舍五入,有toExponential()方法进行指数表示,有toPrecision()方法来保留指定数目的有效数字。

【两个单体内置对象Global对象和Math对象】在所有的代码执行之前,作用域中就已经存在两个内置对象:Global和Math。在大多数ECMAScript的实现中都不能直接访问Global对象,不过在JavaScript中window对象担任有此角色。Math对象提供了很多属性和方法,辅助完成复杂的数学计算任务。在使用IE浏览器进行测试时,发现了ie中Global对象不是Object类型,而是DispHTMLWindow2的,感到奇怪,先存疑吧。

④类型识别问题

简单地说来,对于简单类型的判断可以使用typeof操作符,对于引用类型的判断,可以使用instanceof操作符来判断。http://blog.csdn.net/wangchenggong1988/article/details/8131579

第三、对JavaScript的预编译、作用域、闭包等概念有了初步的认识。

JavaScript代码的执行是有预编译和执行两个阶段的。在预编译阶段,对于使用var声明的变量都先赋以undefined,对于使用function关键字声明的函数对象,则是怎么声明,就怎么定义。在执行阶段,再对变量进行赋值。如果某行代码对已经使用function关键字声明过函数对象又使用var进行了赋值,那么执行到此处时,先前的函数定义就会被此处的定义覆盖。

[javascript] view plain copy print ?
  1. fun();//hi
  2. var fun = function(){
  3. alert("hello");
  4. };
  5. function fun(){
  6. alert("hi");
  7. }
  8. fun();//hello


作用域该如何理解?

作用域就是变量或函数能被访问的范围。说一个变量或函数拥有全局作用域,意思就是说,它能在全局范围内被访问。JavaScript中作用域只有两种:全局作用域和函数作用域,没有块作用域。

作用域链如何理解?

作用域链是在调用函数时才有的概念。函数在调用时,其内部可能还有对别的函数的调用,如何实现对不同层次的函数体内定义的变量进行有序地访问呢?这就出现了作用域链。

简单地说,不同层次的函数作用域都用一个对象来代表,把这些对象的指针按顺序罗列出来就是作用域链。

在搜索标识符时,就是从作用域的前端向后一级一级单向搜索,如果找到,就停止返回。

在此推荐一篇比较好的文章介绍了JavaScript的作用域和作用域链:http://www.cnblogs.com/lhb25/archive/2011/09/06/javascript-scope-chain.html

 

你可能感兴趣的:(JavaScript学习小结)