内容
ECMAScript核心语法结构:
一、语法
1.类型、值和变量
1) 类型:区分数据类型
在JS中使用var关键词声明变量,变量的类型会根据其所赋值来决定(动态类型)。
JS中数据类型分为原始数据类型(5种)和引用数据类型(Object类型)。
- 原始数据类型(5):Number、String、Boolean、Undefined、Null。需要注意的是JS中字符串属于原始数据类型。
- typeof返回值(6):number、string、boolean、undefined、object、function 。null返回object
- instanceof:解决引用类型判断问题 详解
3) 变量
- 变量:局部变量和全局变量
- 变量提升:
函数及变量的声明都将被提升到函数的最顶部。
变量可以在使用后声明,也就是变量可以先使用再声明。
2.语句
1) 条件语句
- if:
- switch:
2)循环语句
- while
- for
- do
3)强制跳转语句
- continue:跳出单次循环
- break:跳出单层循环
- return:函数返回语句,但是返回的同时也将函数停止
- throw:创建或抛出异常
4)应用
1.label应用-跳出多层循环
二、对象
1.对象字面量
var obj={
"last-name":"yue",//'-'是不合法的,必须使用引号
firstName:"su"
}
2.检索
1)检索对象里包含的值:
obj["last-name"] //yue
obj.firstName //su
2)用||填充默认值
var name=obj.name||'susu';
3)用&&避免错误
obj.likeInfo.model// throw "TypeError"
obj.likeInfo && obj.likeInfo.model //undefined
3.更新
通过赋值语句更新,如果有值则替换,没有值则新增
obj.firstName='susu'
4.引用
对象通过引用来传递。它们永远不会被复制
var x=obj;
x.nick='aaa';
console.log(obj.nick);//aaa
// x,obj指向同一个对象的引用
var a={},b={},c={}//a,b,c每个都引用一个不同的空对象
var a=b=c={};//a,b,c引用同一个空对象
5.原型
每个对象都连接到一个原型对象,并从中继承属性
通过字面量创建的对象都连接到Object.prototype
object的create方法可以选择某个对象作为它的原型
if(typeof Object.beget!=='function'){
Object.create=function(proto){
function F(){};
F.prototype=proto;
return new F();
}
}
var a=Object.create(obj);
// 当对a对象做出改变时,不会影响该对象的原型
6.反射
检查对象并确定对象的属性
删除不需要的属性:
1) 检查并丢弃值是函数的属性
typeof obj.toString // 'function'
2) hasOwnProperty 检查对象自己的属性,不会检查原型链
obj.hasOwnProperty('toString') //false
7.枚举
for in:遍历对象中的所有属性名,包含原型中自己添加的属性
注意:属性名出现的顺序是不确定的,(可以创建包含属性名的数组,再用for循环来解决)
8.删除
delete :删除对象的属性,不会触及原型链中的任何对象
如果存在该属性,则被移除,并返回true;
如果不存在该属性,返回undefined;
// 不会影响原型
Object.prototype.name='1111111';
delete name;//true
obj.name;//1111111
9.减少全局变量污染
最小化使用全局变量的方法之一:只创建一个唯一的全局变量
var local={
data:{},
methods:{
test(){
console.log(123)
}
}
}
local.methods.test();//123
三、函数
1.函数对象
函数对象连接到Function.prototype(该原型对象本身连接到Object.prototype)
每个函数在创建时会附加两个隐藏属性:函数的上下文和实现函数行为的代码。
每个函数对象在创建时也随配有一个prototype属性。它的值是一个拥有controctor属性且值即为该函数的对象。
这和隐藏连接到Function.prototype完全不同。
2.函数字面量
函数对象通过函数字面量来创建:
var add=function(a,b){
return a+b;
}
函数字面量包含四个部分:
1) 保留字function
2) 函数名:可以省略(匿名函数),可以用来递归调用自己,
3)参数:
4) 函数主体
3.调用
调用时每个函数接收两个附加参数:this,arguments。
调用模式:这些模式在如何初始化this上存在差异
1) 方法调用模式
当一个函数被保存为对象的一个属性时,我们称它为一个方法。
当方法调用时,this被绑定到该对象
var myObj={
value:0,
increment:function(inc){
this.value+=typeof inc==='number'?inc:1;
}
}
myObj.increment(2);
console.log(myObj.value);//3
2) 函数调用模式
当一个函数并非一个对象的属性时,那么它就是被当作函数来调用的。
调用函数时,this被绑定到全局对象。
var sum=add(3,4); //7
解决this指向:在外部函数中定义that=this,那么内部函数可以通过that访问外部的对象。
3) 构造器调用模式
如果在一个函数前面带上new来调用,那么背地里将会创建一个连接到该函数的prototype成员的新对象,
同时this会被绑定到新对象上。会改变return语句的行为。
var Obj=function(str){
this.status=str;
}
Obj.prototype.get_status=function(){
return this.status;
}
var myobj=new Obj('aaa');
myobj.get_status();//aaa
4) apply调用模式
aplly(绑定给this的值,参数数组):构建一个参数数组传递给调用函数
var statusObj={
status:123
}
Obj.prototype.get_status.apply(statusObj);//123
4.参数
当函数调用时,会接受一个附加参数arguments数组(类数组:有length属性,但没有数组的任何方法)。
可以编写一个无须指定参数个数的函数。
var sum=function(){
var sum=0;
for(var i=0;i<arguments.length;i++){
sum+=arguments[i];
}
return sum;
}
sum(1,2,3)//6
5.返回
return语句可用来使函数提前返回。
如果使用构造器调用(有new前缀),且返回不是一个对象,则返回this(该新对象)。
6.异常
异常是干扰程序的正常流程的不寻常
throw语句中断函数的执行,并抛出exception对象,该对象会被传递到try语句的catch从句。
var add=function(a,b){
if(typeof a!=='number'||typeof b !=='number'){
throw{
name:'TypeError',
message:'必须是数字'
}
}
return a+b;
}
try{
add(1,'aaa')
}catch(e){
console.log(e.name,e.message);//TypeError 必须是数字
}
7.扩充类型的功能
通过给基本类型增加方法,新的方法立刻被赋予到所有对象的实例上,提高语言的表现力。
Function.prototype.method=function(name,func){
if(!this.prototype[name]){
this.prototype[name]=func;
}
return this;
}
Number.method('interge',function(){
return Math[this<0?'ceil':'floor'](this);
});
2.3.interge();//2
8.递归
递归函数就是会直接或间接的调用自身的一种函数。
递归函数操作树形结构,如浏览器端的文档对象模型(DOM)
// 汉诺塔游戏
var hanoi=function(n,from,ass,to){
if(n>0){
hanoi(n-1,from,to,ass);
console.log("移动第"+n+"个从"+from+"到"+to);
hanoi(n-1,ass,from,to);
}
}
hanoi(2,"A","B","C");
// 移动次数
function moveTimes(n){
if(n==1){
return 1;
}
return 2*moveTimes(n-1)+1;
}
9.作用域
作用域控制着变量与参数的可见性及生命周期,它减少了名称冲突,并提供了自动内存管理。
优点:内部函数可以访问定义它们的外部函数的参数和变量(除了this和arguments)。
10.闭包
当内部函数被保存到外部时,将会生成闭包。闭包会导致原有作用域链不释放,造成内存泄漏。
闭包可以访问它被创建时所处的上下文环境。
// 避免在循环中创建函数,可以在循环之外创建一个辅助函数,
// 让这个辅助函数再返回一个绑定了当前i值的函数
var add_handlers=function(nodes){
var helper=function(i){
return function(){
console.log(i);
}
}
for(var i=0;i<nodes.length;i++){
nodes[i].onclick=helper(i)
}
}
add_handlers(document.getElementsByTagName('li'))
11.回调
发起异步请求时,提供一个当服务器的响应到达时随即触发的回调函数,异步函数立即返回,这样客户端就不会被阻塞。
12.模块
使用函数和闭包来构造模块。
模块模式利用了函数作用域和闭包来创建被绑定对象与私有成员的关联。
一般形式:一个定义了私有变量和函数的函数;利用闭包创建可以访问私有变量和函数的特权函数;最后返回这个特权函数,或者保存到一个可访问到的地方。
String.method('deentityify',function(){
// 字符实体表,若放在函数内部每次执行函数时该字面量都会被求值一次,会带来运行时的损耗,
var entity={
quot:'"',
lt:'<',
gt:'>',
}
return function(){
return this.replace(/&([^&;]+);/g,function(a,b){
var r=entity[b];
return typeof r==='string'?r:a;
})
}
}())
'>"<'.deentityify(); // >"<
13.级联
如果方法返回this,就会启用级联。
在一个级联中,可以在单独一条语句中依次调用同一个对象的很多方法。
14.柯里化
柯里化允许我们把函数与传递给它的参数相结合,产生一个新的函数。
Function.method('curry',function(){
var slice=Array.prototype.slice,
args=slice.apply(arguments);
that=this;
return function(){
return that.apply(null,args.concat(slice.apply(arguments)));
}
});
function add(a,b){
return a+b;
}
var add1=add.curry(1);
var res=add1(6);//7
15.记忆
记忆:函数可以将先前操作的结果记录在某个对象里,从而避免无谓的重复运算。
// memo:初始数组; formula:函数 公式
var memoizer=function(memo,formula){
var recur=function(n){
result=memo[n];
if(typeof result !=='number'){
result=formula(recur,n);
memo[n]=result;
}
return result;
}
return recur;
}
var fibonacci=memoizer([0,1],function(recur,n){
return recur(n-1)+recur(n-2);
});
var res=fibonacci(10);
四、继承
1.伪类
缺点:没有私有环境,所有的属性都是公开的。
// 当采用构造器调用模式,函数执行的方式会被修改。
// 如果new运算符是一个方法,它可能会像这样执行
Function.method('new',function(){
// 创建一个新对象,它继承自构造器的原型对象
var that=Object.create(this.prototype);
// 调用构造器函数,绑定this到新对象上
var other=this.apply(that,arguments);
// 如果它的返回值不是一个对象,就返回改新对象
return (typeof other=='object'&& other)||that;
})
可以构造一个伪类来继承父类,这是通过定义它的constructor函数并替换它的prototype为一个父类的实例来实现
Function.method('inherits',function(Parent){
this.prototype=new Parent();
return this;
})
2.对象说明符
在编写构造器时让它接受一个简单的对象说明符。
多个参数可以按任何顺序排列,如果构造器使用默认值,一些参数可以忽略,代码易阅读。
var obj=testFn({
first:f,
middle:m,
last:l
});
3.原型
一个新对象可以继承一个旧对象的属性
用Object.create(parent)构造出更多的实例
差异化继承:通过定制一个新对象,我们指明它与所基于的基本对象的区别
// 作用域继承,内部作用域继承外部作用域遇到一个左花括号时block函数被调用,
var block=function(){
// 记住当前作用域,构造一个包含了当前作用域中所有对象的新作用域
var oldScope=scope;
scope=Object.create(oldScope);
// 传递左花括号作为参数调用advance
advance('{');
// 使用新的作用域进行解析
parse(scope);
// 传递左花括号作为参数调用advance并抛弃新作用域,恢复原来老的作用域。
advance('}');
scope=oldScope;
}
4.函数化
应用模块模式解决私有变量和私有函数
函数化构造器步骤:
1)创建一个新对象
2)有选择的定义私有实例变量和方法。
3)给这个对象扩充方法,这些方法拥有特权去访问参数。
4)返回那个新对象
// 伪代码模板
var constructor=function(spec,my){
var that,其他的私有实例变量
my=my||{};
把共享的变量和函数添加到my中
that=一个新对象
添加给that的特权方法
return that;
}
// 处理父类的方法
Object.method('superior',function(name){
var that=this,
method=that[name];
return function(){
return method.apply(that,arguments);
}
})
//例子
var mammal=function(spec){
var that={};
that.get_name=function(){
return 'hello'+spec.name;
}
return that;
}
var cat=function(spec){
var that=mammal(spec);
var super_get_name=that.superior('get_name')
that.get_name=function(){
return spec.name+'like'+super_get_name();
}
return that;
}
var my=cat({name:'su'});
console.log(my.get_name());//su like hello su
5.部件
从一套部件中把对象组装出来。
五、数组
1.定义:
- 字面量: var arr=[1,2,3];
- 构造方法: new Array(length/content);
2.属性
- constructor: 返回创建此对象的数组函数的引用。
- length:设置或返回数组中元素的长度。
- prototype:向对象添加属性和方法
3.删除
1)delete 会在数组中留下一个空洞
2)splice 可以删除元素并替换为其他元素
var arr=['a','b','c','d'];
delete arr[1];
console.log(arr);//["a", empty, "c", "d"]
// 对于大型数据,可能效率会不高
var arr1=['a','b','c','d'];
arr1.splice(2,1)
console.log(arr1);//["a", "b", "d"]
4.方法
Array.method('reduce',function(f,value){
for(var i=0;i<this.length;i++){
value=f(this[i],value)
}
return value;
})
function add(a,b){
return a+b;
}
var arr=[1,2,3];
// 因为数组是对象,可以给单独的数组增加一个方法
// 因为字符串total不是整数,所以不会改变数组的长度
arr.total=function(){
return this.reduce(add,0)
}
console.log(arr.total());//6
Object.create方法用在数组是没有意义的,因为它产生一个对象,而不是一个数组。
产生的对象将继承这个数组的值和方法,但是它没有那个特殊的length属性。
5.指定初始值
// fill 初始化
var arr=new Array(5);
arr.fill(0);//[0, 0, 0, 0, 0]
// 初始化一维数组
Array.dim=function(dimension,initial){
var a=[];
for(var i=0;i<dimension;i++){
a[i]=initial;
}
return a;
}
Array.dim(5,0);//[0, 0, 0, 0, 0]
// 初始化多维数组
Array.matrix=function(m,n,initial){
var a=[],mat=[];
for(var i=0;i<m;i++){
for(var j=0;j<n;j++){
a[j]=initial
}
mat[i]=a;
}
return mat;
}
Array.matrix(2,2,0);//[[0,0],[0,0]]
6.原型-方法
1).改变原数组:
- push:向数组的末尾添加一个或更多元素,并返回新的长度。
- pop:删除并返回数组的最后一个元素
- shift: 删除并返回数组的第一个元素
- unshift:向数组开头添加一个或多个元素,并返回新的长度。
- reverse:逆转顺序,并返回新数组
- sort:排序,返回排序后的数组
- splice:(从第几位开始,截取多少的长度,在切口处添加新的数据) 删除元素,并向数组添加新元素,返回被截取元素的数组
- fill(value, start, end) 将一个固定值替换数组的元素。
2).不改变原数组
- concat: 连接两个或更多的数组,并返回结果。
- slice:(从该位开始截取,截取到该位] 截取,返回被截取元素的数组
- join:通过指定的分隔符(默认逗号)进行分隔,返回字符串
- toString:把数组转换为字符串,并返回结果。
7.原型-循环方法
1)参数相同 (回调函数(当前元素,当前元素的索引,原数组),this)
- forEach: 代替普通for循环,没有返回值
- map:通过指定函数处理数组的每个元素,并返回处理后的数组。
- filter:过滤 ,返回符合条件所有元素的数组。
- every:检测数值中的每个元素是否都符合条件。返回true或false
- some:检测数组中是否有元素符合条件。返回true或false
5.应用
1.数组-参考手册
2.数组,类数组
3.forEach,for in ,for of的区别
六、正则表达式
1.结构
1)创建RegExp对象:
- 字面量 var my_reg=/[A-Z]/g;
- 构造器,适用于必须在运行时动态生成正则表达式的情形。var my_reg=new RegExp(‘[A-Z]’,’g’);
创建字符串时需要注意,因为反斜杠在正则表达式和字符串中含义不同,通常需要双写反斜杠,以及对引号进行转义
2)正则表达式标识
标识 | 含义 |
---|---|
g | 全局的(匹配多次;不同的方法对g标识的处理各不相同) |
i | 大小写不敏感(忽略字符大小写) |
m | 多行(^和$能匹配行结束符) |
3)RegExp对象的属性
属性 | 用法 |
---|---|
global | 如果标识g被使用,值为true |
ignoreCase | 如果标识i被使用,值为true |
multiline | 如果标识m被使用,值为true |
lastIndex | 下一次exec匹配开始的索引。初始值为0 |
source | 正则表达式源码文本 |
2.元素
1)正则表达式分支
一个正则表达式包含一个或多个正则表达式序列。
这些序列被|(竖线)字符分隔,如果这些字符中的任何一项符合匹配条件,那么这个选择就被匹配。
它常是按顺序依次匹配这些序列项
// 因为in已被成功匹配,所以不会匹配int
var a='into'.match(/in|int/);//['in']
2)正则表达式序列
一个正则表达式序列包含一个或多个正则表达式因子。
每个因子能选择是否跟随一个量词,这个量词决定着这个因子被允许出现的次数。
如果没有指定这个量词,那么该因子只会被匹配一次
3)正则表达式因子
一个正则表达式因子可以是一个字符、一个由圆括号包围的组、一个字符类,或是一个转义序列
这些字符需要转义: / { } ? + * | . ^ $
4)正则表达式转义
反斜杠字符在正则表达式因子和在字符串中均表示转义,但在正则表达式因子中有点不同
\d : [0-9],\D:[^d]
\s : [tnrvf ] 空白字符, \S:[^s]
\w : [0-9A-Za-z_] ,\W:[^w]
\b : 单词边界 \B:[^b]
\1 : 指向分组1所捕获到的文本的一个引用。 如:\2,\3
5)正则表达式分组
- 捕获型: 一个捕获型分组是一个被包围在圆括号找那个的正则表达式分支。每个捕获型分组都被指定了一个数组。
任何匹配这个分组的字符都会被匹配。
在正则表达式中第一个捕获(的是分组1,第二个捕获(的是分组2。 - 非捕获型: 非捕获型分组有一个(?:前缀,仅做简单的匹配,并不会捕获所匹配的文本,不会干扰捕获型分组的编号。
- 向前正向匹配: 向前正向匹配分组有一个(?=前缀,类似非捕获型分组,
但在这个组匹配后,文本会倒回到它开始的地方,实际上并不匹配任何东西。 - 向前负向匹配: 向前负向匹配分组有一个(?!前缀,类似向前正向匹配分组,
但只有当它匹配失败时它才继续向前进行匹配
6)正则表达式字符集
正则表达式字符集是一种指定一组字符的便利方式。
如果匹配一个元音字母,可以写成(?:a|e|i|o|u),
但可以更方便的写成一个类[aeiou], 类的求反 [^aeiou]
7)正则表达式量词
正则表达式因子可以用一个正则表达式量词后缀来决定这个因子应该被匹配的次数。
包围在一对花括号中的一个数组表示这个因子被匹配的次数。
n+ : {1,} 1到多个
n* : {0,}
n? : {0,1}
n{X} : X个
n{X,Y} : X-Y个
n{X,} : X到多个
?=n : 匹配其后紧接n 的字符串
?!n : 匹配其后没有紧接n 的字符串
如果只有一个量词,表示趋向于进行贪婪性匹配,即匹配尽可能多的副本直至达到上限。
如果这个量词附加一个后缀?,表示趋向于进行非贪婪性匹配,即只匹配必要的副本就好。
七、方法
1.Array
2.Function
function.apply(thisArg,argArray)
apply方法调用function,传递一个会被绑定到this上的对象和一个可选的数组作为参数
Function.method('bind',function(that){
var method=this;
var slice=Array.prototype.slice;
var args=slice.apply(arguments,[1])
return function(){
return method.apply(that,args.concat(slice.apply(arguments)));
}
})
var test=function(){
return this.value+':'+Array.prototype.slice.apply(arguments);
}.bind({value:666},1,2);
console.log(test(3));//666:1,2,3
3.Number
4.Object
Object.hasOwnProperty(name)不会检查原型链中的属性
var a={member:true};
var b=Object.create(a);
var c=a.hasOwnProperty('member');//true
var d=b.hasOwnProperty('member');//false
var e=b.member;//true
5.RegExp
1.regExp.exec(string)
如果通过循环去查询一个匹配模式在字符串中发生了几次,需要注意:如果提前退出了循环,再次进入这个循环前必须把
RegExp.lastIndex重置为0,而且,^仅匹配RegExp.lastIndex为0的情况。
如果带有g全局标识,查找不是从这个字符串的起始位置开始,而是从regExp.lastIndex位置开始。
var reg1=/ab/g;
var str='ababab';
console.log(reg1.exec(str),reg1.lastIndex)
console.log(reg1.exec(str),reg1.lastIndex)
console.log(reg1.exec(str),reg1.lastIndex)
console.log(reg1.exec(str),reg1.lastIndex)
console.log(reg1.exec(str),reg1.lastIndex)
// 打印结果:
["ab", index: 0, input: "ababab", groups: undefined] 2
["ab", index: 2, input: "ababab", groups: undefined] 4
["ab", index: 4, input: "ababab", groups: undefined] 6
null 0
["ab", index: 0, input: "ababab", groups: undefined] 2
2.regExp.test(string)
// 如果这个方法使用g标识,会改变lastIndex的值
var reg=/[A-Z]/g;
var arr=['Asdsd','BsdsCds','Dasas','E1212'];
for(var i=0;i<arr.length;i++){
console.log(reg.test(arr[i]),reg.lastIndex);//true 1,true 5,false 0,true 1
}
6.String
附录A-毒瘤
1.全局变量
全局变量使得在同一个程序中运行独立的子程序变得更难。
因为全局变量可以被程序的任何部分在任意时间修改,使程序的行为变得极度复杂,降低程序的可靠性
// 定义全局变量的方法
var foo=value;
window.foo=value;
foo=value;
2.作用域
没有块级作用域,代码块中声明的变量在包含此代码块的函数的任何位置都是可见的。
更好的方式:在每个函数的开头部分声明所有变量
3.自动插入分号
有一个自动修复机制,它试图通过自动插入分号来修正有缺损的程序.
它可能会掩盖更为严重的错误。
// 在return语句后自动插入分号导致的后果
function test1(){
return{
name:'aaa'
}
}
function test2(){
return
{
name:'aaa'
}
}
console.log(test1(),test2());//{name: "aaa"} undefined
4.保留字
保留字不能被用来命名变量或参数。
当保留字被用做对象字面量的键值时,它们必须被引号括起来,不能用在点表示法中,必须使用括号表示法。
var case;//非法,都不支持
// ie8及以下不支持 非法的写法
var obj={case:'123'};//非法
var a=obj.case//非法
var object={'case':'1212'};//ok
var b=object['case'];//ok
5.Unicode
Unicode把一对字符视为一个单一的字符,而js认为一对字符是两个不同的字符
6.typeof
typeof null返回object
正则表达式,返回object,在safari(3.x版本)中返回function
// 区分null与对象
if(value&& typeof value=='object'){
//value是一个对象或数组
}
7.parseInt
parseInt把字符串转换为整数,
// 遇到非数字时会停止解析
parseInt('16')==parseInt('16aa123');//ture
// ie8及以下会出现以下问题:
// 如果字符串第一个字符是0,那么字符串会基于八进制来求值,()
// 会导致程序解析日期和时间时出现问题,可以用parseInt的第二个参数作为基数解决
console.log(parseInt('08'),parseInt('09'));//0 0
console.log(parseInt('08',10),parseInt('09',10));//8 9
8.+
+运算符可以用于加法运算或字符串连接,如何执行取决于其参数的类型
加法运算:两个运算数都是数字
字符串连接:其中一个运算数是空字符串,则另一个运算数被转换成字符串进行连接。
9.浮点数
二进制的浮点数不能正确处理十进制的小数。
0.1+0.2 //0.30000000000000004
//浮点数中的整数运算是正确的,所以小数表现出来的错误可以通过指定精度来避免
(0.1*10+0.2*10)/10 //0.3
10.NaN
NaN是一个特殊的数量值。它表示的不是一个数字,但是 typeof NaN===’number’
该值会在试图把非数字形式的字符串转化为数字时产生,如:Number(’12px’) //NaN
NaN===NaN //fales
// 区分数字与NaN
isNaN(NaN);//true
isNaN('aaa');//true
判断一个值是否可用做数字使用isFinite函数,因为它会筛掉NaN和Infinity
但是它会试图把运算数转换为一个数字,isFinite(’10’)//true
var isNumber=function(value){
return typeof value==='number'&&isFinite(value)
}
isNumber('10');//false
11.伪数组
// 辨别数组
// arguments是一个类数组,返回[object Arguments]
if(Object.prototype.toString.apply(value)==='[object Array]'){
//value是一个数组
}
12.假值
以下这些值全部等同于假,但它们是不可互换的
值 | 类型 |
---|---|
0 | Number |
NaN(非数字) | Number |
”(空字符串) | String |
false | Boolean |
null | Object |
undefined | Undefined |
13.hasOwnProperty
hasOwnProperty可以解决for in的隐患
但它是一个方法,在任何对象中,它可能会被一个不同的函数甚至一个非函数的值所替换
var name,obj={};
obj.hasOwnProperty=null;//地雷
for(name in obj){
if(obj.hasOwnProperty(name)){//触雷
console.log(name,obj[name])
}
}
14.对象
js对象永远不会是真的空对象,因为它可以从原型中取得成员属性
// 如果字符串里面包含constructor,会返回{hello: 3, word: 1, constructor: "function Object() { [native code] }1"}
// 可以用hasOwnProperty处理 返回{hello: 1, word: 1, constructor: 1}
var text='hello word hello,Hello constructor';
var words=text.toLowerCase().split(/[\s,.]+/);
var count={},word;
for(var i=0;i<words.length;i++){
word=words[i];
if(Object.hasOwnProperty(count[word])&&count[word]){
count[word]+=1;
}else{
count[word]=1;
}
}
console.log(count);
附录B-糟粕
1.==
==,!=运算符只有在两个运算类型一致时才会做出正确的判断,如果两个运算数是不同的类型,它们试图去强制转换值的类型
==,!=运算符缺乏传递性,所以使用===和!==
'' == '0'//false
0 == '0' //true
false == 'false'//false
false == '0' //true
false == undefined //false
false == null //fasle
null == undefined //true
'\t\r\n' == 0 //true
2.with语句
with语句本意是用来快捷的访问对象的属性,但有时不可预料,所以应该避免使用它
严重影响了js处理器的速度,因为它阻断了变量名的词法作用域绑定
with(obj){
a=b;
}
// 和下面的代码做的是同样的事情
if(obj.a===undefined){
a = obj.b === undefined ? b : obj.b;
}else{
obj.a = obj.b === undefined ? b : obj.b;
}
// 所以,它等同于这些语句中的某一条:
a = b;
a = obj.b;
obj.a = b;
obj.a = obj.b;
3.eval
eval函数传递一个字符串给javaScript编辑器,并且执行结果
1)使用eval形式的代码更加难以阅读,这种形式使得性能显著降低,因为它需要运行编译器,但也许只是为了执行一个赋值语句。
2)它会让JSLint失效,让此工具检测问题的能力大打折扣
3)减弱了应用程序的安全性,因为它被求值的文本授予了太多的权利,与with语句执行方式一样,降低语言的性能
4)Function构造器是eval的另一种形式,应该避免使用(new Function ([arg1[, arg2[, …argN]],] functionBody))
5)setTimeout,setInterval当接收字符串参数时,也会像eval那样处理,应避免使用字符串参数
4.continue语句
continue语句跳到循环的顶部
但是如果一段代码通过重构移除continue语句之后,性能都会得到改善
console.time(111)
var sum=0;
for(var i=0;i<100;i++){
if(i==50){
continue;
}
sum+=i;
}
console.timeEnd(111);//0.051025390625ms
console.time(222)
var sum=0;
for(var i=0;i<100;i++){
if(i!=50){
sum+=i;
}
}
console.timeEnd(222);// 0.02099609375ms
5.switch穿越
除非你明确地中断流程,否则每次条件判断后都穿越到下一个case条件。
6.缺少块的语句
if,while,do,for语句可以接受一个括在花括号找那个的代码块,也可以接受单行语句
但是单行语句模糊了程序的结构,使得在随后的操作代码中可能很容易插入错误
//容易导致错误
if(ok)
t=true;
advance()
7.++
这两个运算符鼓励了一种不够谨慎的变成风格,大多数的缓冲区溢出的错误所造成的安全漏洞,都是由像这样的代码导致的
并且它会使代码变得过于拥挤,复杂和隐晦
8.位运算符
在java里,位运算符处理的是整数,但是js没有整数类型,只有双精度的浮点数,因此,位操作符把它们的数字运算数先转换成整数,在执行运算,然后在转换回去。
在大多数语言中,位运算符接近于硬件处理,所以非常快。但js的执行环境一般接触不到硬件,所以非常慢,js很少被用来执行位操作
9.function语句对比function表达式
function语句在解析时会发生被提升的情况,不管function被放置在哪里,它会被移动到被定义时所在作用域的顶层,
这放宽了函数先声明后使用的要求,这会导致混乱
function foo(){
}
// foo是一个包含一个函数值的变量,函数就是数值
var foo=function(){
}
一个语句不能以一个函数表达式开头,但可以把函数调用括在一个圆括号之中
(function(){
var a=1;
// 这个函数可能对环境有一些影响,但不会引入新的全局变量
})()
10.类型的包装对象
new Boolean,new Number,new String会返回一个对象,该对象有一个valueOf方法会返回被包装的值
避免使用new Array和new Object,可使用[]和{}来代替
11.new
1)new运算符创建一个继承于其构造器函数的原型的新对象,然后调用该构造器函数,把新创建的对象绑定给this,
这给构造器函数一个机会在返回给请求者前自定义新创建的对象
2)如果忘记new运算符,就是一个普通的函数调用,this被绑定到全局对象,而不是创建一个新对象。污染全局变量
3)构造器函数应该以首字母大写的形式命名,并且首字母大写的形式应该只用于来命名构造器函数
12.void
在很多语言中,void是一种类型,表示没有值
但在js中,void是一个运算符,它接受一个运算数并返回undefined,这没有什么用,应避免使用它
//当用户链接时,void(0) 计算为 0,但 Javascript 上没有任何效果。
<a href="javascript:void(0)">单击此处什么也不会发生</a>