- JavaScript语言核心——表达式、对象、数组、函数
JavaScript语言核心——表达式、对象、数组、函数
上一篇文章讲了类型、值和变量,这一篇,我们接着上面的讲,既然讲到了变量,不得不提一下变量的作用域问题
JavaScript变量作用域(scope)
一个变量的作用域是程序源码中定义这个变量的区域。
全局变量拥有全局作用域,在js中,任何地方都是有定义的。
在函数内声明的变量只在函数体有定义,它们是局部变量,作用域是局部性的。 函数参数也是局部变量,他们只在函数体内有定义,在函数体内,局部变量的优先级高于同名的全局变量,如果在函数内声明一个局部变量或者函数参数中带有的变量和全局变量重名,那么全局变量就被局部变量所覆盖。
意思是说,全局变量是全局性的,即在JavaScript代码中,它处处都有定义。局部变量的作用域是局部性的,只在函数体内起作用,在函数体内,局部变量优先级高于同名的全局变量,在函数体内对局部变量赋值,会覆盖同名的全局变量。
JavaScript的表达式与运算符
- 表达式是JS的一个短语,js解释器会将表达式计算出一个结果
- 原始表达式
- JavaScript原始表达式包含常量、关键字和变量
- 1,1.2,null,aa等
- 对象和数组的初始化表达式
- 由方括号或者大括号和其中逗号隔开的列表构成
- [1,2,3] ,{x:1,y:2}等
- 函数定义表达式
- 由function跟随一对圆括号再接一对大括号
- 属性访问表达式
- 由点来运算得到一个对象或数组元素的值
- 调用表达式
- 调用函数或方法来得到计算值
- f(0),Math.max(x,y,z);s.sort()
- 对象创建表达式
- 用new来创建一个对象来得到一个对象
- 原始表达式
- 运算符是用于完成操作的符号
- 算术运算
- +、-、×、÷
- 关系运算
- <、>、=、!=
- 逻辑运算
-
&&、 、!
-
- 赋值运算
- 用=来江右边的值赋值给左边的变量或对象属性(数组属性)
- 表达式运算
- eval()
- eval()是一个函数,但由于它已经被当成运算符来对待了
- 其他运算符
-
条件运算符(三目运算符) ? :
判断句 ? true执行语句 : false执行语句
-
typeof运算符
用于返回()内的类型
-
delete运算符
一元操作符,删除对象或者数组元素
-
void运算符 void最常用在客户端的 URL中<a href=”javascript:void window.open():” >打开一个新的窗口 </a> );
-
- 算术运算
JavaScript的语句
-
如果表达式是js中的短语,则语句就是js中的完整的句子或者命令。
语句三大类:条件(if和switch)、循环(while和for)、跳转(break、return和throw)
-
表达式语句:赋值语句是一类比较重要的表达式语句,delete运算符也是表达式语句的一部分,函数调用也是表达式语句的一大类。
-
复合语句和空语句 多条语句用花括号括起来就可以形成一条复合语句
语句块注意点:
第一,语句块结尾不需要分号。块中语句必须以分号结束
第二,语句块中的行都有缩进,但不是必须的。
第三,js中没有块级作用域,在语句块中声明的变量并不是语句块私有的
-
声明语句 var和function都是声明语句,它们声明或定义变量或函数。
-
条件语句
- if是基本的控制语句,它让js程序可以选择执行路径
- switch,if是创建一条分支,并且使用else if来处理多条分支,当所有分支都依赖同一个表达式时,可以用switch和case来创建多条分支,注意switch语句的执行非常高效,没一个case后的执行语句后要加上break来终止case语句块,switch结尾要用default,来保证switch的整个语句块不被跳过。
-
循环语句
如果说条件语句是代码的一条条分支路径,则循环语句是程序路径的一条回路。
(1).do/while 与wihle循环相似,只不过它是在循环的尾部而不是顶部检测循环表达式,这就意味着循环体至少会执行一次
(2). for语句提供了一种比while更方便的循环控制结构。
for(initialize;test;increment){ statement } initialize负责初始化操作,test负责循环条件判断, increment负责计数变量更新
for/in语句也是使用for关键字,但和for循环完全不同的一类
for(variable in object ) {statemenet}
variable通常是一个变量名,也可以是一个可以产生左值的表达式或者一个通过var语句声明的变量 object是一个表达式,这个表达式的计算结果是一个对象
-
跳转
Js中的跳转语句,break语句是跳转到循环或者其他语句的结束。
Continue语句是终止本次循环的执行并开始下一次循环的执行。
Return语句可以让解释器跳出函数体的执行,并提供本次调用的返回值。
Throw语句触发或者抛出一个异常,一般与try/catch/finally语句一同使用,用于捕获异常
-
其他语句类型
with, debugger 和 use strict
with语句用于临时扩展作用域链,with(object) statement,将object添加到作用域链的头部,注:在严格模式中禁止使用with,非严格模式中不推荐使用
debugger语句通常什么也不做,只是用来打断点进行调试
use strict是ECMAScript 5 引入的一条指令,指令不是语句(非常接近于语句)
目的是说明后续的代码将会解析为严格代码
严格模式和非严格模式之间的区别:(前三条尤为重要)
(1).严格模式禁止使用with
(2).严格模式中所有变量都要先声明,如果一个未声明的变量、函数、函数参数、catch从句参数或者全局对象属性赋值,将会抛出引用错误异常(在非严格模式中,这种隐式声明的全局变量的方法是给全局对象新添一个新属性)
(3)在严格模式中,调用的函数中的一个this值是undefined,非严格模式中,调用的函数中的this值总是全局对象。可以利用这种特性来判断js实现是否支持严格模式
JavaScript中的对象
- 对象是js的基本数据类型
- 对象是一种复合值,它将很多值(原始值或者其他对象)聚合在一起,通过名字访问这些值。
- 对象也可以看成是属性的无序集合,每个属性都是一个名字
- 对象的属性名是字符串,所以对象是从字符串到值的映射。也称为“散列”、“散列表”、“字典”、“关联数组”
- 对象是动态的,可以添加也可以删除属性,常用来模拟静态类型语言中的“结构体”
创建对象:
可以通过对象直接量、关键字new和ECMAScript 5中的 Object.create()函数来创建对象
(1)对象直接量是一个表达式,这个表达式每次运算都创建并初始化一个新的对象,每次计算对象直接量的时候,也都会计算它的没一个属性的值。
(2)new运算符创建并初始化一个新对象。关键字new后跟随一个函数调用。这里的函数称做构造函数。构造函数用以初始化一个先创建的对象。
(3)Object.create(Object)可以通过任意原型创建新的对象,也就是说可以是任意对象可继承。
属性的查询和设置
可以通过.和[]运算来获取属性的值,已经有的属性可直接赋值
赋值:
如果o中的属性p是可读的:不能给只读属性重新赋值
如果o中的属性p是继承属性,且它是只读的:不能通过同名自有属性覆盖只读的继承属性
如果o中不存在自有属性p:o也没有使用setter方法继承属性p,并且o的可扩展性是false。
如果o中不存在p,而且没有setter方法提供调用,则p一定会添加至o中。
但如果o不是可扩展的,那么在o中不能定义新属性。
删除属性
detele 语句,不能删除那些可配置性为false的属性
detele Object.prototype; //不能删除,属性不可配置的 var x=1; detelet this.x;//不能删除这个属性 function f(){}; detelet this.f;//不能删除这个全局函数
只能删除自有属性,不能删除继承属性,要删除继承属性必须从定义原型对象上删除它。
检测属性(in、for/in)
-
in运算的左侧是属性名,右侧是对象,如果包含则返回true
var o ={x:1} “x” in o//true “y” in o //false “toString” in o //true o继承了toString属性
-
hasOwnProperty()方法用来检测给定的名字是否是对象的自有属性,对于继承属性返回false
var o = {x:1} o.hasOwnProperty(“x”); //true:o有一个自有属性x o.hasOwnProperty(“x”); //false :o中不存在属性y o.hasOwnProperty(“x”);//false:toString是继承属性
-
propertyIsEnumerable()是hasOwnProperty()的增强版,只有检测到自有属性且是可枚举的才返回 true
var o =inherit { y : 1} o.x = 1; o.propertyIsEnumerable(“x”);//true :o有一个可枚举的自有属性x o.propertyIsEnumerable(“y”);//false:y是继承来的 Object.prototype.propertyIsEnumerable(“toString”);//false:不可枚举
除了in之外,!==判断一个属性是否是undefined
var o ={x:1}
o.x !==undefined;//true:o中有属性x
o.y !==undefined;//false:o中没有属性y
o.toString !== undefined;//true o 继承了toString属性
枚举属性
for/in循环
var o = {x:1,y:2,z:3}
for(p in o){console.log(p); }
属性getter和setter方法
由getter和setter定义的属性称做“存取器属性”,不同于”数据属性”,数据属性只是一个简单的值 当有getter和setter时,说明属性是可读写的存取器属性 当只有getter时,说明属性是只读取的属性
对象的三个属性
每一个对象都有与之相关的原型(property)、类(class)和可扩展性(extensible attribute)
原型属性:对象的原型属性是用来继承属性的。是实例对象创建之初就设置好的。
var p ={ x :1};//定义一个原型对象
var o = Object.create(p);//使用这个原型创建一个对象
p.isPrototypeOf(o)//true:o继承自p
Object.prototype.isPrototypeOf(o)//true: p继承自Object.prototype
isPrototypeOf()方法,检测p是否是o的原型
类属性:对象的类属性是一个字符串,用以表示对象的类型信息 classof(1)//Number
可扩展的属性
对象的可扩展属性用以表示是否可以给对象添加新属性。所以内置对象和自定义对象都是显式可扩展的
Object.preventExtensions()、Object.seal()、 Object.freeze()
//创建一个封闭对象,包括一个冻结的原型和一个不可枚举的属性
var o = Object.seal(Object.create(Object.freeze({ x : 1}),
{y: {value: 2,writable:true}}));
序列化对象
是指将对象的状态转换为字符串,也可将字符串还原为对象JSON.stringify()和JSON.parse()来序列化和还原js对象。这些方法都是使用JSON作为数据交换格式,它的语法和js对象和数组直接量的语法非常相近。
对象方法
toString()方法:没有参数,返回表示调用这个方法的对象值的字符串
toLocaleString()方法:这个方法返回一个表示这个对象的本地化字符串,可以用它对数字、日期和时间做本地化转换
toJSON()方法:如果在待序列化的对象中存在这个方法,则调用它,返回值即是序列化的结果
valueOf() 方法:这个方法和toString()方法非常相似,但往往当js需要将对象转换为某种原始值而非字符串的时候才会调用它,尤其是转化为数字的时候。
JavaScript中的数组
数组是值的有序集合
每一个值叫做一个元素,而每个元素在数组中有一个位置,以数字表示,称为索引。
数组是JavaScript对象的特殊形式
ECMAScript 5中可以使用Array.isArray({});和Array.isArray([]);来区判定是否为数组。
Typeof操作在这帮不上忙(除函数以外的所有对象都是如此)
.instanceof操作只能用于简单的情形: instanceof的问题是在Web浏览器中有可能有多个窗口或窗体(frame)存在。
数组也是动态的
创建数组时无需声明一个固定的大小或者在数组大小变化时无需重新分配空间
创建数组
(一)方括号中用逗号将数组元素隔开即可。
数组中不一定要有常量,他们可以是任意表达式。
它可以包含对象直接量或者其他数组直接量。
如果省略数组直接量中的某个值,省略的元素将被赋予undefined值。
(二)调用构造函数Array()创建数组也是一种方法。有三种方法可以调用构造函数
方法一:var a=new Array(),等同于直接量[];
方法二: var a = new Array(10);创建指定长度的数组;
方法三:显式指定两个或多个数组的一个非数值元素:var a = new Array(1,2,3,”adc,aaa”);
数组元素的读和写
读数组array[index]
写数组 array[index]=4;
稀疏数组
定义:稀疏数组就是包含从0开始的不连续索引的数组。即数组的length>元素个数 足够稀疏的数组通常在实现上比稠密数组更慢、内存利用率更高,在这样的数组中查找元素与常规对象的属性查找时间一样
数组长度
每一个数组都有一个length属性[].length
数组元素的添加和删除
添加:a.push(“1”);a.push(“1”,”2”);
删除 delete a[1];注:删除后a在索引1的位置
不再有元素,数组索引1并未在数组中定义,但delete操作并不影响数组的长度
数组遍历
for循环遍历是最常见的遍历方法
for(var i=0; i < a.length; i++ ){
if(!a[i])
continue;//跳过null、undefined和不存在的情况
}
for(var i in a){
if(!a[i])
continue;//跳过null、undefined和不存在的情况
}
ECMAScript 5中定义了新的数组遍历的方法:forEach()方法
a.forEach(function(x){
//此处x指a[0]-a[a.length]的遍历
})
多维数组
Js不支持真正的多维数组,但可以用数组的数组来近似多维数组。
数组方法
-
join()方法:将数组中所有元素都转化为字符串并连接在一起,返回最后生成的字符串。并不改变原数组join(“-”)指定拼接符
-
reverse()方法:将数组中的元素颠倒顺序,返回逆序的数组,它采取了替换,原先数组改变
-
sort()方法:将数组中的元素排序并返回排序后的数组。改变了原数组
-
concat()方法:创建并返回一个新的数组,他的元素包括调用concat()原始数组的元素和concat()的每个参数。如果这些参数中的任何一个自身是数组,则连接的是数组元素,而非数组本身。例如:var a=[1,2],a.concat(4,5)//[1,2,4,5]
-
slice()方法:返回指定数组的一个片段或者子数组。
-
splice()方法:数组中插入或者删除元素的通用方法a.splice(index),a.splice(index1,index2),第一个参数指定了插入或删除的起始位置。第二个参数指定了应该从删除元素的个数。如果第二个参数省略,则从起始位置到数组结尾的元素都将被删除。
-
push()和pop()方法:push()是在数组尾部添加一个或多个元素,并返回新数组的长度。pop()相反,删除数组的最后一个元素,减小数组的长度并返回。
-
unshift()和shift()方法:unshift()和shift()跟push()和pop()相对,从头部增加一个或多个元素,并返回数组长度。shify()删除数组第一个元素,并将其返回,然后把所有随后的元素下一个位置来填补数组头部的空缺.
-
toString()和toLocaleString()方法:toString()针对数组,该方法将每个元素庄花为字符串并输出用逗号分隔字符串列表。toLocaleString()是toString()方法的本地化版本。它调用toLocaleString()方法将每个数组元素转化为字符串,并且使用本地化或自定义分隔将这些字符串连接起来生成最终的字符串。
ECMAScript 5中的数组方法
(1)forEach()方法从头至尾遍历数组,为每个元素调用指定的函数。
data.forEach(
function(value){
...
})
注意:无法在所有元素都传递给调用函数之前终止遍历。也就是说,没有像for循环中使用的相应的break语句。如果要提前终止,必须把forEach()方法放在一个try块中,并抛出异常。这样才会提前终止循环。
(2)map()方法:
该方法将b=a.map(function(x){x*x})//调用的数组的每个元素传递给指定的函数,并返回一个数组,它包含该函数的返回值。例:x表示a[0]~a[a.length]
(3)filter()方法:
返回的数组元素是调用的数组的一个子集。传递的函数是用来逻辑判定的。如果逻辑判定值是true则输出该元素到一个作为返回值的数组中,false则不返回。多用于压缩空缺并删除undefined元素。
例:a=[1,2,3,4,5];a.filter(function(x,i){return i%2});//[1,3,5]
a=a.filter(function(x){return x!=undefined&&x!=null});//压缩空缺
(4)every()和some()
every()当且仅当针对数组中的所有元素调用判定函数都返回true,它才返回true
some()当数组中至少有一个元素满足判定函数,则返回true
(5)reduce() 和reduceRight()
reduce()和reduceRight()方法使用指定的函数将数组元素进行组合,生成单个值,这在函数式编程中是常见的操作。
例 var a=[1,2,3,4,5];
sum=a.reduce(function (x,y){return x+y},0); //15 数组求和
max=a.reduce(function(x,y){return (x>y)?x:y}); //5 数组求最大值
reduce()需要两个参数。第一个是执行化简操作的函数。化简函数的任务是用某种方法把两个数值组合或简化为一个值,并返回化简后的值。第二个(可选)的参数是一个传递给函数的初始值。
reduceRight()和reduce()的工作原理一样,不同的是它按照数组索引从高到低处理数组,而不是从低到高。
注意:reduce()和reduceRight()都能接收一个可选的参数,它指定了化简函数调用是的this关键字的值。 可选的初始值人需要占一个位置,如果想让化简函数作为一个特殊对象的方法调用,请参见Function.bind()方法。
(6)indexOf()和lastIndexOf()
indexOf()和lastIndexOf()搜索整个数组中具有给定值的元素,返回找到的第一个元素的索引或者如果没有找到就返回-1。indexOf()从头至尾搜索,而lastIndexOf()则是反向搜索。
数组类型
数组是具有特殊行为的对象。ECMAScript 5中可以使用Array.isArray({});和Array.isArray([]);来区判定是否为数组。
Typeof操作在这帮不上忙(除函数以外的所有对象都是如此).instanceof操作只能用于简单的情形:
instanceof的问题是在Web浏览器中有可能有多个窗口或窗体(frame)存在。
类数组对象
Js中数组的一些特性是其他对象所没有的:
当新元素添加到列表时,自动更新length属性。
设置length为一个较小值将阶段数组。
Array.prototype中继承一些有用的方法。
其类属性为Array,这些可以看出数组和常规的对象有明显的区别。
实践中,类数组对象实际上偶尔出现,虽然它们不能直接调用数组方法或者length属性有什么特殊的行为,但仍然可以针对真正数组遍历的代码来遍历它们。Js数组方法是特意定义为通用的,因此它们不仅应用在真正的数组而且在类数组对象上都能正确工作。
作为数组的字符串
在ECMAScript 5中字符串的行为类似于只读的数组。
JavaScript中的函数
函数是JavaScript中只定义一次,但可被执行和调用任意次
函数是参数化的:函数的定义会包括一个称为形参的标识符列表,这些参数在函数体中像局部变量一样工作 如果函数挂载在一个对象上,作为对象的一个属性,就称为对象的方法
函数可以嵌套在其他函数中定义,这样他们就可以访问他们被定义时所处的作用域中的任何变量
函数给JavaScript带来了强劲的编程能力
函数的定义
使用 function关键字来定义,它可以用在函数定义表达式或者函数申明语句里。两种形式,都是从function关键字开始的。function 后面跟随函数名称标识符,函数名称是函数声明语句不可或缺的部分,它的用途就像是变量的名称,这个名字是可选的,如果存在,该名字只存在于函数体中,并指代该函数本身。
一对圆括号,其中包含0个多个用逗号隔开的标识符组成的列表,这些标示符是函数的参数名称,它们就像函数体中的局部变量一样。
一对花括号,其中包含0条或多条js语句,这些语句构成了函数体,一旦调用函数,就会执行这些语句。
函数的调用
有四种方法来调用js函数:
- 作为函数//直接函数名a(x)
- 作为方法//o.m();
- 作为构造函数 //函数或方法调用之前带有关键词new
- 通过call()和apply()方法间接调用 //js中函数也是对象,函数对象也可以包含方法call ()和apply()可以间接的调用函数,且都允许显式指定调用所需的this值。
函数的实参和形参
定义时就是形参,调用时就是实参
作为值的函数
函数也可以作为值传递给一个变量
作为命名空间的函数
(function(){
//CODE
})();//最佳的方式是使用匿名函数创建作用域。
闭包
Js也采用词法作用域,也就是说,函数的执行依赖变量作用域,这个作用域是在函数定义时决定的,而不是函数调用时决定的。
注:严格讲,闭包内的逻辑是可以使用this的,但这个this和当初定义函数时的this不是同一个,即便是同一个this,this的值是随着调用栈的变化而变化的。可以将this转存为一个变量的做法可以避免this的不确定性带来的歧义.
函数式编程
是指数学中的函数,即自变量的映射。也就是说一个函数的值仅决定于函数参数的值,不依赖其他状态。在js中可以操控对象一样操控函数,也就是说可以在js中应用函数式编程技术。
函数的属性、方法和构造函数
-
length属性
arguments.length表示传入函数的参数的个数
-
portotype属性
每个函数都包含一个portotype属性,这个属性是指向一个对象的引用,这个对象称作”原型对象”
-
call()方法和apply()方法
我们可以将它们看做是某个对象的方法,通过调用方法的形式来间接调用函数。call()和apply()的第一个实参是要调用函数的母对象,他是调用上下文,在函数体内通过this来获得对它的引用。
要想以对象o的方法来调用函数f(),可以这样使用call()和apply():f.call(o);和f.apply(o);
-
bind()方法
bind()是ECMAScript 5中新增的方法,这个方法主要作用是将函数绑定至某个对象。
function f(){return this.x+y;} //这个是待绑定的函数 var o ={x:1}; //将要绑定的对象 var g= f.bind(o); //通过调用g(x)来调用o.f(x) g(2)
-
toString()方法,和js对象一样,函数也有toString()方法,返回一个字符串没这个字符串和哈数声明的语句的语法相关
-
function()构造函数
var f= new function(“x”,”y”,”return x*y;”); 等价于var f=function(x,y){return x*y;}
注:
- function()构造函数允许js在运行时动态创建并编译函数。
- 每次调用function()都会解析函数体,并创建新的函数对象,如果在一个循环或者多次调用的函数中执行这个构造函数,执行效率会受影响,相比之下,循环体中的嵌套函数和函数定义表达式则不会每次执行时都重新编译
- function()构造函数最重要的一点,就是它所创建的函数并不是使用词法作用域,相反,函数体代码的编译总是会在顶层函数执行。因此,构造函数在实际编程过程中很少用到