JS面向对象

248 阅读10分钟

学习了coderwhy的JavaScript高级语法视频课的笔记

如有错误或者不合适的地方,敬请见谅,欢迎指出和拓展,谢谢各位了

一、面向对象是现实的抽象方式

  1. 对象是JavaScript中一个非常重要的概念,这是因为对象可以将多个相关联的数据封装到一起,更好的描述一个事物:
  • 比如我们可以描述一辆车:Car,具有颜色(color)、速度(speed)、品牌(brand)、价格(price),行驶(travel)等 等;
  • 比如我们可以描述一个人:Person,具有姓名(name)、年龄(age)、身高(height),吃东西(eat)、跑步(run) 等等。
  1. 对象来描述事物,更有利于我们将现实的事物,抽离成代码中某个数据结构
  • 所以有一些编程语言就是纯面向对象的编程语言,比Java;
  • 你在实现任何现实抽象时都需要先创建一个类,根据类再去创建对象。

二、JavaScript的面向对象

  1. JavaScript其实支持多种编程范式的,包括函数式编程和面向对象编程
  • JavaScript中的对象被设计成一组属性的无序集合,像是一个哈希表,有key和value组成;
  • key是一个标识符名称,value可以是任意类型,也可以是其他对象或者函数类型;
  • 如果值是一个函数,那么我们可以称之为是对象的方法
  1. 如何创建一个对象呢?
  • 早期使用创建对象的方式最多的是使用Object类,并且使用new关键字来创建一个对象;
var obj = new Object()
obj.name = "why"
obj.age = 18
obj.height = 1.88
obj.running = function() {
  console.log(this.name + "在跑步~")
}
  • 后来很多开发者为了方便起见,都是直接通过字面量的形式来创建对象。
var info = {
  name: "kobe",
  age: 40,
  height: 1.98,
  eating: function() {
    console.log(this.name + "在吃东西~")
  }
}
  • 对属性操作的控制
1//获取指定属性值
obj.name

2// 删除属性
delete obj.name

3// 遍历属性
for (var key in obj) {
  console.log(key)
}
  1. Object.defineProperty(obj,prop,descriptor)方法
  • obj要定义属性的对象;
  • prop要定义或修改的属性的名称或 Symbol;
  • descriptor要定义或修改的属性描述符
// 属性描述符是一个对象
Object.defineProperty(obj, "属性名", {
  // 很多的配置
  value: 值
})
  • 属性描述符分类(两种)
    • 数据属性(Data Properties)描述符(Descriptor);
    • 存取属性(Accessor访问器 Properties)描述符(Descriptor); image.png a. 数据属性描述符:

image.png 注意下面两种方式中,数据描述符默认值的差别:

1// name和age虽然没有使用属性描述符来定义, 但是它们也是具备对应的特性的
// configurable: true
// enumerable: true
// writable: true
var obj = {
  name: "why",
  age: 18
}
2// 数据属性描述符
// 用了属性描述符, 那么会有默认的特性
Object.defineProperty(obj, "address", {
  value: "北京市", // 默认值undefined
  
  // 该特殊不可删除/也不可以重新定义属性描述符
  configurable: false, // 默认值false
  
  // 该特殊是配置对应的属性(address)不可以枚举
  enumerable: false, // 默认值false
  
  // 该特性是属性不可以赋值(写入值) 
  writable: false // 默认值false
})

b. 存取属性描述符:

image.png

var obj = {
  name: "why",
  age: 18,
  _address: "北京市"// '_'代表私有属性,js里面是没有严格意义的私有属性
}

// 存取属性描述符
// 1.隐藏某一个私有属性被希望直接被外界使用和赋值
// 2.如果我们希望截获某一个属性它访问和设置值的过程时, 也会使用存储属性描述符
//不能和value和writable一起用
Object.defineProperty(obj, "address", {
  enumerable: true,
  configurable: true,
  get: function() {
    foo()
    return this._address
  },
  set: function(value) {
    bar()
    this._address = value
  }
})

console.log(obj.address)
  • 上面的Object.defineProperty是定义一个,Object.defineProperties() 方法直接在一个对象上定义多个新的属性或修改现有属性,并且返回该对象。
