继承与函数进阶

对象之间的继承

对象拷贝

  • for…in:父对象的属性拷贝给子对象
    // 父级的对象
    var laoli = {
    
    
      name: "laoli",
      money: 1000000,
      house: ["商铺", "住宅"],
      tech: function () {
    
    
        console.log("厨艺")
      }
    };
    // 子级的对象
    var xiaoli = {
    
    
      name: "xiaoli"
    }
    // 对象之间进行继承,使用 for……in
    // for (var k in laoli) {
    
    
    //   // 子级有的属性不需要继承
    //   if (xiaoli[k]) {
    
    
    //     continue;
    //   }
    //   xiaoli[k] = laoli[k];
    // }

    // 封装一个对象之间继承的函数
    function extend(parent, child) {
    
    
      for (var k in parent) {
    
    
        // 子级有的属性不需要继承
        if (child[k]) {
    
    
          continue;
        }
        child[k] = parent[k];
      }
    }
    // 调用函数实现继承
    extend(laoli,xiaoli);
    console.log(xiaoli);

原型继承

  • 封装的构造函数就是用来创建一类对象
  • 继承指的是 类型 和 类型之间的继承
  • 学生类型 老师类型
  • 抽象,提取所有的公共的属性,放到一个 父类型中
  • 当前学习阶段,没有一个专门的用来继承的方法
  • 方法有一定缺陷,但是说明这条路是可行的
    // 人类类型
    function Person(name,age,sex) {
    
    
      this.name = name;
      this.age = age;
      this.sex = sex;
    }
    // 学生类型
    function Student(score) {
    
          
      this.score = score;
    }
    // 老师类型
    function Teacher(salary) {
    
    
      this.salary = salary;
    }
    // 原型对象,可以将自己的属性和方法继承给将来的实例对象使用
    Student.prototype = new Person("zs",18,"男");
    Student.prototype.constructor = Student;
    // 生成一个实例
    var s1 = new Student(89);
    var s2 = new Student(100);
    console.dir(s1);
    console.dir(s2);
    console.log(s1.name);
    console.log(s1.constructor);

函数的 call 方法

  • 函数本身就是一种对象,就能够有自己的属性和方法
  • call 方法本身是一种执行函数的方法
  • call 方法在调用函数的时候,有两个功能
    1. 更改函数内部的 this 指向
    2. 调用函数执行内部代码
  • 参数: 第一个参数用来指定 this,第二个及以后,就是传的实参
    function fn(a,b) {
    
    
      console.log(this);
      console.log(a + b);
    }
    var o = {
    
    
      name: "zs"
    }
    // 普通函数调用
    // fn(2,3);
    fn.call(o,3,4);

借用构造函数继承属性

    // 构造函数的属性的继承
    // 人类类型
    function Person(name,age,sex) {
    
    
      this.name = name;
      this.age = age;
      this.sex = sex;
    }
    // 学生类型
    function Student(name,age,sex,score) {
    
    
      // 直接对父类型的构造函数进行一个普通调用
      // Person 普通调用过程中,内部的 this 指向的是 window
      // 可以通过 call 方法更改Person 内部的 this
      Person.call(this,name,age,sex); 
      this.score = score;
    }
    // 老师类型
    function Teacher(name,age,sex,salary) {
    
    
      Person.call(this,name,age,sex); 
      this.salary = salary;
    }
    // 创建学生的实例对象
    var s1 = new Student("zs",18,"男",89);
    var s2 = new Student("ls",19,"男",92);
    console.dir(s1);
    console.dir(s2);

构造函数方法的继承

    // 构造函数的属性的继承
    // 人类类型
    function Person(name,age,sex) {
    
    
      this.name = name;
      this.age = age;
      this.sex = sex;
    }
    // 父类型的原型对象中有方法也需要继承
    Person.prototype.sayHi = function () {
    
    
      console.log("你好");
    };
    // 学生类型
    function Student(name,age,sex,score) {
    
    
      Person.call(this,name,age,sex); 
      this.score = score;
    }
    // 子类型的原型对象上,需要继承父类型原型对象的方法
    // 方法1:对象拷贝继承
    // for (var k in Person.prototype) {
    
    
    //   // 保留自己的 constructor 不要进行继承
    //   if (k === "constructor") {
    
    
    //     continue;
    //   }
    //   Student.prototype[k] = Person.prototype[k];
    // } 

    // 方法2:原型继承
    Student.prototype = new Person();
    Student.prototype.constructor = Student;
    
    // 老师类型
    function Teacher(name,age,sex,salary) {
    
    
      Person.call(this,name,age,sex); 
      this.salary = salary;
    }
    // 创建学生的实例对象
    var s1 = new Student("zs",18,"男",89);
    var s2 = new Student("ls",19,"男",92);
    console.dir(s1);
    console.dir(s2);
    s1.sayHi();

