JS 对象、原型对象、继承、理解关键字new

 

目录

 创建对象

1. new Object() 方式

2. 字面量方式

3. 构造函数方式

4. class方式

5. 特殊场景下简化对象方式

使用和扩展对象

1. 使用对象的属性和方法

2. 扩展对象的属性和方法

原型对象

1. 什么是原型对象

2. 原型对象的结构、原型链和查找机制

3. 继承

通过行为来理解 new


 

 创建对象

 

1. new Object() 方式

 

因为对象结构无法复用, 每次使用都需要重新定义结构, 适合只使用一次的场景

<script>
  let obj = new Object();
  obj.name = 'ares5k'
  obj.age = 27
  obj.say = function () {
    console.log('Hello World!')
  }
  obj.say()
</script>

 

2. 字面量方式

 

new Object() 方式的语法糖,依然无法复用, 但是语法更简单

<script>
  // 方式 1
  let obj = {
    name: 'ares5k',
    age: 27,
    say: function () {
      console.log('Hello World!')
    }
  }

  // 方式 2:简化方法定义
  let obj2 = {
    name: 'ares5k',
    age: 27,
    say() {
      console.log('Hello World!')
    }
  }
</script>

 

3. 构造函数方式

 

(1) 用关键字 this 给对象定义属性和方法

(2) 和普通函数语法一样, 只不过习惯函数名首字母大写, 真正让它具备创建对象能力的, 是关键字 new

(3) 适合结构稳定, 不常改变, 且会重复使用该结构的场景。对象结构可复用, 所以修改要小心, 要考虑到其他使用该结构的地方

<script>
  function Person(name, age) {
    this.name = name
    this.age = age
    this.say = function () {
      console.log('Hello World!')
    }
  }
  let obj = new Person('ares5k', 27)
</script>

 

4. class方式

 

1. 定义方法不能写 function 关键字

2. 定义属性, 要写在固定方法 constructor() 中

3. class方式其实就是构造函数方式的一个语法糖

<script>
  class Obj{
    constructor(name, age){
      this.name = name
      this.age = age
    }
    say(){
      console.log(this.name) // 结果:ares5k
      console.log(this.age) // 结果:27
      console.log('Hello World!') // 结果:Hello World!
    }
  }

  let obj = new Obj('ares5k', 27)
  obj.say()
</script>

 

5. 特殊场景下简化对象方式

 

<script>
  // 定义变量和函数
  let name = 'ares5k'
  let age = 27
  let say = function(){console.log('Hello World!' + this.name)}

  // 正常方式
  let obj = {
    name: name,
    age: age,
    say: say
  }

  // 简化对象方式
  let obj2 = {
    name,
    age,
    say
  }
</script>

 

使用和扩展对象

 

1. 使用对象的属性和方法

 

<script>

  // 创建对象
  let obj = {
    name: 'ares5k',
    say(){
      console.log('Hello World!')
    }
  }

  // 使用属性的两种方式
  console.log(obj.name) // 结果:ares5k
  console.log(obj['name']) // 结果:ares5k

  // 调用方法的两种方式
  obj.say() // 结果:Hello World!
  obj['say']() // 结果:Hello World!

</script>

 

2. 扩展对象的属性和方法

 

<script>

  // 创建对象
  let obj = {
    name: 'ares5k',
    say(){
      console.log('Hello World!')
    }
  }

  // 扩展属性的两种方式
  obj.age = 27
  obj['hobby'] = 'Jiu-Jitsu Brasileiro'

  // 扩展方法的两种方式
  obj.walk = function(){ console.log('走路!') }
  obj['sport'] = function(){ console.log('运动!') }

  // 测试
  console.log(obj.name) // 结果:ares5k
  console.log(obj.age) // 结果:27
  console.log(obj.hobby) // 结果:Jiu-Jitsu Brasileiro
  obj.say() // 结果:Hello World!
  obj.walk() // 结果:走路!
  obj.sport() // 结果:运动!

</script>

 

原型对象

 

1. 什么是原型对象

 

