《JavaScript高级程序设计》读书笔记-在读

285 阅读11分钟

摘要

关于《JavaScript高级程序设计》中的事例代码可在附件中查看。

第二章 <script>元素

defer属性

设置defer="defer",脚本会被延迟到整个页面都解析完毕后在运行,多个延迟脚本并不一定会按照先后顺序执行。

async属性

设置async="async",异步加载脚本内容,但是多个异步脚本之间并不一定会按照先后顺序执行。

<noscript>元素

该元素可以包含能够出现在<body>中的任何html元素(script除外),只有当浏览器不支持脚本或者支持脚本,但是脚本被禁用时,浏览器才会显示<noscript>中的内容。

第三章 基本概念

typeof操作符

typeof是一个操作符,能够返回该数据的数据类型。但是注意其中对于基本类型,除null(返回Object,null被认为是一个空的对象引用)以外,均可以返回正确的结果;对于引用类型,除function(返回Function)以外,一律返回 object 类型。

Boolean类型

使用Boolean()函数能够将任意类型的一个值转换为Boolean类型值。使用“!!data”能够达到同样的效果,第一个逻辑非操作符是将值转换为布尔值然后取反,第二个逻辑非操作符再次取反获取这个值真正的布尔值。

Number类型

JS中将最小值保存在Number.MIN_VALUE中,值为5e-324;将最大值保存在Number.MAX_VALUE中,值为1.7976931348623157e+308。如果某次计算的值超过这个范围,这个值就会自动转换为特殊的Infinity值(Infinity为正无穷,-Infinity为负无穷),使用isFinite()可以判断一个值是否为有穷。

NaN即非数值,涉及NaN的操作都会返回NaN并且NaN与任何值都不相等,包括NaN本身。isNaN()函数接受一个参数,能够判断这个参数是否“不是数值”。NaN也适用于对象,首先调用对象的valueOf(),然后确定返回值是否可以转换为数值,如果不能,则基于这个返回值在调用toString(),在测试返回值。

Number()、parseInt()、parseFloat()三个函数能够将非数值转换为数值:

  • Number():将任何数据类型转换为数值。null值转换为0;undefined转换为NaN;空字符串转换为0。
  • parseInt():将字符串转换为数值。空字符串转换为NaN。接受第二个参数设置转换时的进制。
  • parseFloat():将字符串转换为数值,能够解析十进制。

String类型

将一个值转换为String有两种方法。

  • 通过toString()方法:每个值都存在toString()方法,返回相应值的字符串表现(null与undefined没有这个方法)。在调用数值的toString()时,可以传递一个参数,用来指定输出数值的进制基数。
  • 使用转型函数String(),如果值存在toString()方法,返回toString()相应的结果,而null与undefined返回“null”与“undefined”。

一元加操作符

一元加运算符除了用于基本的算术运算,对非数值应用一元加操作符时,能够像Number()转型函数一样对这个值执行转换。

函数

由于ECMAScript函数没有签名,其参数是由包含零或者多个值的数组来表示,而没有函数签名,真正的重载是不可能做到的。

第四章 变量、作用域和内存问题

变量值复制

  • 如果从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后将该值复制到为新变量分配的位置上,两个变量可以参加任何操作而不会互相影响。
  • 当一个变量向另一个变量复制引用类型的值时,因为变量中存储的是对象的指针,所以会将指针值复制一份到新变量分配的空间中,因此复制结束后,两个变量实际上将引用同一个对象,改变其中一个变量,会影响另一个变量。

参数传递

把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样。基本类型值传递等同基本类型变量复制,引用类型值传递等同引用类型变量复制。

执行环境及作用域

每个执行环境都有一个与之关联的变量对象,其中定义了环境中的所有变量和函数。
当代码在一个环境中执行的时候,会创建变量对象的一个作用域链。作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端始终都是当前执行代码所在环境的变量对象。
如果这个环境是函数,则将起活动对象作为变量对象。活动对象最开始的时候只包含arguments对象。

变量声明