组合继承

    // 组合继承:属性在构造函数内部继承,方法通过原型继承
    function Person(name,age) {
    
    
      this.name = name;
      this.age = age;
    }
    Person.prototype.sayHi = function () {
    
    
      console.log("你好");
    }
    // 生成一个子类型
    function Teacher(name,age,salary) {
    
    
      // 继承父类的属性
      Person.call(this,name,age);
      this.salary = salary;
    }
    // 方法继承,通过原型对象继承
    Teacher.prototype = new Person();
    Teacher.prototype.constructor = Teacher;
    // 生成老师的一个实例
    var t1 = new Teacher("wang",45,10000);
    console.dir(t1);
    console.log(t1.name);
    t1.sayHi();

函数声明和函数表达式

  • 两者的区别
    • 函数声明必须有名字
    • 函数声明会函数提升,在预解析阶段就已创建,声明前后都可以调用
    • 函数表达式类似于变量赋值
    • 函数表达式可以没有名字,例如匿名函数
    • 函数表达式没有函数提升,在执行阶段创建,必须在表达式执行之后才可以调用
// 函数声明提升
    // fun();
    // fn();
    // 函数声明
    // 必须定义函数名
    // function fun() {
    
    
    //   console.log(1);
    // }
    // 函数表达式
    // 是将函数赋值给一个变量,可以是一个匿名函数
    // var fn = function () {
    
    
    //   console.log(2);
    // };
    // fun();
    // fn();

    // 提前调用
    // 现代浏览器进行的是变量声明提升
    // fn();
    // console.log(fn);
    // 低版本浏览器可以进行函数声明提升
    // 进行 if 语句中的函数提升

    // if (true) {
    
    
    //   function fn() {
    
    
    //     console.log("fn-true");
    //   }
    // } else {
    
    
    //   function fn() {
    
    
    //     console.log("fn-false");
    //   }
    // }
    var fn;
    if (true) {
    
    
      fn = function () {
    
    
        console.log("fn-true");
      }
    } else {
    
    
      fn = function () {
    
    
        console.log("fn-false");
      }
    }
    fn();

函数也是对象

  • 函数本身也是对象,可以调用属性和方法
    function fn(a,b) {
    
    
      var a = 1;
      console.log(a + b);
    }
    // 通过构造函数方法定义函数
    // 函数本身也是一种对象
    var fun = new Function('a','b','var a = "1";console.log(a+b)');
    fun(2,3);
    console.dir(fun);

函数的调用和this

  1. 普通的函数,是通过 给函数名或者变量名添加 () 方式执行,内部的 this 默认指向 window
    function fun() {
    
    
      console.log(this);
    }
    fun();
  1. 构造函数,是通过 new 关键字进行调用,内部的 this 指向的是将来创建的实例对象
    function Person(name) {
    
    
      this.name = name;
      console.log(this);
    }
    var p1 = new Person("zs");
    Person();
  1. 对象中的方法,是通过对象打点调用函数,然后加小括号,内部的 this 默认指向的是调用的对象自己
    var o = {
    
    
      sayHi: function () {
    
    
        console.log("haha");
      },
      fn: fun
    }
    // this 的指向是要联系执行的上下文,在调用的时候,是按照什么方式调用,指向是不一样的
    o.fn();
    // o.sayHi();
  1. 事件函数,不需要加特殊的符号,只要事件被触发,会自动执行函数,事件函数的内部 this 指向的是事件源
    document.onclick = function () {
    
    
      console.log("事件");
    };
  1. 定时器和延时器中的函数,不需要加特殊的符号,只要执行后,在规定的时间自动执行,默认内部的 this 指向的是 window
    setInterval(function () {
    
    
      console.log("time");
    },1000);
调用方式 非严格模式 备注
普通函数调用 window 严格模式下是 undefined
构造函数调用 实例对象 原型方法中 this 也是实例对象
对象方法调用 该方法所属对象 紧挨着的对象
事件绑定调用 绑定事件对象
定时器函数 window

call、apply、bind方法

