1.函数概念,声明及调用
- JS中的函数:把一段需要重复使用的代码,用function语法包起来,方便重复调用,分块和简化代码。复杂一点的,也会加入封装、抽象、分类等思想。
- 声明: 三种方式
- 方式一 :普通函数
function fn()
{
console.log('我是一个普通函数');
}
//调用
fn(); // 我是一个普通函数
- 方式二:匿名函数
函数表达式
var func = function(){
console.log('我是一个匿名函数');
func ();//我是一个匿名函数
};
- 方式三:箭头函数
()=>{
console.log('我是一个箭头函数');
}
2.JS预解析机制(变量提升Hoisting)
JS预解析机制(变量提升(Hoisting)):JS在读取到一个script标签(或者一个函数作用域)时,会先进行一个预解析的过程,在这个过程中,会把var声明的变量和function声明的函数体,提升到整个scriptt标签(或者一个函数作用域)最前边去。在预解析完之后,JS才会从上到下一行一行解析代码并执行。
- var在预解析时,会把声明提升到最前边(赋值前打印返回undefined)。只提升声明,不会把赋值过程进行提升。
- function的函数体在预解析时,会把整个函数体提升至最前边。(函数体:function fn(){ console.log(1);})
- 函数表达式(函数表达式:var fn = function(){};)只会提升函数表达式的声明,不会执行(真正执行函数表达式前调用会返回undefined)
- 在预解析时,会先预解析var(包括变量声明和函数表达式的变量声明),把var放在最前面,然后再预解析function,所以当var和function重名时,function会覆盖var;
//JS var变量的预解析
console.log("var变量的预解析:"+a);//undefined
var a = 0;
//此时正确的执行顺序为
var a;
console.log("var变量的预解析:"+a);
a = 0;
//JS函数体的预解析
console.log("函数体的预解析:"+fn()); //函数体的预解析: 我被调用了
function fn(){
console.log("函数");
}
//此时正确的执行顺序为
function fn(){
console.log("函数");
return '我被调用了';
}
console.log("函数体的预解析:"+fn);
//JS函数表达式的预解析
console.log("函数表达式的预解析:"+fnn);//函数表达式的预解析:undefined
var fnn = function(){
console.log("函数表达式");
return '我被调用了';
};
//此时正确的执行顺序为
var fnn;
console.log("函数表达式的预解析:"+fnn());
fnn = function(){
console.log("函数表达式");
};
3.作用域
全局作用域:
- 声明在任何函数以外的变量或者函数体 - 全局变量(全局函数)
- 在浏览器中,默认不写var 就是全局变量
局部作用域 :
- 声明在函数内部的数据,作用域只在函数内部
块级作用域
4.作用域链
作用域链:JS中数据的查找规则。
作用域链查找过程:在JS中我们调用一条数据时,会先在当前作用域进行查找,如果找不到,就从向上找父作用域的数据,还找不到就接着向上,一直找到全局作用域(window对象),window都找不到就报错。
<script>
function fn(){
var b = 0;
return function(){
b++;
console.log(b);
};
}
var f = fn();
console.log(f);//ƒ (){ b++; console.log(b); }
f();//1
f();//2
f();//3
fn()();//1
</script>
//对上面函数的理解
//f 此时等于fn 的返回值
var f = fn();
此时 f = {
b++;
console.log(b);
};
f();//就是执行return 中的函数
//可以看作fn()();
立即执行函数:声明一个函数,并马上调用这个匿名函数就叫做立即执行函数;即立即执行函数是定义函数以后立即执行该函数
5.全局污染(命名冲突问题)
全局变量污染:大家都在全局中写代码,很容易造成命名冲突,导致代码冲突。ES6中代码冲突会直接报错。所以要养成好的习惯不要在全局去声明变量。
<body>
<div id="list"></div>
<div class="memulist"></div>
<script>
var list = document.getElementById("list");
var list = document.querySelector(".memulist");
//命名冲突导致只获取了最后一个dom
//<div class="memulist"></div>
console.log(list);
</script>
</body>
-
结果:发现最后获取的只有一个元素,所以很容易造成代码冲突
-
解决:不要声明全局变量
1.立即执行函数:声明一个函数,并马上调用这个匿名函数就叫做立即执行函数;即立即执行函数是定义函数以后立即执行该函数
(function(){
var list = document.getElementById("list");
console.log(list);
})();
(function(){
var list = document.querySelector(".memulist");
console.log(list);
})(); //结果
6.闭包
1.概念
闭包函数:声明在一个函数中的函数,叫做闭包函数。
闭包:内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回(寿命终结)了之后。
2、特点
让外部访问函数内部变量成为可能;
局部变量会常驻在内存中;
可以避免使用全局变量,防止全局变量污染;
会造成内存泄漏(有一块内存空间被长期占用,而不被释放)
3、闭包的创建:
闭包就是可以创建一个独立的环境,每个闭包里面的环境都是独立的,互不干扰。闭包会发生内存泄漏,每次外部函数执行的时 候,外部函数的引用地址不同,都会重新创建一个新的地址。但凡是当前活动对象中有被内部子集引用的数据,那么这个时候,这个数据不删除,保留一根指针给内部活动对象。
闭包内存泄漏为: key = value,key 被删除了 value 常驻内存中; 局部变量闭包升级版(中间引用的变量) => 自由变量;
4、闭包的应用场景
结论:闭包找到的是同一地址中父级函数中对应变量最终的值
最终秘诀就这一句话,每个例子请自行带入这个结论!!!!!!!!!!!!!
- 例子1
function outerFn(){
var i = 0;
function innerFn(){
i++;
console.log(i);
}
return innerFn;
}
var inner = outerFn(); //每次外部函数执行的时候,都会开辟一块内存空间,外部函数的地址不同,都会重新创建一个新的地址
inner();
inner();
inner();
var inner2 = outerFn();
inner2();
inner2();
inner2(); //1 2 3 1 2 3
- 例子2
function outerFn(){
var i = 0;
function innnerFn(){
i++;
console.log(i);
}
return innnerFn;
}
var inner1 = outerFn();
var inner2 = outerFn();
inner1();
inner2();
inner1();
inner2(); //1 1 2 2
7.this当前执行代码的环境对象
默认情况下:
- 函数外:window
- 函数内:函数中的this指向谁,取决于这个函数是怎么调用的
严格模式下,默认为undefined
作为对象的属性(方法,事件(方法的一种))调用,指向当前对象
其余情况执行window
<script>
//函数外:window
// 函数内:函数中的this指向谁,取决于这个函数是怎么调用的
// 作为对象的属性(方法)调用,指向当前对象
// 其余情况执行window
function fn(){
console.log(this);
}
//直接调用函数,this代表window
console.log("没有作为对象的属性进行调用,而是直接调用:");
fn();//this指向window
//作为对象的属性或方法调用
//作为对象的属性进行调用
console.log("作为对象的属性进行调用:");
document.fn = fn;
document.fn();//this 执行document
//事件里,把this绑定在事件上
console.log("作为对象的属性(事件)进行调用:");
document.onclick = fn;
document.onclick();//this 执行document
//数组里,把函数放到数组里,再由数组调用,此时this指向当前数组
console.log("作为对象(数组)进行调用:");
var arr = [fn,1,2];
arr[0]();//this指向当前数组
//obj对象里
console.log("作为对象(object对象)进行调用:");
var obj = {
fn:fn
};
obj.fn();//this指向object对象
</script>
8.严格模式下的this指向
在script标签最上面加上 ‘use strict’;,加上’use strict’后预解析已经不能使用,会报错。
严格模式下的function指向问题:在严格模式下,function如果不是作为对象的属性和方法被调用(即直接调用方法)就指向undefined。
<script>
'use strict';
function fn(){
console.log(this);
};
console.log("严格模式下,函数直接被调用(没有通过函数的属性或方法被调用,this就指向undefined):");
//严格模式下,函数直接被调用(没有通过函数的属性或方法被调用,this就指向undefined)
//undefined
fn();
//通过函数的属性或方法被调用,就指向被调用的对象
console.log("通过函数的属性或方法被调用,就指向被调用的对象:");
document.onclick = fn;
//document
document.onclick();
</script>
9 .this指向的修改
- 1 function.call()
function.call(this指向谁,参数1,参数2…)调用函数,并修改函数中的this指向;
执行函数的call方法,会调用该函数,并且修改函数中 的this指向;
call中的第0个参数,代表当前函数执行时,函数中的this指向谁
其他参数都是给函数传的实参
注意修改执行为body时,一定要使用document.body
<script>
function fn(a,b){
console.log(this,a,b);
}
//直接执行,this指向window
console.log("直接调用函数,this指向window:");
fn(1,2);//window
//通过call更改当前函数的this指向
//更改this指向为document
console.log("调用函数的call方法,更改this指向document:");
fn.call(document,'a','b');//document
//更改this指向为document.body
console.log("调用函数的call方法,更改this指向document.body:");
fn.call(document.body,'a','b');//body
</script>
结果:
- 16.2 function.apply()
function.apply(this指向谁,[参数1,参数2…])调用函数,并修改函数中的this指向
指向函数的apply方法,会调用该函数,并且修改函数中的this指向;
apply中的第0个参数,代表当前执行时,函数中的this指向谁;
apply中第1个参数是个数组,数组中代表了我们要往函数中传递的参数;且所有参数只能放在一个数组里,有多个数组时,除了第一个,其他数值的参数不会被接收
apply和call唯一的区别在于,call方法直接在方法里传参,而apply是将所有参数已数组形式进行传递;
注意修改执行为body时,一定要使用document.body
<script>
function fn(a,b){
console.log(this,a,b);
}
//直接调用,this指向window
console.log("直接调用,this指向window:");
fn('s','r');
//调用函数的apply方法,更改this指向为document
console.log("调用函数的apply方法,更改this指向document:");
fn.apply(document,['2','4']);
//调用函数的apply方法,更改this指向document.body
console.log("调用函数的apply方法,更改this指向document.body:");
fn.apply(document.body,['2','4']);
</script>
- 3 function.bind()
function.bind(指向,参数1,参数2,…)绑定this指向
调用函数的bind方法,会返回一个绑定了this执行的新函数;
第0个参数是bind返回的新函数的this指向
返回新函数的this指向被绑定了,不能再被更改
新函数的this指向在修改原函数this指向时就已经被绑定,一旦被绑定不能再次修改
总结:调用函数的bind方法,会生成新的函数,绑定的this指向是针对新函数的,新函数this指向被绑定后,不能再继续被绑定(call和apply也不行);如果调用时再传入新的参数,会将新的参数和被绑定的参数进行合并,被绑定的参数会一直存在;而原函数的this指向一直没有变,还可以继续调用bind方法,生成新的函数,同时给新的函数绑定新的this指向
<script>
function fn(a,b){
console.log(this,arguments);
}
//直接调用函数,this指向window
console.log("直接调用函数,this指向window:");
fn(1,2);//window
//使用函数的bind方法
console.log("使用函数的bind方法,返回新的函数:");
var fn2 = fn.bind(document,3,4);
console.log(fn2 == fn);//false 新函数和旧函数不是同一个
console.log("原函数的this指向:");
fn(5,6);//原函数的this指向不变,依然是window,且还可以继续调用bind方法
console.log("新的函数的this指向:");
console.log("如果新的函数调用时传入新的参数,会将绑定的参数和新传入的参数进行合并:");
fn2(7,8);//3,4,7,8 新函数的this指向即原函数绑定的this指向
//新函数的this指向在修改原函数this指向时就已经被绑定,一旦被绑定不能再次修改,且被绑定的参数也不能再被修改
//只是如果调用新函数时传入新参数,会合并两次的参数
console.log("新函数的this指向再修改原函数this指向时就已经被绑定,一旦被绑定不能再次修改:");
fn2.call(window,9,0);//这里即使再次更改this指向,fn2新函数的this指向永远不会再改变
//再次调用fn的bind方法
console.log("再次调用fn的bind方法,返回新的函数:");
var fn3 = fn.bind(document.body,'a','b');
console.log(fn3 == fn);
fn3('c','d');
</script>