【js高级 Day4】深入理解apply和call方法,作用域,闭包,递归

01 课程介绍

【js高级 Day4】深入理解apply和call方法,作用域,闭包,递归_第1张图片

02 复习

原型:

  1. 每个实例对象中都有一个属性__proto__,是原型,浏览器使用的,不标准的属性
  2. 每个构造函数中都有一个属性prototype,是原型,程序员使用的,

面向对象和面向过程都是编程思想

  1. 面向对象注重的是结果
  2. 面向过程注重的是过程

面向对象的特性:封装,继承,多态

继承:

1.通过原型实现继承,改变原型的指向,属性在初始化的时候就已经固定了,如果是多个对象实例化,那么每个实例对象的属性的值在初始化的时候都是一样的
2.借用构造函数继承,不能继承方法
3.组合继承,可以解决属性和方法的继承的问题
4.拷贝继承,就是把一个对象中的原型中的所有的属性和方法复制一份给另一个对象

创建对象的三种方式

  1. 字面量的方式
  2. 调用系统的构造函数
  3. 自定义构造函数

原型链:实例对象和原型对象之间的关系,主要是通过__proto__和prototype来联系

原型的指向是可以改变,所以,js中是通过改变原型来实现继承

原型的作用:
实现数据共享,继承, 都是为了节省内存空间
如果属性和方法都需要共享,那么就把属性和方法添加到原型中

函数中的this的指向

  • 普通的函数中this是window
  • 构造函数中的this,构造函数一般都是创建实例对象使用的,是通过new关键字,构造函数也是函数
  • 构造函数中的this是实例对象
  • 方法中的this是实例对象
  • 原型中的方法中的this是实例对象
  • 定时器中的this是window

函数是对象,构造函数也是函数,也是对象
对象是不是函数呢?不一定

对象中有__proto__
函数中有prototype

Math是对象,但不是函数

 console.dir(Math);

  setInterval(function () {
    console.log(this);
  },1000);

  function f1() {
    console.log(this);
  }
  f1();
  console.dir(f1);

  function Teacher() {
    console.log(this);
    this.sayHi=function () {
      console.log(this);
    };
  }
  Teacher.prototype.sayHello=function () {
    console.log(this);
  };
  var t=new Teacher();
  t.sayHi();
  t.sayHello();

  //字面量的方式创建对象
  var obj={
    age:10,
    sex:"男",
    sayHi:function () {
      console.log("您好,十一过的快乐吗");
    }
  };

  //调用系统的构造函数创建对象
  var obj2=new Object();
  obj2.age=20;
  obj2.sex="男";
  obj2.sayHi=function () {
    console.log("中秋节过的快乐吗");
  };

  //自定义构造函数创建对象

  function Person(age,sex) {
    this.age=age;
    this.sex=sex;
    this.sayHi=function () {
      console.log("今天就上课了,快乐吗");
    };
  }



  //继承的代码

  function Animal(age,sex) {
    this.age=age;//年龄
    this.sex=sex;//性别
  }
  //通过原型添加属性
  Animal.prototype.color="黄色";
  //通过原型添加方法
  Animal.prototype.eat=function () {
    console.log("中秋的时候没吃到月饼,但是,吃到了馒头,也是一样的");
  };

  //小狗的构造函数
  function Dog(age,sex,name) {
    //借用构造函数
    Animal.call(this,age,sex);
    this.name=name;
  }
  //改变原型指向,为了继承方法的
  Dog.prototype=new Animal();

  var dog=new Dog(10,"女","小苏");
  console.log(dog.name);
  console.log(dog.age);
  console.log(dog.sex);
  console.log(dog.color);
  //调用方法
  dog.eat();

03 apply和call方法的使用

apply和call的使用
作用:可以改变this的指向

 function f1(x,y) {
   console.log("结果是:"+(x+y)+this);
   return "10000";
 }
 f1(10,20);//函数的调用
 console.log("========");

