JavaScript 高级部分 之函数、闭包、递归、apply、call、bind、沙箱

函数、闭包、递归、apply、call、bind、沙箱


函数中this的指向

  • 普通函数中的this是谁?-----window
  • 对象.方法中的this是谁?----当前的实例对象
  • 定时器方法中的this是谁?----window
  • 构造函数中的this是谁?-----实例对象
  • 原型对象方法中的this是谁?—实例对象

函数的不同的调用方式

  • 普通函数方式
function f1() {
      console.log("我是普通函数");
    }
    f1();
  • 构造函数—通过new来调用,创建对象
function F1() {
      console.log("我是构造函数");
    }
    var f=new F1();
  • 对象的方法
 function Person() {
      this.play=function () {
        console.log("我是对象里的方法");
      };
    }
    var per=new Person();
    per.play();

函数也是对象

函数是对象,对象不一定是函数

   function F1() {
   }

   console.dir(F1);

   console.dir(Math);//中有__proto__,但是没有prorotype

在这里插入图片描述
运行代码在控制台我们可以看到Function F1既有__proto__又有prototype,而对象Math只有__proto__,得出结论:

  • 对象中有__proto__,函数中应该有prototype
  • 如果一个东西里面有prototype,又有__proto__,说明是函数,也是对象

所有的函数实际上都是Function的构造函数创建出来的实例对象:

   var f1=new Function("num1","num2","return num1+num2");
   console.log(f1(10,20));
   console.log(f1.__proto__==Function.prototype);

所以,函数实际上也是对象。

数组中的函数的调用

数组可以存储任何类型的数据包括函数。

 var arr=[
        function () {
          console.log("十一假期快乐");
        },
        function () {
          console.log("十一假期开心");
        }
        ,
        function () {
          console.log("十一假期健康");
        }
        ,
        function () {
          console.log("十一假期安全");
        },
        function () {
          console.log("十一假期如意");
        }
    ];
     //回调函数:函数作为参数使用
    arr.forEach(function (ele) {
      ele();
    });

apply和call方法的使用

apply和call的作用:可以改变this的指向
我想要使用其他对象的某个方法,其他对象.方法名.apply(当前对象,参数,…);
这个方法就会被当前的对象所使用,同时这个方法中的this就是当前的对象,在调用方法的时候改变了this的指向

函数改变this指向

 function f1(x,y) {
         console.log((x+y)+":===>"+this);
         return "这是函数的返回值";
       }
       //apply和call调用
       var r1=f1.apply(null,[1,2]);//此时f1中的this是window
       console.log(r1);
       var r2=f1.call(null,1,2);//此时f1中的this是window
       console.log(r2);
       console.log("=============>");
       //改变this的指向
       var obj={
         sex:"男"
       };
       //本来f1函数是window对象的,但是传入obj之后,f1函数此时就是obj对象的
       var r3=f1.apply(obj,[1,2]);//此时f1中的this是obj
       console.log(r3);
       var r4=f1.call(obj,1,2);//此时f1中的this是obj
       console.log(r4);

方法改变this的指向

function Person(age) {
     this.age = age;
   }
   Person.prototype.sayHi = function (x, y) {
     console.log((x + y) + ":====>" + this.age);//是实例对象
   };

   function Student(age) {
     this.age = age;
   }
   var per = new Person(10);//实例对象
   var stu = new Student(100);//实例对象
   //sayHi方法是per实例对象的,通过调用apply和call将this指向stu对象,那么再输出age输出的是stu的年龄100
   per.sayHi.apply(stu, [10, 20]);//30:====>100
   per.sayHi.call(stu, 10, 20);//30:====>100

apply和call的使用方法

  • apply的使用语法
    • 函数名字.apply(对象,[参数1,参数2,…]);
    • 方法名字.apply(对象,[参数1,参数2,…]);
  • call的使用语法
    • 函数名字.call(对象,参数1,参数2,…);
    • 方法名字.call(对象,参数1,参数2,…);

作用:改变this的指向
apply、call不同的地方:参数传递的方式是不一样的
只要是想使用别的对象的方法,并且希望这个方法是当前对象的,那么就可以使用apply或者是call的方法改变this的指向
实例对象调用方法,方法要么在实例对象中存在,要么在原型对象中存在。
所有的函数都是Function的实例对象,apply和call方法实际上并不在函数这个实例对象中,而是在Function的prototype中

bind方法

bind方法复制一个方法或者是函数,是在复制的同时改变了this的指向,参数可以在复制的时候传进去,也可以在复制之后调用的时候传入进去.
apply和call是调用的时候改变this指向
bind方法,是复制一份的时候,改变了this的指向

