以下更得是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