前端学习-继承和函数进阶

112 阅读10分钟

继承

对象拷贝

for......in :父对象的属性拷贝给子对象。

// 父级的对象
var laoli = {
  name: "laoli",
  money: 1000000,
  house: ["商铺", "住宅"],
  tech: function () {
    console.log("厨艺")
  }
};

// 子级的对象
var xiaoli = {
  name: "xiaoli"
}

// 封装一个对象之间继承的函数
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方法

// 人类类型
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;
}

// 创建学生的实例对象
var s1 = new Student("zs",18,"男",89);
console.dir(s1);

组合继承

// 组合继承:属性在构造函数内部继承,方法通过原型继承
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();

函数定义方式

函数声明

//必须定义函数名

function fun() {
   console.log(1);
}

函数表达式

// 是将函数赋值给一个变量,可以是一个匿名函数
var fn = function () {
   console.log(2);
};

new Function

// 通过构造函数方法定义函数
// 函数本身也是一种对象
var fun = new Function('a','b','var a = "1";console.log(a+b)');
fun(2,3);
console.dir(fun);

函数声明与函数表达式的区别

  • 函数声明必须有名字
  • 函数声明会函数提升,在预解析阶段就已创建,声明前后都可以调用
  • 函数表达式类似于变量赋值
  • 函数表达式可以没有名字,例如匿名函数
  • 函数表达式没有函数提升,在执行阶段创建,必须在表达式执行之后才可以调用

函数的调用方法

普通函数

  • 通过给函数名或者变量名添加()方式执行
  • 内部的 this 默认指向 window
function fun() {
  console.log(this);
}
fun();

构造函数

  • 是通过 new 关键字进行调用
  • 内部的 this 指向的是将来创建的实例对象
function Person(name) {
  this.name = name;
  console.log(this);
}
var p1 = new Person("zs");
Person();

对象中的方法

  • 是通过对象打点调用函数,然后加小括号
  • 内部的 this 默认指向的是调用的对象自己
var o = {
  sayHi: function () {
    console.log("haha");
  },
  fn: fun
}
// this 的指向是要联系执行的上下文,在调用的时候,是按照什么方式调用,指向是不一样的

o.sayHi();

事件函数

  • 不需要加特殊的符号,只要事件被触发,会自动执行函数
  • 事件函数的内部 this 指向的是事件源
document.onclick = function () {
   console.log("点击事件");
};

定时器和延时器中的函数

  • 不需要加特殊的符号,只要执行后,在规定的时间自动执行
  • 默认内部的 this 指向的是 window
setInterval(function () {
   console.log("time");
},1000);

函数内 this 指向的不同场景

  • 函数的调用方式决定了 this 指向的不同:
调用方式非严格模式备注
普通函数调用window严格模式下是underfined
构造函数调用实例对象原型方式中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, ...
    指定的参数列表
// {} 的对象自己是没有 push 方法的
// 类数组对象 getElementsByTagName
var o = {
  0: 10,
  1: 20,
  2: 30,
  length: 3
};

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

apply

  • apply() 方法调用一个函数, 第一个参数是一个指定的 this 值,第二个参数是以一个数组 (或类似数组的对象)形式提供的参数。
  • 注意:该方法的作用和 call() 方法类似,只有一个区别,就是 call() 方法接受的是若干个参数的列表,而 apply() 方法接受的是一个包含多个参数的数组。
  • 语法:
    fun.apply(thisArg, [argsArray])
// 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

  • bind() 函数会创建一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数) 具有相同的函数体(在 ECMAScript 5 规范中内置的 call 属性)。

  • 当目标函数被调用时 this 值绑定到 bind() 的第一个参数,该参数不能被重写。绑定函数被 调用时,bind() 也接受预设的参数提供给原函数。

  • 一个绑定函数也能使用 new 操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。

  • 语法:
    fun.bind(thisArg,arg1, arg2, arg3, ...)

  • 参数:

  • thisArg:当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用 new 操作符调用绑定函数时,该参数无效。

  • arg1, arg2, ...:当绑定函数被调用时,这些参数将置于实参之前传递给被绑定的方法。

  • 返回值:
    返回由指定的 this 值和初始化参数改造的原函数拷贝。

// 想修改的是定时器的函数内部的 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);

总结

call 和 apply 特性一样

  • 都是用来调用函数,而且是立即调用
  • 但是可以在调用函数的同时,通过第一个参数指定函数内部 this 的指向
  • call 调用的时候,参数必须以参数列表的形式进行传递,也就是以逗号分隔的方式依次传递即可
  • apply 调用的时候,参数必须是一个数组,然后在执行的时候,会将数组内部的元素一个一个拿出来,与形参一一对应进行传递
  • 如果第一个参数指定了 null 或者 undefined 则内部 this 指向 window

bind

  • 可以用来指定内部 this 的指向,然后生成一个改变了 this 指向的新的函数
  • 它和 call、apply 最大的区别是:bind 不会调用
  • bind 支持传递参数,它的传参方式比较特殊,一共有两个位置可以传递
    1. 在 bind 的同时,以参数列表的形式进行传递
    2. 在调用的时候,以参数列表的形式进行传递
  • 那到底以谁 bind 的时候传递的参数为准呢还是以调用的时候传递的参数为准
    两者合并:bind 的时候传递的参数和调用的时候传递的参数会合并到一起,传递到函数内部

函数的其他成员

  • arguments 实参集合
  • arguments.callee 函数本身,arguments的一个属性
  • fn.caller 函数的调用者,如果在全局调用,返回的调用者为 null。
  • fn.length 形参的个数
  • fn.name 函数的名称函数的名称
// 看一下函数内部的成员
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);
}

// 灵活使用 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)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。
  • 函数定义时天生就能记住自己生成的作用域环境和函数自己,将它们形成一个密闭的环境,这就是闭包。不论函数以任何方式在任何地方进行调用,都会回到自己定义时的密闭环境进行执行。
// 体会闭包
// 将一个内部函数拿到父函数的外面,观察是否还能调用父函数内部的变量

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