目录
函数
通过函数可以封装任意多条语句,而且可以在任何地方、任何时候调用执行。在javascript里,函数即对象,程序可以随意操控它们。函数可以嵌套在其他函数中定义,这样它们就可以访问它们被定义时所处的作用域中的任何变量。
9.1 函数概述
– 一处定义,处处调用;
– 如果把函数作为一个对象的属性,则称为方法;
– 每次调用函数会产生一个this:谁调用这个函数或者方法,this就指向谁;
– 函数就是对象,可以给他设置属性或方法;
9.1.1 函数定义
总共有三种函数定义的方式:函数声明语句、函数表达式、内置构造函数。
函数被调用一次,就会重新执行一次函数体中的所有代码
函数声明语句
function functionName(parameters) { //执行的代码 }函数声明后不会立即执行,会在我们需要的时候调用到。函数被调用一次,就会执行函数体中的所有代码
Function内置构造函数
var functionName = new Function("parameters","执行的代码") //注意引号不可省
这种方式不推荐,无法写递归。
用Function()构造函数创建一个函数时并不遵循典型的作用域,它一直把它当作是顶级函数来执行。很多时候无法获取局部变量,作用域始终是全局作用域 。所以,在 JavaScript 中,很多时候,需要避免使用 new 关键字。
函数可以嵌套在其他函数里面,也就是在函数里面可以定义函数
被嵌套的函数可以访问嵌套他们的函数的变量或参数。
函数的调用,是将实参取值后再传入函数进行执行,意思是是把a的数据值传进去,而不是传入变量名
//函数的写法其实有两种:声明函数、定义一个函数
//声明会提升,定义不会提升
//1、声明函数:直接在作用域写一个函数
//(浏览器运行的初期就会运行此函数)
function fn(){
}
// 2、定义一个函数:直接创建一个函数 把它当做数据一样
// (运行到此行才会运行此函数)
var obj={
say:function(){}
} //或者
var a=function(){}
var arr=[function fn(){},200,300] //定义式函数
arr[0]();
(function(){})() //定义式函数
采用一个内置对象Function来创建(动态函数)
var 函数名 = new Function("参数列表", "函数体");
var a; var b=1; var f1 = new Function("a,b","var a=20;console.log(a,b)") //20 und f1(10)
9.1.2 函数调用
javascript一共有4种调用模式:函数调用模式、方法调用模式、构造器调用模式和间接调用模式。 每种方式的不同在于 this 的初始化。
1、函数调用方式
a, this是指向Window的
b, 返回值是由return语句决定的,如果没有return则表示没有返回值
2、方法调用模式
先定义一个对象,然后在对象的属性中定义方法,通过obj.say来执行方法。
a, this 是指向调用该方法的对象
b, 返回值还是由return语句决定,如果没有return表示没有返回值
3、构造器调用模式
a, this是指向构造函数的实例
b, 如果没有添加返回值的话,默认的返回值是this
c, 如果有返回值,且返回值是简单数据类型(Number,String,Boolean··)的话,最后仍回返回this
d, 如果有返回值,且返回值是复杂数据类型(对象)的话,最终返回该对象
4、间接调用模式
也称之为“apply、call调用模式” 或 “上下文调用模式”。
a, 传递的参数不同,this的指向不同,this会指向传入参数的数据类型
b, 返回值是由return决定,如果没有return表示没有返回值。
注:
1、函数的调用 最后一定会生成一个结果(数据)
没有写返回值:调用的结果就是undefined
写了返回值: 就是返回的数据
函数是否必须写返回值(不是)
函数是不是一定有返回值(是)2、return 关键字后面紧跟的是一个表达式
return 一旦运行了 函数体后来无论还有多少代码 都不会执行了 直接函数生成结果
不写return 或者 return紧跟着的后面不写代码的话 就是一个空表达式,会生成undfined
return //这个后面没有紧跟着写代码,就会生成undefined
re;return( //这样写的话就算是紧跟着有表达式
a+20)return a //这样就算是a+20
+203、fn是否能访问自己
fn标识符的问题,因为它是函数定义时的名字,函数体内部可以直接访问
var obj={say:function fn(n){ }
fn(n-1) //自己调用自己。上面函数是这样function fn,有名字就可以调用
say(n-1) //会报错。这是访问对象里面的方法
}4、argumrnts 和 arguments.callee( )
//arguments:实参-->调用时传入的数据(两种写法:数据直接量、变量)
定义式的函数可以是匿名的函数,可以没有名字
arguments.callee 运行的函数的对象:在自己函数内部可以自调用,自己还没有写函数名,就可以:arguments.callee( )5、对象的深拷贝 (js几个常考面试题之一)
var o2=copy(obj)
//o2跟obj一模一样,o2自己和它内部的多余引用数据不能相等
9.1.3 函数提升
升提升(Hoisting)是 JavaScript 默认将当前作用域提到前面去的的行为;
提升应用在变量的声明与函数的声明。 使用表达式定义函数时无法提升!
因此,函数可以在声明之前调用。
变量的隐式提升:
隐式操作:把var 修饰的变量名提前声明
每一个作用域在运行时,js引擎会先把作用域内部的关键字隐式提前扫描 并声明 ,但不赋值
函数隐式提升:
变量值提升声明,函数提升的是整个函数体
定义式函数不可以提升(var a =fn () {})、声明式函数可以提升(function fn () {})
同名标识符提升
变量变量同名时:依次提升,提升会被覆盖,然后就按顺序执行
函数和函数同名时:依次提升,提升会被覆盖,然后就按顺序执行
总结---形实函运
在一个作用域的代码运行的时候 js引擎会执行代码的过程有一个执行流程
1.先隐式提升当前作用域内部的所有形参变量和局部变量 (只是声明提升,不提升赋值)
2.再把实参赋值给形参变量
3.然后执行函数的隐式提前声明
4.再按照代码顺序运行代码
9.1.4 自调用函数
(1) 函数表达式(定义式)可以 "自调用",称之为自调用表达式。如果表达式后面紧跟 () ,则会自动调用: var fn=function () { } ();
强制运算符()
//方式一,调用函数,得到返回值。强制运算符使函数调用执行
var test1 = (function(x,y){ alert(x+y); return x+y; }(3,4));
//方式二,调用函数,得到返回值。强制函数直接量执行再返回一个引用,引用在去调用执行
var test2 = (function(x,y){ alert(x+y); return x+y; })(3,4);
(2) 不能自调用声明的函数!
- 可通过添加括号,来说明它是一个函数表达式:
(function fn ( ) { } ) ( ) ;
- 加上void,即忽略返回值。 让 JavaScript 引擎把一个function关键字识别成函数表达式而不是函数声明
void function fn ( ) { } ( );
语法总结:只有函数表达式才能被执行符号()执行。所以
+ function test(){}(); //正常打印出a,因为正号+将函数声明转化成了函数表达式 //还可以用负号-、叹号!等(*和/号不可以) //其实上面“函数表达式”自调用的写法就是通过等号=将函数声明转化成了函数表达式 //总之,匿名函数自动执行,多种写法,只要不要function开头,开头加上不报错的符号就行
自调用函数特点1——函数自调用完成,函数自动被销毁。
自调用函数也可称之为”立即执行函数“,函数执行完函数空间就被释放,不能再被访问,但函数返回值可以被保存。故这种调用方法多是针对初始化功能的函数,即函数只执行一次的情况。
(function abc(){ console.log("hello") }()) abc(); //函数自调用执行之后(打印出字符串hello),报错abc is not defined上例验证了,被立即执行的函数,其函数名称就会自动被忽略,均不能再通过函数名再次被调用。所以,在写立即执行函数时就没有必要写函数名称。
自调用函数特点2——逗号运算符,即返回逗号语句最后一个表达式的值。
var f=( function f(){ return "1"; }, function g(){ return 2; } )(); console.log(typeof f); // number 结果是2
9.1.5 函数名后的多个括号
f()意思是执行f函数,返回子函数 f()()执行返回的子函数,返回孙函数 f()()()执行返回的孙函数 //"函数调用"这个表达式的值始终由函数返回值决定但注意,如果想这样执行,函数结构必须是这样,f的函数体里要return 子函数,子函数里要return 孙函数,如果没有return关键字,是不能这样连续执行的,会报错。
9.2 函数参数
函数不介意传递进来多少个参数,也不在乎传进来的参数是什么数据类型,甚至可以不传参数。
函数参数分为两类:函数显式参数(Parameters)与隐式参数(Arguments)
1、显式参数(Parameters)
function fn(a,b,c) { } fn(1,2,3)函数显式参数在函数定义时列出(即形参)。
函数调用未传参时,参数会默认设置为: undefined。
2、隐式参数(Arguments)
function fn( ) { } fn(1,2,3)
JavaScript 函数有个内置的对象 arguments 对象。
argument 对象包含了函数调用的参数数组(实参数组)。
arguments:
arguments(参数们),代表实际传入函数的参数的列表(类数组)
函数的length属性代表的是形参的个数(笔试题)
arguments对象与传入参数的映射规则:
//arguments对象与形参是相互独立的,但又存在映射规则:
//当传入参数与形参个数相等时,arguments对象与形参才是一一对应的;
//当传入参数与形参个数不等时,arguments对象与有传入参数的形参才存在映射规则。
特殊情况1:同名形参
在非严格模式下,函数中可以出现同名形参,且只能访问最后出现的该名称的形参。
function add(x,x,x){ return x; } console.log( add(1,2,3) ); //3 //在严格模式下,出现同名形参会抛出语法错误
特殊情况2:参数个数
当实参比函数声明指定的形参个数要少,剩下的形参都将设置为undefined值。可能会影响程序的执行逻辑,但可以解决:设计函数时提前预设。
当实参多于形参,则只使用有效的实参,多出部分没影响。多的会保存在arguments里面,没有被使用。
9.3 js的预编译
js完成解释执行分为三个步骤:1.语法分析;2.预编译(全局预编译、函数预编译);3.执行语句。
9.3.1 函数预编译
第一步:检查
语法分析。符号、大括号等语法检查;
第二部:函数预编译 (创建AO对象+形实函运)
函数调用了以后,在运行代码之前,会生成一个对象:执行期上下文对象。函数每次调用都会生成对象。
变量声明提升,function函数声明整体提升;发生在函数执行的前一刻(实际过程如下):
(1) 创建AO对象--Activation Object(执行期上下文):AO{ }; (2) 找函数内部的形参变量和局部变量声明,将变量和形参名作为AO属性名,即变量提升过程,值为undefined; (3) 将实参的值放到形参中去 (4) 在函数体里面找函数声明,值赋予函数体
9.3.2 全局预编译
”全局“即从页内js的script 的开始标签到结束标签,从页外js文件的第一行到最后一行。
全局预编译过程与函数预编译过程大致相似,只是全局上无形参、实参的概念。
1、生成一个GO对象--Global Object{},GO===window 2、变量提升:把所有的全局变量,设置为GO的属性名 3、函数提升:把所有的函数名作为GO的成员名,把函数体赋值给这个成员 全局预编译还有一步-->不同的环境中运行js代码不一样 GO对象的成员全部浅拷贝给环境对象window node.js环境中没有这一步 拓展知识点----关于访问成员问题 console.log(a)-->访问的是GO对象的成员 console.log(window.a) 不报错,原型链没有就返回undefined
9.3.3 作用域
作用域: 指一个变量它在哪些代码范围能够被使用,这些地方就是变量的作用域
对象的大括号没有作用域的说法 ,作用域只针对于函数的大括号
在es5中 函数:
代码块内部的代码 可以访问形参变量 也可以访问外部的变量(全局) 就近优先
函数外面的代码不能直接访问函数内部的变量
全局变量(在内部和外部都可以使用)==>变量会在程序运行时 把它设置为window对象的属性
局部变量 :就是函数内部能使用 外部不能使用的变量( var,形参)
函数是一个引用数据,标识符可以在任何作用域去引用一个函数 但是:
函数运行时的作用域在哪里? 函数在生成(定义和声明)时 所在用在的作用域
函数运行时 是在 写函数代码的地方运行代码 不是在调用代码的地方运行代码
函数每次运行的时候都会在自己声明的那个作用域重新运行
9.3.4 作用域链
执行期上下文:
当函数执行时,会创建一个称为执行期上下文的内部对象。一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,当函数执行完毕,它所产生的执行上下文被 "销毁"。
函数执行前一刻所产生的AO对象
[[scope]]:
每个js函数都是一个对象,对象中有些属性我们可以访问,但有些不可以,这些属性仅供js引擎存取,[[scope]]就是其中一个。
function test() { } 我们可以访问的函数属性(如:test.length/test.prototype); 我们不能访问但着实存在的函数属性(如:test.[[scope]])[[scope]]指的是我们所说的作用域,其中存储了运行期上下文的集合。
作用域链:
[[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式连接,我们把这种链式连接叫做作用域链。
查找变量:
从作用域链的顶端依次向下查找(再补充:在哪个函数里查找变量,就去哪个函数的作用域顶端去查找),最标准的说法。
函数有属性:length name [[scoped]]
js对象有两种成员
一种是上文成员(js语法可以直接访问的成员)
一种是下文成员(底层语法访问的成员)
[[scopes]]括起来的成员名 就是下文成员
函数在定义/声明的时候 就有了[[scopes]] 里面保存了上层的AO对象
每个函数scopes数组中天生就有一个AO对象 就是这个函数的上层的AO
函数调用时会生成AO对象 AO保存在scopes对象内部的 每次调用都会放在scopes前面(顶部)
函数生成了就会有个属性 [[scopes]] 作用域"数组"(只能引擎使用)
9.4 函数闭包
官方对闭包的解释是:一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。
闭包是什么?(面试题) 答:闭包是可访问上一层函数作用域里变量的函数,即便上一层函数已经关闭。
9.4.1 闭包的特点
作为一个函数变量的一个引用,当函数返回时,其处于激活状态。
一个闭包就是当一个函数返回时,一个没有释放资源的栈区。
简单的说,Javascript允许使用内部函数---即函数定义和函数表达式位于另一个函数的函数体内。而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量、参数和声明的其他内部函数。当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包。
优点:可以访问局部变量。
缺点:局部变量一直占用内存,内存占用严重,还容易造成内存泄漏(内存被占用,剩余的内存变少,程序加载、处理速度变慢)。
9.4.2 闭包的写法
第一种:写在原型对象的方法上
function Person() {
}
Person.prototype.type="人类";
Person.prototype.getType=function () {
return this.type;
}
var person = new Person();
console.log( person.getType() );//"人类"
第二种:内部函数语句访问外部函数的变量,将内部函数写在外部函数的return中
var Circle = function() {
//var this={}
var obj = new Object(); //obj={}
var a=100;
obj.PI = 3.14159;
obj.area = function( r ) {
console.log(a)
return this.PI * r * r;
//this访问了外部函数的变量obj
}
return obj;
//return this;
}
var c = new Circle(); //{PI:3.14159,area:function(){……}}
alert( c.area( 1.0 ) );
第三种:通过表达式写在对象的方法上
var Circle = new Object();
Circle.PI = 3.14159;
Circle.Area = function( r ) {
return this.PI * r * r;
}
alert( Circle.Area( 1.0 ) );
第四种:通过属性创建写在对象的方法上
var Circle={
PI:3.14159,
area:function(r){
return this.PI * r * r;
}
};
alert( Circle.area(1.0) );
第四种:通过全局变量赋值(类似于第二种写法的原理)
var demo;
function test(){
var aaa=100;
function b(){
console.log(aaa)
}
return b;
//demo=b;
}
demo=test()
test();
demo();
9.4.3 闭包的用途
实现公有变量 (可以使用全局变量,函数设置计数器递增:
可以做缓存
可以实现封装,属性私有化
模块化开发,防止污染全局变量
实现类和继承
9.4.4 闭包的避免
9.4.5 闭包的面试
面试官问:闭包、回调、ES5和ES6的个人的一些东西,异步编程
谈谈对js中闭包的理解
1、直接回答问题的答案(总结:100%对)
2、这个技术的详细的东西(展开说)使用场景
3、特点:优缺点(缺点的解决方案或者替代方案)
4、项目中的真实情况
1.闭包的描述
闭包是可访问上一层函数作用域里变量的函数,即便上一层函数已经关闭。
2.闭包的使用场景举例
3.闭包的看法(优点,缺点:解决)
优点:上一步中 的技术难点 用闭包解决的方式
1.函数内部的变量 想要被外部程序使用 但是语法决定了外部不能使用,可以利用闭包解决
2.一些临时数据 比如for循环的i的值 希望延时业务中使用 可以使用闭包把临时数据保存到局部作用域中
3.防止变量污染 可以用闭包把一些业务变量放在局部作用域中
缺点:
虽然闭包好用 可以解决很多问题 但是它玩不好的话就会有一些致命的问题:内存泄漏
内存管理机制:垃圾回收机制,引用计数 底层浏览器的代码实现的功能
系统会定期查看我们的js执行情况,观察创建的对象有没有可能会被使用,如果没有可能 就释放内存,每一个对象都有"人"引用它 如果引用的"人"数为0就释放内存
内存泄漏:浏览器运行网页 就会执行js代码,引用数据会在内存中占用内存空间
如果有一个对象创建了 而且占用了内存 缺没有什么业务使用(想用都用不了) 这种情况就是内存泄漏
内存泄漏的解决方案:
1.尽量避开 不要使用闭包
2.在可能存在泄漏的地方把标识符引用为null
闭包这个技术的好处是什么?
通过业务点对闭包做区分
一:一个函数返回函数内部的工具函数,外部通过工具函数间接访问函数局部变量的过程
函数内部的变量外部是无法访问的 但是可以通过返回一个闭包
外部操作闭包来间接访问函数内部的变量,闭包可以决定这个变量的操作方式
(利用了函数的作用域和运行时作用域)
二:利用函数自调用,每次调用内部的局部变量或者形参都是独立的 来保存一些临时数据
利用了函数每次调用时生成的独立调用栈
利用函数的形参保存临时变量的值
三、利用函数的独立作用域 来生成业务代码块 内部的变量相互不冲突污染全局变量
//下面两种写法都是一样的 fn()() fn() () //加分号才是两个语句 fn(); () //所以闭包也要加分号 (function () {})();
四、回调函数.利用函数复用的功能,制作复用工具,参数返回值
利用函数复用的功能,制作复用工具,参数返回值
闭包根据函数的使用场景不一样 业务不一样 可以有很多方面的回答 等等
9.5 函数-特殊的对象
9.6 高阶函数
9.7 回调函数
英文是:callback 原名是:c钩子函数
主要思想:一函数1作为实参调用函数2,先运行函数2内的代码,再通过函数2的代码调用函数1
function jquery(url,callback) {
var res=url+"5s"
callback(res)
}
//jquery 函数在开发中,就是一个工具函数,执行某个功能产生的数据,调用回调函数,执行业务代码
//callback函数就是传入的业务函数
jquery("http:wwww.baidu.con",function(data){
console.log(data)
//function(Data)是再全局下运行的,即使它是在局部里调用的
}) //结果为 http:wwww.baidu.con 5s