此时的f1实际上是当成对象来使用的,对象可以调用方法
apply和call方法也是函数的调用的方式

 f1.apply();
 f1.call();
 console.log("==========");
 f1.apply(null);
 f1.call(null);

apply和call方法中如果没有传入参数,或者是传入的是null,那么调用该方法的函数对象中的this就是默认的window

 f1.apply(null,[100,200]);
 f1.call(null,100,200);

apply和call都可以让函数或者方法来调用,传入参数和函数自己调用的写法不一样,但是效果是一样的

 var result1=f1.apply(null,[10,20]);
 var result2=f1.call(null,10,20);
 console.log(result1);
 console.log(result2);
  function f1(x,y) {
   console.log("这个函数是window对象的一个方法:"+(x+y)+this.sex);
 }
 window.f1(10,20);
 //obj是一个对象
 var obj={
   age:10,
   sex:"男"
 };
 window.f1.apply(obj,[10,20]);
 window.f1.call(obj,10,20);
 console.dir(obj);

 //apply和call可以改变this的指向

 function Person(age,sex) {
   this.age=age;
   this.sex=sex;
 }
 //通过原型添加方法
 Person.prototype.sayHi=function (x,y) {
   console.log("您好啊:"+this.sex);
   return 1000;
 };
 var per=new Person(10,"男");
 per.sayHi();

 console.log("==============");
 function Student(name,sex) {
   this.name=name;
   this.sex=sex;
 }
 var stu=new Student("小明","人妖");
 var r1=per.sayHi.apply(stu,[10,20]);
 var r2=per.sayHi.call(stu,10,20);

 console.log(r1);
 console.log(r2);

04 总结apply和call方法的使用

apply和call都可以改变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实例对象的
  per.sayHi.apply(stu, [10, 20]);
  per.sayHi.call(stu, 10, 20);

apply和call的使用方法
apply的使用语法

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

作用:改变this的指向
不同的地方:参数传递的方式是不一样的

只要是想使用别的对象的方法,并且希望这个方法是当前对象的,那么就可以使用apply或者是call的方法改变this的指向

 function f1() {
   console.log(this+":====>调用了");
 }
 //f1是函数,f1也是对象
 console.dir(f1);
 //对象调用方法,说明,该对象中有这个方法
 f1.apply();
 f1.call();
 console.log(f1.__proto__==Function.prototype);
 //所有的函数都是Function的实例对象
 console.log(Function.prototype);//ƒ () { [native code] }
 console.dir(Function);
 //apply和call方法实际上并不在函数这个实例对象中,而是在Function的prototype中


 function Person() {
   this.sayHi=function () {
     console.log("您好");
   };
 }
 Person.prototype.eat=function () {
   console.log("吃");
 };

 var per=new Person();
 per.sayHi();
 per.eat();
 console.dir(per);
 //实例对象调用方法,方法要么在实例对象中存在,要么在原型对象中存在

05 bind方法

function f1(x, y) {
     console.log((x + y) + ":=====>" + this.age);
   }

复制了一份的时候,把参数传入到了f1函数中,
x===>10,y===>20,null就是this,默认就是window
bind方法是复制的意思,参数可以在复制的时候传进去,
也可以在复制之后调用的时候传入进去
apply和call是调用的时候改变this指向
bind方法,是赋值一份的时候,改变了this的指向

var ff=f1.bind(null);
ff(10,20);


function Person() {
  this.age = 1000;
}
Person.prototype.eat = function () {
  console.log("这个是吃");
};
var per = new Person();

var ff = f1.bind(per, 10, 20);
ff();

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();

bind是用来复制一份
使用的语法:

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

06 bind方法的使用

