函数
声明式函数
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;
//如果函数体只有一条语句,可把大括号去掉,以这条语句的执行结果作为返回值。
- 不可以当作构造函数,也就是说,不可以使用
new
命令,否则会抛出一个错误。- 不可以使用
arguments
对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。- 不可以使用
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
预编译
预编译发生在函数执行前的前一刻
函数预编译
- 创建AO对象(Activation Object,执行期上下文)
- 找形参和变量声明,将形参的名和变量声明的名作为AO对象的属性名,值为undefined
- 将形参和实参相统一
- 找函数里面的函数声明,值赋予函数体
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(){...}
}
执行期才把赋值操作执行
*/
全局预编译
- 生成一个GO对象(Global Object),这个Go对象其实是上文提到的那个window对象
- 找变量声明,将变量的名作为Go对象的属性名,值为undefined
- 找函数声明,值赋予函数体
先全局预编译,后函数预编译,函数预编译直接忽略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、模块化开发、防止污染全局变量(见命名空间--使用闭包)
闭包的危害
无意间形成的闭包会导致浏览器占用内存无法释放,大量的闭包会使得浏览器卡顿。