6、JavaScript 函数、作用域、闭包

函数

声明式函数

function GetHello(){
      console.log("hello world");  
}

表达式函数

var GetHello = function(){
    console.log("hello world"); 
}
/*此表达式右边为一个匿名函数,即没有名字的函数*/

立即执行函数

此类函数没有声明,在一次执行后即释放,浏览器再也找不到这个函数的引用,适合做初始化工作。

var num = (function Test(a, b, c){
    return a + b + 2*c;
}(1, 2, 3))
/*----------------------------------------------------------------------------------*/
(function(){}())//w3c推荐
(function(){})()//另一种写法

()是执行符号,可以执行表达式。

表达式函数和声明式区别

Fn1();//执行
Fn2();//报错
function Fn1(){
    console.log("声明式")
}
var Fn2 = function(){
    console.log("表达式")
}

js的解析过程分为两个阶段,预处理和执行期。Fn1()会被提升到文档最上面,Fn2()只提升了var Fn2,所以Fn2()时就会报错。

箭头函数

使用场景:一个函数以另一个函数为参数。setTimeout(function(){},100);

const fun = (a,b) => { console.log(a*b) }
//函数名-----参数列表--------函数体
const fun1 = a => { console.log(a*a) }
//如果只有一个参数可把参数列表的括号去掉,没有参数时要写空括号。
const fun2 = a => a*a;
//如果函数体只有一条语句,可把大括号去掉,以这条语句的执行结果作为返回值。
  1. 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
  2. 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
  3. 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
箭头函数的this

箭头函数体内的this对象,就是定义该==函数==(非对象)时所在的作用域指向的对象,而不是使用时所在的作用域指向的对象。(call、bind也无法改变)

var name = 'window'; 
var A = {
   name: 'A',
   sayHello: function(){
       //必须使用函数才能产生作用域?
      var s = () => {
        console.log(this.name)
      }
      return s//返回箭头函数s
   },
   sayHi:()=>{
     console.log(this);
   },
}

var B = {
   name: 'B'
}
let sayHello = A.sayHello();

A.sayHi();//window
sayHello();// 输出A 
sayHello.call(B);// call也改变不了的this,输出A 

函数里面的argument

function Test(){
    for (var i = 0;i<arguments.length;i++){
        console.log(arguments[i])
    }
}
Test(1,2,3);

函数没有定义形参,调用的时候传递实参,函数内部与一个数组叫arguments,arguments会保存调用函数时传递的实参列表,实现了即使没有形参也可以使用参数。

函数调用时有实参有多少个,arguments元素就有多少个。并且对应的实参和arguments元素具有一个映射关系。一个值变了另一个之也会跟着变。如果对应不上了那么他们的映射关系就不成立了。比如说:

function Test(a,b){
    console.log(argument[0]);//1
    console.log(argument[1]);//undefine
    //映射关系
    a=2;
    console.log(argument[0]);//2
    argument[0]=3;
    console.log(a);//3
    /*a和arguments[0]形成映射关系,一个变另一个也变*/
}
    Test(1);

高阶函数:一个函数以另一个函数作为参数,或者返回值是一个函数,这个函数成为高阶函数。

改变this指向

call/apply/bind

作用:改变this指向

区别:后面传的参数不同

call

改变调用者(函数)的this指向,并且执行它。

function Person(name,age){
this.name = name;
this.age = age;
}
var person = new Person("zou",100);
var obj = {}
//Person.call() 没参数等效于 person();
//有参数,改变Person里面的this指向obj
Person.call(obj,"jia",200);//第一个参数用于指向新增的属性在哪个对象

call的实际应用 --> 用于继承

function Person(name,age){
    this.name = name;
    this.age = age;
}
function Student(name,age,tel){
    /*调用别人的函数实现自己的功能*/
    /*相当于,工厂可以全部零件都自己生产,也可以去买半成品(call)再自己加工*/
    Person.call(this,name,age);//调用Person方法,利用call改变Person的this指向Student
    this.tel = tel;
}
var stu = new Student("zou",20,130256);
apply