使用var声明的变量会自动被添加到最接近的环境中。如果初始化变量时没有使用var声明,该变量会自动添加到全局变量中。

垃圾回收

离开作用域的值将会被自动标记为可以回收,将在垃圾收集期间被删除。
"标记清除"是目前主流的垃圾收集算法,给当前不使用的值加上标记然后回收其内存。
"引用计数"是跟踪记录所有值被引用的次数,但是目前已经不使用这种方式了,因为当存在循环引用的时候,“引用计数”算法就会导致问题。

第五章 引用类型

Object类型

创建Object实例的方式有两种。一种是使用new操作符后面跟Object构造函数;另一种是使用对象字面量表示法。在通过对象字面量定义对象的时候,实际上不会调用Object构造函数。

Array类型

创建数组的基本方式有两种:

  • 第一种是使用Array构造函数,允许给构造函数传递参数,如果传递的数值就会按照该数值创建指定项数的数组,如果是其他类型的参数,就会创建包含参数的数组;使用Array构造函数时可以省略new操作符。
  • 第二种是使用数组字面量表示法,与对象一样,在使用数组字面量表示法时,不会调用Array构造函数。

数组的length属性不仅仅只读,通过设置这个属性,可以从数组的末尾移除项或向数组中添加新项。

数组的sort()排序方法,是先调用每一个数组项的toString()方法,然后比较得到的字符串,即使数组项是数值,也是比较的字符串;
sort()方法可以接受一个比较函数作为参数,比较函数设置有两个参数,如果第一个参数应该位于第二个之前,则返回一个负数,以此类推。

Date类型

使用new操作符和Date构造函数能够创建一个基于当前日期和时间的日期对象。使用Date.parse()和Date.UTC()

  • Date.parse()接受一个表示日期的字符串参数,根据这个字符串返回相应日期的毫秒数。如果直接将表示日期的字符串传递给Date构造函数,后台也会调用Date.parse()。
  • Date.UTC()接受的参数分别是年份、基于0的月份(一月是0)、月中的哪一天(1~31)、小时数(0~23)、分钟、秒以及毫秒数,年和月为必须的,其余默认为最小值。将参数直接传递给Date构造函数,会基于本地时区创建时间。
// 二者等同
var now = new Date("May 25, 2004");
var someDate = new Date(Date.parse("May 25, 2004"));

// GMT时间2000年1月1日0时
var gmtTime = new Date(Date.UTC(2000, 0));
// GMT时间2005年5月5日17时55分55秒 
var gmtTime2 = new Date(Date.UTC(2005, 4, 5, 17, 55, 55));

// 本地时间2000年1月1日0时
var localTime = new Date(2000, 0);
// 本地时间2005年5月5日17时55分55秒 
var localTime2 = new Date(2005, 4, 5, 17, 55, 55);

Date对toString()和toLocalString()进行了重写,对于valueOf()返回的是日期毫秒表示。

RegExp类型

正则表达式语法

var expression = / pattern/ flag

其中模式(pattern)部分可以是任何简单或复杂的正则表达式,每个正则表达式都可以带有一个或多个标志(flag),正则表达式的匹配模式支持下列3个标志:

  • g:表示全局(global)模式,即模式将被应用于所有字符串,而非在发现第一个匹配项时立即停止;
  • i:表示不区分大小写(case-insensitive)式,即在确定匹配项时忽略模式与字符串的大小写;
  • m:表示多行(multiline)模式,即在到达一行文本末尾时还会继续查找下一行中是否存在与模式匹配的项。

对于RegExp实例的exec(),如果没有设置全局标志(g),那么在同一个字符串上多次调用exec()将始终返回第一个匹配项的信息;如果设置了全局标志(g),每次调用exec()会在字符串中继续查找新匹配项。

Regexp的每个实例都具有下列属性,通过这些属性可以取得有关模式的各种信息。

  • global:布尔值,表示是否设置了g标志。
  • ignorecase:布尔值,表示是否设置了i标志。
  • lastindex:整数,表示开始搜索下一个匹配项的字符位置,从0算起.
  • multiline:布尔值,表示是否设置了m标志。
  • source:正则表达式的字符串表示,按照字面量形式而非传入构造函数中的字符串模式返回。