通过对象,调用方法,产生随机数

 function ShowRandom() {
   //1-10的随机数
   this.number=parseInt(Math.random()*10+1);
 }
 //添加原型方法
 ShowRandom.prototype.show1=function () {
   //改变了定时器中的this的指向了,本来应该是window,现在是实例对象了
   window.setInterval(this.show2.bind(this),1000);
 };
 //添加原型方法
 ShowRandom.prototype.show2=function () {
   //显示随机数--
   console.log(this.number);
 };
 //实例对象
 var sr=new ShowRandom();
 //调用方法,输出随机数字
 //调用这个方法一次,可以不停的产生随机数字
 sr.show1();

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

 function f1(x,y) {
   console.log(f1.name);
   console.log(f1.arguments.length);
   console.log(f1.length);
   console.log(f1.caller);//调用者
}
 f1.name="f5";
 f1(10,20,30,40);
 console.dir(f1);


 function f2() {
   console.log("f2函数的代码");
   f1(1,2);
 }
 f2();

08 高阶函数之函数作为参数使用

function f1(fn) {
  console.log("f1的函数");
  fn();//此时fn当成是一个函数来使用的
}
//fn是参数,最后作为函数使用了,函数是可以作为参数使用
//传入匿名函数
f1(function () {
  console.log("我是匿名函数");
});
//命名函数
function f2() {
  console.log("f2的函数");
}
f1(f2);
//函数作为参数的时候,如果是命名函数,那么只传入命名函数的名字,没有括号



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

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

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

function f1() {
   console.log("f1函数开始");
   return function () {
     console.log("我是函数,但是此时是作为返回值使用的");
   }

 }

 var ff=f1();
 ff();


 var num=10;
 console.log(typeof num);//获取num这个变量的数据类型
 var obj={};//对象
 //判断这个对象是不是某个类型的
 console.log(obj instanceof Object);
 //获取某个对象的数据类型的样子
 //Object.prototype.toString.call(对象);//此时得到的就是这个对象的类型的样子

//此时输出的是Object的数据类型   [object Object]
console.log(Object.prototype.toString());
//输出的数组的数据类型      [object Array]
console.log(Object.prototype.toString.call([]));

var arr=[10,20,30];
console.log(Object.prototype.toString.call(arr));

console.log(Object.prototype.toString.call(new Date()));

获取某个对象的类型是不是你传入的类型
[10,20,30] 是不是"[object Array]"
type—是变量----是参数----"[object Array]"
obj—是变量-----是参数----[10,20,30];

  //判断这个对象和传入的类型是不是同一个类型
  function getFunc(type) {
    return function (obj) {
      return Object.prototype.toString.call(obj) === type;
    }
  }

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

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

10 函数作为参数的练习

var arr = [1, 100, 20, 200, 40, 50, 120, 10];
 //排序
 arr.sort();
 console.log(arr);

 var arr = [1, 100, 20, 200, 40, 50, 120, 10];
 //排序---函数作为参数使用,匿名函数作为sort方法的参数使用,那么此时的匿名函数中有两个参数,
 arr.sort(function (obj1,obj2) {
   if(obj1>obj2){
     return -1;
   }else if(obj1==obj2){
     return 0;
   }else{
     return 1;
   }
 });
 console.log(arr);

 var arr1=["acdef","abcd","bcedf","bced"];
 arr1.sort(function (a,b) {
   if(a>b){
     return 1;
   }else if(a==b){
     return 0;
   }else{
     return -1;
   }
 });
 console.log(arr1);

11 函数作为返回值练习

排序,每个文件都有名字,大小,时间,都可以按照某个属性的值进行排序

三部电影,电影有名字,大小,上映时间

 function File(name, size, time) {
   this.name = name;//电影名字
   this.size = size;//电影大小
   this.time = time;//电影的上映时间
 }
 var f1 = new File("jack.avi", "400M", "1997-12-12");
 var f2 = new File("tom.avi", "200M", "2017-12-12");
 var f3 = new File("xiaosu.avi", "800M", "2010-12-12");
 var arr = [f1, f2, f3];

 function fn(attr) {
   //函数作为返回值
   return function getSort(obj1, obj2) {
     if (obj1[attr] > obj2[attr]) {
       return 1;
     } else if (obj1[attr] == obj2[attr]) {
       return 0;
     } else {
       return -1;
     }
   }
 }

 var ff = fn("name");

 //函数作为参数
 arr.sort(ff);
 for (var i = 0; i < arr.length; i++) {
   console.log(arr[i].name + "====>" + arr[i].size + "===>" + arr[i].time);
 }

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