apply作用与call一样,只是参数的形式不一样。

function Person(name,age){
    this.name = name;
    this.age = age;
}
function Student(name,age,tel){
    Person.apply(this,[name,age]);
    /*apply方法中的第一个参数后面 有且只有 一个数组形式的参数*/
    this.tel = tel;
}
var stu = new Student("zou",20,130256);

apply用于求数组的最大最小值,很是方便

var arr=[1,4,6,2,7,9,5,3]
var maxNum = Math.max.apply(Math,arr)
bind

改变调用者(函数)的this指向,不执行但返回改变后的函数。

 <body>
    <button>按钮</button>
</body>
<script>
    /*点击按钮后禁止,三秒钟后重新开放*/
    var btn = document.querySelector("button");
    btn.onclick = function(){
        this.disabled = true;
        setTimeout(function(){
            this.disabled = false
        }.bind(this),3000)
    }
</script>

预编译

初识预编译

console.log(Test());//输出执行
function Test(){
    return "执行";
}
//预编译使函数的声明整体提升到最上面
console.log(a); //输出undefine
var a = 10;
/*相当于下面-------------------------------------------------------------------------*/
var a;//把声明提升
console.log(a); //输出undefine
a = 10;//赋值留在原地

对于函数 :函数声明整体提升
对于变量 :变量声明提升,赋值没有提升

暗示全局变量

如果一个变量未经声明就直接赋值,那么这个变量为全局对象(window对象)所有。一切声明的全局对象全都是window的属性。

a = 10;// => window.a = 10
var b = 10;// => window.b = 10
function(){
    var c = d = 10;  
    //d是未经声明就赋值的变量,所以 => window.d = 10,c不在全局作用域内所以不归window所有。
}
console.log(a);//访问全局变量a,其实是访问window.a

预编译

预编译发生在函数执行前的前一刻

函数预编译
  1. 创建AO对象(Activation Object,执行期上下文)
  2. 找形参和变量声明,将形参的名和变量声明的名作为AO对象的属性名,值为undefined
  3. 将形参和实参相统一
  4. 找函数里面的函数声明,值赋予函数体
function fn(a){
    console.log(a);
    var a = 123;
    console.log(a);
    function a(){};
    var b = function(){}
    console.log(b);
    function d() {};
}
fn(1);//看看打印的都是啥
/*最终的AO
AO={
    a:function a(){...},
    b:undefined,
    d:function(){...}
}
执行期才把赋值操作执行
*/
函数预编译过程
全局预编译
  1. 生成一个GO对象(Global Object),这个Go对象其实是上文提到的那个window对象
  2. 找变量声明,将变量的名作为Go对象的属性名,值为undefined
  3. 找函数声明,值赋予函数体

先全局预编译,后函数预编译,函数预编译直接忽略if,for等判断语句,里面有声明也要提升.

let a = 10;
console.log(b,c);//undefined undefined
if(a==0){
  var b=20;
}else{
  var c=30;
}

可见if else语句中的var语句被提升到了外面。事实上if、else if、else、for、switch、while之类(除function外)的都会这样。

作用域

作用域

每一个javascript函数都是一个对象,对象中有一些属性我们可以访问,有一些不可以,这些属性仅供javascript引擎存取,[[scope]]就是其中一个。[[scope]]指的就是我们所说的作用域,其中储存了运行期上下文的集合。

运行期上下文

当函数执行时,会创建一个成为执行期上下文的内部对象。一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是不一样的,所以多次调用会导致创建多个执行上下文,当函数执行完毕他所在的在执行上下文就会被销毁。

变量查找

一个函数在查找变量时,会从作用域顶端依次向上查找。

作用域链

[[scope]]中所存储的执行其上下文对象的集合,这个集合呈链式链接,我们把这种链式链接叫做作用域链。

例子

function a(){
    function b(){
        var b = 234;
    }
    var a = 123;
    b();
    console.log(a);
}
var glob = 100;
a();
查找变量的方式

