函数
函数的定义
function(){}
varsum=function(n1,n2){return n1+n2}
var sum=newFunction(‘n1’,’n2’,’return n1+n2’)
最后一个参数当做函数体
函数的特点
ECMAScript中的函数在定义时可不必指定是否返回值,任何函数可以在任何时候通过return语句后跟要返回的值来实现。
一个函数也可以跟多个return语句
return语句可以不带有任何返回值。这种情况下,函数在停止执行后返回undefined。不会再执行后面的语句
理解参数
不介意传递进来参数的个数和数据类型,它ECMAScript的参数在内部是以一个数组的形式来存储的,函数接收到的始终是这个数组。在函数体内可以通过arguments对象来访问传递过来的参数。
arguments对象只是类数组,可以使用方括号法访问它的每一个元素,可以使用length属性来确定传递过来多少个参数。arguments对象可以和命名参数一起使用。
特点:命名的参数只提供便利,但不是必须的。
没有重载
如果定义了两个相同的函数,则后者会覆盖前者,改名字只属于后定义的函数。
作为值得函数
函数本身就是变量,函数可以作为值来使用,可以把函数传递给另一个函数,而且也可以将一个函数作为另一个函数的结果返回。
函数的内部属性
arguments
含有属性callee属性,该属性是一个指针,指向拥有这个arguments对象的函数
function factorial(num){
if(num<=1){
return 1;
}
else{
return num*arguments.callee(num-1);
}
}
console.log(factorial(5));
this
引用的是函数据以执行的环境对象(全局作用域时this指向window)
caller
保存着当前函数的函数的引用,如果是在全局作用域中调用当前函数,它的值为null
function outer(){
inner();
}
function inner(){
alert(inner.caller);
}
outer();
函数属性和方法
函数是对象也有属性和方法
length
表示函数接收的命名参数的个数
prototype
保存实例方法在ECMAScript中prototype属性是不可枚举的,不能够使用forin访问到
apply()
在特定的作用域中调用函数,实际上等于设置函数体内this对象的值
会接受两个参数,
第一个参数是在其中运行作用域的函数
第二个参数可以是Array实例,也可以是arguments对象
call()
和apply的作用相同,接收的参数形式不同,第一个参数还是运行作用域的函数,其余参数必须逐个枚举出来。
apply()和call()能够扩充函数的运行的作用域
bind()
会创建一个函数实例,其this值会被绑定到传给bind()函数的值
var color='red';
var o={
color:'green'
}
function sayColor(){
alert(this.color)
}
sayColor.call(this);//red
sayColor.call(o);//green
sayColor.apply(o);//green
sayColor.bind(o)();//green
函数声明和函数表达式的区别
1、函数声明会被解析器率先解析,并使其在执行任何代码之前可用(可以访问)函数提升
函数表达式必须等到解析器执行到它所在的代码行,才会真正被解析执行,会被当做一个普通的变量声明,解析为undefined
console.log(a);//ƒ a(){console.log('hello');}
function a(){
console.log('hello');
}
console.log(a);//undefined
var a=function(){
console.log('hello');
}
2、函数表达式可以在后边加括号立即执行,而函数声明不可以
var fn=function(){
var a=1;
console.log(a);//1
}();
3、函数表达式的几种形式
+(function(){
console.log('b');
})
4、函数表达式转为立即执行函数
(function(){
console.log('a');
})();
js中的函数名后面加括号和不加括号的区别
函数只要是调用进行执行的,都必须加括号。
不加括号的都是把函数名作为函数的指针,一个函数的名称就是这个函数的指针,不会运行函数体代码,只是传递了函数体所在的地址位置,在需要的时候可以找到函数体去执行。
function fn(){
console.log('a');
}
var result=fn;
result();
将fn整体赋值给result,result就是一个指向函数的指针,必须调用才能执行。
init函数不会立即执行,浏览器加载文档时这句话会被加载,会告知文档加载完要执行那个函数,但实际上没有当时就执行,而是等到整个文档加载完成之后才通过init这个指针去执行init()函数。
function init(){
}
window.οnlοad=init;
传递参数
函数的参数都是按值传递的,也就是说把函数外部的值赋值给函数内部的参数,就相当于把一个变量复制到另一个变量。基本类型的值得传递如同基本类型变量的复制一样,而引用类型值得传递,则如同引用类型变量的复制一样
在向参数传递基本类型的值时,被传递的值会被赋给一个局部变量(既命名参数或者arguments)
在向参数传递引用类型的值时,会把这个值在内存中的地址复制给一个局部变量,因此这个局部变量的变化被被反映在函数的外部。
var obj={
name:'haha'
}
function setName(obj){
obj.name='hehe';
var obj=new Object();
obj.name='lala'
console.log(obj.name);//lala
}
setName(obj);
console.log(obj.name);//hehe
即使在函数内部修改了参数的值,但是原始的引用任然保持围边,实际上,挡在函数内部重写obj时,这个变量引用的就是一个局部变量,这个局部对象会在函数执行完毕后立即销毁
执行上下文
范围:一段<script>标签,或者一个函数
全局:变量的定义、函数声明
函数:变量定义、函数声明、this、arguments
执行环境定义了变量或函数有权访问的其他数据,决定了各自的行为,每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中,虽然我们写代码的时候无法访问这个对象,但解析器在处理数据时会在后台使用它。
全局执行环境是最外围的一个执行环境,根据ECMAScript实现的宿主环境不同,表示执行环境的独享也不一样,在web浏览器中,全局执行环境就是window对象,因此所有的全局变量和函数都是作为window对象的属性和方法创建的,某个执行环境中的所有代码执行完毕后,该环境就被销毁,保存在其中的所有变量和函数定义也被销毁,全局执行环境直到应用程序退出如关闭网页或者浏览器时才被销毁。
每个函数都有自己的执行环境,当执行流进入一个函数时,函数的环境就会被推入一个环境栈中,而在函数执行完毕后,栈将其环境弹出,把控制权返回给之前的执行环境。
当代码在一个环境栈中执行时,会创建变量对象的一个作用域链,作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问,作用域链的前端始终是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象作为变量对象。活动对象在最开始的时只包含一个变量,即arguments对象(这个对象在全局执行环境是不存在的)。作用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象来自下一个包含环境,这样一直延续到全局执行环境,全局执行环境的变量对象始终是作用域链中的最后一个对象。
查询标识符
标识符解析是沿着作用域链一级一级地搜索标识符的过程,搜索过程从作用域链的前端开始,然后逐级的向后回溯,如果在局部环境中找到了该标识符,搜索过程停止,变量就绪,如果在全局环境中仍没有找到这个标识符,意味着该变量尚未声明,找不到通常会发生错误。
var a=100;
function F1(){
var b=200;
function F2(){//F2的父级作用域就是F1
var c=300;
console.log(a);//100
console.log(b);//200
console.log(c);//300
}
F2();
}
F1();
作用域
域:空间、范围、区域
作用:读写
全局变量 全局函数 script标签 函数
自上而下
作用域分析方法
先进行预解析,分析解析过程,再进行代码执行过程
程序在执行过程,会将代码读取到内存中检查,会将所有的声明在此时进行标记,所谓的标记就是让js解析器知道有这个名字,后面再使用名字的时候,不会出现未定义的错误,这个标记过程就是变量提升
名字声明,标识符的声明(变量声明)
名字的声明就是让js解析器知道有这个名字
名字没有任何数据与之对应
函数的声明
函数声明包含两部分
首先函数声明告诉解析器有这个名字存在,该阶段与名字声明一样
然后告诉解析器,这个名字对应的函数体是什么(函数名和函数体绑定在连接)
注意:函数声明和函数表达式不同
浏览器 js解析器
1) 找一些东西 var function 参数
a=
所有的变量,在正式运行代码钱,都会提前赋一个值:未定义
fn=function(){alert(2)}
所有的函数,在正式运行代码前,都是整个函数块
预解析过程中:重名的只留一个
函数名和变量名重名时,会取函数名
函数名重名时,会取后者
2)逐行解析代码
表达式:=+-*、% ++ -- ! 参数
表达式可以修改预解析的值
alert(a);//function a(){alert(4);}
var a=1;
alert(a);//1
function a(){alert(2);}
alert(a);//1
var a=3;
alert(a);//3
function a(){alert(4);}
alert(a);//3
a();//报错
首先进行预解析
var a=1,预解析为a=undefined
functiona(){alert(2);} 解析为 a=function a(){alert(2);}
var a=3 预解析,a=undefined不能覆盖函数,保留函数
functiona(){alert(4);} 预解析 后来的函数会覆盖前者 a=function a(){alert(4);}
解析完成之后,逐行解读代码
alert(a) 此时a= functiona(){alert(4);} 会弹出整个函数体
a=1 表达式可以修改预解析的值 此时 a=1
alert(1) 弹出a=1;
functiona(){alert(2);} 只是一个函数声明并未调用,不做任何改变
alert(a) 弹出a=1
a=3 a被赋值为3
alert(a) 弹出a=3
functiona(){alert(4);} 只是一个函数声明并未调用,不做任何改变
alert(a) 弹出a=3
a() 此时a只是一个变量,并不是函数,所以会报错
变量作用域
使用var声明的变量会被自动添加到最近的执行环境中,在函数内部最接近的环境就是函数的局部环境,在with语句中,最接近的环境就是函数环境,
在js中全局中定义的变量是全局的,在代码的任何地方都是可以用的,在函数体内,函数的参数和函数体内的定义的变量只在函数体内能够访问。
局部变量的优先级高于同名的全局变量,也就是说局部变量与全局变量重名时,局部变量会覆盖全局变量。
变量的查找是就近原则,去寻找var定义的变量,当就近没有找到的时候就去查找外层。
声明局部变量时一定要使用var声明,否则解析器会吧该变量当做全局对象window的属性。
var n=10;
function fn(){
var n=20;
console.log(n);//20
}
fn();