JavaScript比较特殊的一些概念(一)
0. 语句 语句以";"结尾,但也可以省略,引擎解析器会自动添加必要的分号,不过此可能导致引入BUG。 1. 变量提升 因JavaScript引擎的工作方式,先解析代码,然后获取所有被声明的变量,然后再一行一行地运行; 在获取所有变量的声明时,变量声明类似于会被提升到代码的头部的操作。 例如: console.log(a); var a = 101; // 若没有var声明,则提升后也会报错。 提升后,不会报错,但输出结果为undefined: var a; console.log(a); a = 1; 2. 注释 同C、C++,支持单行注释、多行注释,此外为兼容HTML,JavaScript还可以使用<!--内容-->形式的单行注释。 3. 区块 C/C++/Java等区块具有区域作用域的概念,但JavaScript不具有此概念,作用域会扩展到函数作用域。 也即是(最后a值为11): var a = 10; { var a = 11; } a; 也意味着区块对该使用意义不大,而一般在for、if、while、function等更复杂语法、语句结构时才有意义。 4. 严格相等运算符”(===)与 “相等运算符”(==) 两者的区别主要为: 后者可能会调用对象的toString、valueOf方法,若此两个方法被覆写,则可能出现不明确的异常问题,比如类似于'0'==0结果为true,而'0'===0为false。 前者为严格意义上的相等,类型和值对象;后者只需要值相等或引用对象相等抑或是被toString、valueOf返回后的结果相等则也会相等。 5. switch/case语句 除了一般的匹配外,还支持字符串匹配、匹配表达式计算,如switch(2+3){case 2+1:fun();break;default:fun2();break;}; switch语句后面的表达式与case语句匹配时采用的时严格相等运算比较,比较时不会发生类型转换。 6. break、continue与标签Label 一般不带标签时,break、continue语句同C/C++一样,只用于内部循环、跳出等。 若带标签时,可用于跳转到或跳出指定的标签label;break label表示跳出某个标签标记的范围区块或循环,
而continue label,则表示继续回到标签label位置,此时又有点类似于C/C++的goto语句。 7. null与undefined 前者表示此值为无值、无状态的。后者表示未定义或不存在的值。 前者若转化为数值时为0,后者则转为NaN。此外null == undefined返回true,null === undefined返回false. 除了这两个数据类型,JavaScript还有number数值、string字符串、boolean布尔值、object对象、Symbol共计7种 原始数据类型;undefined和null,一般将它们看成两个特殊值。 对象:狭义上的object、array、function等。 8. typeof与instanceof 前者运算符为获取某对象的数据类型,对于一个未定义的变量a,typeof(a)返回"undefined"而不是报错。 typeof(someobj)返回有"number"、"string"、"boolean"、"function"、"undefined"、"object"。 特殊:typeof(null)返回object(本质上null并不是对象而应是类似于undefined), typeof(undefined)返回undefined; 后者可以进一步区分某对象的数据类型。如:var a = []; var b = {}; typeof(a)与typeof(b)均为object; 此时a instanceof Array 返回true,b instanceof Array 返回false。 9. 布尔类型 undefined、null、false、0、NaN、""或''(空字符串)可自动转为false,其他均为true; 10. 数值 浮点(单精度/双精度)、整型均为64位的浮点数形式存储,1 === 1.0返回true;当进行整型运算时, 其JavaScript会自动把64位浮点数转成32位整数,再进行运算。浮点会引入精度损失,故0.1+0.2 === 0.3返回false; 而1.0 + 2.0 === 3.0返回为true。数值精度范围:-(2^53-1)到2^53-1,数值范围:Number.MIN_VALUE到Number.MAX_VALUE。 字面量: 二进制:0b或0B; 八进制:0o或0O; 十进制:123; 十六进制:0x或0X; +0与-0;+0===-0,0===+0, 0===-0均返回true,但作为分母时,前者返回Infinity无穷大,后者返回-Infinity,但是若此时分子又为0, 则返回值均为NaN; NaN: 非数字,一种特殊值,5 - 'x'结果为NaN, 5+'x'返回为字符串5x;此外某些数学运算结果可能也会出现NaN,如Math.log(-1)、0/0、或 者与任何带有NaN的运算均返回NaN。NaN === NaN返回false;typeof(NaN)返回number;判断一个是否为NaN可靠的方法:value != value, 也即只有NaN != NaN才会返回true;此外NaN与Infinity比较大小,始终返回false; isNaN判断NaN不太靠谱,如isNaN("fee")也会返回true(因内部先转为数值(此时对象或字符串被隐式转为NaN),然后再判断)。 isFinite可用来判断是否是一个正常数值。 与数值相关的内置工具函数: parseFloat将一个字符串转为浮点数、parseInt将字符串转为整数。 11. 单引号与双引号 类似于python中的字符串,不区分单、双引号,不过同样的需要匹配,以及相互包裹的原则。 字符串可以被视为字符数组,同样以0开始;不过字符串和数组区别也很大,var s = 'some';s[1] = 'N';修改会失败,然而Array数组可以被修改。 12. 字符、字符集 JavaScript使用Unicode(UTF-16两个字节)表示字符,以Unicode储存字符,还允许直接在程序中使用Unicode形式来表示字符,也即写成\uxxxx的形式。 虽然以两个字节表示一个字符,但是字符串长度仍然以字符个数而不是字节数。 JavaScript提供Base64转码,btoa():字符串或二进制值转为Base64编码,atob():Base64编码转为原来的编码;不过只能用在ASCII码,对于非ASCII码 则可通过其他方式转化后再使用Base64转码(如encodeURIComponent)。 13. 对象{} JavaScript中var a = {};与Python中 a = {};前者为对象,后者为dict字典。 JavaScript除了上面一种创建对象外,还有new 创建以及Object.create()方式; 对象中键名或属性名均为字符串,引号可以不用写,不过对于某些不符合标识名的属性名如'4func', 'hava some'等,则应加上引号。 保留关键字也可作为属性名,不过不建议作为属性名;此外属性可以随时添加而不像编译时语言只能在声明时添加。 对象引用相同的对象时,会相互修改影响,而对于原始类型的数据则是传值拷贝。 { foo: 1 }与({ foo: 1}),前者JavaScript解释为语句块,后者为对象。 属性读取或赋值,可使用.操作,或者[]操作,若属性为数值时,则只能用[]。 读取一个不存在的键,会返回undefined。 Object.keys可获取对象本身的所有属性的结果列表。 delete可删除对象的某个属性,不过无法删除不允许删除的属性以及无法删除继承的属性。 in可判断某对象是否存在某个属性,但无法确认属性是继承而来的还是当前对象所有的,可使用hasOwnProperty判断是否为当前对象的属性。 for in 除了不可遍历的属性外其可遍历对象的所有属性,包括继承而来的。 with(obj){objattr};可简写操作,语句块内部使用的属性或变量则为obj对象的属性,若不存在则将产生全局变量, 此外with语句会导致不好维护和代码段优化的问题,一般不建议使用with语句,而是用一个临时变量来取代,如: with(o1.o2.o3){console.log(p1 + p2);},可修改为var temp = o1.o2.o3;console.log(temp.p1 + temp.p2);。 14. 数组[] 类似于对象可任意添加属性一样,数组也可在需要的任何时候赋值,调整大小; 此外同Python一样,数组可保存任意类型的数据。 对于多维数组,则是数组元素内容也是数组,数组也是一种对象,其键名固定为整数字符串0、1、2...,不需要指定,但也可使用 非数值的字符串作为键名; 只能用[]操作访问元素,JavaScript使用一个32位整数,保存数组的元素个数; length属性的值总是比最大的那个整数键名大1,而不是有效的个数,且length属性可任意修改。 函数中的arguments对象、字符串、DOM元素集等,均类似于数组的对象。 同对象,数组也可以使用for in来遍历获取键名进而获取对应的值。 数组除了可以用for、while遍历外,还可用forEach方法遍历数组。 数组可有空位,即A[a,,b],此时length认为2,但是A[1]===undefined; delete命令删除一个数组成员,会形成空位,并且不会影响length属性,所以以length遍历数组可能出现问题,此时可使用 数组的forEach方法、for...in以及Object.keys方法进行遍历,空位都会被跳过,但是本身值为undefined(不是空位)的不会 被调过。 15. 函数 函数可被赋值、返回、当做其他函数的参数,此外函数也被看作变量,同变量一样也有声明提升的问题。 length属性,即为调用函数的声明时的参数个数,与实际调用时候的参数个数可能不同,也即可省略(undefined)或更多,不过 为了实现省略前面变量参数而保留使用后面的参数,则前面的参数需要显式地传入undefined。 toString方法可获取到该函数的源码包括注释内容。 JavaScript中的作用域有:函数作用域和全局作用域,在函数作用域内声明的变量,若没有用var声明则仍然是全局变量。 同全局作用域一样,函数作用域也存在变量提升的问题,即函数内部包括函数内部的某些if、try块中用var声明的变量均会被提升到 函数的头部,另外函数也有自己的作用域同变量一样即在其声明时的所在作用域,包括函数体内声明的函数,这种绑定的作用域机制便 出现了闭包的概念。 函数参数传入时若为原始数据类型,则为传值,若为复合类型如数组、对象、其他函数等则以传地址的方式; 若函数内部对该对象完全的修改掉,则也不会影响外部的对象,而是内部创建副本(此可能会引起一些问题,如: var obj = [1, 2, 3];function f(o){o = [2, 3, 4];};f(obj);obj不会被修改; 而var obj = [1, 2, 3];function f(o){o[1] = 3;};f(obj);则obj会被修改)。 若出现同名参数,则函数内部使用最后的那个参数,此外出现同名函数,则最后那个函数有效。 函数在被调用时,函数内部有个arguments对象,其包含了函数实际调用时候的参数列表,该对象只能被函数内部使用; 此外该函数也可在函数内部运行时修改,但严格模式下可能为只读的对象。 函数被调用时借助arguments对象和length属性可以确定函数实际参数和多余参数的个数。 arguments对象有个callee属性,其返回对应的原函数,可依据此属性实现调用自身、递归的目的而可防止被外部修改函数名而导致递归失效。 JavaScript中一些对象为了使用其他类型对象的方法,则可以使用目标类型或其原型的apply或call方法并把自身作为参数的一部分传入; 或者将对象转变为目标对象,使用目标对象的对应方法。 16. 闭包 在函数作用域和全局作用域下,函数外部无法访问函数内部的变量,而函数内部可访问函数外部的变量; 为了实现访问函数内部的变量,可采取在函数内部再嵌套一次函数声明(嵌套的函数可以访问其外部的函数变量) 并返回函数内部嵌套声明的函数,利用此方式可以实现目的,此时内部嵌套的函数便是闭包。 闭包即可保存最近一次调用的环境,还可以实现封装对象的私有属性和方法(同闭包的形式的函数对象(函数内部有多个变量和多个方法, 并返回包装好的方法对象(可用对象包装),不过因每次调用均会产生闭包,滥用时可能导致内存消耗过大,故不能滥用闭包))。 17. 立即调用的函数表达式 在声明的时候立即使用函数,(function(){/* code */}());或(function(){/* code */})();事实上除了不能以function开头直接后面紧跟() 时(也即function(){/* code */}();)会解释出错外,语句前面可以加任何合理的方式也可以实现立即使用函数的目的; 如new function(){/* code */}或!function(){/* code */}();等。 立即调用函数的目的:一方面避免污染全局变量或函数作用域的变量,另一方面可以保护内部私有数据可防止外部读取或修改数据。 18. eval 同python的eval类似,将字符串当前语句执行。因为eval内部执行字符串,当直接调用时其内部语句的作用域仍然为调用时所在的作用域, 可能导致修改到所在的作用域内的值,故而存在一定的安全性问题(eval的任何间接调用方式,其内部的作用域仍然是全局作用域), 此外eval的命令字符串难以得到引擎的优化运行效率会有一定的影响。 eval的一个类似的Function构造函数;该函数最后一个参数为函数执行体,也可执行字符串命令,因此也应避免使用。