//方法调用bind
  function Person(age) {
      this.age=age;
    }
    Person.prototype.play=function () {
      console.log(this+"====>"+this.age);
    };

    function Student(age) {
      this.age=age;
    }
    var per=new Person(10);
    var stu=new Student(20);
    //复制了一份
    var ff=per.play.bind(stu);
    ff();//20

bind使用语法:
函数名字.bind(对象,参数1,参数2,…);---->返回值是复制之后的这个函数
方法名字.bind(对象,参数1,参数2,…);---->返回值是复制之后的这个方法

//函数调用bind
    var ff=f1.bind(null);
       ff(10,20);//undefined   (window没有age属性)
//复制了一份的时候,把参数传入到了f1函数中,x===>10,y===>20,null就是this,默认就是window
   function Person() {
     this.age = 1000;
   }
   Person.prototype.eat = function () {
     console.log("这个是吃");
   };
   var per = new Person();

   var ff = f1.bind(per, 10, 20);
   ff();//30:====>1000(person的age为1000)

函数中几个成员

 function f1(x,y) {
      console.log(f1.name);//f1
      console.log(f1.arguments.length);//2
      console.log(f1.length);//2
      console.log(f1.caller);//调用者 [Function: f2]
    }
    function f2() {
      console.log("f2函数的代码");
      f1(1,2);
    }
    f2();

函数中有一个name属性----->函数的名字,name属性是只读的,不能修改
函数中有一个arguments属性—>实参的个数
函数中有一个length属性---->函数定义的时候形参的个数
函数中有一个caller属性---->调用(f1函数在f2函数中调用的,所以,此时调用者就是f2)

高阶函数之函数作为返回值使用

函数作为参数使用的时候,这个函数可以是匿名函数,也可以是命名函数

   function f1(fn) {
     console.log("f1的函数");
     fn();//此时fn当成是一个函数来使用的
   }
   //fn是参数,最后作为函数使用了,函数是可以作为参数使用
   //1.传入匿名函数
   f1(function () {
     console.log("我是匿名函数");
   });
   //2.传入命名函数
   function f2() {
     console.log("f2的函数");
   }
   f1(f2);
   //函数作为参数的时候,如果是命名函数,那么只传入命名函数的名字,没有括号
   //如果是f1(f2())代表把f2函数执行后的返回值当做参数,然而f2函数没有返回值。所以这样会报错

应用案例:

   function f1(fn) {
     setInterval(function () {
       console.log("定时器开始");
       fn();
       console.log("定时器结束");
     },1000);
   }

   f1(function () {
     console.log("好困啊,好累啊,就是想睡觉");
   });

执行以上代码的结果是每隔一段时间便输出一次“好困啊,好累啊,就是想睡觉”。

高阶函数之函数作为返回值使用

    function f1() {
         console.log("f1函数开始");
         return function () {
           console.log("我是函数,但是此时是作为返回值使用的");
         }
         console.log("f1函数结束");//这行不会执行,因为函数执行在遇到return就结束了。
       }
    
       var ff=f1();
       ff();

执行结果为:f1函数开始
我是函数,但是此时是作为返回值使用的

判断某个对象的类型

浅谈Object.prototype.toString.call()方法

  • 在JavaScript里使用typeof判断数据类型,只能区分基本类型。
    • 即:number、string、undefined、boolean、object。
  • 对于null、array、function、object来说,使用typeof都会统一返回object字符串。
  • 要想区分对象、数组、函数、单纯使用typeof是不行的。在JS中,可以通过Object.prototype.toString方法,判断某个对象之属于哪种内置类型。
  • 分为null、string、boolean、number、undefined、array、function、object、date、math。
判断基本类型
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(“abc”);// "[object String]"
Object.prototype.toString.call(123);// "[object Number]"
Object.prototype.toString.call(true);// "[object Boolean]"
判断原生引用类型

//**函数类型**
Function fn(){
  console.log(“test”);
}
Object.prototype.toString.call(fn); // "[object Function]"

//**日期类型**
var date = new Date();
Object.prototype.toString.call(date); // "[object Date]"

//**数组类型**
var arr = [1,2,3];
Object.prototype.toString.call(arr); // "[object Array]"

//**正则表达式**
var reg = /[hbc]at/gi;
Object.prototype.toString.call(reg); // "[object RegExp]"

//**自定义类型**
function Person(name, age) {
    this.name = name;
    this.age = age;
}
var person = new Person("Rose", 18);
Object.prototype.toString.call(person); // "[object Object]"
//很明显这种方法不能准确判断person是Person类的实例,而只能用instanceof 操作符来进行判断,如下所示:

//判断某个对象是不是某个类型
console.log(person instanceof Person); // true

