什么是函数?
函数是一段可以反复调用的代码块。函数还能接受输入的参数,不同的参数会返回不同的值。

定义函数

JS中有三种声明函数的方式:

函数声明

function 命令声明的代码区块,就是一个函数。(function关键字 + 函数名 )函数名后面圆括号内是传入函数的参数

  1. // 函数声明提升(在执行代码之前会先读取函数声明,可以把函数声明放在调用之后)
  2. sayName(); // 使用 sayName()这种形式,就可以调用相应的代码
  3. function sayName(value){
  4. console.log("fanison");
  5. }

函数表达式

创建一个函数并将它赋值给变量;
因为赋值语句的等号右侧只能放表达式,所以又称函数表达式

  1. var x = function(y) {
  2. return y * y;
  3. }; // 函数表达式在语句的结尾加上分号,表示语句结束
  1. // 没有函数声明提升
  2. sayName(); // 错误:函数还不存在 sayName is not a function
  3. var sayName = function(){
  4. console.log("fanison")
  5. }

Function构造函数

  1. var add = new Function('x','y','return x + y')
  2. // 相当于
  3. function add(x,y){
  4. return x + y;
  5. }

递归

函数可以调用自身,这就是递归。

  • 递归函数是在一个函数通过名字调用自身的情况下构成的。

    1. function recursion(num){
    2. if(num <= 1){
    3. return 1;
    4. }else{
    5. return num * recursion( num - 1 ); // 调用自身
    6. }
    7. }

    上面代码中,recursion函数内部又调用了 recurison。

  • arguments.callee是一个指向正在执行的函数的指针,可以实现对函数的递归调用

    1. function recursion(num){
    2. if(num <= 1){
    3. return 1;
    4. }else{
    5. return num * arguments.callee(num-1); // 使用arguments.callee代替函数名
    6. }
    7. }
  • 使用函数表达式

    1. var recursion = (function f(num){ // 创建名为 f()的命名函数表达式,赋值给变量 recursion
    2. if(num <= 1 ){
    3. return 1;
    4. }else{
    5. return num * f(num -1);
    6. }
    7. });

    闭包

闭包与对象

JavaScript 有两种作用域:全局作用域和函数作用域。

  • 函数内部可以直接读取全局变量。

    1. var speed = 0;
    2. function car(){
    3. console.log(speed);
    4. }
    5. car() // 0

    上面代码中,函数 car 可以读取全局变量 speed

  • 函数外部无法读取函数内部声明的变量

    1. function car(){
    2. var speed = 0;
    3. }
    4. console.log(speed) // Uncaught ReferenceError: speed is not defined

    上面代码中,函数car 内部声明的变量speed,函数外是无法读取的。

  • 在函数内部再定义一个函数,可以得到函数内的局部变量

    1. function car(){
    2. var speed = 0;
    3. function fn(){
    4. speed++
    5. console.log(speed);
    6. }
    7. }

    在函数 car 内部再定义 函数 fn, 此时fn可以读取car的局部变量

    1. function car(){
    2. var speed = 0
    3. function fn(){
    4. speed++
    5. console.log(speed)
    6. }
    7. return fn // 将fn作为返回值,就可以在函数外部读取它的内部变量
    8. }
    9. var speedUp = car()
    10. speedUp() // 1
    11. speedUp() // 2

    上面代码中,函数car的返回值就是函数fn,由于fn可以读取car的内部变量,所以就可以在外部获得car的内部变量了。

闭包就是能够读取其他函数内部变量的函数。可以把闭包简单理解成“定义在一个函数内部的函数”。
典型的闭包是一个函数A内声明并返回一个函数B供外部使用,函数B内用到了A内部的局部变量或者形参。相互引用导致局部作用域不被释放一起构成闭包。

闭包的作用:

  • 用来暂存“当时”的状态;
  • 用来“封装”一些数据。

this对象

含义

this就是属性或方法“当前”所在的对象

this对象是在运行时基于函数的执行环境绑定的: 全局函数中,this等于window;函数被作为某个对象的方法调用时,this等于该对象;匿名函数的this指向window

  1. var name = "Window name"
  2. var object = {
  3. name: "Object name",
  4. getNameFunc: function(){
  5. return function(){ // 返回匿名函数
  6. return this.name;
  7. }
  8. }
  9. }
  10. console.log(object.getNameFunc()())

创建全局变量 name,创建包含name属性的object对象。object对象的 getNameFunc()方法返回匿名函数,匿名函数返回 this.name。由于this指向window,所以输出“Window name”

  1. var name2 = "Window name2"
  2. var object2 = {
  3. name: "Object name2",
  4. getNameFunc: function(){
  5. var that = this // 把 this 对象赋值给 that 变量,this指向为object2
  6. return function(){
  7. return that.name
  8. }
  9. }
  10. }
  11. console.log(object2.getNameFunc()())

