JS对象创建-封装、原型、原型链、寄生组合式继承、对象的方法补充

面向对象有三大特性:封装、继承、多态

封装:我们前面将属性和方法封装到一个类中,可以称之为封装的过程;

继承:继承是面向对象中非常重要的,不仅仅可以减少重复代码的数量,也是多态前提(纯面向对象中);

多态:不同的对象在执行时表现出不同的形态

JavaScript原型链

我们知道,从一个对象上获取属性,如果在当前对象中没有获取到就会去它的原型上面获取:

那么什么地方是原型链的尽头呢

我们会发现它打印的是 [Object: null prototype] {}

事实上这个原型就是我们最顶层的原型了

从Object直接创建出来的对象的原型都是 [Object: null prototype] {}

一、创建对象 – 构造函数和原型组合

我们在构造函数的方式创建对象时,有一个弊端:会创建出重复的函数,比如running、eating这些函数

那么有没有办法让所有的对象去共享这些函数呢?

可以,将这些函数放到Person.prototype的对象上即可

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, "洛杉矶市")

p1.eating()
p2.eating()

附:构造函数创建对象

// 规范: 构造函数的首字母一般是大写
function Person(name, age, height, address) {
  this.name = name
  this.age = age
  this.height = height
  this.address = address

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

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


var p1 = new Person("张三", 18, 1.88, "广州市")
var p2 = new Person("李四", 20, 1.98, "北京市")

console.log(p1)
console.log(p2)
p1.eating()
p2.eating()

二、原型

对象的原型

JavaScript当中每个对象都有一个特殊的内置属性 [[prototype]],这个特殊的对象可以指向另外一个对象。

那么这个对象有什么用呢?

当我们通过引用对象的属性key来获取一个value时,它会触发 [[Get]]的操作;

这个操作会首先检查该属性是否有对应的属性,如果有的话就使用它;

如果对象中没有改属性,那么会访问对象[[prototype]]内置属性指向的对象上的属性

获取的方式有两种:

方式一:通过对象的 __proto__ 属性可以获取到(但是这个是早期浏览器自己添加的,存在一定的兼容性问 题);

方式二:通过 Object.getPrototypeOf 方法可以获取到;

函数的原型 prototype

函数也是一个对象

函数作为对象来说, 它也是有[[prototype]] 隐式原型

console.log(foo.__proto__) 

函数它因为是一个函数, 所以它还会多出来一个显示原型属性: prototype

console.log(foo.prototype)

再看new操作符

我们前面讲过new关键字的步骤如下:

  • 在内存中创建一个新的对象(空对象);
  • 这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性;(后面详细讲);

那么也就意味着我们通过Person构造函数创建出来的所有对象的[[prototype]]属性都指向Person.prototype

此时new的这个函数就称为构造函数,通过new调用一个构造函数生成一个新的对象

所以在new操作的时候,会把函数的显式原型赋值给创建出来对象的隐式原型

function Person() {

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

// 都是为true
console.log(p1.__proto__ === Person.prototype)
console.log(p2.__proto__ === Person.prototype)

创建对象的内存表现

赋值为新的对象

// 直接修改整个prototype对象
foo.prototype = {
  // constructor: foo,
  name: "why",
  age: 18,
  height: 1.88,
  seting: function() {
      console.log(this.name + "在吃东西~")
  }
}

事实上原型对象上面是有一个属性的:constructor

默认情况下原型上都会添加一个属性叫做constructor,这个constructor指向当前的函数对象; 

// 真实开发中我们可以通过Object.defineProperty方式添加constructor
Object.defineProperty(foo.prototype, "constructor", {
  enumerable: false,
  configurable: true,
  writable: true,
  value: foo
})

三、原型链

Object是所有类的父类

原型链关系的内存图

四、 寄生组合式继承

组合继承是比较理想的继承方式, 但是存在两个问题:

        问题一: 构造函数会被调用两次: 一次在创建子类型原型对象的时候, 一次在创建子类型实例的时候.

        问题二: 父类型中的属性会有两份: 一份在原型对象中, 一份在子类型实例中.

事实上, 我们现在可以利用寄生式继承将这两个问题给解决掉.

        你需要先明确一点: 当我们在子类型的构造函数中调用父类型.call(this, 参数)这个函数的时候, 就会将父类型中的属性和方法复制一份到了子类型中. 所以父类型本身里面的内容, 我们不再需要.

        这个时候, 我们还需要获取到一份父类型的原型对象中的属性和方法.

封装实现继承方式

1.抽离出createObject

//定义object函数:创建新函数,将新函数的原型指向传入的对象o的原型
function createObject(o) {
  function Fn() {}
  Fn.prototype = o
  return new Fn()

//定义寄生式核心函数:SubType子类型 SuperType父类型
function inheritPrototype(SubType, SuperType) {
  //子函数的原型指向 新生成的函数的原型,新生成函数原型指向SuperType.prototype
  SubType.prototype = createObject(SuperType.prototype)

  //给当前函数对象原型添加constructor
  Object.defineProperty(SubType.prototype, "constructor", {
    enumerable: false,
    configurable: true,
    writable: true,
    value: SubType
  })
}

2.直接用 Objec.create()

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

使用

子类型原型指向新创建函数的原型,而这个新创建函数的原型指向父类型。进而使new出来的stu对象_proto_不再指向继承函数Student的原型,而是指向Person的prototype,中间用一个新建函数链接,不会产生问题

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) {
  //属性类型用call来继承
  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)

五、对象的方法补充

hasOwnProperty

对象是否有某一个属于自己的属性(不是在原型上的属性)

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)
console.log("name" in info)

// for in:打印原型 不管在当前对象还是原型中都打印
for (var key in info) {
  console.log(key)  //address name age
}

instanceof

用于检测 构造函数的pototype ,是否出现在 某个实例对象的原型链

isPrototypeOf

用于检测 某个对象 ,是否出现在 某个实例对象的原型链
var obj = {
  name: "why",
  age: 18
}

var info = Object.create(obj)

// console.log(info instanceof obj) //报错 因为obj是对象 不是函数
console.log(obj.isPrototypeOf(info)) //true obj对象在info的原型链上
原型继承关系

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值