call

  • call() 方法调用一个函数,其具有一个指定的this值和分别地提供的参数(参数的列表)
  • 注意:
    • 该方法的作用和apply() 方法类似,只有一个区别,就是call() 方法接受的是若干个参数的列表,而apply() 方法接受的是一个包含多个参数的数组
  • 语法:
    • fun.call(thisArg, arg1, arg2, arg3, …)
  • thisArg
    • 在 fun 函数运行时指定的 this 值
    • 如果指定了 null 或者 undefined 则内部 this 指向 window
  • arg1, arg2, …
    • 指定的参数列表
    function fun(a,b,c,d) {
    
    
      console.log(this);
      console.log(a + b + c + d);
    }

    // call 方法
    // 1.功能:第一个可以指定函数的 this,第二个可以执行函数并传参
    // 2.参数:第一个参数,传入一个指定让 this 指向的对象,第二个参数及以后,是函数参数的列表
    // 3.返回值:就是函数自己的返回值
    // 4.测试
    var o = {
    
    
      name: "zs"
    }
    fun.call(o,1,2);

apply

  • apply() 方法调用一个函数,第一个参数是一个指定的this值,第二个参数是以一个数组(或类似数组的对象)形式提供的参数
  • 注意:
    • 该方法的作用和call() 方法类似,只有一个区别,就是 call() 方法接受的是若干个参数的列表,而 apply() 方法接受的是一个包含多个参数的数组
  • 语法:
    • fun.apply(thisArg, [argsArray])
    function fun(a,b,c,d) {
    
    
      console.log(this);
      console.log(a + b + c + d);
    }

    var o = {
    
    
      name: "zs"
    }

    // apply 方法
    // 1.功能:第一个可以指定函数的 this,第二个可以执行函数并传参
    // 2.参数:第一个参数,传入一个指定让 this 指向的对象,第二个参数是函数的参数组成的数组
    // 3.返回值:就是函数自己的返回值
    // 4.测试
    fun.apply(o,[4,5]);

bind

  • bind() 函数会创建一个新函数(称为绑定函数)新函数与被调函数(绑定函数的目标函数)具有相同的函数体(在ECMAScript5规范中内置的call属性)
  • 当目标函数被调用时 this 值绑定到bind()的第一个参数,该参数不能被重写。绑定函数被调用时,bind() 也接受预设的参数提供给原函数
  • 一个绑定函数也能使用 new 操作符创建对象:这种行为就像把原函数当成构造器。提供的this 值被忽略,同时调用时的参数被提供给模拟函数。
  • 语法:
    • fun.bind(thisArg, arg1, arg2, arg3, …)
    function fun(a,b,c,d) {
    
    
      console.log(this);
      console.log(a + b + c + d);
    }
    
    var o = {
    
    
      name: "zs"
    }

    // bind 方法
    // 1.功能:第一个可以指定函数的 this,bind 方法不能执行函数,但是可以传参
    // 2.参数:第一个参数,传入一个指定让 this 指向的对象,第二个参数及以后,是函数参数的列表
    // 3.返回值:返回一个新的指定了 this 的函数,也可以叫绑定函数
    // 4.测试
    var fn = fun.bind(o,2,3);
    console.log(fn);
    fn(6,7);

call 的应用

    // 构造函数扩展原型对象方法
    // Array.prototype.getSum = function () {
    
    
      // this 的指向
    // };
    // 数组中的方法
    // var arr = [1,2,3,4];
    // 使用的是 Array 构造函数的原型对象上的方法
    // 方法内部的 this 指向的就是 arr 数组对象,操作的也是 arr 的对象
    // arr.push();
    // arr.splice();
    // arr.getSum();

    // {} 的对象自己是没有 push 方法的
    // 类数组对象 getElementsByTagName
    var o = {
    
    
      0: 10,
      1: 20,
      2: 30,
      length: 3
    };
    // console.log(o[0])
    // 增加一项新的数据
    // o["3"] = 40;
    // o.length = 4;

    // 利用数组中的 push 方法,指定内部的this 为对象 o,就可以处理类数组对象的数据
    Array.prototype.push.call(o,50);
    console.log(o);

apply 的应用

    // apply 方法可以指定一个函数的 this,并且通过数组方式进行传参
    // fun.apply(this,[1,2]);

    // 定义一个数组,利用 apply 方法,可以将它拆开进行操作
    var arr = [1,3,4,6,8];

    // 想借用一些现在内置在js 中的方法
    // console.log(Math.max(1,3,5,7,9));

    // 利用 apply 方法,将数组传给 max 的第二个参数
    // console.log(Math.max.apply(Math,arr));

    console.log(1,2,3);
    console.log.apply(console,arr);

bind 的应用

    // 想修改的是定时器的函数内部的 this
    var o = {
    
    
      name: "zs",
      age: 18,
      s: function () {
    
    
        setInterval(function () {
    
    
          console.log(this.age);
        }.bind(this),1000);
      }
    }
    // o.s();
    // 更改 事件函数中的 this
    document.onclick = function () {
    
    
      console.log(this);
    }.bind(o);

