函数进阶
定义函数的方式
- 声明函数
function xxx(){}
这种方式是最直接的声明方式,js执行时这种方式会被预解析(声明提升) - 函数表达式
var fn = function() {};
这种方法是通过赋值,将函数赋值给变量,预解析只有变量和函数本身,赋值操作不会,所以这种方式函数不会被预解析 - 构造函数方式
var fn = new Function('参数','参数','内部代码');
这种定义方式不会用,执行速度慢,只做了解 - 另外在新版浏览器中,
if
语句中的函数声明不会被预解析,但老版浏览器又会。所以如果想通过判断条件来声明函数,又要顾及兼容性,可以用表达式的方式
伪变量this
-
this
是函数内部的伪变量,用于指向调用者。关于它的指向其实完全不会混淆,谁调用了该函数,那么this
就指向谁,记住这一点就行
函数对象的成员进阶
- 前面已经说过了函数就是一个对象,它有默认的属性和方法。下面说下call、apply、bind的进阶用法
-
.call(改变指向对象,方法对应参数)
这个方法的作用是调用函数,并修改函数内部this的指向- 进阶用法举例:比如一个伪数组(数组的形式,但没有通过Array构造函数创建,所有不能使用原型对象中的默认方法)。如果我们想要让这个伪数组也使用数组的方法,那么可以
Array.prototype.push.call(伪数组);
,这样实际上是让构造函数调用方法,但是通过call来改变指向,达到目的。这只是一个用法,不是唯一
- 进阶用法举例:比如一个伪数组(数组的形式,但没有通过Array构造函数创建,所有不能使用原型对象中的默认方法)。如果我们想要让这个伪数组也使用数组的方法,那么可以
-
.apply(改变指向对象,数组)
这个方法的作用是调用函数,并修改函数内部this的指向,并且参数是数组的形式传入,它会在自动拆分数组将值依次传入- 进阶用法举例:
Math.max()
这个方法可以返回一组数字中的最大值,但是它的参数只能是一组数字,不能是数组。这个时候就可以Math.max.apply(Math, 数组)
,通过apply
方法自动拆分数组后,就能直接实现了。注意这里第一个参数因为不涉及改变指向,所以传它本身调用者即可
- 进阶用法举例:
-
.bind(改变指向对象,方法对应参数)
这个方法的作用是修改函数内部this的指向,并返回一个新的函数,不会直接调用- 进阶用法:它主要的应用场景是在函数不需要立刻调用的时候。比如函数作为一个参数,或者作为一个赋值对象时。当时并不需要立即执行函数,就可以用它
-
aguments
函数中默认有这个变量,也有这个属性(不是同一个),但是作用都是相同。用来记录传入的实参,它本身是一个伪数组。在不确定传入实参的情况下,可以用它直接获取到,较常用需要掌握 -
caller
属性记录该函数的调用者 -
name
属性记录函数名 -
length
属性记录形参个数
高阶函数
- 当一个函数作为参数或者作为返回值的是,它就是高阶函数
- 函数作为参数可以通过sort方法的参数(函数)调整排序方式来理解
- 把函数作为返回值也是很常用的。举例:很多场景下,我们需要一个函数来接受参数,但是因为参数的动态变化,如果只有一个函数,那么参数会被定死。所以我们可以返回一个函数,让返回的函数再接收值并执行逻辑,这样就是可以让参数动态变化
- 具体可以看案例代码理解
闭包
- 闭包是一个很抽象的概念,准确来说它是一种程序现象也是一种组合环境。在js中创建函数时,会自动生成一个作用域环境。其他部分语言中该作用域中的局部变量或函数会随着作用域执行完后销毁而变得不可访问,但在js中却不是这样
- 哪怕作用域销毁,但是只要其他作用域访问其中局部变量或函数(内部函数或者本身),依然能够访问到
- 所以闭包也能理解了,它是由函数和创建该函数的词法环境组合而成,它包含了该函数中所有能访问的局部变量或函数(内部函数或者本身),让它们在函数执行完销毁后仍然可以被访问
- 闭包的作用,简单来说就是延展作用域,在某些情景下可以让功能实现更方便。但不是所有场景都一定要用闭包
定时器工作原理
- js代码在执行时,是从上往下执行的。这是因为执行是,会把所有代码放到执行栈当中。但是定时器的参数函数并不会在执行栈中处理,而是把函数放到任务队里中进行排队。当执行栈中的代码执行完毕后,根据消息循环(就是触发条件),执行任务队列中对应的函数
- 不光是定时器,事件触发函数也是同样的原理,也会把函数放到事件队列中。当触发事件后,会从事件队列中取出对应函数并执行
关于多次调用容易发生的理解误区
- 假设一个对象
obj
它有一个方法fun
,fun
中返回值是一个函数 - 那么输出
obj.fun()()
时,返回函数中的this
指向的谁呢?答案是window
,而不是obj
- 因为
obj.fun()
这段代码执行时,this指向的是obj,执行完毕后此时obj.fun()
就是返回的一个函数。再次调用返回的函数,注意返回值函数是没有对象调用的,所以默认是由window来调用的
递归
- 递归实际上就是函数再内部不停的调用自己,在使用递归时一般都会加上一个结束条件,不然递归就是一个死循环,最终导致内存不足报错
- 具体执行过程,可以通过断点的方式查看分析
- 递归在使用时没必要完全想通数学逻辑,实际上只需要明白要实现的功能该怎么写,并套用对应公式即可
对象拷贝
- 浅拷贝
- 正常情况下如果想要拷贝一个对象,使用
for in
遍历对象并依次赋值给新对象即可 - 但是这样会出现一个问题,如果拷贝的成员中,有一个属性是对象。那么实际上拷贝的是该对象的引用地址,而不管哪个对象修改值,都会通过引用地址修改对象成员的值,那么就不是我们想要的结果了
- 这种拷贝也叫做浅拷贝
- 正常情况下如果想要拷贝一个对象,使用
- 深拷贝
- 深度拷贝就是将对象成员也进行拷贝,而不再是拷贝引用地址,这样上面的问题也就解决了
- 具体深度拷贝实现代码查看案例,也是递归的一种应用场景