前言:js由三部分组成,1. 核心(ECMAScript),语法标准 2.文档对象模型(DOM) 3.浏览器对象模型(BOM)
JavaScript 的核心语言特性在ECMA-262中是以名为ECMAScript的伪语言的形式来定义的。ECMAScript中包含了所有基本的语法,操作符,数据类型以及完成基本的计算任务所必需的对象,但没有对取得输入和产生输出的机制作出规定。
1.在web中引入js
<script></script> 使html与js混合
defer : 延迟到页面全部解析完后再加载js
type : mime类型, 默认"text/javascript"。可省略
引入外部js:
<script src=""></script> 可引入不用域的js,引入外部js时同时在标签内嵌入js代码会被忽略。
注意:不要在js中出现 </script> ,会造成浏览器错误解析(以为js已经结束)。
小tips:浏览器按照<script>标签先后顺序解析,如果将js代码放head里,浏览器会先解析js,然后呈现body中的页面元素(页面延迟,呈现空白)
2.数据类型
ECMAScript 中规定的5种基本数据类型: Undefined,Null,Boolean,Number,String
1种复杂数据类型: Object 由一组无序的键值对组成
typeof操作符: 用于返回检测的变脸类型 : undefined,object,boolean,number,string,function
typeof(null); //返回"object" 空对象指针(对空对象的引用)
Undefined类型
只有一个值 undefined,变量声明后未初始化,值就是undefined。(未声明和声明后未定义值都是undefined)
var name;
alert(name);//"undefined"
alert(age);//"undefined"
Null类型
同样只有一个值 null。
var name = null;
alert(typeof name);//"object" 对空指针的引用
小tips:在定义一个变量用来保存对象时,初始化时最后给null,这样就只需要检查null值就可以判断变量是否保存了对象的引用.
如:
var person = null; if(person!=null){ }
alert(null == undefined); //true, undefined值是派生自null值的
Boolean类型
任何其他类型都有与Boolean等价的值
Boolean(""); //false, 非空字符串返回true Boolean(0);//0和NaN 返回 false , 非零(包括无穷大)返回true Boolean(null);//返回false Boolean(undefined);//false
使用if语句进行流程控制时,会自动调换用Boolean()函数进行转换
Number类型
var num1 = 55; //十进制 var num2 = 070; //八进制 0开头 56 var num3 = 0x1f; // 十六进制 0x开头 31
- 浮点数值
由于保存浮点数值需要的存储空间是整数值的两倍,所以ECMAScript会尽可能将浮点数转成整数。
var floatNum1 = 10.0; //保存的是整数10
注意:0.1+0.2 !=0.3 浮点数精度问题(永远不要测试某个特定的浮点数值)
2. 数值范围
Number.MAX_VALUE,Number.MIN_VALUE,Number.NEGATIVE_INFINITY,Number.POSTIVE_INFINITY
Infinity正无穷, -Infinity负无穷。
isFinite()函数来判断是否在这个范围内,超出范围的数值会被自动转成相应的无穷大值
3. NaN(Not a Number)非数值
任何数值除以0返回NaN,不会抛错(不会影响后面代码的执行)
注意:任何涉及NaN的操作都会返回NaN。NaN与任何值都不相等(包括NaN)
alert(NaN == NaN); //false
isNaN();//判断是否 "不是数值"
alert(isNaN(NaN)); // true alert(isNaN(10)); //false alert(isNaN("10")); //false,可转换成数值 alert(isNaN("blue")); //true,不可转成数值 alert(isNaN(true)); //false 可转成数值
4. 数值转换函数 Number() ,parseInt()和parseFloat()
Number(null); // 0
Number(undefined); //NaN
Number("hehe"); //NaN
Number(""); //0
parseInt("0xAF",16); //175
parseInt("AF"); //NaN 需指定进制
parseInt("10",2); // 2 (二进制)
String 类型
由零或多个 16位 Unicode字符组成的字符序列
注意:字符串一旦创建,值就不能改变。如果改变,是先销毁原来的字符串,然后用新字符串来填充该变量
字符串转换:
toString();几乎每个值都有该方法
toString(2); //可指定数值的转换进制
null和undefined没有 toString()函数
可以使用String()函数来转换:String(null); // "null"
String(undefined); //"undefined"
Object类型 (所有对象的基础)
Obejct属性和方法
Constructor:构造函数(用于创建当前对象的函数)
hasOwnProperty(propertyName):检索当前对象实例中的属性(不是实例原型)是否存在,参数必须是字符串
isPrototypeOf(object):传入的对象是否是另一个对象的原型
propertyIsEnumerable(propertyName):属性是否能用for-in 遍历
toLocalString():返回对象的字符串表示
toString():
valueOf():返回对象的字符串,数值或布尔值表示。通常与toString()返回相同
3.操作符
1.位操作符
32位二进制表示整数,第32位为符号位
~ 按位非(NOT)
& 按位与(AND)
| 按位或(OR)
^ 按位异或(XOR)
<< 左移,不影响符号位
>> 有符号右移,保留符号位,不影响正负
<<< 无符号右移,连着符号位一起右移
2.关系操作符
4.语句
1.for-in 精准的迭代语句,可以用来枚举对象的属性
for(var propName in window){ document.write(propName); }
输出BOM中 window对象的所有属性
迭代前最好先检测对象是否为null或undefined。(ECMAScript5中不执行循环体,以前会报错)
2.label语句,给代码添加标签
3.break和continue语句(break跳出循环,直接执行循环后的代码。continue跳出当前循环,接着进入下一次循环)
var num =0; outer: for(var i=0;i<10;i++){ for(var j=0;j<10;j++){ if(i == 5 && j==5){ continue outer; } num++; } } alert(num); //95
4. with语句(不建议使用,可读性差)
5. switch语句
var num = 15; switch (true){ //输出 Between 10 and 20,如果是false就输出 Less than 0 case num<0: alert("Less than 0"); break; case num>=0&&num<=1: alert("Between 0 and 1"); break; case num>10&&num<=20: alert("Between 10 and 20"); break; default: alert("More than 20"); }
6.函数
function关键字来定义函数,
注意:函数无所谓传进来多少个参数。因为在内部是用一个数组来表示的,在函数体内可以通过arguments来访问这个数组。
arguments对象只是与数组类似(并不是array的实例)
function doAdd(num1,num2){ arguments[1] = 10; //会同步影响 num2的值,这种影响是单向的,反过来修改num2并不会影响arguments中的值 alert(arguments[0]+num2); }注意:arguments对象长度由传入的参数个数决定,不由定义函数时声明的参数个数决定,没有传值的参数自动赋值undefined
5.变量,作用域,内存
5.1 基本类型和引用类型
基本类型值指的是简单的数据段,而引用类型值指那些可能由多个值构成的对象。基本类型是按值访问,所以可以操作保存在变量中的实际的值。
引用类型的值是保存在内存中的对象,js不允许直接 访问内存中的位置(不能直接操作对象的内存空间),实际操作的是对象的引用。
var person = new Object(); person.name = "张三"; //给引用类型变量动态添加属性并赋值 alert(person.name);// 张三 //无法给基本类型变量添加属性,不报错但是无效。
变量复制: 基本类型
var num1 = 5; var num2 = num1;// num1和num2是两个独立的值,在进行任何操作时不会相互影响。引用类型:
var obj1 = new Object(); var obj2 = obj1; obj1.name = "张三"; alert(obj2.name); // 张三 //obj1和obj2指向的是堆内存中的同一个对象参数传递:
ECMAScript中所有函数的参数都是按值传递的。在传递基本类型值是,被传递的值会赋给一个局部变量(命名参数,arguments对象中的一个元素)
注意:传递引用类型时,会把这个值在内存中的地址赋值给一个局部变量(对堆内存中对象的引用),所以指向的还是同一个内存空间,因此对参数的修改会影响到函数外。
function setName(obj){ obj.name = "张三";//obj引用指向堆中的person对象,person.name = "张三" obj = new Object();//给obj(参数,局部变量) 指向新的对象(该对象在setName函数中创建,是局部的,函数结束,对象销毁) obj.name = "李四";//给obj引用的局部变量动态添加name属性赋值为"李四" } var person = new Object(); setName(person); alert(person.name); //张三类型检测:
通常用typeof来检测基本类型
instanceof来检测引用类型,根据它的原型链来识别。
所有引用类型都是Object的实例,用instanceof检测所有基本数据类型,都会返回false。基本类型 不是对象。
5.2 执行环境及作用域
说明:执行环境定义了变量或函数有权访问的其他数据,决定了他们各自的行为。每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。虽然我们编写的代码无法访问这个对象,但解析器在处理数据是会在后台使用它。
全局执行环境是最外围的一个执行环境。在web浏览器中,全局执行环境就是window对象,因此所有全局变量和函数都是作为window对象的属性和方法
创建的。某个执行环境中的所有代码执行完后,保存在其中的变量和函数定义也随之销毁。
每个函数都有自己的执行环境。当执行流进入一个函数时,函数环境会被推入环境栈。执行完后弹出栈,控制权交给之前的执行环境
作用域链(scope chain)。保证对执行环境有权访问的所有变量和函数的有序访问
var color = "blue"; function changeColor(){ var anotherColor = "red"; function swapColors(){ var temColor = color; color = anotherColor; anotherColor = temColor; } }
上面代码作用域链,如图:三个作用域,内部环境可以通过作用域链访问所有的外部环境
延长作用域链:
1.try-catch 中的catch块2.with语句
function buildUrl(){ var qs = "?debug=true"; with(location){ var url = href + qs; //location.href,url添加到最近的环境(buildUrl函数中) } return url; }with语句接收的是location对象,因此其变量对象中就包含了location对象的所有属性和方法,而这个变量对象被添加到了作用域链的前端,所以在函数内部可以访问。
没有块级作用域:
js没有块级作用域,不像java等语言,if(){},for(){}语句中定义的都是局部变量
for(var i=0;i<10;i++){ doSomething(i); } alert(i); // 10js中会将if,for中声明的变量添加到当前的执行环境中
变量声明:
使用var声明的变量会自动被添加到最接近的环境中。在函数内部,最接近的就是当前函数的局部环境;在with语句中,最接近的环境是函数环境。如果初始化变量时没有var声明,则自动添加到全局环境。function(num1,num2){ sum = num1+num2; //sum未声明,添加到全局环境 return sum; } var result = add(10,20); //30 alert(sum); //30,可访问到查询标识符:同名变量,从最近环境开始搜索,优先取最近环境中的值然后停止搜索。
5.3 垃圾收集
说明:JavaScript具有自动垃圾收集机制,执行环境会负责管理代码执行过程中使用的内存
局部变量只在函数执行的过程中存在。在这个过程中,会为局部变量在栈(或堆)内存上分配相应的空间,以便存储它们的值。在函数执行结束,局部变量就没有存在的必要,因此可以释放它们的内存。
标记清除
JavaScript中最常用的垃圾收集方式是标记清除(mark-and-sweep)。当变量进入环境时(如:在函数中声明一个变量),就将这个变量标记为"进入环境",当变量离开环境时,则将其标记为"离开环境"。
垃圾收集器在运行的时候会给存储在内存中的所有变量加上标记,然后会去除环境中的变量以及被环境中的变量引用的变量的标记。在此之后再被加上标记的变量将被视为准备删除的变量(环境中变量已经无法访问这些变量了)。最后,垃圾收集器完成内存清除工作,销毁带标记的值并回收内存空间。
引用计数
说明:引用技术会跟踪每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量,则这个值的引用次数就是1。如果该值又被赋给另外一个变量,则该值的引用次数加1。相反,如果对这个值引用的变量取得了另外一个值,则这个值的引用次数减一。当这个值的引用次数变成0的时候,就说明没法再访问这个值了,就可以将其占用的内存回收。当垃圾收集器下次再运行的时候就会释放这些空间。
问题:对象之间循环引用会造成内存无法回收
function problem(){ var objectA = new Object(); var objectB = new Object(); objectA.someOtherObject = objectB; objectB.anotherObject = objectA; }A,B对象通过各自的属性相互引用。它们之间的引用次数永远不会是0。如果这个函数被重复多次调用,就会导致大量内存无法回收。
问题:IE中有一部分并不是原生JS对象,BOM和DOM中的对象就是使用C++以COM(Component Object Model,组件对象模型)对象的形式实现的,而COM对象的垃圾收集机制采用的就是引用计数策略。所以,即使IE的JS引擎是使用标记清除策略,但JS访问的COM对象依然是基于引用计数策略的。这就照成只要在IE中涉及COM对象就会存在内存引用的问题。
var element = document.getElementById("some_element"); var myObject = new Object(); myObject.element = element; element.somObject = myObject;//DOM元素和JS对象相互引用,循环引用照成即使DOM从页面中移除,它也不会被回收。可以通过赋值为null来手动断开这两个元素间的引用。如:
myObject.element =null;element.somObject = null;
IE9中把BOM和DOM对象都转成了JS对象,也可以避免两种垃圾回收算法并存的问题(消除常见的内存泄漏现象)。性能问题
说明:垃圾收集器是周期性运行的,原IE7策略问题:根据内存分配量运行,达到256个变量,4096个对象(或数组)或者64KB其中任一标准,垃圾收集器就会运行,这就导致如果一个脚本中包含这么多变量,那么这个脚本在器生命周期中很可能会一直保有这么多变量。这样一来,垃圾收集器就会频繁运行,导致性能急剧下降。
解决方法:如果回收的内存分配量低于15%,则临界值加倍。如果到了回收85%的内存分配量,就将临界值重置回默认值。这样循环往复,看似简单,实则极大提升了IE在运行包含大量JS的页面时的性能。
5.4 内存管理
说明:出于安全考虑,浏览器的可用内存数量比较少(防止运行JS的网页耗尽全部系统内存而导致系统崩溃)。内存限制不仅影响给变量分配内存,同时还会影响调用栈以及在一个线程中能够同时执行的语句数量。
tips:一旦数据不再有用,最好通过将其值设置为null来释放其引用(解除引用dereferencing)。该做法适用大多数全局变量和全局对象属性。局部变量会在它们离开执行环境时自动被解除引用
function createPerson(name){ var localPerson = new Object(); //localPerson局部变量,不用手工解除 localPerson.name = name; return localPerson; } var globalPerson = createPerson(); //手动解除globalPerson的引用 globalPerson = null;解除globalPerson的引用,让值脱离执行环境,以便垃圾收集器下次运行时将其回收
小结:
- 基本类型值在内存中占据固定大小的空间,被保存在栈内存中
- 从一个变量向另一个变量复制基本类型的值,会创建这个值的一个副本
- 引用类型的值是对象,保存在堆内存中
- 包含引用类型值的变量实际上包含的并不是对象本身,而是一个指向该对象的指针
- 从一个变量向另一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终都指向同一个对象
- 确定一个值是哪种基本类型使用typeof(),确定值是哪种引用类型使用instanceof()
所有变量都存在于一个执行环境(作用域)中,这个执行环境决定了变量的生命周期,以及哪一部分代码可以访问其中的变量。
- 执行环境有全局执行环境和函数执行环境之分;
- 每次进入一个新执行环境,都会创建一个用于搜索变量和函数的作用域链;
- 函数的局部环境不仅有权访问函数作用域中的变量,而且有权访问其父环境,乃至全句环境中的变量;
- 全局环境只能访问在全局环境中定义的变量和函数,而不能直接访问局部环境中的任何数据;
- 变量的执行环境有助于确定应该何时释放内存
JavaScript是自动进行垃圾回收的,开发人员不必关心内存分配和回收问题。
- 离开作用域的值将被自动标记为可以回收,因此将在垃圾收集期间被删除。
- "标记清除"是目前主流的垃圾收集算法,思想就是给当前不使用的值加上标记,然后再回收其内存
- "引用计数"的思想是跟踪记录所有值被引用的次数,JS引擎目前都不使用这种算法
- 在代码中循环引用时,"引用计数"算法会出现问题(内存泄漏)
- 解除变量引用不仅有助于消除循环引用现象,而且对垃圾收集也有好处。为了确保有效地回收内存,应该及时解除不再使用的全局对象,全局对象属性及循环引用变量的引用。