js的面向对象

面向对象(oop)

javascript没有类的概念,但是javascript通过构造函数来实现类,通过原型链实现继承。
面向对象的三大特性:
  • 封装:将对数据的操作细节隐藏起来,只暴露对外的接口,外界调用端不需要(也不可能)知道细节,就能通过对外提供的接口来访问该对象。同时也保证外界无法任意改变对象内部的属性和方法
  • 继承:子类继承父类,子类除了拥有父类的所有特性,还有一些自己的具体方法
  • 多态:由继承而产生了相关的不同的类,对同一个方法可以有不同响应
 

1、创建对象

 

1.1 工厂模式

 
工厂模式就是,创建一个函数,用函数来封装以特定接口创建对象的细节
 
function createPerson(name, age, job) {
  var o = new Object();
  o.name = name;
  o.age = age;
  o.job = job;
  o.say = function (){
    console.log(this.name)
  }
  return o;
}
var person1 = createPerson('sun', '23', 'doctor');
var person2 = createPerson('sang', '25', 'teacher')
 
优点:可以创建多个相似对象的问题
缺点:只能返回固定的属性和方法
 

1.2 构造函数模式

 
function Person(name, age, job) {
  this.name = name;
  this.age = age;
  this.job = job;
  this.say = function (){
    console.log(this.name)
  }
}
var person3 = new Person('sun', '23', 'doctor');
var person4 = new Person('sang', '25', 'teacher');
 
// 可以将他的实例标识为一中特定的类型、
person3 instanceof  Person   // true
person3 instanceof  Object   // true
 
优点:没有显式的直接创建对象,可以将他的实例标示为一种特定的类型
缺点:每个方法都在每个实例上重新创建了一遍,比如:上述的say方法,实例person3和person4时,都会创建一遍。
 
注:new操作符做了以下步骤:
    • 创建一个新对象
    • 将构造函数中的作用域赋值给新对象
    • 执行构造函数中的代码
    • 返回新对象
 
为啥叫构造函数:    
        person3和person4实例中都有一个构造函数(constructor)属性,他指向Person.
        即:person3.constructor === Person; person4.constructor === Person
 

1.3 原型模式

 
相同的属性,放置在构造函数上。而通用的方法等,放置在其原型上,解决上述构造函数缺点。
 
function Person(name, age, job) {
  this.name = name;
  this.age = age;
  this.job = job
}
Person.prototype.sayName = function () {
  console.log(this.name)
}
var person5 = new Person('sun', '23', 'doctor');
var person6 = new Person('sang', '23', 'teacher')
 
优点:让所有对象实例共享它所包含的属性和方法,不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中
 

1.3.1 原型对象

 
每当我们创建一个函数,就会为该函数创建一个prototype原型属性,这个属性指向的是该函数的原型对象,在默认情况下,所有原型对象都会自动获得一个constructor构造函数属性,它指向的 是prototype属性所在函数的指针,我们通过构造函数,可以继续为原型对象添加其他属性和方法。
在每个原型的实例中,都有一个__proto__属性,指向的是该实例的原型对象prototype
 
以下为构造函数 + 原型对象模式, 集两者之长
 
// 构造函数
function Person(name, age, job) {
  this.name = name;
  this.age = age;
  this.job = job;
}
// 原型
Person.prototype.say = function () {
  console.log(this.job)
}
Person.prototype.a = 'bc'
 
var person1 = new Person('sun', '23', 'teacher')
var person2 = new Person('sang', '22', 'doctor')
console.log(Person.prototype.constructor) // Person
console.log(person1.constructor) // Person
// Person{say: Function , a: 'bc' }
console.log(person1.__proto__)
 
构造函数,原型对象和实例的关系如下:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针
 

1.3.2 原生对象原型

 
所有原生对象的引用类型,都是采用原型模式来创建的
例如:在Array.prototype中可以找到sort()  , 在String.prototype中可以找到substring(),
我们可以介于此,来创建新的方法,或者改变原来的方法
 
例如:数组中新增删除方法
 
Array.prototype.delete = function (v){
  var index = this.indexOf(v);
  if (index > -1) {
    this.splice(index, 1)
  } else {
    console.log('error')
  }
}
 
var arr = [1,2,3,4,5]
arr.delete(1)
console.log(arr) // [2,3,4,5]

 

1.3.3 判断属性来自实例还是原型

 
  •  in 操作, 无法判断是否是实例属性还是原型属性,只能判断该对象是否有该属性
  •  hasOwnProperty()  判断属性来自实例属性还是原型属性
// 构造函数
function Person(name, age, job) {
  this.name = name;
  this.age = age;
  this.job = job;
}
// 原型
Person.prototype.say = function () {
  console.log(this.job)
}
Person.prototype.a = 'bc'
 
var person1 = new Person('sun', '23', 'teacher')
 
 
person1.cc = 'sa'
console.log(person1.hasOwnProperty('cc')) // true 来自实例
console.log(person1.hasOwnProperty('name')) // true 来自构造函数
console.log(person1.hasOwnProperty('a')) // false 来自原型
console.log('name' in person1) // true
console.log('cc' in person1) // true

 

2、继承

 

2.1 原型链

 
  原型链:利用原型让一个引用类型继承另一个引用类型的属性和方法, 其为实现继承的主要方法
 
