JavaScript函数+闭包(四)


以下更得是JavaScript函數以及闭包等内容,有需要的可参考前两篇博客, 这里主要记得是HTML和css部分这里主要记的是js最基础部分


函数

函数是由事件驱动的或者当它被调用时执行的可重复使用的代码块。

  • 函数就是包裹在花括号中的代码块,前面使用了关键词 function,函数名functionname
function functionname()
{
    // 执行代码
}
  • 当调用该函数时,会执行函数内的代码。
  • 可以在某事件发生时直接调用函数(比如当用户点击按钮时),并且可由 JavaScript 在任何位置进行调用。
  • function必须小写
  • 函数声明的方式总的有两种,一种是函数声明,一种是函数表达式,虽然都可以声明函数,但其实作用是不一样的
带参数的函数
  • 向函数传值,这些值被称为参数
  • 参数用,分隔,参数可在函数内使用
  • 参数在函数内不要var,直接写入
    变量和参数必须以一致的顺序出现。第一个变量就是第一个被传递的参数的给定的值。
<button onclick="myFunction('Harry Potter','Wizard')">点击这里</button>
<script>
function myFunction(name,job){
    alert("Welcome " + name + ", the " + job);
}
</script>

在这里插入图片描述

return

  • 通过return语句将函数值返回调用他的地方
  • 使用return时,函数会停止执行,并返回指定的值
function functionName(target){
	//隐式调用 +自动转成number型
	return +target;
	
}
var num=functionName('123');
console.log(typeof(num)+" "+num); 

在这里插入图片描述
仅仅希望退出函数时

function myFunction(a,b)
{
    if (a>b)
    {
        return;
    }
    x=a+b
}

如果 a 大于 b,则上面的代码将退出函数,并不会计算 a 和 b 的总和
插入几个小练习:

  • 写一个函数。告知你选定的小动物叫声
function scream(animal){
	switch(animal){
		case "dog":
			document.write('wang');
			return;
		case "cat":
			document.write("miao");
			return;
			}
	}

在这里插入图片描述

  • 实现加法计数器功能
function add() {
	var sum = 0;
			for (var i = 0; i < add.arguments.length; i++) {
					sum += add.arguments[i];
				}
				return sum;
			}
alert(add(143,51,21,97));

在这里插入图片描述

arguments.length为函数实参个数
这里了解一下arguments的用法

  • 定义·一组函数,输入数字,逆转输出汉字形式
function sum(){
	var num = window.prompt("请输入你的值:");

	var str = "";

   	for (var i = num.length-1;i >= 0; i--) {
   	    str	+=transfer(num[i]);
	}

    document.write(str);
} 

function transfer(target){
	switch(target){
		case "1":
		  return "壹";
		case "2":
		  return "贰";
	    case "3":
		  return "弎";
		case "4":
		  return "四";
		case "5":
		  return "伍";
		case "6":
		  return "陆";
		case "7":
		  return "七";
		case "8":
		  return "八";
		case "9":
		  return "玖";
	}
}

sum();

在这里插入图片描述在这里插入图片描述

  • 写一个函数,实现n的阶乘
function n(x) {
            if(x == 1) {
                return 1;
            } else {
                return x * n(x - 1);
            }
        }
       
        alert(n(5));

在这里插入图片描述

作用域

作用域是可访问变量的集合。

  • 在 JavaScript 中, 对象和函数同样也是变量。
  • 在 JavaScript 中, 作用域为可访问变量,对象,函数的集合。
  • JavaScript 函数作用域: 作用域在函数内修改。
局部变量

变量在函数内声明,变量为局部作用域,只能在函数内部访问。
局部变量在函数开始执行时创建,函数执行完后局部变量会自动销毁。

// 此处不能调用 carName 变量
function myFunction() {
    var carName = "Volvo";
    // 函数内可调用 carName 变量
}
全局变量

变量在函数外定义,即为全局变量。全局变量有 全局作用域: 网页中所有脚本和函数均可使用。

var carName = " Volvo";
 
// 此处可调用 carName 变量
function myFunction() {
    // 函数内可调用 carName 变量
}