b的[[scope]]里面有三个对象,他自己的AO在最顶端0位置,全局GO在最底端。当它要寻找变量时,在0位置的AO找不到,会去找下一位的AO,找到则使用,找不到则继续下一位。

b中a的AO对象,与a自 身的AO对象是同一个对象,b只是保存了a的AO对象的引用,指向那个对象。

闭包

内部函数保存到外部,必定形成闭包。闭包会导致原有的作用域链不释放,造成内存泄漏。

函数和对其周围状态(lexical environment词法环境)的引用捆绑在一起构成闭包(closure)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在 JavaScript 中,每当函数被创建,就会在函数生成时生成闭包。

function a(){
    function b(){
        //定义b()
        var bbb = 234;
        console.log(a);
    }
    var aaa = 123;
    return b;//返回b()
}
glob = 100;
var demo = a();
demo();//执行b
  • a()执行时b()被定义但未执行,a()执行完毕后return b赋值给demo,销毁他自己的执行期上下文AO。
  • 由于b()未执行所以并没有生成他自己的执行其上下文,所以只有a的AO和GO。
  • 即使a()执行完毕后销毁了,但是b有被存起来,而b中存起来的跟a未销毁时是一样的,所以此时demo中可以访问到a()的属性aaa。
闭包查找变量过程

例子

function test(){
    var arr = [];
    for(var i = 0; i < 10; i++){
        arr[i] = function(){
            console.log(i);
        }
    }
    return arr;
}
var myArr = test();
    for(var j = 0;j < 10; j++){
        myArr[j]();
    }
//输出10个10
/*
因为myArr里面存的是个函数访问的是test的AO,test执行是i最后赋值为10,这个AO里面存的i=10,所以会打印10
每一个函数都与test形成闭包,但是test里面的i已经固定为10呢,所以 console.log(i)一定打印10
*/

但我非要输出0-9呢?

解决(important!)-->使用立即执行函数

//使用立即执行函数
function test(){
        var arr = [];
        for(var i = 0; i < 10; i++){
            (function(j){
                arr[j] = function(){
                    console.log(j);
                }
            }(i));
//把每次循环的i值赋值给立即执行函数,由于立即执行函数在读取的时候就会执行(其他函数读取时不执行),所以i的值可以被保存下来
        }
        return arr;
    }
    var myArr = test();
    for(var j = 0;j < 10; j++){
        myArr[j]();
//此时数组里面的函数打印的是一个真实的数,而不是test的OA对象的属性
    }

闭包的作用

1、实现公有变量:函数计数器

function Count(){
    var count = 0;
    function add(){
        count++;
        console.log(count);
    }
    function sub(){
        count--;
        console.log(count);
    }
    return [add,sub];
}
var arr = new Count();
var add=arr[0];
var sub=arr[1];
add();//1
add();//2
add();//3
sub();//2

2、可以做缓存(存储结构):eater

function eater(){
    var food = "";
    var obj = {
        eat : function(){
            console.log("我在吃" + food);
            food="";
        },
        push : function(myFood){
            food = myFood;
        }
    }
    return obj;
}

var eater1 = eater();
eater1.push("苹果");
eater1.eat();

3、可以实现封装,属性私有化:Person(高级使用)

function f1(n) {
  return function () {
    return n++;
  };
}
var a1 = f1(1);
a1() // 1
a1() // 2
a1() // 3
var a2 = f1(5);
a2() // 5
a2() // 6
a2() // 7
//这段代码中,a1 和 a2 是相互独立的,各自返回自己的私有变量。

4、模块化开发、防止污染全局变量(见命名空间--使用闭包)

闭包的危害

无意间形成的闭包会导致浏览器占用内存无法释放,大量的闭包会使得浏览器卡顿。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,922评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,591评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,546评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,467评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,553评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,580评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,588评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,334评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,780评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,092评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,270评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,925评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,573评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,194评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,437评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,154评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352