在定义匿名函数前,把this对象赋值给that变量,使用闭包访问that变量,that变量引用object2;所以输出“Object name2”

使用场景

  • 全局环境

全局环境使用 this ,它指的就是顶层对象 window

  1. this = window // true
  2. function f(){
  3. console.log(this === window)
  4. }
  5. f() // true

只要是在全局环境下运行, this 就是指顶层对象 window

  • 构造函数

构造函数中的 this 指的是实例对象

  1. var Obj = function(p){
  2. this.p = p;
  3. }
  4. var o = new Obj('Hello World');
  5. o.p // "Hello World"

定义构造函数 Obj, this 指向实例对象,所以在构造函数内部定义 this.p,就相当于定义实例对象有一个p属性。

  • 对象的方法

对象的方法里面包含 this ,this的指向就是方法运行时所在的对象。

  1. var obj = {
  2. foo: function(){
  3. console.log(this);
  4. }
  5. };
  6. obj.foo() // obj

obj.foo方法执行时,内部的this指向 obj

绑定this

  • Function.prototype.call()

函数实例的 call 方法,可以指定函数内部 this 的指向,然后在所指定的作用域中,调用函数
call方法的参数,应该是一个对象。如果参数为空、null和undefined,默认传入全局对象

  1. var n = 123;
  2. var obj = { n: 456};
  3. function a(){
  4. console.log(this.n);
  5. }
  6. a.call() // 123
  7. a.call(null) // 123
  8. a.call(undefined) // 123
  9. a.call(window) // 123
  10. a.call(obj) // 456

a函数中的this关键字,如果指向全局对象,返回结果为123。使用cakk方法将this关键字指向obj对象,返回结果为456.

call方法可以接受多个参数,第一个参数就是this指向的对象,后面的参数是函数调用时所需的参数

  1. function add(a,b){
  2. return a + b;
  3. }
  4. add.call(this,1,2) // 3

call方法指定函数add内部的this绑定当前环境,函数参数为1和2,函数add运行后得到3。

  • Function.prototype.apply()

apply方法也是改变this指向,然后再调用该函数,它接受一个数组作为函数执行时的参数

第一个参数是this所要指向的对象(若为null或undefined,等于全局对象),第二个参数是一个数组

  1. func.apply(thisValue, [arg1, arg2, ...])
  2. function add(a,b){
  3. return a + b;
  4. }
  5. add.apply(null,[1,2]) // 3
  • Function.prototype.bind()

bind()方法用于将函数体内的 this 绑定到某个对象,然后返回一个新函数
如果第一个参数是nullundefined,等于将this绑定到全局对象

  1. var counter = {
  2. count: 0,
  3. inc: function(){
  4. this.count++;
  5. }
  6. };
  7. var func = counter.inc.bind(counter)
  8. func();
  9. counter.count // 1

counter.inc() 方法被赋值给变量 func,使用bind()方法将inc()内部的this绑定到counter

内存泄漏

  1. function assignHandler(){
  2. var element = document.getElementById('idname')
  3. element.onclick = function(){
  4. console.log(element.id)
  5. }
  6. }

创建闭包,闭包会引用包含函数的整个活动对象,匿名函数保存对 assignHandler()活动对象的引用,只要匿名函数存在,element的引用至少是1,所以element占用的内存就永远不会回收。

模仿块级作用域

JS中没有块级作用域(在块语句中定义的变量,实际是在包含函数中创建的而不是语句)

  1. function outputNumbers(count){
  2. for(var i=0;i<count;i++){
  3. console.log(i);
  4. }
  5. var i // 重新声明
  6. console.log('i:'+i)
  7. }

在函数中定义 for 循环,变量i初始值设置为0,循环结束后,i仍存在于 outputNumbers。
即使重新声明变量i,JS也不会报错

可以使用匿名函数来模仿块级作用域,避免变量多次声明

  1. var someFunction = function(){
  2. // 块级作用域
  3. }
  4. someFunction();

先定义一个函数(创建一个匿名函数,把匿名函数赋值给变量 someFunction),然后立即调用它(在函数名称后面添加一对圆括号)

将函数声明转换成函数表达式

  1. (function(){
  2. // 块级作用域
  3. })()

定义一个匿名函数,并立即调用。 将函数声明包含在圆括号中,另一对圆括号会立即调用该函数。

使用场景:
需要临时变量,可以使用私有作用域

  1. function outputNumber(count){
  2. (function(){
  3. for(var i=0;i<count;i++){
  4. console.log(i)
  5. }
  6. })();
  7. console.log(i) // 报错
  8. }

在for循环外部插入一个私有作用域,在匿名函数中定义的变量会在执行结束时被销毁,所以变量i只能在循环中使用。