如果变量在函数内没有声明(没有使用 var 关键字),该变量为全局变量。

// 此处可调用 carName 变量
 
function myFunction() {
    carName = "Volvo";
    // 此处可调用 carName 变量
}
变量生命周期
  • JavaScript 变量生命周期在它声明时初始化。
  • 局部变量在函数执行完毕后销毁。
  • 全局变量在页面关闭后销毁。
HTML 中的全局变量
预编译
  • js运行时会进行三件事:1语法分析 2.预编译 3.解释执行

  • 语法分析会在代码执行前对代码进行通篇检查,以排除一些低级错误

  • 预编译发生在函数执行的前一刻
    小知识穿插:

imply global 暗示全局变量:即任何变量,如果变量未经声明就赋值,此变量就为全局对象(window)所有,所有数据变量都属于 window 对象
我们先不定义直接赋值

a=123

此时a会被认为是全局的一个对象,即window所属的值
相当于

`window.b  =123;`

window就是全局
我们这里声明一个变量a并赋值

var a=123

相当于

windows.a=123;

也就是

a=123

对预编译的浅显理解:
在这里插入图片描述

  • 在函数执行前函数声明(function(){})会整体提升至逻辑的最前方
 <script>
   test();
   functiont  test(){
   console.log('a');

  }
</script>

输出的结果为: a

  • 变量的声明提升到函数调用前面,不是赋值,只是声明
 <script>
  
    console.log(a);
    var a = 123;
    
</script>

输出结果为:undefined;

  • 函数声明才存在变量提升
function a(){};,
var b =function (){};//不会提升

预编译的四部曲:

    1.创建AO对象,Activation Object,函数执行生成了存储空间库
    2.找形参和变量声明,将变量和形参名作为AO对象属性名,值为undefined
    3.将实参值和形参统一
    4.在函数体里面找函数声明,值赋予函数体

重要性是4>3>2>1
举个例,形参变量实参一样情况下

function fn (a) {
  console.log(a);

  var a = 123;

  console.log(a);
  
  function a(){};

  console.log(a);

  var b =function (){};

  console.log(b);
}
fn(1);

1.创建AO对象,我们隐式的在函数中创建了一个AO的对象来盛放函数中的变量,此时对象中并没有值;

AO{ 
}

2:找形参和变量声明,将变量和形参名作为AO()属性名,值为undefined

AO{
 a:undefined
 b:undefined
}

3.将实参值和形参值统一,此时将实参带入函数中,fn(1),因此AO中a 为 1

AO{
 a:1 
 b:undefined
}

4.在函数体里找函数声明,值赋予函数体,由于在函数中有 function a() {} ,这一函数因此此时AO中 a = function a() {}

AO{
  a:function a(){}
  b:undefined
  d:function d(){}
}

预编译完后进行执行:

  • 一句一句执行,执行第一句console.log(a);那么,会在AO对象中调取a,在AO对象中a等于functiona(){},那么就输出function a(){} 函数声明
  • var a = 123,var a 已经被提升了,所以不用管,直接执行a = 123,所以,在AO对象中,a变成了123
  • AO对象变成123后,执行console.log(a);现在a等于123,那么就是输出123,
  • 第四句是函数,由于函数已经被提升了,跳过
  • 第五句是console.(a),所以输出还是123
  • 第六句是var b = function (){},所以就要把在AO对象中的b的属性改为function(){}
  • 第七句b的输出就是function(){},第八句直接被提升了,所以不用读了

结果为:

function a(){}
123
123
function (){}

全局预编译:
生成GO对象 global object GO==window

<script>
    global = 100;
    function fn(){
    console.log(global);
    global=200;
    console.log(global);
    var global=300;
}
    fn();
    var global;
</script>

结果为:

undefined
200

执行过程:

  • 首先生成 global:undefined
  • 执行第一行:global = 100
  • 执行fn()时生成AO{ global = undefined; }
  • 先生成GO再生成AO
    js是块编译,即一个script块中预编译然后执行,再按顺序预编译下一个script块再执行
    此时上一个script块中的数据都是可用的了,而下一个块中的函数和变量则是不可用的。