变量---->局部变量和全局变量,
作用域:就是变量的使用范围
局部作用域和全局作用域
js中没有块级作用域—一对括号中定义的变量,这个变量可以在大括号外面使用
函数中定义的变量是局部变量

  while(true){
    var num=10;
    break;
  }
  console.log(num);

  {
    var num2=100;
  }
  console.log(num2);

  if(true){
    var num3=1000;
  }
  console.log(num3);

  function f1() {
    //局部变量
    var num=10;
  }
  console.log(num);

作用域链:变量的使用,从里向外,层层的搜索,搜索到了就可以直接使用了
层层搜索,搜索到0级作用域的时候,如果还是没有找到这个变量,结果就是报错

var num=10; //作用域链 级别:0
var num2=20;
var str = "abc"
function f1() {
  var num2=20;
  function f2() {
    var num3=30;
    console.log(num);
  }
  f2();
}
f1();

预解析:就是在浏览器解析代码之前,把变量的声明和函数的声明提前(提升)到该作用域的最上面

变量的提升

 console.log(num);
 var num=100;

函数的声明被提前了

f1();
 function f1() {
   console.log("这个函数,执行了");
 }

 var f2;
 f2=function () {
   console.log("小杨好帅哦");
 };
 f2();

13 闭包

闭包的概念:函数A中,有一个函数B,函数B中可以访问函数A中定义的变量或者是数据,此时形成了闭包(这句话暂时不严谨)

闭包的模式:函数模式的闭包,对象模式的闭包

闭包的作用:缓存数据,延长作用域链

闭包的优点和缺点:缓存数据

闭包的应用

函数模式的闭包:在一个函数中有一个函数

  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;
   return function () {
     console.log(num);
     return num;
   }
 }
var ff= f1();
var result= ff();
 console.log(result);
  function f2() {
    var num=100;
    return {
      age:num
    }
  }

 var obj= f2();
  console.log(obj.age);

14 闭包小案例

普通的函数

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

函数模式的闭包

 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

15 闭包案例产生多个相同的随机数

function showRandom() {
    var num=parseInt(Math.random()*10+1);
    console.log(num);
  }

  showRandom();
  showRandom();
  showRandom();

闭包的方式,产生三个随机数,但是都是相同的

 function f1() {
   var num=parseInt(Math.random()*10+1);
   return function () {
     console.log(num);
   }
 }

 var ff=f1();

 ff();
 ff();
 ff();

总结:如果想要缓存数据,就把这个数据放在外层的函数和里层的函数的中间位置

闭包的作用:缓存数据.优点也是缺陷,没有及时的释放

局部变量是在函数中,函数使用结束后,局部变量就会被自动的释放

闭包后,里面的局部变量的使用作用域链就会被延长

16 闭包案例点赞应用

 <style>
    ul {
      list-style-type: none;
    }

    li {
      float: left;
      margin-left: 10px;
    }

    img {
      width: 200px;
      height: 180px;
    }

    input {
      margin-left: 30%;
    }
  style>
<ul>
  <li><img src="images/ly.jpg" alt=""><br/><input type="button" value="赞(1)"></li>
  <li><img src="images/lyml.jpg" alt=""><br/><input type="button" value="赞(1)"></li>
  <li><img src="images/fj.jpg" alt=""><br/><input type="button" value="赞(1)"></li>
  <li><img src="images/bd.jpg" alt=""><br/><input type="button" value="赞(1)"></li>
