JavaScript是一种专为与网页交互而设计的脚本语言,由下列三个不同的部分组成:
核心:ECMAScript,由ECMA-262定义,提供核心语言功能;
文档对象模型:DOM,提供访问和操作网页内容的方法和接口;
浏览器对象模型:BOM,提供与浏览器交互的方法和接口。
ECMAScript是JavaScript的基础,它的宿主比如web浏览器可以给它提供实现与扩展,以便于实现针对环境的操作。
ECMAScript规定了语法、类型、语句、关键字、保留字、操作符、对象。
ECMA-262给出了ECMAScript兼容的定义。要想成为ECMAScript的实现,则该实现必须做到:
此外,兼容的实现还可以进行下列扩展:
上述要求为兼容实现的开发人员基于ECMAScript开发一门新语言提供了广阔的空间和极大的灵活性。
文档对象模型(DOM, Document Object Model )是针对XML但经过扩展以用于HTML的应用程序编程接口( API, Application Programming Interface )。
DOM把整个页面映射为一个多层节点结构。HTML或XML页面中的每个组成部分都是某种类型的节点,这些节点又包含着不同类型的数据。例如:
DOM1级由两个模块组成: DOM核心( DOM Core)和DOM HTML。其中, DOM核心规定的是如何映射基于XML的文档结构,以便简化对文档中任意部分的访问和操作。DOM HTML模块则在DOM核心的基础上加以扩展,添加了针对HTML的对象和方法。
DOM1级的目标主要是映射文档的结构,而DOM2级在原来的基础上又扩充了鼠标和用户界面事件、范围、遍历(迭代DOM文档的方法)等细分模块,而且通过对象接口增加了对CSS (Cascading Style Sheets,层叠样式表)的支持。DOM1级中的DOM核心模块也经过扩展开始支持XML命名空间。
DOM2级引入了下列新模块,也给出了众多新类型和新接口的定义:
DOM3级则进一步扩展了DOM,引入了以统一方式加载和保存文档的方法。
W3C的推荐标准:基于XML:
本质上,BOM处理浏览器窗口与框架,而习惯上将所有针对浏览器的JavaScript扩展都算作BOM:
HTML4.01为< script >定义了6个属性:
外部脚本可以不是.js扩展名,但是需要确保服务器能正确返回MIME类型。
当不包含async与defer属性时,< script >元素将被按照先后顺序依次解析。
在head中包含的JS代码被加载、解析与执行完成后,才会呈现body中的页面,所以优化首屏加载速度应当将JS代码放在body后面。
包含defer属性的脚本(立即下载、延迟执行)执行顺序不定(理论上先后,实际中不定),最好只包含一个延迟脚本。包含async属性的异步脚本执行顺序不定,建议不要在加载期间修改DOM。
使用外部文件的优点:方便维护、可缓存、适应未来。
< noscript >标签的使用场景:
区分大小写。
标识符,即变量、函数、属性名或函参,需要遵守:首字符为字母、下划线_、美元符号$,其他字符可为此基础上加数字。其中,字母可包含ASCII与Unicode但不推荐。惯用驼峰命名法。不可将关键字、保留字、true/false/null作为标识符。
严格模式:顶部添加“use strict”;
将JavaScript引擎切换至严格模式,旨在处理ES3中的不确定行为与不安全操作。未经声明的变量赋值会在严格模式下抛出ReferenceError错误。定义名为eval或argument的变量也会报错。
typeof是操作符而不是函数,因此其后的圆括号并非必须。function属于object,但是拥有特别的属性,因此有必要使用typeof进行区分。
null表示空对象指针,因此typeof null返回object。
定义变量用于保存对象时应使用null,通过检查null值来判断是否已经保存了对象的引用。(if value != null)
变量声明后未初始化时,值为undefined。而未经声明的变量仅能执行typeof操作,并返回undefined。二者都返回undefined,代表均不可以进行真正的操作。
Boolean()函数用于转换为boolean值,false、空串“”、0与NaN、null与undefined将被转换为false值。
流控制语句(如if语句)中会自动进行Boolean转换。
Number:表示八进制时,首位为0,其余为0~7,若数值超出范围,前导0将被忽略,其后当做十进制解析八进制字面量在严格模式下无效、报错。而十六进制前导0x,其余位中A ~ F可为大小写。计算时,二者都被转化为十进制进行计算。
JavaScript可保存正负零,认定为相等。
小数点后无其他数字或为0时,浮点数值被解析为整数。
科学计数法:e代表10的指数幂。默认小数点后六位都是0则转换为e表示。
超出边界值的数值表示为正负Infinity。通过isFinit
函数判断是否越界。
任何数值除0返回NaN(Not a Number),isNaN
函数将判断参数是否可以转变为数值,参数为boolean:true时,true可被转换为1,因此isNaN(true)返回false。
数值转换:
Number()
函数的转换规则如下:
parseInt()
函数会找到第一个非空格字符,若非数字或正负号则返回NaN,接着判断其后的字符,转换完成或遇到非数字字符时停止解析:“123blue”返回123,“22.5”返回22。此外,也可识别八进制与十六进制(识别前导0与0x)。ES3识别八进制前导0而ES5不识别,因此为parseInt提供第二个参数,即转换的基数(2,8,10,16)。
parseFloat
()忽略前导0,因此十六进制字符串会始终被转换为0;解析到文末或遇到无效字符时终止,因此22.35.48会被转换为22.35。若字符串可被解析为整数则返回整数。
String可由单双引号表示。创建后不可变,更改时新建并销毁原串。null和undefined没有toString()
方法。toString方法可以传入数值转换基数,以输出二进制等等任意有效进制格式表示的字符串。当不确定是否为null与undefined时,可使用String()
方法,当值有toString方法则调用无参数的toString,否则对应返回“null”或“undefined”。
ECMAScript中,不给构造函数传递参数时,圆括号可以省略。
Object的每个实例都具有下列属性和方法:
BOM与DOM中的对象为宿主对象,由宿主实现提供与定义,可能不会继承Object。
前置递增递减在取值之前改变值,后置在取值之后改变值。
负数使用二进制补码存储,计算补码:
在对NaN与Infinity运用位操作符时,二者会被当做0处理。
~
:NOT:按位非,相当于操作数负值减一。
^
:XOR:按位异或:同为0,异为1。
左移<<
不影响符号位,补0。对应有符号右移>>
,保留符号位。
无符号右移>>>
不保留符号位,正负一律补0。
!
:逻辑非:返回布尔值:对空串、数值0、null、NaN、undefined返回true,其余返回false。可用!!
模拟Boolean()
函数的行为。
&&
:逻辑与:有一个操作数为null、NaN、undefined时返回对应值,未声明的变量会报错。两个操作数中包含对象时:第一个为对象则返回第二个,第二个为对象则当第一个求值结果为true时返回该对象,两个都是对象则返回第二个。
||
:逻辑或:有一个操作数为null、NaN、undefined时返回对应值。包含对象的情况:第一个是对象则返回第一个,第一个求值为false返回第二个,两个都是对象则返回第一个。第一个为true而第二个未声明时第二个不会被求值,而第一个为false时第二个未声明会报错。
通常为变量赋予后备值来避免null或undefined:
var x = x1 || x2 ;
大小比较:字符串大小比较时比较对应的字符编码值。当一方为数值时,另一方会进行数制转换。任何与NaN比较结果都为false。
相等与不相等:布尔值比较前转化为数值0/1,null 与 undefined 是相等的。任一为NaN则为不相等。**全等与不全等:仅在转换前相等时返回true。**推荐使用全等与不全等。
逗号操作符:在赋值时,逗号操作符会返回表达式的最后一项:
var num = ( 1, 2, 3); // var值为3
使用for-in循环来遍历BOM中的window对象的所有属性:
for (var propName in window){
document.write(propName);
}
在进行for-in循环之前,建议先检测对象值是否为null 或undefined。
ECMAScript中参数使用数组来表示,函数接收这个数组,在函数体内可以通过arguments
对象来访问参数数组,通过其length
属性来获取传入了多少参数。arguments的长度由输入的参数
因此,传递参数时并不一定需要按照定义的参数来进行。命名的参数只提供便利,而非必须。
命名参数可以与arguments[i]一同使用。当修改arguments[i]时,对应的命名参数也会被改变。二者并不访问相同的内存空间,他们的内存空间是独立的。ES5严格模式下,这种影响是单向的,改变命名参数时,并不会改变arguments中的值;非严格模式下,更改命名参数时arguments会相应更新。
函数可以返回任何值,未指定返回值时返回undefined。
ECMAScript中所有的参数传递的都是值,而非引用。
ECMAScript中的函数没有重载,后定义的会覆盖先定义的。
基本类型值指的是简单的数据段,而引用类型值指那些可能由多个值构成的对象。
引用类型的值是保存在内存中的对象。与其他语言不同,JavaScript 不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。为此,引用类型的值是按引用访问的。
复制基本类型的值:创建新值、复制到新变量分配的位置上。复制的两个变量完全独立,不会相互影响。
复制引用类型的值:复制值到新空间中,该值实际上是指针,指向存储在堆中的一个对象。两个变量实际上引用同一个对象,改变其中一个,会影响另一个。
向函数传递基本类型的参数时,值会复制给一个局部变量,即命名参数(或者说是arguments的一个元素)。
而传递引用类型时,实际上是将这个值在内存中的地址复制给局部变量。因此此时,在函数内部的变化会反映在函数外部。
检测某个值是什么类型的对象,往往使用instanceof
操作符,判断变量是否是给定引用类型的实例,根据原型链来识别。
所有引用类型都是Object的实例。
instanceof
检测基本类型值会始终返回false,因为基本类型不是对象。
执行环境定义了变量或函数有权访问的其他数据。
每个执行环境对应关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。
全局执行环境是最外围的执行环境。根据ECMAScript实现所在的宿主环境不同,表示执行环境的对象也不一样。在Web浏览器中,全局执行环境被认为是window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的。
某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁(全局执行环境直到应用程序退出,例如关闭网页或浏览器时才会被销毁)。
每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。
当代码在一个环境中执行时,会创建变量对象的一个作用域链( scope chain )。作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问。作用城链的前端,始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象( activation object)作为变量对象。
活动对象在最开始时只包含一个变量,即arguments对象(这个对象在全局环境中是不存在的)。作用域链中的下一个变量对象来自包含它的(外部)环境,而再下一个变量对象则来自下一个包含环境。这样一直延续到全局执行环境;全局执行环境的变量对象始终都是作用域链中的最后一个对象。
标识符解析是沿着作用城链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端开始,然后逐级地向后回溯,直至找到标识符为止(如果找不到标识符,通常会导致错误发生)。
内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部的任何变量和函数。环境间的联系是线性的、有次序的。每个环境都可以向上搜索作用域链,以查询变量和函数名。搜索标识符时,从作用域链前端开始向上搜索追溯到全局变量。
延长作用域链:try-catch语句的catch块会在作用域链的前端添加新的变量对象,其中包含被抛出的错误对象的声明。
没有块级作用域:if / for 等由花括号封闭的代码块没有自己的作用域,其中定义的变量会添加到其外部的执行环境。
JavaScript具有自动垃圾收集机制。函数中的局部变量在堆/栈上分配空间来存储值,当函数执行结束后释放内存。
标记清除:mark-and-sweep:最常用的垃圾收集方式。当变量进入环境时(比如在函数中声明变量),对变量标记“进入环境”,在离开环境时标记“离开环境”。
引用计数:reference counting:跟踪记录每个值被引用的次数。当一个值赋给变量时,该值的引用次数加一;当值赋给另一个变量时,引用次数 同样加一。相反,当包含该值的对象赋有了新的值,则该值引用次数减一。当一个值的引用次数为0时,则说明无法访问该值,因此可以回收其内存空间。但是,如果两个对象相互调用,则引用次数永远非0,导致占用内存无法回收。即循环引用问题。消除循环引用的方法:赋值null。
性能问题:垃圾收集器是周期运行的,重点在于确定垃圾收集器的收集间隔。
IE的垃圾收集器根据内存分配量运行,256个变量、4096个对象或64KB字符串中达到任何一个临界值都会触发运行。导致问题在于,若一个脚本中包含多变量,可能其生命周期中都一直保有多变量,而垃圾收集器就会频繁运行导致严重性能问题。
IE7发布后,改变为动态修正临界值:若垃圾收集例程回收的内存分配量小于15%,则临界值加倍;若大于85%,则临界值重置为默认值。
管理内存:一旦数据不再有用则赋值为null来释放引用,即解除引用(dereferencing)。
创建方式有两种,一是new操作符后跟Object构造参数,另一种是使用对象字面量方法:
var object = {
name:"object",
otherType:"...",
//属性名可以为字符串、数值,数值会自动转化为字符串
"size":260,
5:true
}
对象字面量可以作为向函数传递大量参数的方式:
func({
k1:"v1",
k2:"v2"
});
function func(args){
...}
访问属性使用person.name
或者person["name"]
,方括号的优势在于可以使用变量访问:
var s="name";
person[s]
或者变量名会导致语法错误时:person["first name"]
,使用点号会因为空格导致语法错误。
一般使用点号来访问属性。
ECMAScript数组的每一项都可以保存任何类型的数据,且可以自动调整大小根据内容增长。
创建数组:使用构造函数new Array()
或Array()
,传递数组的长度或内容:Array(20)
、Array("red","blue","yellow")
。或者使用数组字面量表示法:var color= ["red","blue"];
。
数组不是只读的,可以向数组末尾移除或添加项:
var color= ["red","blue"];
//add
color.length = 3;
alert(color[2]);//undefined
//delete
color.length = 1;
alert(color[1]);//undefined
//add
color[color.length] = "black";
//因为数组最后一项索引是length-1
//所以新项添加索引为length
检测数组:value instanceof Array
,但是instanceof假定单一的全局环境,若包含多个框架、存在多个不同的全局执行环境,则数组具有不同的构造函数。因此使用Array.isArray(value)
来确定数组。
数组转换为字符串会返回数组中每一个值的字符串以逗号分隔的字符串,使用toLocaleString()
、toString()
、ValueOf()
方法时,前二者调用每一项的对应方法,而后者调用toString()
方法;三者默认都使用逗号分隔。使用join()
方法可以使用其他分隔符来构建字符串:
var color= ["red","blue"];
alert(color.join("||"));
//red||blue
当join()
方法传入undefined
或不传值时默认使用逗号分隔。
若数组中某一项值为null或undefined,则表示使用空串。
栈方法:使数组表现像栈一样后进先出、在栈顶插入(推入)和移除(弹出),使用数组的push()
和pop()
方法,pop删除并返回数组的最后一项。
var colors=new Array();
colors.push("red","blue");//red,blue
colors.pop();//red
队列方法:队列数据结构先进先出,在列表前端移除、在末端添加,使用push()
与shift()
方法,shift删除并返回数组的第一个项。
var colors=new Array();
colors.push("red","blue");//red,blue
colors.shift();//blue
unshift()
方法与shift相反,在数组前端添加任意项并返回数组的长度。通过unshift()
与pop()
方法可以模拟反向队列。
重排序方法:reverse()反转顺序。sort()升序:对每一项的toString()结果进行字符串比较。二者都返回重排序后的数组。此外可以向sort传入比较函数来决定顺序:
//适用于大多数数据类型
function compare(value1, value2){
if(value1 < value2){
return -1;
}else if(value1 > value2){
return 1;
}else{
return 0;
}
}
//更简洁的情况:
//降序地:
function compare(value1, value2){
return value2 - value1;
}
concat()
方法基于当前数组创建新数组,没有参数时相当于复制并返回副本,传入多参数可为值或数组,依次添加到末尾。
slice()
方法基于当前数组创建新数组,接收一或两个参数,即返回项的起始与结束位置,当只有一个参数则作为起始位置。返回项包括起始、不包括结束位置。结束位置小于起始位置则返回空数组。包含负数则加上数组长度计算。
splice()
方法可以进行删除、插入、替换等,具体参数有三个:起始位置、要删除的项数、要添加的项(可依次传入多个)。会改变原数组。返回被删除的项(可为空)。
splice(0,2)
代表删除前两项。(不添加)splice(2,0,"value1","value2")
代表从位置2添加两项。splice(2,1,"value1","value2")
代表从位置2删除当前项,并添加两项。位置方法:indexOf()
与lastIndexOf()
,都接收两个参数:要查找的项与(可选)表示查找位置的索引。前者从开头向后找,后者从末尾向前找。查找时使用全等操作符,需要二者严格相等。没找到返回-1。
迭代方法:(ES5)共5个,每个都接收两个参数:在每一项上运行的函数,和(可选)运行该函数的作用域对象(影响this值)。传入的方法接收三个参数:数组项的值item
、该项在数组中的位置index
和数组本身array
。
every()
:若每一项运行函数都返回true,则返回true。some()
:若任一项返回true,则返回true。filter()
:返回运行函数会返回true的项组成的数组。forEach()
:仅运行,无返回值。map()
:返回每一项返回结果组成的数组。var numbers = {
1,2,3,4,5,6,7,8,9};
var everyResult = numbers.every(function(item,index,array){
return (item < 2);
});
//false
var someResult = numbers.some(function(item,index,array){
return (item < 2);
});
//true
var filterResult = numbers.filter(function(item,index,array){
return (item < 3);
});
//[1, 2]
var mapResult = numbers.map(function(item,index,array){
return item * 2;
});
//[2,4,6,8,10,12,14,16,18]
//forEach与for同理,对每一项执行函数
缩小方法:reduce()
和reduceRight()
,迭代所有项并返回最终的值。前者从第一项开始向后遍历,后者从最后一项开始向前遍历。都接收两个参数:在每一项上运行的函数与(可选)遍历初始值。
接收四个参数:前一个值prev
、当前值cur
、该项的索引index
、数组本身array
。执行一项后结果会作为第一个参数prev
传递给下一项。因此第一次迭代发生在第二项上,其第一个参数为第一项。
var numbers = [1,2,3,4,5];
var sum = values.reduce(function(prev,cur,index,array){
return prev + cur;
});
//15
在Java.util.Date
的基础上构建,使用自UTC(Coordinated Universal Time,国际协调时间)1970年1月1日零时开始经过的毫秒数来保存日期。
new Date()
将在当前创建日期与时间,此外提供Date.parse()
和Date.UTC()
。
Date.parse()
接收日期字符串作为参数:
在构造函数中传入字符串也会在后台调用parse方法。若不符合日期规范会返回NaN。
Date.UTC()
参数为年份、月份(基于0),以及可选的月中的哪一天(1-31)、小时数(0-23)、分钟、秒与毫秒。可选部分若为空,除天数填充1,其余填充0。
当构造函数中传入UTC方法的参数时,会根据本地时区而非GMT来创建。
ES5添加Date.now()
方法,表示调用该方法时的日期与时间毫秒数:
var start = Date.now();
doSomeThing();
var end = Date.now();
var result = end - start;
//当now方法不兼容时
//使用+new Date()将结果转换为字符串
//可达到相同的结果
Date类型的valueOf
方法返回毫秒数,因此可以使用比较操作符比较日期值。
格式化方法:toDateString()显示星期、月、日、年,toTimeString()显示时分秒与时区。
正则表达式,形如:
var expression = / pattern / flags ;
其中模式pattern部分为正则表达式,flags标志代表正则表达式的行为。
支持三个标志:
//匹配所有“at”
var reg = /at/g;
//匹配第一个“bat”或“cat”,不区分大小写
var reg = /[bc]at/i;
//匹配所有以“at”结尾的3个字符,不区分大小写
var reg = /.at/gi;
元字符需要转义:" \ " :
() [] {} \ ^ | $ ? * + .
//匹配第一个“[bc]at”,不区分大小写
var reg = /\[bc\]at/i;
//匹配所有“.at”,不区分大小写
var reg = /\.at/gi;
以上为字面量形式定义,使用RegExp构造函数:
var reg = new RegExp("[bc]at","i");
由于字符串为参数,需要双重转义:“ \ ”使用“ \ \”来转义。
在ES3中,字面量创建会共享同一个实例,而构造函数创建会创建新实例。
属性:
捕获组:组0为原表达式:
RegExp
对象的主要方法是exec(),接收一个参数为应用模式的字符串,返回包含第一个匹配项信息的数组,没有匹配项返回null。返回的Array实例包含两个额外的属性:index
与input
,index代表匹配项在字符串中的位置,input代表应用正则表达式的字符串。数组中,第一项是与整个模式匹配的字符串(匹配捕获组0)
var text = "mom and daddy and baby";
var pattern = /mom( and daddy( and baby)?)?/gi;
var matches = pattern.exec(text);
//matches.index: 0
//matches.input: "mom and daddy and baby"
//matches[0]: "mom and daddy and baby"
//matches[1]: " and daddy and baby"
//matches[2]: " and baby"
设置全局模式,exec方法仍然返回第一个匹配项,但是多次调用会依次返回继续查询匹配的结果,其lastIndex属性值在每次调用后都会增加,而不设置全局属性就会始终保持不变。
test()
方法接收字符串参数,模式与字符串匹配则返回true,常用于if语句中。
字符串匹配正则表达式的方法是match()
方法返回捕获组:matches = string.match(pattern)
,还有search()
方法返回第一个匹配项,从前向后未找到返回-1。
“函数是对象,函数名是指针”
不带圆括号的函数名是访问函数的指针,而非调用函数。
没有重载,第二次声明的函数会覆盖第一个。
函数声明:function f(){...}
:解析器会先读取函数声明,使其在任何代码执行前可用(可用访问)。通过函数提升,解析器声明函数并将函数声明提升到顶部。
函数表达式:var f = function(){...}
:函数位于初始化语句而非函数声明,不会进行函数提升。在该语句之前执行f函数会报错“unexpected identifier”意外标识符错误。
此外,函数表达式与函数声明是等价的。
函数可以作为值传入函数与被函数返回。
函数内部属性:arguments
与this
。
arguments数组有特殊的属性callee
,作为指针指向拥有该arguments对象的函数。如果存在函数执行与函数名紧耦合的情况,使用arguments.callee
来代替函数名进行调用。
this
引用函数执行时的环境变量,如果是网页的全局作用域调用this,则其引用对象为window;在调用函数前,this的值并不确定。
ES5定义了函数属性caller
,保存调用当前函数的函数的引用,若在全局函数中调用函数,则其值为null。
严格模式下,arguments.callee
会报错,且不能对caller属性赋值。为了保证安全性。
函数的属性与方法:每个函数都包含两个属性:length和prototype,其中length表示函数希望接收的命名参数的个数。
prototype在ES5中不可枚举,因此for-in无法发现。
每个函数包含两个非继承的方法:apply()与call(),用途都是在特定作用域中调用函数,等于设置函数体内this对象的值。
apply()
方法接收两个参数,运行函数的作用域与参数数组,第二个参数可为Array的实例,也可为arguments数组。
call()
方法与apply()方法作用相同,区别仅在于接收参数的方式不同。其第二个参数不是数组,而是将参数值依次列举传入:
f1.apply(this,new Array(v1,v2,v3...));
f1.call(this,v1,v2,v3...);
使用apply与call仅在于传参的方式。二者作用在于扩充函数赖以运行的作用域。
ES5定义了bind()方法,创建一个函数实例,其this值会被绑定为bind()函数的参数值。
函数继承的toString方法会返回函数的代码,valueOf同样。
特殊的引用类型:Boolean
、Number
、String
,每当读取基本类型时会创建对应的包装类型的对象,从而可以调用一些方法来操作数据。
当使用基本类型对象的方法时,后台相当于完成创建对应的基本包装类型的新实例、在实例上调用指定方法、销毁这个实例。
正常引用类型与基本包装类型的主要区别在于生命周期,前者在执行流离开当前作用域之前都一直保持在内存中,后者只存在于调用方法的一行代码的执行瞬间,然后立即被销毁。即代表不可以在运行时为基本类型对象添加属性和方法,因为它们当即销毁。
显式调用基本包装类型时,对其实例调用typeof会返回object,转换时对应true。
Object构造函数像工厂方法一样,根据传入值的类型返回相应的基本包装类型的实例。
var obj = new Object("some text");
alert (obj instanceof String); //true
注意,new Number()
构造函数返回的实例的type对应object(本质上为引用类型),而转型函数Number()
返回的对象为number基本类型(强转)。
注意:即使使用Boolean显示构造布尔值,其转换时作为引用类型object仍会转换为true,而非对它的值进行求值:
var falseObject = new Boolean (false);
var result = falseObject && true;
alert (result); //true
因此建议不使用Boolean。
此外,Number还提供了toFixed()
方法,意为显示几位小数,必要时填充与舍入。还有格式化方法toExponential()
,表示指数表示法(e表示法),接收一个参数作为输出结果中的小数位数:
var num = 10;
alert (num.toRxponential(1));// "1.0e+1"
得到一个数的固定位数的表达(固定大小格式或者指数格式),使用toPrecision()
方法,传入参数代表显示数字的位数。
字符串方法:concat()
方法,返回拼接的一个或多个字符串,不改变调用方法的原字符串实例的值,不如加号拼接方便。
slice()
、substr()
、substring()
,都接收一或两个参数,第一个参数指定开始位置,substr()的第二个参数指定返回串的长度,其余二者的第二个参数指定结束位置(不包含)。当参数为负数时,substring会将负数视为0,而其余二者转换为长度加参数。
通过增加indexOf
方法的起始查找位置来遍历整个母串包含子串的每个位置:
注意:对子串是空串的情况,indexOf会返回0。
var stringValue = "Lorem ipsum dolor sit anet, consectetur adipisicing elit";
var positions = new Array() :
var pos = stringValue.indexOf ("e");
while(pos > -1) {
positions.push (pos);
pos = stringValue.indexOf("e", pos + 1);
}
alert (positions);
//"3,24.32,35,52"
trim()
方法会过滤字符串前后的空格返回副本。
replace()
方法接收两个参数,前者为被替换的子串,后者为需要替换为的子串。如果第一个参数是字符串,则只会匹配第一个搜索结果;需要全部替换则使用对应的正则表达式,并且指定g
标志。
replace的第二个参数可为函数,接收三个参数:模式的匹配项match
、模式匹配项在字符串中的位置pos
、原始字符串originText
,函数返回对应替换的字符串。
HTML转义:
function htmlEscape (text) {
return text.replace(/[<>*&]/g,function (match,pos,originalText) {
switch (match} {
case "<":
return "<";
case ">":
return ">";
case "&":
return "&";
case "\"":
return """;
}
});
}
由ECMAScript实现提供的不依赖于宿主环境的对象,这些对 象在ECMAScript程序执行之前就已经存在了。
除了之前的Object、Array、String等等,还有Global与Math。
Global不属于其他任何对象的属性和方法。所有在全局作用域中定义的属性和函数,都是Global对象的属性。isNaN、isFinite、parseInt、parseFloat等都是Global对象的方法。
URI编码方法:encodeURI()
方法不会对原本属于
URI的特殊字符进行编码,比如:/?#
,而encodeURIComponent()
方法会对所有非字母数字字符进行编码。
eval()方法:接收一个参数,即要执行的JS字符串。解析器会将参数当做JS代码来解析。被执行的代码具有与当前执行环境相同的作用域链。eval中创建的变量与函数都不会提升,仅在eval执行的时候被创建。
严格模式下eval创建的变量或函数无法被外界访问。不建议在用户输入使用eval否则易引起代码注入。
特殊值undefined、NaN、Infinity与构造函数Object、Function等都是Global对象的属性。
web浏览器将Global全局对象当做window对象的一部分进行实现 。
Math对象的属性:一些计算特殊值:
min、max方法:
var min = Math.min(3,16,456,232);//3
var values = [3,156,2,64,23,648];
var min = Math.min.apply(Math,values);
数组的情况,需要将Math作为第一个参数从而设置this,第二个参数传入数组。
舍入方法:ceil()
向上舍入、floor()
向下舍入、round()
四舍五入。
其他方法:
random()
方法:返回0-1区间的随机数,不包括边界。
ECMA-262把对象定义为“无序属性的集合,其属性可以包含基本值、对象或者函数。”严格来讲,这就相当于说对象是一组没有特定顺序的值。对象的每个属性或方法都有一个名字,而每个名字都映射到一个值。我们可以把ECMAScript的对象想象成散列表,无非就是一组名值对,其中值可以是数据或函数。
创建自定义对象:字面量方式:
var person = {
name: "dsadsa",
age: 15,
getName: function(){
return this.name;
}
};
属性类型:
内部使用的特性,用以描述属性的特征,由两对方括号包裹。包含数据属性和访问器属性。
1. 数据属性
[[Configurable]]
:表示能否通过delete删除属性从而重新定义属性、能否修改属性的特性,或者能否把属性修改为访问器属性。[[Enumerable]]
:表示能否通过for-in 循环返回属性。[[Writable]]
:表示能否修改属性的值。[[value]]
:包含这个属性的数据值。读取属性值的时候.从这个位置读;写入属性值的时候,把新值保存在这个位置。默认值为undefined。自定义对象的前三个属性为true,value保存在[[value]]中。
修改默认特性使用Object.defineProperty()
方法,接收三个参数:属性所在对象、属性名与一个或多个以上的描述符对象(特性):
var person = {
};
Object.defineProperty(person,"name",{
writable:false,
value:"dsadsa"
});
(此后再为name属性赋新值也无法改变其值,严格模式下对其进行赋值操作会报错)
可以多次调用该方法修改属性,但是一旦configure特性设置为false不可配置,之后就不可以再修改,严格模式下会报错。在调用该方法时,configure、enumerable、writable默认值都为false。
2. 访问器属性
除了configure、Enumerable以外,包含一对getter与setter函数,由以下属性定义(默认值undefined):
访问器属性不能直接定义,必须使用Object.defineProperty()
:
var book = {
_year: 2004,
edition: 1
};
Object.defineProperty(book, "year",{
get: function(){
return this._year;
},
set: function(value){
if(value > 2004){
this._year = value;
this.edition += value - 2004;
}
}
});
属性名前的下划线通常表示只能通过对象方法访问的属性。访问器属性year包含getter与setter函数,getter返回_year的值,setter计算正确的版本。这是访问器属性常用方式,即设置属性值导致其他属性的变化。
getter与setter只写一个代表另一个方法的禁用。
同时定义多个属性使用Object.defineProperties()
,接收两个参数:需要操作的对象与进行操作的属性(添加、修改):
var book = {
};
Object.defineProperties(book,{
_year: {
value: 2004
},
edition: {
value: 1
},
year: {
get: function(){
return this._year;
},
set: function(value){
if(value > 2004){
this._year= value;
this.edition= value- 2004;
}
}
}
});
获取属性的特性(描述符)使用Object.getOwnPropertyDescriptor()
方法, 接收两个参数:属性所在对象和描述符属性名称,返回一个对象,如果是访问器属性,则对象具有configure、enumerable、get、set;是数据属性则具有configure、enumerable、writable、value。
工厂模式:用函数来封装以特定接口来创建对象的细节:
function createPerson(name,age,job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.getName = function(){
return this.name;
}
return o;
}
var person1 = createPerson("Greg",15,"Doctor");
工厂模式解决创建多个相似对象的问题,但是无法解决对象识别问题。
构造函数模式:
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.getName = function(){
return this.name;
};
}
var person1 = new Person("Nico", 26, "Doctor");
与工厂模式的方法不同于:在函数内部没有显示创建对象、没有return语句、直接将属性和方法赋值给this对象。按照惯例,构造方法以大写字母开头,其他函数以小写开头。
创建person的新实例使用new 操作符,调用构造函数实际上经历了:
以上person实例的constructor属性指向其构造函数Person()。使用person instanceof Person 会返回true,代表着创建自定义的构造函数会将它的实例标识为自定义的特定类型。
普通函数与构造函数的区别仅在于new操作符的使用。
以上例子中每个实例都创建了getName方法,且不同实例的getName方法是不同的,而这显然是不必要的,因此建议将函数定义转移到构造函数外部,内部仅调用而非创建。而此时许多仅在内部调用的方法出现在全局作用域,封装性差。
原型模式:函数的prototype原型属性指向一个对象,而这个对象包含特定类型的所有实例共享的属性和方法。即,不必在构造函数中定义实例的信息,而是将信息直接添加在原型对象中。
实例对象的内部属性[[Prototype]]
(对应使用__proto__
属性)指向其构造函数的原型,与其构造函数没有直接的关系。这种关系可以使用isPrototypeOf()
方法来确定:
Person.prototype.isPrototypeOf(person1);//true
ES5新增Object.getPrototypeOf()
,返回[[Prototype]]
的值,即对象的原型:
Object.getPrototypeOf(person1) == Person.prototype;
//true
当代码读取对象的某个属性时,会从实例本身开始搜索,找到则返回,否则根据指针继续搜索其原型对象;这是多个对象共享原型所保存的属性和方法的基本原理。
不可通过实例来重写原型中的属性值,创建时会屏蔽原型中的属性,阻止访问原型中的对应属性;当实例中的属性设置为null,也不会恢复指向原型属性连接,只能使用delete
操作符来进行:delete person1.name
;
使用hasOwnProperty()
可以检测一个属性是否存在于实例中(返回true),还是存在于它的原型中(返回false)。
当属性在对象中可以访问(实例中或原型中),使用in
操作符返回true:"name" in person1;
取得对象的所有可枚举属性,使用ES5的Object.keys()方法。接收一个对象作为参数,返回包含所有可枚举属性的字符串数组;对实例使用该方法时,不可枚举其原型中的属性。
使用字面量重写原型对象:
function Person(){
}
Person.prototype = {
name : "Nicholae",
age : 29,
job: "software Engineer",
SayName : function () {
alert (this.name);
}
};
使用对象字面量重写了默认的prototype,则constructor属性为该原型对象的构造函数,即Object()构造函数。此时,虽然instanceof会返回正确的结果,但是constructor属性无法返回正确的类型(会返回Object)。如果需要,在prototype的属性中添加constructor: Person,
来手动指定。而此时导致constructor属性的Enumerable特性变为true,即可枚举(而原生的constructor不可枚举)。可以用defineProperty方法更改其枚举特性。
var friend = new Person() :
Person. prototype. sayHi = function() {
alert("hi");
};
friend.sayHi();//"hi"
在原型中做出的更改会立即在实例上反映,即使是先建立实例再改变原型也是可以的,因为实例与原型之间的关系是指针,而非副本。实例中的指针仅指向其原型,而非构造函数。
虽然可以随时更改原型的属性和方法,但是如果重写整个原型对象,则:因为调用构造函数会为实例添加一个指向其原型的[[Prototype]]指针,而将原型改变为另一个对象就等于切断了构造函数与最初原型的关系。
function Person() {
}
var friend = new Person();
Person.prototype = {
constructor: Person,
name : "Nicholas" ,
age : 29,
job :"Software Engineer" ,
sayName : function () {
alert (this.nane);
}
};
friend. sayName(); //error
所有原生的引用类型都使用原型模式来创建,其方法都在原型上定义。
而在实例中修改属性时,如果属性保存在其原型中,那么这个原型的其他实例的该属性也会被修改,需要注意。
组合使用构造函数模式与原型模式:创建自定义类型的最常见方式,构造函数模式用于定义实例属性,而原型模式用于定义方法和共享属性。每个实例有自己的属性副本,同时又共享着方法的引用,集二者之长。
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
}
Person.prototype = {
constructor: Person,
getName: function(){
return this.name;
}
}
var person1 = new Person("Nico", 26, "Doctor");
var person2 = new Person("Baber", 30, "Engineer");
动态原型模式:在构造函数中封装了所有信息与初始化原型:
function Person (name, age, job) {
this.name = name;
this.age = age;
this.job = job;
if (typeof this.sayName != "function"){
Pereon.prototype. sayName = function(){
alert (this.name) :
};
}
}
对原型的初始化操作仅会在第一次调用构造函数时才会执行。
接口继承与实现继承,接口继承只继承方法签名,实现继承则继承实现的方法;ECMAScript使用原型链来支持实现继承。
务必切记不要使用对象字面量创建原型,因为会造成重写原型,断开原先的原型链。
实现原型链的基本模式:
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
//继承
SubType.prototype = new SuperType();
SubType.prototype.getSuperValue = function(){
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue());//true
解决原型链中的引用类型问题,使用“借用构造函数”(伪造对象或经典继承),即在子类的构造函数内部调用父类的构造函数,使用apply()
与call()
方法:
function SuperType(color){
this.colors = ["red","blue","green"] .concat(color);
}
function SubType(){
SuperType.call(this);
}
调用父类的构造函数,在子类对象上执行父类函数中定义的对象初始化代码。
组合继承:也称伪经典继承,将原型链与借用构造函数的方法结合,使用原型链实现对原型属性和方法的继承,通过构造方法实现对实例属性的继承。最常用的继承模式。
function SuperType(name){
this.name = name;
this.colors = ["red","blue"];
}
SuperType.prototype.getName = function(){
return this.name;
};
function SubType(name,age){
SuperType.call(this,name);
this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.getAge = function(){
return this.age;
}
原型式继承:ES5新增Object.create()
方法规范原型式继承,接收两个参数:用作新对象的原型的对象与可选的新对象额外属性,后者与defineProperties()同理。
var person = {
name: "Nico",
friends: ["Shyfd","GRWaa"]
};
var person1 = Object.create(person,{
name:{
value: "Frgge"
}
});
当不需要创建构造函数、仅需要保证与另一个对象类似的情况下,可以使用原型式继承。同样需要注意,包含引用类型的属性值会始终共享同一个引用与值。
组合继承的不足:无论什么情况下总会调用两次父类构造函数:一次是创建子类原型时,一次是在子类的构造函数内部。可以使用寄生组合模式:不必为了子类的原型而调用父类的构造函数,仅需要其副本:
function inheritPrototype(subType, superType){
var prototype = Object(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
继承函数里,创建父类的副本,然后添加constructor属性,弥补因重写原型而失去的默认的constructor;然后将副本赋值给子类的原型。从而替换之前的为子类的原型赋值的过程。
function SuperType(name){
this.name = name;
}
SuperType.prototype.getName = function(){
return this.name;
}
function SubType(name, age){
SuperType.call(this,name);
this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.getAge = function(){
return this.age;
}
这种方式的高效率在于只调用了一次父类构造函数,也避免了在子类的原型链上创建多余的属性,且还可以正常使用instanceof与isPrototypeOf。是引用类型最理想的继承范式。
浏览器给函数定义的非标准属性name
,可以访问函数名。
函数声明的重要特征是函数提升,在执行代码之前先读取函数声明。
使用函数表达式创建的函数看起来像是变量赋值,即创建一个函数并赋值给变量,这种函数叫做匿名函数,因为function关键字之后没有标识符(函数名),其name属性为空串。
指有权访问另一个函数作用域中的变量的函数。
创建闭包的常见方式,即在一个函数内部创建另一个函数。
当函数第一次被调用时,会创建一个执行环境及相应的作用域链,并将作用域链赋值给内部属性[[scope]]
,然后使用this、arguments、与其他命名参数的值来初始化函数的活动对象(activation object)。在作用域链中,其外层、外层的外层等等函数的活动对象依次位于第二位、第三位…直到作用域链终点的全局执行环境。
function compare {
value1, value2){
if (valuel < value2) {
return -1;
} else if {
value1 > value2) {
return 1;
} else {
return 0;
}
}
var result = compare(5, 10);
此处包含的compare()函数执行时的作用域链:
每个执行环境都有一个变量对象,全局环境的变量对象始终存在,函数这样的局部环境的变量对象只在函数执行的过程中存在。
在创建函数时,会创建一个预先包含全局变量对象的作用域链,保存在[[scope]]属性中。调用函数时,会为函数创建一个执行环境,通过复制其scope属性中的对象构建函数的作用域链。
作用域链本质上是指向变量对象的指针列表。
一般地,函数执行完毕后,局部活动对象会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象),但是闭包的情况不同:
function createComparisonFunction (propertyName) (
return function (object1, object2) {
var value1 = object1[propertyName];
var value2 = object2 [propertyName];
if (valuel < value2){
return -1;
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
};
}
var compare = createComparisonFunction ("name");
var result = compare({
name:"Nico"},{
name:"Niii"});
compare = null;
createComparisonFunction 函数的活动对象会被包含在其内部函数的作用域链中,内部的匿名函数可以访问其所有属性。
在createComparisonFunction 执行完毕之后,活动对象也不会被销毁,因为匿名函数的作用域链仍然引用这个活动对象;当它返回后,它的执行环境的作用域链被销毁,而它的活动对象仍然保存在内存中。当compare对象置空时,解除该函数的引用,以便释放其内存。
闭包会携带包含它的函数的作用域,因此会占用较多的内存,慎重使用。
这里返回的是函数数组,result中的匿名函数保存的都是对i的引用,导致i最终取值为10,每个函数都会返回10。
此时,将i的值传给匿名函数,而非使用i的引用,则i的值会被复制给num参数,匿名函数内部又创建并返回了一个访问num的闭包,则每个函数都有自己的num变量的副本,可以返回不同的值。
this
问题来自:https://www.jb51.net/article/161019.htm
var name = 'window'
var person = {
name :'Alan',
sayOne:function () {
console.log(this.name)
},
sayTwo:function () {
return function () {
console.log(this.name)
}
}
}
person.sayOne()//Alan
person.sayTwo()() // window
解释:
函数内部的this
指向调用者
sayOne调用者是person对象,所以this指向person;
sayTwo的调用者虽然也是person对象,但是区别在于这次调用是在全局返回了一个匿名函数
而这个匿名函数不是作为某个对象的方法来调用执行,是在全局执行
改变this的指向:在匿名函数中保存this的值:
var that = this;
return that.name;
内存泄漏问题:
匿名函数保存了assignHandler的活动对象的引用,只要匿名函数存在,element的引用次数就至少为1,内存无法回收。
此处通过置空element,解除对DOM对象的引用。将id的副本保存在变量中,在闭包中引用对应变量而避免了引用element。
用作块级作用域(私有作用域)的匿名函数的语法:
(function(){
//块级作用域
})()
此处,并列的两对圆括号中,前者包含函数,后者代表使用该函数,意为:function f(){...}; f();
,为了避免function关键字被当做函数声明的开始,而函数声明后不能跟圆括号,因此将其转变为函数表达式,使用一对圆括号包裹。
function outNum(count){
(function(){
for(var i=0;i<count;i+++){
alert(i);
}
})();
alert(i);//error
}
匿名函数中,变量 i 随着函数的执行完毕被销毁,因此在下方打出 i 的值会报错,因为此时 i 已经被销毁。
这种做法可以避免污染全局的变量,避免变量命名冲突;
而且减少闭包的内存占用,因为没有指向匿名函数的引用,函数执行完毕就立即销毁其作用域链。
任何在函数中定义的变量都可以认为是私有变量,因为不能在函数的外部访问这些变量。私有变量包括函数的参数、局部变量和在函数内部定义的其他函数。
有权访问私有变量和私有函数的公有方法称为特权方法。
模块模式(module pattern):为单例创建私有变量和特权方法。
单例(singleton):指只有一个实例的对象。
常见的使用单例来管理应用级程序的信息:添加与访问数组信息:
var application = function() {
//私有变量和函数
var components = new Array{
};
//初始化
components.push (new BaseComponent ());
//公共
return {
getComponentCount : function(){
return components.length;
}
registerComponent : function (component) {
if (typeof component == "object") {
components.push (component);
}
}
};
}();
如果需要创建一个对象并使用数据进行初始化,同时需要公开访问数据的方法,则可以使用模块模式。
如果上述例子中的application必须是指定BaseComponent的实例,则修改如下:
var application = function() {
//私有变量和函数
var components = new Array{
};
//初始化
components.push (new BaseComponent {
});
//创建局部副本
var app = new BaseComponent ();
//公共接口
app.getComponentCount = function(){
return components.length;
};
app.registerComponent = function (component) {
if (typeof component == "object") {
components.push (component);
}
};
//返回副本
return app;
}();
下一篇:《JavaScript高级程序设计》读书笔记 【8章~】