愿你坚持不懈,努力进步,进阶成自己理想的人

—— 2017.09, 写给3年后的自己

Javascript学习总结——面向对象(方法、原型)

一、方法

1、对象内定义的函数,叫做方法
2、关于this,只能在对象函数的顶级作用域内起作用,如:

var me = {
  name: "Ruphi",
  birth: 1996,
  age: function() {
    function foo() {
      var y = new Date().getFullYear();
      return y - this.birth;
    }
    return foo();
  }
}
console.log(me.age());
// strict模式下,this会指向undefined,所以报错,非严格模式下,指向window全局对象

修正如下:

var me = {
  name: "Ruphi",
  birth: 1996,
  age: function() {
    var that = this;
    function foo() {
      var y = new Date().getFullYear();
      return y - that.birth;
    }
    return foo();
  }
}
console.log(me.age());

3、首先看以下例子:

function getAge() {
  var y = new Date().getFullYear();
  return y - this.birth;
}

var me = {
  name: "Ruphi",
  birth: 1996,
  age: getAge
}

getAge(); // 对于getAge来说,严格模式下this未定义,会报错。非严格模式下,它的this是指向window,window.birth未定义
me.age(); // 可以正常使用,因为此时this就会指向me对象

我们其实可以自由控制this指向哪个对象,使用apply(object, paramsArray)即可,如:

function getAge() {
  var y = new Date().getFullYear();
  return y - this.birth;
}

var me = {
  name: "Ruphi",
  birth: 1996,
  age: getAge
}

console.log(getAge.apply(me));

4、apply和call的区别,apply传递参数打包成Array,而call则按顺序传递
5、对于普通函数,this绑定null即可,如:

Math.max.apply(null, [1, 2, 3]); // 返回3
Math.max.call(null, 1, 2, 3);



二、原型与原型链

大部分现代编程语言中,实现面向对象主要是通过类(Class)和对象来实现的。但是JS是基于原型的语言,它的面向对象特性,主要是通过原型来实现的

1、定义类

1)在其他语言中,类的定义主要通过class关键字来定义,然后声明构造函数(事实上,实例化的过程,是通过构造函数来完成的),而在ES6之前,JS中并没有定义类的概念,所以当我们需要定义一个类的时候,实际上我们应该做的是声明一个构造函数,然后使用new关键字实例化一个对象,如:

function Employee() {
  this.name = "";
  this.dept = "general";
}

var tom = new Employee;

2)如果要定义一个带有参数的构造函数,且构造函数可以有默认值,则可以:

function Employee(name, dept) {
  this.name = name || "";
  this.dept = dept || "general";
}

如上的代码,相当于PHP中的:

<?php
class Employee {
  function __construct($name = "", $dept = "general") {
  }
}


2、继承

1)在JS中,实现继承是通过指定原型来实现的,和其他语言不同的是,JS的一个继承关系中,在运行过程中是可以改变的,但是其他语言则不可以,要实现继承的层级结构,我们需要做两步:

  • 第一,声明子类的构造函数
  • 第二,将子类构造函数的prototype属性指向一个新建的父类实例,如要实现Manager类继承自Employee类,则:
// 声明子类的构造函数
function Manager(reports) {
  this.reports = reports || [];
}
// 实现继承(相当于PHP中的 Manager extends Employee)
Manager.prototype = new Employee;

2)构造器的继承,有两种方式,如:

function Manager(reports) {
  Employee.call(this);
}
// 或者使用以下方式
function Manager(reports) {
  this.base = Employee;
  this.base();
}


3、原型链

对于一个对象而言,在获取它的属性的时候,查找过程如下:

  • 首先,查找对象自己的属性(称为本地属性)
  • 如果在本地属性中没有找到,则通过对象的__proto__属性(__proto__属性是联系对象和它的原型的一个桥梁,__proto__指向它的原型)查找原型中是否有该属性
  • 如果当前原型中还是没有找到,那么会再通过原型的__proto__向上查找,直到在Object.prototype中也没有找到的时候,就会停止寻找并返回undefined,如:
var tom = new Manager();
// 将会有如下关系
tom.__proto__ == Manager.prototype;
tom.__proto__.__proto__ == Employee.prototype;
tom.__proto__.__proto__.__proto__ == Object.prototype;
tom.__proto__.__proto__.__proto__.__proto__ == null;

像上面那样子,tom -> Manager.prototype -> Employee.prototype -> Object.prototype -> null,就形成了一个链式结构,称之为原型链,而__proto__在这里充当了指针的作用(相当于链表中的next),我们可以通过__proto__一直向上查找

因此,当我们需要定义一个方法或者属性让所有原型链中的对象共享的话,需要在prototype属性上进行操作,才能传播到原型链中的其他对象


4、关于new操作符

new操作符用于实例化一个对象,只有使用new操作符实例化构造函数,构造函数中的this才会绑定为新建的对象

new操作符所做的工作,则相当于:

// 首先创建一个空对象
var f = {};
// 其次,将对象的__proto__指向构造函数的原型
f.__proto__ = ConsFunc.prototype;
// 再者,将构造函数里的this绑定为当前对象
ConsFunc.call(this);