</ul>
<script>

  //获取所有的按钮
  //根据标签名字获取元素
  function my$(tagName) {
    return document.getElementsByTagName(tagName);
  }
  //闭包缓存数据
  function getValue() {
    var value=2;
    return function () {
      //每一次点击的时候,都应该改变当前点击按钮的value值
      this.value="赞("+(value++)+")";
    }
  }
  //获取所有的按钮
  var btnObjs=my$("input");
  //循环遍历每个按钮,注册点击事件
  for(var i=0;i<btnObjs.length;i++){
    //注册事件
    btnObjs[i].onclick=getValue();
  }

  var btnObjs=my$("input");
  var value=1;
  //循环遍历每个按钮
  for(var i=0;i<btnObjs.length;i++){

    //为每个按钮注册点击事件
    btnObjs[i].onclick=function () {
      console.log("哈哈");
      this.value="赞("+(value++)+")";
    };
  }

</script>

17 沙箱

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

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

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

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

  var num=100;
  (function () {
    var num=10;
    console.log(num);//10
  }());


  console.log(num);//100

18 沙箱小案例

 (function () {
   var str="小白喜欢小黑";
   str=str.substr(2);
   console.log(str);
 }());


 //沙箱
 (function () {
   var str="小明喜欢小红";
   str=str.substr(2);
   console.log(str);
 }());
<body>
<input type="button" value="按钮" id="btn"/>
<script>
  //点击按钮,弹出对话框
  (function () {
    document.getElementById("btn").onclick=function () {
      console.log("按钮被点击了");
    };
  }());
</script>
</body>

19 沙箱的案例

<body>
<div>这是div</div>
<div>这是div</div>
<div>这是div</div>
<p>这是p</p>
<p>这是p</p>
<p>这是p</p>
<script>
var getTag = 10;
var dvObjs = 20;
var pObjs = 30;
(function () {
  //根据标签名字获取元素
  function getTag(tagName) {
    return document.getElementsByTagName(tagName)
  }
  //获取所有的div
  var dvObjs = getTag("div");
  for (var i = 0; i < dvObjs.length; i++) {
    dvObjs[i].style.border = "2px solid pink";
  }
  //获取所有的p
  var pObjs = getTag("p");
  for (var i = 0; i < pObjs.length; i++) {
    pObjs[i].style.border = "2px solid pink";
  }
}());
console.log(getTag);
console.log(dvObjs);
console.log(pObjs);
</script>
</body>

20 递归

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

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

  }

  f1();

21 递归的案例

求n个数字的和,5 计算1+2+3+4+5

  var sum=0;
  for(var i=1;i<=5;i++){
    sum+=i;
  }
  console.log(sum);

递归实现:求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

递归案例:求一个数字各个位数上的数字的和: 123 —>6 —1+2+3
523

function getEverySum(x) {
   if(x<10){
     return x;
   }
   //获取的是这个数字的个位数
   return x%10+getEverySum(parseInt(x/10));
 }
console.log(getEverySum(1364));//5

递归案例:求斐波那契数列

 function getFib(x) {
   if(x==1||x==2){
     return 1
   }
   return getFib(x-1)+getFib(x-2);
 }
 console.log(getFib(12));

其他参考链接

【js高级 Day1】深入理解原型及作用,构造函数和实例对象和原型对象之间的关系

【js高级 Day2】深入理解原型添加方法,私有函数,面向对象的编程思想(案例小贪吃蛇)

【js高级 Day3】深入理解原型的方式继承,借用构造函数继承,组合继承,拷贝继承

【js高级 Day4】深入理解apply和call方法,作用域,闭包,递归

【js高级 Day5】深入理解浅拷贝,深拷贝,遍历DOM树,正则表达式

【如果你是新手】推荐链接

【 js基础 Day1】js的介绍及基本语法变量,运算符

【 js基础 Day2】js的流程控制:分支语句,循环.顺序结构

【 js基础 Day3】关键字的使用,数组(重点)和函数(重点)

【 js基础 Day4】面向过程,面向对象,自定义对象,内置对象

【 js基础 Day5】函数(重点),作用域,预解析,arguments,对象

【 js基础 Day6】内置对象和基本包装类型等知识

你可能感兴趣的:(js)