var obj = {
  // 私有属性(js里面是没有严格意义的私有属性)
  _age: 18,
  
  //也可以在这样写,但是没有直接定义数据属性描述符的操作
  set age(value) {
    this._age = value
  },
  get age() {
    return this._age
  }
}

Object.defineProperties(obj, {
  name: {
    configurable: true,
    enumerable: true,
    writable: true,
    value: "why"
  },
  
  age: {
    configurable: true,
    enumerable: true,
    get: function() {
      return this._age
    },
    set: function(value) {
      this._age = value
    }
  }
})
  1. 对象方法补充
  • 获取对象的属性描述符:
    • getOwnPropertyDescriptor
    • getOwnPropertyDescriptors
// 1、获取某一个特性属性的属性描述符
Object.getOwnPropertyDescriptor(obj, "name")
// 2、获取对象的所有属性描述符
Object.getOwnPropertyDescriptors(obj)
  • 禁止对象扩展新属性:preventExtensions
    • 给一个对象添加新的属性会失败(在严格模式下会报错)
Object.preventExtensions(obj)
  • 密封对象,不允许配置和删除属性:seal
    • 实际是调用preventExtensions
    • 并且将现有属性的configurable:false
Object.seal(obj)
  • 冻结对象,不允许修改现有属性: freeze
    • 实际上是调用seal
    • 并且将现有属性的writable: false
Object.freeze(obj)

三、简单叙述对象的原型

  1. JavaScript当中每个对象都有一个特殊的内置属性 [[prototype]],这个特殊的对象可以指向另外一个对象。
  2. 那么这个对象有什么用呢?
  • 当我们通过引用对象的属性key来获取一个value时,它会触发 [[Get]]的操作;
  • 这个操作会首先检查该属性是否有对应的属性,如果有的话就使用它;
  • 如果对象中没有改属性,那么会访问对象[[prototype]]内置属性指向的对象上的属性;
  1. 那么如果通过字面量直接创建一个对象,这个对象也会有这样的属性吗?如果有,应该如何获取这个属性呢?
  • 答案是有的,只要是对象都会有这样的一个内置属性;
  1. 获取的方式有两种:
  • 方式一:通过对象的 __ proto __ 属性可以获取到(但是这个是早期浏览器自己添加的,存在一定的兼容性问 题);
  • 方式二:通过 Object.getPrototypeOf 方法可以获取到;
1var obj = { name: "why" } // [[prototype]]
console.log(obj.__proto__) // [Object: null prototype] {}

2var obj = { name: 'why' }
// ES5之后提供的Object.getPrototypeOf
console.log(Object.getPrototypeOf(obj))// [Object: null prototype] {}

四、简单叙述函数的原型和原型链

function foo() {
}

// 1、函数也是一个对象
console.log(foo.__proto__) // 函数作为对象来说, 它也是有[[prototype]] 隐式原型

// 2、函数它因为是一个函数, 所以它还会多出来一个显示原型属性: prototype
console.log(foo.prototype)

var f1 = new foo()
var f2 = new foo()

console.log(f1.__proto__ === foo.prototype)//true
console.log(f2.__proto__ === foo.prototype)//true
  • 创建对象的内存表现
function Person(){}

var p1=new Person()
var p2=new Person()

image.png

  • 重写原型对象
function foo() {}

// 直接修改整个prototype对象
foo.prototype = {
  ...
}

// 真实开发中我们可以通过Object.defineProperty方式添加constructor
Object.defineProperty(foo.prototype, "constructor", {
  enumerable: false,
  configurable: true,
  writable: true,
  value: foo
})
  • 原型链关系的内存图 在上图中,Person函数的原型对象并不是顶层原型,但是最终指向Object.prototype([ Object: null prototype ] {}),也就是顶层原型对象,在这链接查找的过程中,就形成的原型链。原型链最顶层的原型对象就是Object的原型对象。

image.png

image.png

五、JavaScript创建多个对象的方案

  1. 字面量:一个个的创建。
  2. 工厂模式