立即执行函数

原理: 只有表达式才能被执行
没有函数声明,在执行一次过后即被释放,除了这点与其他函数没有区别,适合做初始化工作。
立即执行函数也有预编译过程;
立即执行函数写法:

(function (){
}())  //推荐
(function  (){
})()

函数会将函数变成表达式

function test (){
var a=1;
}

我们在调用test的时候可直接使用
在这里插入图片描述
加个括号

function test (){
var a=1;
}()

在这里插入图片描述
这是函数声明,因为只有表达式才能够被(括号)执行
而上面的式子是函数声明,不是表达式。

var test = function(){
console.log(123");
}()

这就是函数表达式,能被立即执行符号执行的表达式,再被立即执行之后,就变成了立即执行函数,执行完就销毁

添个加号或者-号就成表达式了:

+ function test (){
document.write("abc");
}()

在这里插入图片描述

- , !,&&(要能运行到函数) ,|| ,“+”
加上这些符号就变成了立即执行函数

括号也算数学表达式
所以:

(function functionname (){
})

函数声明加上一对括号变成函数表达式

(function functionName (){
})()

加上一对括号让他执行。
把括号放在里面。

(function functionName (){
console.log('a');
}())

先执行外面的括号,变成表达式,然后再执行里面的表达式
根据立即执行函数的定义,发现functionName在使用后已经没有意义了;
在这里插入图片描述
接着我们去掉函数名,就是立即执行函数了

形式如下:由下图可见执行完之后函数已经被销毁了

 (function functionName(){
	var a=1;
	var b=2;
    document.write(a+b);
	
}())

在这里插入图片描述
与其他函数一样,可以传递实参和形参

(function functionName(a,b){
    document.write(a+b);
	
}(1,2))

在这里插入图片描述

var num =(function test(a,b){
	var c;
	c=a+b;
	return c;
}(1,2))

在这里插入图片描述

闭包

下面是几个理解:
闭包是将函数内部和函数外部连接起来的桥梁

闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成定义在一个函数内部的函数。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。

Javascript允许使用内部函数—即函数定义和函数表达式位于另一个函数的函数体内
这些内部函数可以访问它们所在的外部函数中声明的所有局部变量、参数和声明的其他内部函数。
当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包。

只要将内部函数传递到所在的词法作用域以外,它都会持有对原始作用域的引用,无论在何处执行这个函数都会使用闭包。

// JavaScript Document
function test(){
        var arr = [];
        for(var i = 0 ; i<10;i++)
        {
              arr[i] = function(){
                    document.write(i);
              }
        }
        return arr;
    }
    var  myArr =  test();
    for(var j = 0 ; j<10;j++)
        {
              myArr[j]();//结果为10个10
        }

输出
在这里插入图片描述
记一下要注意的几点
函数体不是立刻执行,函数引用不打印函数体内语句也就是说 arr[i] = function(){
document.write(i); 执行位置不是他现在定义的位置,而是在函数调用的地方 myArrj;
当函数调用完成才会执行输出i
在函数外部可调用内部函数 return 函数与test()形成了闭包 都能用test()ao执行上下文arr随着for循环变化,for循环会产生十组数据,也就是内部函数执行10次。
十次结束后returnarr,将十组数据保存在外部,在外部执行,所以10个方法的i是共用的,在执行完test()后,此时的i相当于缓存在myArr中,且存入的值是test()的AO销毁前值,在销毁之前i存储的值是10
利用闭包解决闭包

那我们怎么输出0-9呢

function test(){
        var arr = [];
        for(var i = 0 ; i<10;i++)
        {
            (function(j){//此处隐式使用闭包的存储功能,存储的内容为j的数据
              arr[j] = function(){
                    document.write(j);
               }
            }(i));//使用立即执行函数的实参,当执行myArr[i]时,查询的变量i即就转化为对应的立即执行函数的形参k
        }
        return arr;
    }
    var  myArr =  test();
    for(var j = 0 ; j<10;j++)
        {
              myArr[j]();
        }

在这里插入图片描述
这里我们引入立即执行函数,循环十次的同时每个函数保存在arr里面的j都不同。
立即执行函数读到马上就会执行,读后销毁是相对于引用销毁,值依然被保留。

闭包将内部函数作为返回值输出,然后用一个变量去接收外部函数的调用结果,即外部函数的返回值,通过调用这个变量就可以调用该函数了,如果是返回多个函数,则将其存于数组中,根据数组下标找到对应的函数。

找了几个小练习再理解一下

<ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
</ul>
function test(){
var lis = document.getElementsByTagName("li");
for (var i = 0; i < lis.length; i++) {
(function(j){
    lis[i].onclick = function() {
        console.log(i);
    }
   }(i))
 }
}
test();

这段代码本来像实现的功能是点击li标签输出的值为点击li的顺序值,但是结果是每次输出都是3…
其实这个跟上段代码有相同之处,我们知识给li标签定义了点击属性,但是函数体不会立刻执行。
只有点击li时,函数执行,函数中的变量i没有在函数中定义,根据js的作用域链原则,会继续向上级作用域查询,因此找到了全局作用域中的i,这时for循环已经执行结束,此时全局作用域中的i已经变为了3,故打印出来的当然是3

我们同样用闭包解决这个问题

var lis = document.getElementsByTagName("li");
for (var i = 0; i < lis.length; i++) {
    lis[i].onclick = (function(i) {
       var clickLi = function() { 
           console.log(i);         
       }
       return clickLi;
    })(i)
}

在for循环执行时,立即将当前的i值作为形参传入clickLi中,而形参默认为函数内的局部变量,函数外部是不能对i进行操作的。所以,当点击li时,执行clickLi函数时,打印出来的则是li的顺序值。

闭包的用途
  • 可以重复使用变量,并且不会造成变量污染

  • 全局变量可以重复使用,但是容易造成变量污染。局部变量仅在局部作用域内有效,不可以重复使用,不会造成变量污染。闭包结合了全局变量和局部变量的优点。

  • 可以用来定义私有属性和私有方法。

使用闭包的注意点
  • 比普通函数更占用内存,会导致网页性能变差,在IE下容易造成内存泄露。
什么是内存泄露

首先,需要了解浏览器自身的内存回收机制。
每个浏览器会有自己的一套回收机制,当分配出去的内存不使用的时候便会回收;内存泄露的根本原因就是你的代码中分配了一些‘顽固的’内存,浏览器无法进行回收,如果这些’顽固的’内存还在一直不停地分配就会导致后面所用内存不足,造成泄露。

闭包造成内存泄漏

因为闭包就是能够访问外部函数变量的一个函数,而函数是必须保存在内存中的对象,所以位于函数执行上下文中的所有变量也需要保存在内存中,这样就不会被回收,如果一旦循环引用或创建闭包,就会占据大量内存,可能会引起内存泄漏

内存泄漏的解决方案

造成内存泄露的原因:

  • 意外的全局变量(在函数内部没有使用var进行声明的变量)

  • console.log

  • 闭包

  • 对象的循环引用

  • 未清除的计时器

  • DOM泄露(获取到DOM节点之后,将DOM节点删除,但是没有手动释放变量,拿对应的DOM节点在变量中还可以访问到,就会造成泄露)

闭包的使用场景

闭包的应用场景
闭包极其应用场景

😃

再看一道题

var f=(
function f(){
	return "1";
},
	function g(){
		return 2;
	}
)();
console.log(typeof(f));

问输出什么?
在这里插入图片描述

逗号运算符,它将第一个的参数,再计算第二个的参数值。然后返回最第二个参数的值
在看一个

var x=1;
if (function f() {} )){
x+=typeof f;
}
console.log(x);

输出 1undefined
if条件里面能执行,括号里面函数就成为表达式,不是函数定义 f就没得了
任何一个变量未经定义放在typeof不报错 返回字符串类型undefined
要不是字符串类型加起来就是非数 NaN

在这里插入图片描述

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

周偏偏偏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值