函数的其他成员

  • 自己打印输出一个函数
    function fun() {
    
    
      console.log(1);
    }
    console.dir(fun);
  • 看一下函数内部的成员
    function fn(a,b) {
    
    
      // 实际应用中,会在函数内部直接使用 一个 arguments 的关键字
      console.log(arguments);
      // console.log(arguments.callee);
      // 存储的是函数在调用时,传入的所有 实参 组成的一个类数组对象
      console.log(fn.arguments);
      // 函数的调用者,函数在哪个作用域调用,caller 就是谁,如果在全局调用,值就是 null
      console.log(fn.caller);
      // length 指的是形参的个数
      console.log(fn.length);
      // 函数的名字
      console.log(fn.name);
    }
    // function test() {
    
    
    //   fn(1,2,3,4);
    // }
    // test();
  • 灵活使用 arguments 类数组对象,可以记录所有的实参
    // 模仿制作一个max方法
    function max() {
    
    
      // 判断实参中最大的数
      var nowMax = arguments[0];
      for (var i = 1 ; i < arguments.length;i++) {
    
    
        if (arguments[i] > nowMax) {
    
    
          nowMax = arguments[i];
        }
      }
      return nowMax;
    }
    console.log(max(1,4,7,9));

高阶函数

  • 函数可以作为参数
    // 定义一个函数,吃饭的函数,吃完饭之后,可以做其他的事情,看电影、聊天、看书
    function eat(fn) {
    
    
      console.log("吃晚饭");
      // 接下来的要做的事情是不固定的
      fn();
    }
    eat(function () {
    
    
      console.log("看电影");
    });
  • 函数可以作为返回值
// 需求:通过同一段代码实现以下效果
    // 输出 100 + m
    // 输出 1000 + m
    // 输出 10000 + m
    function outer(n) {
    
    
      return function inner(m) {
    
    
        console.log(m + n);
      }
    }
    // 在外部执行 inner 函数
    //  100 + m
    var fun = outer(100);
    fun(3);
    fun(13);
    fun(23);
    var fun1 = outer(1000);
    fun1(3);

闭包

什么是闭包

  • 一个函数和对其轴为状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围)这样的组合就是闭包(closure)也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在JavaXcript中,每当创建一个函数,闭包就会在函数创建的同时被创建出来

闭包

  • 函数定义时天生就能记住自己生成的作用域环境和函数自己,将它们形成一个密闭的环境,这就是闭包。不论函数以任何方式在任何地方进行调用,都会回到自己定义时的密闭环境进行执行。

观察闭包

  • 从广义上来说,定义在全局的函数也是一个闭包,只是我们没办法将这样的函数拿到更外面的作用域进行调用,从而观察闭包的特点。
  • 闭包是天生存在的,不需要额外的结构,但是我们为了方便观察闭包的特点,需要利用一些特殊结构将一个父函数内部的子函数拿到父函数外部进行调用,从而观察闭包的存在

体会闭包

    // 将一个内部函数拿到父函数的外面,观察是否还能调用父函数内部的变量
    function outer() {
    
    
      var a = 10;
      function inner() {
    
    
        console.log(a);
      }
      // 将inner 函数作为返回值
      return inner;
    }
    // 在outer函数的外面,是不能直接访问 a 变量
    // outer();
    // console.log(a);
    // 将 outer 执行的结果,赋值给一个变量
    var inn = outer();
    // console.log(inn);
    // 在全局调用 inn,按道理应该查找全局的 a变量
    inn();
    // 输出的真正结果是 10,来自于 outer 函数内部的变量

闭包的理解和应用

  • 将一个内部函数拿到父函数的外面,观察是否还能调用父函数内部的变量
  • 形成闭包环境中的变量不是一成不变的,可以被更改
    function outer() {
    
    
      // 形成闭包环境中的变量不是一成不变的,可以被更改
      var a = 10;
      function inner() {
    
    
        console.log(a++);
      }
      // 将inner 函数作为返回值
      return inner;
    }
    var inn = outer();
    inn();
    inn();

闭包的用途

  • 可以在函数外部读取函数内部成员
  • 让函数内成员始终活在内存中

闭包的问题

    // 给数组中的每一项赋值一个 函数
    var arr = [];
    for (var i = 0 ; i <= 10 ; i++) {
    
    
      // 自调用函数
      (function (i) {
    
    
        arr[i] = function () {
    
    
          console.log(i);
        };
      })(i);
    }
    // 目的:调用数组对应的项,输出它的对应下标
    arr[0]();
    arr[1]();
    arr[2]();

猜你喜欢

转载自blog.csdn.net/CS_DGD/article/details/111031832