RegExp构造函数包含一些属性,这些属性适用于作用域中所有正则表达式。

  • input 最近一次要匹配的字符串
  • lastMatch 最近一次的匹配项
  • lastParen 最近一次匹配的捕获项
  • leftContext input字符串中lastMatch之前的文本
  • multiline 布尔值,表示是否所有表达式都使用多行模式
  • rightContext input字符串中lastMatch之后的文本
  var text = "this has been a short summer";
  var pattern = /(.)hort/g;
      
  /*
   * Note: Opera doesn't support input, lastMatch, lastParen, or multiline.
   * Internet Explorer doesn't support multiline.
   */        
  if (pattern.test(text)){
      alert(RegExp.input);               //this has been a short summer
      alert(RegExp.leftContext);         //this has been a            
      alert(RegExp.rightContext);        // summer
      alert(RegExp.lastMatch);           //short
      alert(RegExp.lastParen);           //s
      alert(RegExp.multiline);           //false
  }

Function类型

函数声明与函数表达式的区别:解析器会率先读取函数声明,并使其在执行任何代码之前可用;而函数表达式则必须等到解析器执行到它所在的代码行才会被解释执行。

// 函数声明
alert(sum(10,10));    //20 执行正常,在代码执行之前,就将函数声明添加到执行环境中了
function sum(num1, num2){
   return num1 + num2;
}  

// 函数表达式
alert(sum(10,10));    //causes an error 还没有执行到函数表达式,sum中还没有保存对函数的引用,程序报错。
var sum = function(num1, num2){
   return num1 + num2;
}; 

由于JS中的函数就是对象,因此函数也有属性和方法,每个函数都包含有两个属性,length和prototype。

  • length:length属性表示函数希望接受的命名参数的个数。
  • prototype:对于引用类型来讲,prototype保存他们所有实例方法的真正所在。换句话说,诸如toString()、valueOf()等方法实际上都保存在prototypr名下,只不过通过各自对象的实例访问罢了。

每个函数都包含有apply()和call(),这两个方法的用途就是在特定的作用域中调用函数,实际上等于设置函数体内this对象的值。

  • apply()接受两个参数,第一个参数是在其中运行函数的作用域,另一个是参数数组,可以是数组也可以是arguments对象。
  • call()与apply的作用相同,只是接受参数的方式不一样,第一个参数是在其中运行函数的作用域,而其余的参数必须一一传递给函数。
  • bind()会创建一个函数的实例,其中的this值会被绑定到传给bind()函数的值
// apply() demo
function sum(num1, num2){
   return num1 + num2;
}
function callSum1(num1, num2){
   return sum.apply(this, arguments);
}
function callSum2(num1, num2){
   return sum.apply(this, [num1, num2]);
}
alert(callSum1(10,10));   //20
alert(callSum2(10,10));   //20

// call() demo
function sum(num1, num2){
   return num1 + num2;
}
function callSum(num1, num2){
   return sum.call(this, num1, num2);
}
alert(callSum(10,10));   //20

// bind() demo
window.color = "red";
var o = { color: "blue" };                    
function sayColor(){
   alert(this.color);
}
var objectSayColor = sayColor.bind(o);
objectSayColor();   //blue

apply()、call()、bind()这些方法的真正强大之处是能够扩充函数的作用域。

window.color = 'red';
var o = {color:'bluw'};

function sayColor() {
	alert(this.color);
}

sayColor();			// red

sayColor.call(this);	// red
sayColor.call(window);	// red
sayColor.call(o);		// blue

使用call()或者apple()、bind()来扩充作用域的最大好处就是对象不需要与方法有任何的耦合关系。 一般来说我们需要将sayColor()函数放到对象o中,然后在通过o来调用它,而使用call()函数,我们就不需要这么做了,对象与方法没有任何的耦合关系。