function createPerson(name, age, height, address) {
  var p = {}
  p.name = name
  p.age = age
  p.height = height;
  p.address = address

  p.eating = function() {
    console.log(this.name + "在吃东西~")
  }

  p.running = function() {
    console.log(this.name + "在跑步~")
  }

  return p
}

var p1 = createPerson("张三", 18, 1.88, "广州市")
var p2 = createPerson("李四", 20, 1.98, "上海市")

// 工厂模式的缺点(获取不到对象最真实的类型)
console.log(p1, p2)
//返回值
{
  name: '张三',
  age: 18,
  height: 1.88,
  address: '广州市',
  eating: [Function (anonymous)],
  running: [Function (anonymous)]
} {
  name: '李四',
  age: 20,
  height: 1.98,
  address: '上海市',
  eating: [Function (anonymous)],
  running: [Function (anonymous)]
}

3、原型和构造函数

function Person(name, age, height, address) {
  this.name = name
  this.age = age
  this.height = height
  this.address = address
}

Person.prototype.eating = function() {
  console.log(this.name + "在吃东西~")
}

Person.prototype.running = function() {
  console.log(this.name + "在跑步~")
}

var p1 = new Person("why", 18, 1.88, "北京市")
var p2 = new Person("kobe", 20, 1.98, "洛杉矶市")
// 返回结果
Person { name: 'why', age: 18, height: 1.88, address: '北京市' }
Person { name: 'kobe', age: 20, height: 1.98, address: '洛杉矶市' }

六、面向对象的三大特性

  • 面向对象有三大特性:封装、继承、多态
    • 封装:我们前面将属性和方法封装到一个类中,可以称之为封装的过程;
    • 继承:继承是面向对象中非常重要的,不仅仅可以减少重复代码的数量,也是多态前提(纯面向对象中);
    • 多态:不同的对象在执行时表现出不同的形态; 继承:

1-1). 原型链实现继承

function Father() {
  this.P_name = 'P_xxx'
}
Father.prototype.gz = function () {
  console.log('工作赚钱')
}

function Son() {
  this.S_name = 'S_xxx'
}

Son.prototype = new Father()

Son.prototype.by = function () {
  console.log('毕业了')
}

var S = new Son()

S.by()// 毕业了
S.gz()// 工作赚钱
console.log(S.P_name)// P_xxx
console.log(S.S_name)// S_xxx

注意:下列图中P指的是P=new Father()

继承前:

image.png

继承后:

image.png

1-2). 原型链继承的常见弊端

  • 第一,我们通过直接打印S对象是看不到这个继承的属性
console.log(S)//Father { name: 'S_xxx' }
  • 第二,这个属性会被多个对象共享,如果这个对象是一个引用类型,那么就会造成问题; 如果是S.arr='father'添加到的是S实例对象
function Father() {
  this.P_name = 'P_xxx'
  this.arr = []
}
Father.prototype.gz = function () {
  console.log('工作赚钱')
}

function Son() {
  this.S_name = 'S_xxx'
}

Son.prototype = new Father()

Son.prototype.by = function () {
  console.log('毕业了')
}

var S = new Son()
var S2 = new Son()

S.arr.push('father')

console.log(S.arr)// [ 'father' ]
console.log(S2.arr)// [ 'father' ]

image.png

  • 第三,不能给Person传递参数,因为这个对象是一次性创建的(没办法定制化); 2-1). 组合式继承,其中就有借用构造函数继承和原型链继承

解决了原型链继承的常见弊端

function Father(P_name) {
  this.P_name = P_name
}
Father.prototype.gz = function () {
  console.log('工作赚钱')
}

function Son(P_name, S_name) {
  Father.call(this, P_name)
  this.S_name = S_name
}

Son.prototype = new Father()

Son.prototype.by = function () {
  console.log('毕业了')
}

var S = new Son('father', 'son')
console.log(S.P_name)// father
console.log(S.S_name)// son

image.png

2-2). 借用构造函数继承的常见弊端

  • Father函数至少调用了两次
  • S的原型对象上会多出一些属性, 但是这些属性是没有存在的必要 image.png
  1. 父类原型赋给子类原型
