重读JavaScript高级程序设计
不断更新中~~~
第三章 基本概念
1、变量声明但未初始化值是undefined,而未声明的变量只能执行typeof操作,并且未初始化和未声明用typeof都同样返回undefined
2、Number()、parseInt()和parseFloat()的区别:
(1)Number()可以用于任何数据类型,parseInt()和parseFloat()专门用于把字符串转成数值
(2)Number()转换空字符返回0,parseInt()和parseFloat()则转换为NaN
(3)Number()只有在字符串为纯数值时才能转换为数值,parseInt()和parseFloat()只有遇到非数字字符时才停止转换
(4)parseInt()解析八进制字符串,ECMAScript3和5存在分歧,ECAMScript3会将八进制转换为十进制,ECMAScript5则会认为前导的0无效,直接得到十进制数值。所以为了消除以上困惑,parseInt()提供了第二个参数(转换时使用的基数,即多少进制),如parseInt('070', 8) //按八进制解析
(5)parseFloat()始终都会忽略前导的0,parseFloat()只解析十进制,解析十六进制字符串时会返回0,如果字符串包含的是可解析为整数的数,parseFloat()会返回整数,如parseFloat('1234abc') //1234 parseFloat('3.125e7') //31250000
3、把一个值转换为字符串的方法有toString()和String(),null和undefined没有toString()方法,String()可以将任意数据类型转换为字符串,还可以用 + ""的方法转换字符串
4、如果不给构造函数传递参数,则可以省略括号 var o = new Object //有效但不推荐
5、对非数值应用一元加(+)或一元减(-)操作符时会调用Number()转型函数对值进行转换
6、可以用'!!'来模拟Boolean()函数转换布尔值
7、switch语句中case的值不一定是常量,可以是变量或表达式
8、函数不介意传进多少参数,因为js中的参数在内部是用一个数组表示的,函数接受到的始终是这个数组,不关心数组中有哪些参数。在函数体内可以用arguments对象来访问这个参数数组。js函数的一个重要特点:命名的参数只提供便利,但不是必须的。arguments对象可以与命名参数一起使用,并且它的值与命名参数的值保持同步(但它们的内存空间是独立的),如果只传入一个参数,那么arguments[1]设置的值不会反映到命名参数中,因为argumens对象的长度是由传入的参数个数决定的,而不是命名参数的个数决定的。没有传递值的命名参数被赋予undefined
第四章 变量、作用域和内存问题
1、当某个函数被调用时,会创建一个执行环境(execution context)及相应的作用域链。 然后,使用 arguments 和其他命名参数的值来初始化函数的活动对象(activation object)。但在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,......直至作为作用域链终点的全局执行环境。
后台的每个执行环境都有一个表示变量的对象——变量对象。全局环境的变量对象始终存在,而像函数这样的局部环境的变量对象,则只在函数执行的过程中存在。
在创建 compare()函数 10 时,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[[Scope]]属性中。 当调用 compare()函数时,会为函数创建一个执行环境,然后通过复制函数的[[Scope]]属性中的对 象构建起执行环境的作用域链。此后,又有一个活动对象(在此作为变量对象使用)被创建并被推入执 行环境作用域链的前端。
显然,作用域链本质上是一个指向变量对象的指针列表,它只 引用但不实际包含变量对象。
无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量。一般来讲, 当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象)。 但是,闭包的情况又有所不同
2、把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样,基本类型值的传递如同基本类型变量的复制一样,引用类型值的传递如同引用类型变量的复制一样。就是基本类型传递值的是值本身,引用类型传递的值是地址(指针)
3、复制保存着对象的某个变量(引用类型)时,操作的是对象的引用。但在为对象添加属性时,操作的是实际的对象(内存空间)
4、typeof用于检测基本数据类型,instanceof检测引用数据类型
第五章 引用类型
1、使用对象字面量时,数值属性名会自动转换为字符串。
2、如果设置某个值的索引超过了数组现有项数,则会在数组后面添加一项。数组的length属性不是只读的,通过设置length可以从数组的末尾移除项或向数组中添加新项。当把一个值放在超出当前数组大小的位置上时,数组就会重新计算其长度值,即长度值等于最后一项的索引加1
3、检测数组的方法:instanceof或Array.isArray()(IE9+)
4、数组转换成字符串的方法:toString()、toLocaleString()和join(),如果数组中的某一项是null或undefined,转换后是空字符串
5、数组有5个迭代方法:every()、filter()、forEach()、map()和some()。every()和some()都用于查询数组中的项是否满足某个条件,every()是数组里每一项都满足条件,这个方法才返回true,some()是只要有一项满足条件,就会返回true。forEach()没有返回值和for迭代一样,filter()返回满足条件的数组,map()返回对每一项进行操作后的数组
6、数组的归并方法:reduce()和reduceRight(),reduce()是从数组第一项逐个遍历到最后,reduceRight()是从数组最后一项逐个向前遍历
7、Date()构造函数接受的参数为表示该日期的毫秒数,js提供了两个方法Date.parse()和Date.UTC(),简化这一过程。Date.now()返回调用这个方法时的日期和时间的毫秒数(IE9+),使用+Date()也能达到同样目的。常用的日期格式化方法:toDateString()、 toLocaleDateString()、 toTimeString()和toLocaleTimeString()
8、ECMAScript5中规定,使用正则表达式字面量就像直接调用RegExp构造函数一样,每次都创建新的RegExp实例
9、RegExp实例方法:
(1)exec():返回一个数组,数组第一项是匹配的字符串,第二项是第一个捕获组匹配的字符串,第三项是第二个捕获组匹配的字符串等。exec只会返回一个匹配项。不设置全局标志的情况下,多次调用exec始终返回第一个匹配项的信息;而设置全局标志的情况下,每次调用exec都会在字符串中继续查找新匹配项
(2)test():返回true或false
10、函数是对象,函数名是指针
11、函数声明和函数表达式的区别只在于函数声明会进行函数声明提升
12、函数内部属性:arguments对象和this对象。arguments对象有一个callee属性,该属性是一个指针,指向拥有这个arguments对象的函数。用arguments.callee可以解决递归函数紧密耦合的问题
13、函数的属性和方法:
(1)两个属性:length和prototype,length属性表示函数的命名参数的个数
(2)三个方法:apply()和call(),用途都是在特定的作用域中调用函数,apply()的第二个参数可以是一个数组,也可以是arguments。ECMAScript还定义了一个方法:bind(),这个方法会创建一个函数的实例,其this值会被绑定到传给bind函数的值(var objectSayColor = sayColor.bind(o), objectSayColor())
14、基本包装类型(特殊的引用类型):Boolean、Number和String。
每当读取一个基本类型值的时候,后台就会创建一个基本包装类型的对象,从而让我们能够调用一些方法来操作这些数据。
引用类型和基本包装类型的主要区别是对象的生存期,自动创建的基本包装类型的对象,则只存在于一行代码执行的瞬间,然后立即被销毁,这意味着我们不能在运行时为基本类型值添加属性和方法。
对基本包装类型的实例调用typeof会返回“object”。
可以使用Object构造函数返回相应基本包装类型的实例,如var obj = new Object('some str')。
用new调用基本包装类型的构造函数,与直接调用同名的转型函数是不一样的
Number类型提供了将数值格式化为字符串的方法:toFix()方法会按照指定的小数位返回数值的字符串表示,toExponential()方法返回以指数表示法(也称e表示法)表示的数值的字符串形式,toPrecision()方法可以返回数值最合适的格式
String类型:
(1)字符方法:charAt()和charCodeAt()。
(2)字符串操作方法:concat()接受任意多个参数,拼接字符串,slice()、substr()和substring()。slice()方法会将传 入的负值与字符串的长度相加,substr()方法将负的第一个参数加上字符串的长度,而将负的第二个 参数转换为 0。最后,substring()方法会把所有负值参数都转换为 0。
(3)字符串位置方法:indexof()和lastIndexOf()。trim()删除前置和后缀的所有空格并返回结果,还有trimLeft()和trimRight()。
(4)字符串大小写转换方法:toLowerCase()、toLocaleLowerCase()、toUpperCase()和 toLocaleUpperCase()
(5)字符串的模式匹配方法:match()、search()、replace()和split(),match()返回一个数组,search()返回字符串中第一个匹配项的索引,如果没有找到匹配项则返回-1,split()第一个参数可以是字符串也可以是RegExp对象,有第二个可选的参数,用于指定数组的大小,以便确保返回的数组不会超过既定大小
(6)fromCharCode(),静态方法,接受一或多个字符编码,然后将它们转换成一个字符串
15、Global对象,不属于其他对象的属性和方法,都是它的属性和方法,如isNaN()、parseInt()。其他方法:
(1)URI编码方法(encodeURI()和encodeURIComponent()),有效的URI不能包含某些字符如空格,主要区别是encodeURI()不会对本身属于 URI 的特殊字符进行编码,例如冒号、正斜杠、 问号和井字号;而 encodeURIComponent()则会对它发现的任何非标准字符进行编码,所以encodeURI()主要用于整个URI,encodeURIComponent()主要用于URI的某一段。decodeURI()只能对encodeURI()替换的字符进行解码,而decodeURIComponent()可以解码任意特殊字符的编码
(2)eval()方法
(3)Global对象的属性如undefined、NaN、Object、Function等
(4)window对象
第六章 面向对象的程序设计
1、使用new操作符调用构造函数会经历以下4个步骤:
(1)创建一个新对象
(2)将构造函数的作用域赋给新对象(因此this就指向了这个新对象)
(3)执行构造函数中的代码(为这个新对象添加属性)
(4)返回新对象
2、每个对象都有一个constructor(构造函数)属性指向这个对象的构造函数
3、构造函数也是函数,函数可以通过new操作符当作构造函数使用、作为普通函数调用、在另一个对象的作用域中调用
4、每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,这个对象的用途是包含实例所共享的属性和方法
5、创建了自定义的构造函数之后,其原型对象默认只会取得constructor属性,至于其他方法都是从Object继承而来。当用构造函数创建了一个新实例后,该实例的内部将包含一个指针(内部属性[[Prototype]])(可以通过_proto_属性代替内部属性,因为内部属性无法访问),指向构造函数的原型对象
6、访问实例对象的属性或方法会执行两次搜索,第一次是在实例本身中搜索,第二次是在原型上搜索,这也是多个实例对象共享原型所保存的属性和方法的基本原理
7、delete用来删除实例属性
8、hasOwnProperty()可以检测一个属性是存在实例中,还是存在原型中,如果存在实例中返回true,否则返回false
9、in操作符可以单独使用或在for in循环中使用,单独使用是在对象能够访问给定属性时返回true,无论属性是实例属性还是原型属性。in和hasOwnProperty()结合可以确定一个属性是实例属性还是原型属性
10、for in返回的是能够通过对象访问的,可枚举的属性,既包括实例属性也包括原型属性;Object.keys()返回一个数组,包含所有可枚举的实例属性;Object.getOwnPropertyNames()返回所有实例属性,无论是否可以枚举
11、可以使用对象字面量的方式重写原型对象,但此时constructor属性不再指向当前构造函数,而指向Object构造函数,但可以特意设置constructor的值为当前构造函数,但这样会导致constructor属性变为可枚举的,所以可以通过Object.defineProperty()重设constructor
12、原型的动态性:实例尽管是在原型添加新方法之前创建的,但是实例仍然可以访问这个新方法,因为实例与原型的连接只是一个指针,而不是一个副本,所以仍然能访问到新方法(跟访问引用类型类似)。但如果原型是用对象字面量的方式重写,实例又是在其之前创建,这时实例就找不到该方法了,因为调用构造函数会为实例添加一个指向最初的原型的指针,而把原型修改为另外一个对象就等于切断了构造函数与最初原型之间的联系
13、组合使用构造函数模式和原型模式的好处是:每个实例有自己的一份实例属性副本(假如属性定义在原型中,该属性又是引用类型,那么会导致修改该属性会使别的实例上的该属性也被修改),方法又可以共享,节约内存,同时还支持向构造函数传递参数
14、继承:
(1)原型链继承:SubType.prototype = new SuperType() 原理:重写原型对象,值为父类的实例,这时SubType.protype成为父类的实例,也就包含了父类的实例属性和实例方法,并且也包含了一个指针([[Prototype]])指向父类的原型,因此也可以访问父类的原型属性和原型方法。此时子类实例的constructor属性指向父类,因为子类原型指向了父类原型,父类原型的constructor指向的是父类。通过实现原型链也扩大了原型搜索机制。
所有引用类型都默认继承自Object,所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype,这也正是所有自定义类型都会继承toString()、valueOf()等默认方法的根本原因
确定原型和实例的关系:instanceof和isPrototypeOf()
通过原型链实现继承时,不能使用对象字面量创建原型方法,因为这样做会重写原型链
原型链的问题:使用原型链继承可能会继承到父类的引用类型的实例属性,这样就可能会对别的子类实例产生影响,所以很少单独用到原型链继承
(2)借用构造函数继承:在子类构造函数内部调用父类构造函数SuperType.call(this),问题是无法函数复用,所以也很少单独使用借用构造函数
(3)组合继承:用借用构造函数继承实例属性,用原型链继承原型属性和方法,最常用
第7章 函数表达式
1、闭包是指有权访问另一个函数作用域中的变量的函数。闭包会将包含函数(外部函数)的活动对象添加到它的作用域链中,闭包被返回后,它的作用域链被初始化为包含函数的活动对象和全局变量对象,当包含函数返回后,其执行环境的作用域链会被销毁,但它的活动对象仍然会留存在内存中,直到闭包被销毁后,包含函数的活动对象才会被销毁。
2、闭包的应用
function closure() { var arr = new Array(); for (var i = 0; i < 10; i++) { arr[i] = function() { return i; } } return arr; } function closure2() { var arr = new Array(); for (var i = 0; i < 10; i++) { arr[i] = function(num) { return function() { return num; } }(i) } return arr; }
3、每个函数在被调用时都会自动取得两个特殊的变量:this和arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此不可能访问外部函数的这两个变量。所以要想访问作用域中的this和arguments对象,必须把对该对象的引用保存到闭包能够访问的变量中
4、模仿块级作用域:创建匿名函数自调用,因为变量是值的另一种表现形式,所以用实际的值替换变量没有问题,但是function(){}()这么做会出错,因为JavaScript 将 function 关键字当作一个函数声明的开始,而函数声明后面不能跟圆括号。然而,函数表达式的后面可以跟圆括号。要将函数声明转换成函数表达式,只要给它加上一对圆括号即可(function() {})()
第8章 BOM
1、javaScript 是一个单线程序的解释器,因此一定时间内只能执行一段代码。为了控制要执行的代码,就 有一个 JavaScript 任务队列。这些任务会按照将它们添加到队列的顺序执行。setTimeout()的第二个 参数告诉 JavaScript 再过多长时间把当前任务添加到队列中。如果队列是空的,那么添加的代码会立即 执行;如果队列不是空的,那么它就要等前面的代码执行完了以后再执行 。
2、调用 setTimeout()之后,该方法会返回一个数值 ID,表示超时调用。这个超时调用 ID 是计划执 行代码的唯一标识符,可以通过它来取消超时调用。要取消尚未执行的超时调用计划,可以调用 clearTimeout()方法并将相应的超时调用 ID 作为参数传递给它
第10章 DOM
1、Array.prototype.slice.call(nodeList, 0)可以将伪数组转化为数组,在IE中可以用push方法枚举到新数组中
2、Node类型:nodeType、nodeName、nodeValue;节点关系:childNodes、parentNode、firstChild、lastChild、previousSiblings、nextSiblings;节点操作:appendChild()、insertBefore()、replaceChild()、removeChild();其他方法:cloneNode([true])
3、Document类型:document.documentElement、document.body、document.title
第13章 事件
1、js与HTML之间的交互是通过事件实现的。事件流是指从页面中接受事件的顺序。IE的事件流叫事件冒泡,Netscape提出的叫事件捕获。DOM事件流包括事件捕获阶段、处于目标阶段和事件冒泡阶段
2、事件是用户或浏览器自身执行的某种动作,如click、load都是事件的名字,而响应某个事件的函数叫事件处理程序,事件处理程序以“on”开头
3、为事件指定处理程序的方式有:
(1)HTML事件处理程序(<input type="button" onclick="alert('click')")
(2)DOM0级事件处理程序(btn.onclick = function() { alert('click') }),使用该方式指定的事件处理程序被认为是元素的方法,因此,这时事件处理程序是在元素的作用域中运行,所以this指向当前元素。删除事件处理程序:btn.onclick = null
(3)DOM2级事件处理程序(btn.addEventListener('click', function() { alert('click') }, false)),第三个参数是true表示在捕获阶段调用事件处理程序,false表示在冒泡阶段调用事件处理程序。好处是可以为一个事件添加多个事件处理程序。删除事件处理程序:removeEventListener()参数必须和添加处理程序时的相同,这意味着添加的匿名函数无法无法移除,解决方法是用函数名代替匿名函数
最好是将事件处理程序添加到事件冒泡阶段,这样兼容性最好
(4)IE事件处理程序(btn.attachEvent('onclick', function() { alert('click') })),该方式指定的事件处理程序会在全局作用域中运行,因此this等于window。该方式添加的多个事件处理程序执行顺序是从后往前。删除事件处理程序:btn.detachEvent(),参数要和attachEvent相同
4、在事件处理程序内部,this的值和currentTarget相等,而target指的是事件的实际目标
5、添加大量事件处理程序会导致性能问题。解决方案是事件委托,原理是事件冒泡,只指定一个事件处理程序,可以管理某一类型的所有事件。由于从文档中移除带有事件处理程序的元素时(removeChild、replaceChild或innerHTML),事件处理程序还保留在内存中,所以手动移除事件处理程序,事件委托也可以解决这个问题,如果事先知道将来要删除某个带处理程序的元素,那么就可以把处理程序放到较高层次的元素
第14章 表单脚本
1、表单类型(HTMLFormElement)的属性和方法:action、method、name、reset()、submit()
2、提交表单:<input type="submit" />、<button type="submit"></button>、<input type="image" src="graph.gif"/>,以这种方式提交表单,浏览器会在请求发送给服务器之前触发submit事件(onsubmit),这样可以有机会验证表单数据,决定是否提交,阻止这个事件的默认行为就可以取消表单提交。还可以用form.submit()提交表单,这种方式提交表单不会触发submit事件
3、重置表单:<input type="reset"> 、<button type="reset">,用户点击重置按钮时会触发reset事件(onreset),阻止这个事件的默认行为可以取消表单重置。还可以用form.reset()重置表单,与submit()不同,这种方式一样会触发重置事件
4、表单字段(元素):每个表单都有一个elements属性,该属性是表单中所有表单元素(字段)的集合。form.elements[0]取得表单的第一个字段,form.elements['color']取得名为color的字段、form.elements.length取得表单中字段的数量
5、共有的表单字段属性、方法和事件:
属性:disabled、form(指向当前字段所属表单)、name、readOnly、tabIndex、type和value(当前字段被提交给服务器的值),除了form属性之外可以通过js动态修改其他属性(field.disabled = true)
方法:focus()和blur(),HTML5新增属性autofocus。默认情况下,只有表单字段可以获得焦点,对于其他元素,如果将其tabIndex属性设置为-1,然后再调用focus()方法,也可以让这些元素获取焦点
事件:blur、focus、change。当用户改变了当前字段的焦点,或者调用focus()或blur()时,都会触发focus或blur事件。对于<input>和<textarea>元素,当他们从获取焦点到失去焦点且value发生改变的时候,才会触发change事件,对于<select>元素,当选择了不同的选项就会触发change事件
6、<input>元素的maxlength属性可以指定文本框可以接受的最大字符数,<input>和<textarea>都支持select()方法,这个方法用于选择文本框的所有文本,调用select()方法会触发select事件
7、取得选择的文本:
(1)HTML5提供了两个属性:selectionStart和selectionEnd。(IE9+)可以用substring(input.selectionStart, input.selectionEnd)取得在文本框选择的文本。
(2)setSelectionRange()可以选择部分文本,接受两个参数:要选择的第一个字符的索引和要选择的最后的一个字符之后的字符的索引(IE9+)。要看到选择的文本,必须调用该方法之前或之后将焦点设置到文本框
8、过滤输入:
(1)屏蔽字符:向文本框中插入字符是keypress事件,因此禁用此事件的默认行为就可以了
(2)操作剪贴板
9、HTML5约束验证API(有兼容性问题)
(1)必填字段(required)
(2)其他输入类型(type="email" type="url")
(3)数值范围(<input type="number" min="10" max="100" step="5"/>)
(4)输入模式(<input type="text" pattern="/d+")模式的开头和结尾不用加^和$(假定已经有了)
(5)检测有效性:每个表单字段都有checkValidity()方法检测字段是否有效,可以在表单自身调用这个方法检测整个表单是否有效
10、禁用验证:表单设置novalidate;如果一个表单上有多个提交按钮,为了证明某个提交按钮不必验证表单,可以在相应的按钮上添加formnovalidate属性
11、选择框脚本:如果选择了没有指定value属性的option,选择框的value值为该option的文本(IE8会返回空字符串)。<option>元素的属性:index、labe、selected、text(选项的文本)、value(选项的值)
12、JSON的值不能是undefined、变量、函数或对象实例
13、JSON序列化选项:JSON.stringify()除了要序列化的对象之外,还可以接受另外两个参数。第一个参数是一个过滤器,可以是一个数组或一个函数;第二个参数是一个选项,表示是否在JSON字符串中保留缩进
(1)过滤结果:JSON.stringify(book, ['title', 'edition']),返回结果的JSON字符串中将只包含这两个属性;JSON.stringify(book, function(key, value) {})可以通过传入的key对value进行处理
(2)字符串缩进:第三个参数如果是一个数值,表示每个级别缩进的空格数,如果是字符串,这个字符串将被用作缩进字符