function Person() {
  this.name = 'sang'
}
Person.prototype.say = function (){
  console.log(`I'm ${this.name}`)
}
 
function Work() {
  this.job = 'teacher'
}
// 继承了Person
Work.prototype = new Person()
 
Work.prototype.do = function (){
  console.log(`I'm ${this.name} and I'm a ${this.job}`)
}
 
var sang = new Work()
sang.say() // I'm sang
sang.do() // I'm sang and I'm a teacher
 
console.log(sang.__proto__ === Work.prototype) // true
console.log(sang.__proto__ === Person.prototype) // false
 
定义了两个类型:Person和Work, 每个类型有一个属性和方法,Work类型的原型继承了Person(通过创建Person的实例),  所以可以在Work中访问到Person的属性和方法
其实例的__proto__ 指向Work的原型。
 
以下为上述案例的关系
 

2.2 默认原型

 
在之前我们知道,所有原生对象的引用类型,都是采用原型模式来创建的。同时,所有引用类型默认都继承了Object,而这个继承也是通过原型链实现的
所有函数的默认类型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype. 这也正是所有自定义类型都会继承toString(),valueOf()等默认方法的根本原因
 
所以,上述案例的关系,可以改为:
 
所以:Work继承了Person, 而Peron继承了Object
 

2.3 确定原型和实例的关系

 
可以通过两种方式来确定原型和实例的关系
 
  • instanceof  测试实例与原型链中出现的构造函数,如果出现过,则返回true
  • isPrototypeOf  只要是原型链找那个出现过的原型,都可以说是该原型链所派生的实例的原型,则返回true.
 
console.log(sang instanceof Object) // true
console.log(sang instanceof Person) // true
console.log(sang instanceof Work) // true
 
console.log(Object.prototype.isPrototypeOf(sang)) // true
console.log(Person.prototype.isPrototypeOf(sang)) // true
console.log(Work.prototype.isPrototypeOf(sang)) // true

3、ES6中的类

 
ES6中,为为我们提供了一个比较传统的方式,引入了Class, 即类的概念,作为对象模板,通过class关键字,来定义类,通过constructor,来定义构造函数
 

3.1 类的用法

 
class Person{
  // 构造函数
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  say(){
    console.log(`I'm ${this.name}`)
  }
}
let sang = new Person('sang', 23)
sang.say() // I'm sang
 
构造函数的prototype属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype属性上面
在类的实例上面调用方法,其实就是调用原型上的方法
constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。
其中,他的Person.prototype.constructor还是指向构造函数Person:  
 
  console.log(Person.prototype.constructor === Person) // true 
  // 其实例的__proto__ 依旧指向他的原型
  console.log(sang.__proto__ === Person.prototype) // true
 

3.2 class的继承

 
使用extends关键字,来实现继承
 
  class Person{
    // 构造函数
    constructor(name, age) {
      this.name = name;
      this.age = age;
    }
    say(){
      console.log(`I'm ${this.name}`)
    }
  }
  let sang = new Person('sang', 23)
  sang.say() // I'm sang
 
  // extends关键字, 继承了Peron所有的属性和方法
  class Work extends Person{
    constructor(name, age, job) {
      super(name, age) // 调用父类的constructor(name, age)
      this.job = job
    }
    do() {
      console.log(`I'm ${this.name} and I'm a ${this.job}`)
    }
  }
  
  let sun = new Work ('sun', '23', 'teacher');
  sun.do() //I'm sun and I'm a teacher
 
  console.log(sun instanceof Object) // true
  console.log(sun instanceof Person) // true
  console.log(sun instanceof Work) // true
 
  console.log(Object.prototype.isPrototypeOf(sun)) // true
  console.log(Person.prototype.isPrototypeOf(sun)) // true
  console.log(Work.prototype.isPrototypeOf(sun)) // true
 
class定义的类中,依旧可以使用instanceof  和 isPrototypeOf 来判断原型和实例的关系。
 
super关键字有两种情况:1.作为函数使用, 2.作为对象使用
 
  1. 作为函数使用, 在继承时,在constructor内调用,代表父类的构造函数,只能在子类的constructor中使用
class A {}
class B extends A {
  constructor() {
    super();
  }}
    
虽然代表的是其父类的构造函数,但是返回的是父类的实例,所以:这里的super()相当于A .prototype.constructor.call(this)
 
  1. 作为对象使用, 在普通方法中,指向父类的原型对象;在静态方法中,指向父类
class A {}
A.prototype.x = 2;
 
class B extends A {
  constructor() {
    super();
    console.log(super.x) // 2
  }}
 
let b = new B();
 
以上的x是定义在A的原型中,所以super.x可以取到他的值, 这里的super就是作为对象使用,指向的是他的父类原型对象,
 
静态方法: static   直接使用
 
class A {
  constructor() {
    this.x = 1;
  }
  static print() {
    console.log(this.x);
  }
}
 
class B extends A {
  constructor() {
    super();
    this.x = 2;
  }
  static m() {
    super.print();
  }
}
 
B.x = 3;
// 静态方法, 直接调用
// 其中,他的this指向的是B, 而不是B的实例
B.m() // 3

 

在静态方法中, super作为对象使用,他指代的就是他的构造函数,
posted @ 2020-05-29 00:14  whale~alince  阅读(163)  评论(0编辑  收藏  举报