// 上面2-1借用构造函数继承的Son.prototype = new Father()改为下面方式
// 直接将父类的原型赋值给子类, 作为子类的原型
Son.prototype = Father.prototype

弊端:就是所有继承Father子类添加方法都在Father.prototype上共享。

  1. 原型式继承-对象
var obj = {
  name: "why",
  age: 18
}

// 1、原型式继承函数
function createObject1(o) {
  var newObj = {}
  Object.setPrototypeOf(newObj, o)
  return newObj
}

// 2、
function createObject2(o) {
  function Fn() {}
  Fn.prototype = o
  var newObj = new Fn()
  return newObj
}

// var info = createObject2(obj)

// 3、
var info = Object.create(obj)

console.log(info)
console.log(info.__proto__)
  1. 寄生式继承-对象
var personObj = {
  running: function() {
    console.log("running")
  }
}

function createStudent(name) {
  var stu = Object.create(personObj)
  stu.name = name
  stu.studying = function() {
    console.log("studying~")
  }
  return stu
}

var stuObj = createStudent("why")
var stuObj1 = createStudent("kobe")
var stuObj2 = createStudent("james")
  1. 寄生组合式继承(上面所有方案的最终方案)
function createObject(o) {
  function Fn() {}
  Fn.prototype = o
  return new Fn()
}

function inheritPrototype(SubType, SuperType) {
  SubType.prototype = Objec.create(SuperType.prototype)
  Object.defineProperty(SubType.prototype, "constructor", {
    enumerable: false,
    configurable: true,
    writable: true,
    value: SubType
  })
}

function Person(name, age, friends) {
  this.name = name
  this.age = age
  this.friends = friends
}

Person.prototype.running = function() {
  console.log("running~")
}

Person.prototype.eating = function() {
  console.log("eating~")
}


function Student(name, age, friends, sno, score) {
  Person.call(this, name, age, friends)
  this.sno = sno
  this.score = score
}

inheritPrototype(Student, Person)

Student.prototype.studying = function() {
  console.log("studying~")
}

var stu = new Student("why", 18, ["kobe"], 111, 100)
console.log(stu)
stu.studying()
stu.running()
stu.eating()

console.log(stu.constructor.name)

七、原型内容补充

  1. hasOwnProperty
  • 对象是否有某一个属于自己的属性(不是在原型上的属性)
  1. in/for in 操作符
  • 判断某个属性是否在某个对象或者对象的原型
var obj = {
  name: 'why',
  age: 18
}

var info = Object.create(obj, {
  address: {
    value: '北京市',
    enumerable: true
  }
})

// hasOwnProperty方法判断
console.log(info.hasOwnProperty('address'))// true
console.log(info.hasOwnProperty('name'))// false

// in 操作符: 不管在当前对象还是原型中返回的都是true
console.log('address' in info)// true
console.log('name' in info)// true

// for in 遍历对象属性
for (var key in info) {
  console.log(key)
}
  1. instanceof
  • 用于检测构造函数的pototype,是否出现在某个实例对象的原型链上
function createObject(o) {
  function Fn() {}
  Fn.prototype = o
  return new Fn()
}

function inheritPrototype(SubType, SuperType) {
  SubType.prototype = createObject(SuperType.prototype)
  Object.defineProperty(SubType.prototype, "constructor", {
    enumerable: false,
    configurable: true,
    writable: true,
    value: SubType
  })
}


function Person() {

}

function Student() {

}

inheritPrototype(Student, Person)

console.log(Person.prototype.__proto__)

var stu = new Student()
console.log(stu instanceof Student) // true
console.log(stu instanceof Person) // true
console.log(stu instanceof Object) // true
  1. isPrototypeOf
  • 用于检测某个对象,是否出现在某个实例对象的原型链上
function Person() {

}

var p = new Person()

console.log(p instanceof Person)// true
console.log(Person.prototype.isPrototypeOf(p))// true

var obj = {
  name: "why",
  age: 18
}

var info = Object.create(obj)

// console.log(info instanceof obj)
console.log(obj.isPrototypeOf(info)) //true
  1. 原型继承关系图 image.png