JavaScript基础编程-函数,函数表达式,闭包等概念理解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013862108/article/details/81977995

参考《JavaScript高级程序设计3版》

javaScript预编译过程。

javaScript运行三个步骤:

  1. 语法分析
  2. 预编译  //在内存中开辟一些空间,存放一些变量与函数。
  3. 解释执行  /执行代码

JS预编译什么时候会发生?

  1. script中的代码执行前
  2. 函数执行前
<script type=“text/javascript”>
var a = 1;
//console.log(a);  //1
function test(a) {
    console.log(a);  //ƒ a() {}
    var a = 123;
    console.log(a);  //123
    function a() {}
    console.log(a);  //123
    var b = function() {}
    console.log(b);  //ƒ () {}
    function d() {}
}
var c = function (){
    console.log("I at C function");
}
console.log(c);
test(2);
</script>

分析:

  1. 页面产生便创建了GO对象(Glabal Object) (也就是window对象);
  2. 第一个脚步文件加载
  3. 脚本加载完毕后,分析语法是否合法;
  4. 开始预编译

                      查找变量声明,作为GO属性,值赋予undefined

                      查找函数声明,作为GO 属性,值赋予函数体;

//预编译:
//抽象描述
GO/window = {
    a: undefined,
    c: undefined,
    test: function(a) {
        console.log(a);
        var a = 123;
        console.log(a);
        function a() {}
        console.log(a);
        var b = function() {}
        console.log(b);
        function d() {}
    }
}

//解释执行代码(直到test(2),不包含执行test(2))
//抽象描述
GO/window = {
    a: 1,
    c: function (){
        console.log("I at C function");
    },
    test: function(a) {
        console.log(a);
        var a = 123;
        console.log(a);
        function a() {}
        console.log(a);
        var b = function() {}
        console.log(b);
        function d() {}
    }
}

//执行函数test()之前,发生预编译
//1.	创建AO活动对象(Active Object);
//2.	查找形参和变量声明,值赋予undefined;
//3.	实参赋给形参;
//4.	查找函数声明,值赋予函数体;

//预编译之前面1、2两小步如下:

//抽象描述
AO = {
    a:undefined,
    b:undefined,
}

//预编译之第3步如下:

//抽象描述
AO = {
    a:2,
    b:undefined,
}
//预编译之第4步如下:

//抽象描述
AO = {
    a:function a() {},
    b:undefined,
    d:function d() {}
}

//执行test()函数时如下过程变化:

//抽象描述
AO = {
    a:function a() {},
    b:undefined,
    d:function d() {}
}
//--->
AO = {
    a:123,
    b:undefined,
    d:function d() {}
}
//--->
AO = {
    a:123,
    b:function() {},
    d:function d() {}
}

注意:匿名函数不参与预编译;

函数执行前预编译

  1. 创建AO对象(Activie Object)
  2. 查找函数形参及函数内变量声明,形参名及变量名作为AO对象的属性,值为undefined
  3. 实参形参相统一,实参赋给形参
  4. 查找函数声明,值赋予函数体。

 

脚本代码块script执行前预编译

  1. 查找全局变量声明(包括隐式全局变量声明,省略var 声明),变量名作为全局对象的属性,值为undefined
  2. 查找函数声明,函数名作为全局对象的属性,值为函数体/函数引用。

预编译特点,或表象。

1: 函数声明提升。这意味着可以把函数声明放在调用它的语句后面。

2: 变量声明提升

预编译例子
var a=1;
if(true){   //没有块级作用域
    var b=1;
}

console.log(b);

var a = 1;
var b = 2;

function doit(){
    console.log(b);
    var b = 3;
    console.log(b);
}

doit();

console.log(b);

作用域

var a=1;
if(true){
    var b=1;
}

console.log(b); //1

var a = 1;
var b = 2;

function doit(){
    console.log(b);  //undefined;  预编译的时候b被提升了值为undefined;
    var b = 3;
    console.log(b);  //3

    return function () {
        //console.log(b);   //打开,作用域链包含doit的scopes
        console.log('inner');
    }
}

console.dir(doit); //ƒ doit()
var libian = doit();
console.log(b);  //2
console.dir(libian);//注意scopes 从它的外围 AO开始直到GO
libian();  //inner
function kong() {

}
console.dir(kong);  //scopes 对应GO

doit  在<script>内代码预编译时函数被声明后; doit 就有了[[Scopes]];

libian 在doit()执行后,libian就被声明了,libian就有了[[Scopes]];到底需不需要doit对应的scopes 或说活动对象,js引擎能够判断。

定义函数的两种方式:

1. 函数声明, 2 函数表达式

//2函数表达式,也叫匿名函数
sayHi();   //报错
var sayHi = function(){
    alert('Hi!');
}
//可以这样做
var condition = true;
if(condition){
    sayHi = function(){
        alert('Hi');
    }
}else{
    sayHi = function(){
        alert('Yo!');
    }
}
sayHi();   //fire:hi;360:hi;google:hi

函数递归

常见的好的写法如下:

//递归
function factorial(num){
    if(num<=1){
        return 1;
    }else{
        return num*arguments.callee(num-1);
    }
}
alert(factorial(4));   //24
//arguments.callee 是一个指向正在执行的函数的指针。比使用函数名更保险。在严格模式下不能使用
也可以使用
//可以使用命名函数表达式

var factorial = function f(num){  //这里的f根本不是变量;可以在最外边加上小括号
    if(num<=1){
        return 1;
    }else{
        return num*f(num-1);
    }
};
//console.log(f(4));  //f未定义
//console.log(f);  //f未定义; 这个也不是function后面的f
f=null;
console.log(factorial(4));
console.dir(factorial);
console.log(factorial);

分析为什么f=null 没有用,因为 function f的f根本不是变量;它只是函数的名字而已,最多算内部变量供引擎使用。

闭包

指它有权访问另一个函数作用域中的变量,它是函数。

创建常见方式: 在函数内部创建另一个函数。

 

当某个函数被调用时,会创建:

一个活动对象(activation object) 或说AO;

一个执行环境(execution context)或说作用域链;包含了AO;或说执行环境里边有this, 有arguments, 有作用域链,有AO;注意this引用的和AO不是一个东西。

//作用域链本质上是一个指向变量对象的指针列表。

难道说执行环境里不仅仅只包含作用域链,还包括别的?

//闭包,是指有权访问另一个函数作用域中的变量的函数。创建闭包的方式,就是在一个函数内部创建另一个函数,
function createComparisonFunction(propertyName){
    var a = 1;
    console.log(arguments);
    var that = this;
    return function(object1,object2){
        //console.log(this);
        console.log(this.arguments);
        console.log(object1);
        console.log(arguments);
        that;
        a=2;
        a = null;
        var value1 = object1[propertyName];
        var value2 = object2[propertyName];
        if(value1<value2){
            return -1;
        }else if(value1>value2){
            return 1;
        }else{
            return 0;
        }
    }
}
//当某个函数被调用时,会创建一个执行环境,相应的作用域链,活动对象。在函数的执行过程中,为读取和写入变量的值,就需要在作用域链中查找变量。
//全局变量的对象始终存在。一般来讲当函数执行完毕,局部活动对象就会被销毁。但闭包不同
console.dir(createComparisonFunction);
var compareName = createComparisonFunction('name');
console.dir(compareName);
var result = compareName({name:'lhao'},{name:'liang'});
console.log(result);
compareNames = null;
//由于闭包会携带包含他的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多。

说明函数执行完成后会销毁arguments  caller等这些

 

闭包与变量

副作用:闭包只能取得包含函数(外围函数)中任何变量的最后一个值。

//闭包与变量
function createFunctions(){
    var result = new Array();
    for(var i=0;i<10;i++){
        result[i] = function(){
            return i;
        }
    }
    return result;
}
var funs = createFunctions();
var val1 = funs[0]();
alert(val1);   //10;
alert(funs[1]());  //10;


function createFunctions(){
    var result = new Array();
    for(var i=0;i<10;i++){
        result[i] = function(num){
            return function(){
                return num;
            };
        }(i);
    }
    return result;
}
var funs = createFunctions();
alert(funs[0]());
alert(funs[1]());
alert(funs[2]());

关于this对象/this对象

this对象是在【运行】时基于 函数的 【执行环境】绑定的。

在全局环境中 this 等于window.

闭包环境的执行环境通常具有全局性, 因此this对象通常指向window。(但要注意一些有关this赋值的情况)

当然通过call()或apply()改变函数执行环境的情况下,this就会指向其他对象。

//每个函数在被调用的时都会自动取得两个特殊变量:this和arguments.内部函数在搜索这两个变量时,只会搜到其活动对象为止。

var name = 'the window';
var object = {
    name:'My object',

    getNameFunc:function(){
        var that = this;
        return function(){
            return that.name;
        };
    }
}
alert(object.getNameFunc()());//my object

//this和arguments也存在同样的问题。


var name = 'the window';

var object = {
    name:'my object',
    getName:function(){
        return this.name;
    }
}
alert(object.getName());//my object
alert((object.getName)());//my object
alert((object.getName = object.getName)());//the window
//有可能意外得改变this的值。

怎么理解最后一个代码含义:

a= b 表达式返回b,所以 :相当于

function(){

    return this.name

}();

//执行环境是全局

内存泄漏

闭包的问题:容易引起内存泄漏,外部的活动对象得不到消除。

//内存泄露
//IE9之前
function assignHandler(){
    var element = document.getElementById('someElement');
    element.onclick = function(){
        alert(element.id);
    }
}
function assignHandler(){
    var element = document.getElementById('someElement');
    var id = element.id;
    element.onclick = function(){
        alert(id);
    };
    element = null;
}

模仿块级作用域

写一个函数表达式(或匿名函数)立即调用;在全局环境中也可以这么写;写在函数中也可以。

(function(){

                            //     这里是块级作用域

})();

 

这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。适用在大型项目多人协作,或者引入了多个插件。

私有变量

严格来说js中没有私有成员的概念; 所有对象的属性都是公有的。可以o.属性;

