Skip to content

Javascript 面向对象编程

概念

面向对象与面向过程

  • 面向对象注重于抽象事物,面向过程更注重于叙述事物;
  • 面向对象逻辑更加清晰有条理,面向过程对于简单逻辑更方便;
  • JS 通过函数和原型,模拟类,而实现面向对象编程;
  • 面向对象的编程思想,主要为实现三件事:封装,继承和多态。

基础知识

this

  • 在函数执行时,会在函数内部创建两个变量: arguments, this
  • arguments 是存储实参的一个类数组对象
  • this 指向函数的执行上下文(谁调用,this指向谁)
function fn() {
  console.log(this);
}

var a = {
  b: fn,
  c: {
    d: fn
  }
};

fn(); // window
a.b(); // a
a.c.d(); // a.c

箭头函数: this 指向的固定化,箭头函数表达式并不绑定自己的 this,arguments,super或 new.target。 箭头函数会捕获其所在上下文的 this 值,作为自己的 this 值;箭头函数会捕获其所在上下文的 this 值,作为自己的 this 值。 正是因为它没有this,所以也就不能用作构造函数。

call/apply

  • 动态改变 this 指向
function fn() {
  console.log(this);
}

var a = {
  b: fn,
  c: {
    d: fn
  }
};

fn.call(a.c); // a.c

Polyfill bind

var slice = Array.prototype.slice;
Function.prototype.bind = function() {
  var thatFunc = this;
  var thatArg = arguments[0];
  var args = slice.call(arguments, 1);

  return function() {
    var funcArgs = args.concat(slice.call(arguments));
    thatFunc.apply(thatArg, funcArgs);
  }
};

new

function Cat(name) {
  this.name = name;
}

var cat = new Cat('lanlan');
  • new 做了哪些操作
  1. 创建一个空对象

  2. 将构造函数的 prototype 赋值给新对象的 __proto__ 属性

  3. 将构造函数的 this 指向新对象

  4. 执行构造函数

  5. 返回新对象

function Cat(name) {
  this.name = name;
}

// var cat = new Cat('lanlan');
var cat =(function() {
  var  obj = {};
  obj.__proto__ = Cat.prototype;
  obj.constructor = Cat;
  Cat.call(obj, 'lanlan');
  return obj;
})();

编程思想

封装

  • 使用工厂模式封装
function createCat(name) {
  var obj = {};
  obj.name = name;
  return obj;
}

var cat = createCat('lanlan');
  • 使用面向对象封装
function Cat(name) {
  this.name = name;
}

var cat = new Cat('lanlan');

继承

// 父类
function Cat(name) {
  this.name = name;
}

Cat.prototype.getName = function() {
  return this.name;
};

组合式继承

function EnglishCat(name) {
  Cat.call(this, name); // 父构造函数执行一次
  this.type = 'English Cat';
}

EnglishCat.prototype = new Cat(); // // 父构造函数执行两次

var cat = new EnglishCat('lanlan');
console.log(cat);

组合式继承问题:

  1. __proto__ 里的属性多余,没有用

  2. Cat 的构造函数执行了两次

寄生组合式继承

function EnglishCat(name) {
  Cat.call(this, name);
  this.type = 'English Cat';
}

// EnglishCat.prototype = Object.create(Cat.prototype);
// EnglishCat.prototype.constructor = EnglishCat;
function inheritPrototype(subClass, superClass) {
  function F() {}
  F.prototype = superClass.prototype;
  subClass.prototype = new F();
  subClass.prototype.constructor = subClass;
}
inheritPrototype(EnglishCat, Cat);

// 对比一下
// var wrapConstructor = function (NativeConstructor) {
//   var Wrapper = function (a, b, c) {
//     if (this instanceof NativeConstructor) {
//       switch (arguments.length) {
//         case 0: return new NativeConstructor();
//         case 1: return new NativeConstructor(a);
//         case 2: return new NativeConstructor(a, b);
//       }
//       return new NativeConstructor(a, b, c);
//     }
//     return NativeConstructor.apply(this, arguments);
//   };
//   Wrapper.prototype = NativeConstructor.prototype;
//   return Wrapper;
// };

var cat = new EnglishCat('lanlan');
console.log(cat);

多态

相同方法,被不同对象调用,结果不同。

function Base() {}
Base.prototype.fn = function() { console.log('Base fn'); }

function SubA() {
  this.fn = function() { console.log('SubA fn'); }
}
function SubB() {
  this.fn = function() { console.log('SubB fn'); }
}

var subA = new SubA();
var subB = new SubB();

subA.fn();
subB.fn();