小案例====>封装一个判断这个对象和传入的类型是不是同一个类型的函数

function getFuncType(type){
  return function(obj){
    console.log(obj);
    return Object.prototype.toString.call(obj) === type;
  }
}

var ff=getFuncType("[object Array]");
var result=ff([10,20,30]);
console.log(result);

var ff1=getFuncType("[object Object]");
var dt=new Date();
var result1=ff1(dt);
console.log(result1);

运行结果:

在这里插入图片描述

作用域和作用域链及预解析

  • 变量---->局部变量和全局变量
  • 作用域:就是变量的使用范围
    • 局部作用域和全局作用域
  • js中没有块级作用域—一对括号中定义的变量,这个变量可以在大括号外面使用
  • 函数中定义的变量是局部变量
  • 作用域链:变量的使用,从里向外,层层的搜索,搜索到了就可以直接使用了
  • 层层搜索,搜索到0级作用域的时候,如果还是没有找到这个变量,结果就是报错
  • 预解析:就是在浏览器解析代码之前,把变量的声明和函数的声明提前(提升)到该作用域的最上面

闭包

闭包的概念:函数中有另一个函数,或者是一个函数中有另一个对象,里面的函数或者是对象都可以使用外面函数中定义的变量或者数据,此时形成闭包
闭包的模式:函数模式的闭包,对象模式的闭包
闭包的作用:缓存数据,延长作用域链
闭包的优点和缺点:缓存数据,数据没有及时的释放
局部变量是在函数中,函数使用结束后,局部变量就会被自动的释放
如果想要缓存数据,就把这个数据放在外层的函数和里层的函数的中间位置
闭包后,里面的局部变量的使用作用域链就会被延长

  • 函数模式的闭包:在一个函数中有一个函数
   function f1() {
     var num=10;
     //函数的声明
     function f2() {
       console.log(num);
     }
     //函数调用
     f2();
   }
   f1();
  • 对象模式的闭包:函数中有一个对象,这个对象能访问函数中的变量,就形成了闭包
   function f3() {
     var num=10;
     var obj={
       age:num
     };
     console.log(obj.age);//10
   }
   f3();

普通函数与闭包比较

普通函数:

 function f1() {
      var num = 10;
      num++;
      return num;
    }
    console.log(f1());//11
    console.log(f1());//11
    console.log(f1());//11

函数模式的闭包:

function f2() {
      var num = 10;
      return function () {
        num++;
        return num;
      }
    }
    var ff = f2();
    console.log(ff());//11
    console.log(ff());//12
    console.log(ff());//13

沙箱

沙箱:环境,黑盒,在一个虚拟的环境中模拟真实世界,做实验,实验结果和真实世界的结果是一样,但是不会影响真实世界

沙箱避免命名冲突

   var num=10;
   console.log(num+10);//20

   //沙箱---小环境
   (function () {
     var num=10;
     console.log(num);//10
   })();

   //沙箱---小环境
   (function () {
     var num=20;
     console.log(num+10);//30
   }());

   var num=100;
   (function () {
     var num=10;//这个num与外面的num重名但是不影响外面的num
     console.log(num);//10
   }());


   console.log(num);//100

递归

递归:函数中调用函数自己,此时就是递归,递归一定要有结束的条件,否则就是死循环

递归:一般应用在遍历上

递归轻易不要用,效率很低,

var i = 0;
    function f1() {
      i++;
      if (i < 5) {
        f1();
      }
      console.log("从前有个山,山里有个庙,庙里有个和尚给小和尚讲故事:");

    }

    f1();//输出了5遍:从前有个山,山里有个庙,庙里有个和尚给小和尚讲故事:

递归实现:求n个数字的和 n=5—> 5+4+3+2+1

   // 函数的声明
   function getSum(x) {
     if(x==1){
       return 1;
     }
     return x+getSum(x-1);
   }
   //函数的调用
   console.log(getSum(5));

执行过程:
代码执行getSum(5)—>进入函数,此时的x是5,执是5+getSum(4),此时代码等待
此时5+getSum(4),代码先不进行计算,先执行getSum(4),进入函数,执行的是4+getSum(3),等待, 先执行的是getSum(3),进入函数,执行3+getSum(2),等待,先执行getSum(2),进入函数,执行 2+getSum(1);等待, 先执行getSum(1),执行的是x==1的判断,return 1,所以,
此时getSum(1)的结果是1,开始向外走出去
2+getSum(1) 此时的结果是:2+1
执行:
getSum(2)---->2+1
3+getSum(2) 此时的结果是3+2+1
4+getSum(3) 此时的结果是4+3+2+1
5+getSum(4) 此时的结果是5+4+3+2+1

结果:15
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值