var声明的变量,其作用域是函数体的全部,没有块作用域
let声明的变量拥有块级作用域。
1) 局部变量:
函数内声明的变量为局部变量,为局部作用域,只能在函数内访问;
function studentnum()
{
var nums = 10;
console.log(nums);
}
studentnum();
console.log(nums);
2)全局变量:
函数外声明的变量为全局变量,具有全局作用域,在声明处后面的网页脚本中均可使用;
var nums = 10;
function studentnum()
{
console.log(nums);
}
studentnum();
console.log(nums);
//balabala
function studentnum() {
num = 50;
console.log(num);
}
studentnum();
console.log(num);
//balabala
function studentnum() {
num = 50;
console.log(num);
}
num=100;
studentnum();
console.log(num);
如果变量在函数内没有使用var进行声明,则默认为全局变量(前提是函数必须执行了):
3) 变量声明周期:
变量在声明时初始化;
局部变量在函数执行完后销毁;
全局变量在页面关闭后销毁;
二、函数
1.函数基本定义:
function functionname()
{
执行代码
}
2.匿名函数
function(a){return a;}
可用来定义匿名函数来执行某些一次性任务。
3.表达式函数:
函数可以定义为一个表达式储存在变量中;
var y = function (h, q){return h%q;}
document.write(y(3, 4));
这样就定义了一个匿名函数并保存在变量中了;
4.自调用函数:
自调用的方法是
1.匿名函数自己被小括号抱起来();
2.在函数后面紧跟小括号()调用;
表达式函数可以自调用,声明的函数不能自调用;
(function (){
var h = '专业始于专注';
document.write(h);})(); //正常执行
function fnName(){
console.log(123);
}() // 报错
var fnName = function () {
console.log(123);
}() // 正常执行
5.使用关键词Function来new一个函数:
var fun1 = new Function('var h = 3, q = 5; return h + q;');
alert(fun1());
6.内部函数(私有函数)
function a(param){
function b(i){
return i*2;
};
return b(param)+1;
};
当我们调用a()时,本地函数b()也会在内部被调用。由于b()是本地函数,它在a()以外的地方是看不见的,所以b为私有函数
使用私有函数的好处:
7.返回函数的函数
function func(){
var b=3;
return function(){
console.log(b);
}
}
var newFunc=func();
newFunc();
函数的生命周期
函数的的生命周期分为创建和执行两个阶段。
//balabala
var a=123;
function f(){
console.log(a);
var a=1;
console.log(a);
}
f();
JavaScript变量提升和函数提升
1.变量提升:
把变量提升到函数的最开始位置;只是提升变量的声明,不会提升赋值;
function hoistvar()
{
console.log(h);
var h = '专业始于专注';
}
hoistvar();
2.函数提升:
把声明的函数提到前面去;只有声明形式的函数才会提到前面去(字面量形式的不会,字面量就是如何表达这个值,一般等号右边的都认为是字面量,也就是表达式函数);
hoistfun();
function hoistfun(){
var a=2;
var b=3;
return a+b;
}
//balala
console.log(a);
var a=2;
function a() {
console.log(1);
}
a = function() {
console.log(2);
}
a();
//balala
function studentnum() {
var studentsnum = 50;
console.log(studentnum);
}
studentnum();
函数的声明优先于变量声明
在编写程序时,尽量按照提升后的执行顺序写代码,避免不必要的错误和理解误差
当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的用途是保证对执行环境(执行上下文)有权访问的所有变量和函数的有序访问。
变量对象(VO):变量对象即包含变量的对象,变量对象我们无法访问,除此之外和普通对象没什么区别。变量对象存储了在上下文中定义的变量和函数声明
活动对象(AO):是在进入函数执行环境时刻被创建的,它通过函数的 arguments 属性初始化。
变量对象和活动对象的关系
未进入执行阶段之前,变量对象(VO)中的属性都不能访问,只是声明但是进入执行阶段之后,变量对象(VO)转变为了活动对象(AO),里面的属性都能被访问了,然后开始进行执行阶段的操作。它们其实都是同一个对象,只是处于执行环境的不同生命周期。AO 实际上是包含了 VO 的。因为除了 VO 之外,AO 还包含函数的 parameters,以及 arguments 这个特殊对象。
也就是说 AO 的确是在进入到执行阶段的时候被激活,但是激活的除了 VO 之外,还包括函数执行时传入的参数
和 arguments 这个特殊对象。
执行环境(执行上下文)的组成
当JavaScript代码执行的时候,会进入不同的执行环境(执行上下文),这些执行环境会构成一个执行环境栈
(执行上下文栈) (Execution context stack,ECS)。见下图:
执行环境分析
js的执行顺序是根据函数的调用来决定的,当一个函数被调用时,该函数环境的变量对象就被压入一个环境栈中。而在函数执行之后,栈将该函数的变量对象弹出,把控制权交给之前的执行环境变量对象。
eg:
var scope = "global";
function fn1(){
return scope;
}
function fn2(){
return scope;
}
fn1();
fn2();
当某个函数第一次被调用时,就会创建一个执行环境(execution context)以及相应的作用域链,并把作用域链赋值给一个特殊的内部属性([scope])。然后使用this.arguments(arguments在全局环境中不存在)和其他命名参数的值来初始化函数的活动对象(activation object)。当前执行环境的变量对象始终在作用域链的第0位。以上述执行环境分析的小例子为例进行图解:当第一次调用fn1时
解析:可以看到fn1活动对象里并没有scope变量,于是沿着作用域链(scope chain)向后寻找,结果在全局变量对象里找到了scope,所以就返回全局变量对象里的scope值
function outer(){
var scope = "outer";
function inner(){
return scope;
}
return inner;
}
var fn = outer();
fn();
作用域 私有函数
垃圾回收机制 引用
var person = function() {
var personnum = 0; //私有privite,外部不能直接访问
function getCount() {
return personnum++;
}
return getCount; //返回函数名(public,暴露内部信息 )
}
var p = person();//通过子函数引用,能访问局部变量console.log('count...' + p());
function fun() {
var h = 5;
return function() {
console.log(h);
return h++;
};
}
var historys = fun();
historys();
historys();
var historys2 = fun();
historys2();
historys2();
historys2();
fun()();
fun()();
function fn() {
var _num = 0;
return {
add1: function() {
_num++;
console.log(_num);
},
add2: function() {
_num += 2;
console.log(_num);
}
}
}
var c = fn();
c.add1();
c.add2();
c.add1();
function fun(a) {
return function(b){
return function(c){ console.log(a+b+c); };
};
}
var x = fun(1 );
var y = fun(5)(8);
var z = fun(2)(4);
x(2)(1);
y(2); //在y快照基础上再运算 5+8+2
z(2);
//
function f1() {
var n = 999;
nAdd = function () {
n += 1;
console.log(n);
}
function f2() {
n++;
console.log(n);
}
return f2;
}
var n = 668;
console.log(n);
var result = f1();
result();
nAdd();
result();
在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。
为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。