访问器属性使用 get  set      设置访问有点像私有属性的行为但不是。但是有私有变量的东西,可以利用这个特点 来模仿对象的私有属性,做一些事情。

//p205私有变量
//javascript中没有私有成员的概念;所有对象属性都是公有的。在函数中定义的变量(包括函数的参数,局部变量,在内部定义的其他函数),都可以认为是私有变量,

function MyObject(){
    //私有变量和私有函数
    var privateVariable = 10;
    function privateFunction(){
        return false;
    }

    //特权方法
    this.publicMethod = function(){
        privateVariable++;
        //return privateFunction();
    }

    return function () {
        privateVariable++;
    }
}

var person = new MyObject();
console.log(person);  //如果有return 则是return后的东西;不再是一个对象
// console.dir(person.publicMethod);    //privateVariable: 11
// var pri = person.publicMethod();
// //console.log(pri);
// console.dir(person.publicMethod);   //privateVariable: 11
// var person1 = new MyObject();
// console.dir(person1.publicMethod);  //privateVariable: 10;

var perFun = MyObject();
console.dir(perFun);        //privateVariable : 11;
perFun();
console.dir(publicMethod);  //privateVariable : 11;

var perFun2 = MyObject();
console.dir(perFun2);     //privateVariable : 10;

术语: 有权访问私有变量和私有函数的公有方法称为特权方法(private method)

 

在构造函数中定义特权访问方法优缺点: 每个实例都有会创建同样一组方法。不好!

 

静态私有变量:

在私有作用域中;定义私有变量或函数;创建特权方法。 将特权方法(匿名函数/函数表达式)定义为全局;或者将特权方法定义为全局里边对象的方法。

//p206 静态私有变量
(function(){
    //私有变量和私有函数
    var privateVariable = 10;        // 属于MyObject类型的 静态私有变量
    function privateFunction(){
        return false;
    }

    MyObject = function(){
    };//初始化未声明的变量,总是会创建一个全局变量。

    MyObject.prototype.publicMethod = function(){
        privateVariable++;
        return privateFunction();
    }
})();

var person1 = new MyObject();
console.dir(person1.publicMethod);    //scopes privateVariable : 12
person1.publicMethod();
var person2 = new MyObject();
console.dir(person2.publicMethod);
person2.publicMethod();             //privateVariable : 12

例子2:
(function(){
    var name = '';
    Person = function(value){
        name = value;
    };

    Person.prototype.getName = function(){
        return name;
    }

    Person.prototype.setName = function(value){
        name = value;
    }
})();

var person1 = new Person('zhao');
console.log(person1.getName());//zhao
person1.setName('liang');
console.log(person1.getName());//liang

var person2 = new Person('liang2');
console.log(person2.getName());//liang2
console.log(person1.getName());//liang2
//结:会因为使用原型而增进代码复用,但每个实例都没有自己的私有变量。

模块模式:module pattern

为单例创建私有变量和特权方法。

    name : 'xiaoming',
    method : function () {
        //这是方法的代码
    }
}

var singleton = function () {
    //私有变量和私有函数
    var privateVariable = 10;

    function privateFunction() {
        return false;
    }

    //特权方法/ 公有方法和 属性
    return {
        publicProperty : true,
        publicMethod : function () {
            privateVariable++;
            return privateFunction();
        }
    }
}();
console.dir(singleton.publicMethod);
singleton.publicMethod();

这种模式需要对单例进行某些初始化,同时又需要维护其私有变量时。

//p207模块模式
//前面的模式是用于为自定义类型创建私有变量的和特权方法的。
//模块模式则是为单例创建私有变量和特权方法。单例就是只有一个实例的对象。
var application = function(){
    //私有变量和函数
    var components = new Array();

    //初始化
    //components.push(new BaseComponent());

    //公共
    return {
        getComponentCount:function(){
            return components.length;
        },
        registerComponent:function(){
            if(typeof component == 'object'){
                components.push(component);
            }
        }
    };
}();
//在web应用程序中,经常需要使用一个单例来管理应用程序及的信息。
console.dir(application.getComponentCount);

分析: 每个单例都是Object的实例,单例通常作为全局对象存在的,我们不会将它传递给一个函数。因此没必要使用instanceof操作符

增强的模块模式

适用于:必须是某种类型的实例,同时还必须添加某些属性和方法对其增强。

var singleton = function () {
    //私有变量和私有函数
    var privateVariable = 10;

    function privateFunction() {
        return false;
    }

    //创建对象
    var object = new CustomType();

    //添加特权/ 公有属性和方法
    object.publicProperty = true;

    object.publicMethod = function () {
        privateVariable++;
        return privateFunction();
    }

    //返回这个对象
    return object;
}();


//增强的模块模式
var application = function(){
    //私有变量和函数
    var components = new Array();
    //初始化
    components.push(new BaseComponent());
    var app = new BaseComponent();

    app.getComponentCount = function(){
        return components.length;
    }
    app.registerComponent = function(){
        if(typeof component == 'object'){
            components.push(component);
        }
    }

    return app;
}();

猜你喜欢

转载自blog.csdn.net/u013862108/article/details/81977995