每个类型都有一个对应的原型对象( prototype ), 该类型所实例化出来的所有对象, 其内部都保存一个默认指向这个原型对象的引用( __proto__ )

 

作用:

(1) 原型对象可以用来共享方法, 减少内存占用

(2) 原型对象可以用来扩展内置对象

(3) 可以通过原型链和方法查找机制来模拟继承的效果

(4) object 对应的原型对象没有 __proto__ 属性, 它是原型链的重点

 

代码实验:

<script>

  // 定义构造函数
  function Person(name){
    this.name = name
    this.say = function(){
      console.log('Hello World!!')
    }
  }

  // 创建三个实例对象
  let person1 = new Person('p1')
  let person2 = new Person('p2')
  let person3 = new Person('p3')

  // 判断每个对象之间, 对象地址、对象属性、对象方法是否相同
  console.log(person1 === person2 || person1.say === person2.say || person1.name === person2.name) // 结果:false
  console.log(person2 === person3 || person2.say === person3.say || person2.name === person3.name) // 结果:false

  // 判断每个对象默认的内部引用 __proto__ 指向的原型对象是否是同一个
  console.log(person1.__proto__ === person2.__proto__) // 结果:true
  console.log(person2.__proto__ === person3.__proto__) // 结果:true

  // 类型对应的原型对象和对象内指向的是否是同一个  
  console.log(Person.prototype === person1.__proto__) // 结果:true
  
</script>

 

通过上面代码, 可以得到这个内存模型图:

上图可印证我们的观点, 每个类型都有一个对应的原型对象( prototype ), 该类型所实例化出来的所有对象, 其内部都保存一个默认指向这个原型对象的引用( __proto__ )

 

2. 原型对象的结构、原型链和查找机制

 

查看原型对象结构:

<script>
  // 定义构造函数
  function Person(){}
  // 打印原型对象结构
  console.dir(Person.prototype)
</script>

 

运行结果 ( 原型对象结构 ):

原型对象内部有两个主要属性 constructor 和 __proto__

 

constructor:

指向的是该原型对象所对应的构造函数, 本例中原型对象的 constructor 属性指向的就是与其对应的构造函数对象 Person

 

__proto__:

原型对象内的 __proto__ 是形成原型链的重要属性, 通常指向父构造函数所对应的原型对象, 本例中构造函数 Person 没有

显示指定父构造函数, 那么其默认的父构造函数就是 Object, Object 所对应的原型对象很特殊, 它没有 __proto__ 属性

是整条原型链的终点

 

对象结构图如下:

图中黄色部分的这条链路, 就被称为原型链。

 

查找机制就是沿着原型链查找:

(1) 访问对象的属性和方法时, JS 引擎会先在对象本身查找该属性和方法, 找不到执行 (2) 

(2) 在原型对象上查找, 找不到执行 (3) 

(3) 在原型对象的 __proto__ 属性指向的原型对象查找 (既父构造函数对应的原型对象中查找)

反复执行 (3), 直到找到属性和方法为止, 或者一直找不到, 但到了链路终点, 既父类是 object ( object原型对象没有 __proto__ )

 

修改 <什么是原型对象> 章节中的例子:

通过查找机制的特性, 我们可以把例子中一些能够共享的东西, 直接创建在原型对象中, 从而节约内存。

在 <什么是原型对象> 章节的例子中, 3个对象的 say() 方法逻辑相同, 所以可以定义在原型对象中, 让该类型的所有对象都可以共享访问

 

代码如下:

<script>

  // 定义构造函数
  function Person(name){
    this.name = name
  }

  // 将方法定义在该函数对应的原型对象中
  Person.prototype.say = function(){
    console.log('Hello World!!')
  }

  // 创建三个实例对象
  let person1 = new Person('p1')
  let person2 = new Person('p2')
  let person3 = new Person('p3')

  // 方法比较
  console.log(Person.prototype.say === person1.say) // 结果:true
  console.log(person1.say === person2.say) // 结果:true
  console.log(person2.say === person3.say) // 结果:true

</script>

通过上面代码, 又可以得到内存模型图:

通过上图可以看出, say() 方法从原来的 3 份, 变成了 1 份, 减少了内存占用。

 

3. 继承

 

(1) 通过原型链和查找机制来模拟继承

<script>
  // 创建父构造函数
  function Father(name) {
    this.name = name
  }

  // 为父构造函数的原型对象添加方法
  Father.prototype.money = function () {
    console.log(this.name + ' 赚钱')
  }

  // 创建子构造函数
  function Son(name) {
    // 调用父构造函数 (目的是继承父属性)
    Father.call(this, name)
  }

  // 修改子构造函数对应的原型对象
  // 1. 指定父类, 让原型对象中的 __proto__ 指向 Father 对应的原型对象 (目的是通过指定原型链来继承父方法)
  // 2. 因为是整体覆盖了 prototype, 所以要把原型对象中的 constructor 指回到 Son 构造函数对象
  Son.prototype = {
    __proto__: Father.prototype,
    constructor: Son
  }

  // 为子构造函数的原型对象添加方法
  Son.prototype.study = function () {
    console.log(this.name + ' 学习')
  }

  let son = new Son('ares5k')
  son.money() // 结果:ares5k 赚钱
  son.study() // 结果:ares5k 学习
  
</script>

完整结构图如下:

 

 

例中几个重点:

① 原型链:Son.prototype  → Father.prototype → Object.prototype

② 调用 study:Son 本身没有 study 方法, 接着去 Son.prototype 中找, 找到并执行 study 方法

③ 调用 money:Son 本身没有 money 方法, 接着去 Son.prototype 中找也没有, 再去 Father.prototype 中找, 找到并执行

④ 原型对象中 this 的指向:整个原型链中的 this 都是相同的, 都是实例对象, 既 new Son() 这个对象

 

(2) 通过语法糖来实现继承, 其本质还是构造函数 + 原型链方式

<script>

  // 创建父类
  class Father {
    constructor(name) {
      this.name = name
    }
  }

  // 为父类的原型对象添加方法
  Father.prototype.money = function () {
    console.log(this.name + ' 赚钱')
  }

  // 创建子类
  class Son extends Father {

  }

  // 为子类的原型对象添加方法
  Son.prototype.study = function () {
    console.log(this.name + ' 学习')
  }

  let son = new Son('ares5k')
  son.money() // 结果:ares5k 赚钱
  son.study() // 结果:ares5k 学习

</script>

 

通过行为来理解 new

 

通过 new 创建对象有三种情况:

1. 构造函数没有返回值, new 会返回一个构造函数类型的对象

2. 构造函数有返回值, 但是返回值不是对象类型, new 也会返回一个构造函数类型的对象

3. 构造函数有返回值, 并且返回值是对象类型, 那么 new 会直接返回这个对象

 

代码证实:

<script>

  // 定义无返回值构造
  function Person(name) {
    this.name = name
  }

  // 定义有返回值构造 - 返回值为对象以外的场合
  function Man(sex) {
    this.sex = sex
    return '字符串'
  }

  // 定义有返回值构造 - 返回值为对象的场合
  function People(name) {
    this.name = name
    return {age: 27}
  }

  console.dir(new Person('ares5k')) // 结果 Person: {name: ares5k}
  console.dir(new Man('man')) // 结果 Man: {sex: man}
  console.dir(new People('ares5k')) // 结果 Object: {age: 27}

</script>

 

模拟 new 的行为:

<script>
  
  // 定义构造函数
  function Person(name){
    this.name = name
  }

  // 模拟 new
  function dummyNew(fn, arg){
    // 1. 创建空对象 obj
    let obj = {}
    // 2. 改变 obj 的原型指向
    obj.__proto__ = fn.prototype
    // 3. 调用构造函数并修改构造函数内的 this 指向为 obj
    let result = fn.call(obj, arg)
    // 4.  根据构造函数的返回结果,来决定具体返回对象
    return result instanceof Object ? result : obj
  }

  // 测试
  let person = dummyNew(Person, 'ares5k')
  console.dir(person)

</script>

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值