JS 深入理解Object,必会知识点

AI 概述
1. new 操作符2. 原型链3. 继承1. 原型链继承2. 构造继承3. 复制继承4. 组合继承5. 寄生组合4. instanceof 运算符5. Object 的一些函数1. hasOwnProperty()2. Object.create()3. Object.defineProperties()4. Object.getOwnPropertyNames()5. Object.keys()6. 总结 本文总结了Object的相关知识点,日...
目录
文章目录隐藏
  1. 1. new 操作符
  2. 2. 原型链
  3. 3. 继承
  4. 4. instanceof 运算符
  5. 5. Object 的一些函数
  6. 6. 总结

JS 深入理解 Object,必会知识点

本文总结了Object的相关知识点,日常开发或者面试这块都是重中之重,大纲如下:

  1. new 操作符
  2. 原型链
  3. 继承
  4. instanceof 运算符
  5. Object 的一些函数

1. new 操作符

引用类型的实例都需要通过new操作符来生成,我们先看看创建实例对象都发生了:

function Man(name,age){
  this.name = name
  this.age = age
}

// 创建 Man 实例 body 
let boby = new Man() 

/*
 *  上述创建 body 实例的流程如下:
 */ 
// 1. 创建一个空对象
let body = {}  
// 2. 将 body 空对象的原型链指向 Man 的原型
body.__proto__ = Man.prototype 
// 3.  将 Man()函数中的 this 指向 body 变量
Man.call(body)   

通过上面例子,我们知道:new操作符在执行中改变了this的指向。

更进一步了解函数内的this,若没有return值,则默认return this,例子看下:

function Man(name,age){
  console.log(this)  // Man{} 空对象
  this.name = name    
  this.age = age
}
// 可以看到实际上是给 Man 空对象添加属性,且默认返回了 this
new Man('张三',18) // {name:'张三',age:18}

// 写个参照,作为比对
function Man(name,age){
  let obj = {}
  obj.name = name
  obj.age = age
}

// 输出 Man{}空对象,而属性值是赋值到变量 obj。当然若为了得到 name|age 属性,直接 return obj 就可以了。
new Man('李四',18)  // Man {}

2. 原型链

关于原型链知识大家可以看这篇文章《我对 js 中原型原型链的理解(有图有真相)

js 原型链
从图中我们看出几条链路:

链路 1:自定义构造函数

f1 实例通过__proto__属性指向 Foo 构造函数的原型对象。

f1.__proto__ = Foo.prototype;

Foo 构造函数的原型对象通过__proto__执行 Object 类型的原型对象。

Foo.prototype.__proto__ = Object.prototype;

Object 类型的原型对象通过__proto__指向 null

Object.prototype.__proto__ = null;

链路 2:系统构造函数|对象字面量创建的对象

new Object().__proto__ = Object.prototype

链路 3:函数

function.__proto__ = Function.prototype

Function.prototype.__proto__ = Object.prototype

总结:

对象的原型链最终都指向Object.prototype,对象的构造器最终都指向函数构造器Function

function Man(){}
Man.prototype.say = function(){}

let boy = new Man()
boy.__proto__ === Man.prototype    // true
boy.__proto__.constructor === Man  // true
boy.constructor  // Man
boy.constructor.prototype === boy.__proto__ // true

3. 继承

在不影响父类对象的情况下,使得子类对象具有父类对象的特性。这里整理几种实现继承的方法,以下将以 Man 作为父类,总结几种实现继承的方式。

父类

// 作为父类
function Man(name) {
    // 属性
    this.type = 'man'this.name = name
    // 实例方法
    this.eat = function() {
        this.name + '在吃饭'
    }
}

// 原型函数
Man.prototype.getName = function() {
    '我是' + this.name
}

1. 原型链继承

重写子类的prototype属性,将其指向父类的实例

// 子类 Boy
function Boy(name) {
    this.name = name
}
// 原型继承,但同时也继承了 Man 的构造函数,因此需要将 Boy 的构造函数指向本身
Boy.prototype = new Man()
// Boy 的构造函数指向本身
Boy.prototype.constructor = Boy

let boy = new Boy('张三') boy.type // 'man' 继承父类
boy.name // '张三'
// Boy 原型对象指向 Man 实例,在创建 boy 实例会继承 Man 实例的函数和原型方法,在调用 boy 实例方法时 this 指向 boy 实例
boy.eat() // 张三在吃饭  
boy.getName() // 我是张三

优点:

  1. 简单,易于实现,只需设置子类的 prototype 指向父类的实例。
  2. 继承关系纯粹,生成的子类既是子类的实例,也是父类的实例。
  3. 可通过子类直接访问父类原型链属性和函数。

缺点:

  1. 子类的所有实例将共享父类的属性。这会产生严重问题,若父类属性为引用类型,则某个实例修改了引用类型的数据,其他实例该属性值也将变化。
    function Man(){
    	this.hobbys = ['洗衣','做饭']
    }
    function Boy(){}
    Boy.prototype = new Man()
    Boy.prototype.constructor = Boy
    
    let b1 = new Boy()
    let b1 = new Boy()
    b1.hobbys.push('编码')
    
    b1.hobbys  // ['洗衣','做饭','编码']
    b2.hobbys  // ['洗衣','做饭','编码']  b2 实例也跟着变化
    
  2. 创建子类实例时,无法向父类的构造函数传递参数。
  3. 无法实现多继承,子类的 prototype 属性只能设置一个值。
  4. 为子类添加原型对象上的属性和方法,必须放置继承父类实例之后。

2. 构造继承

在子类的构造函数中通过call()函数改变this的指向,调用父类的构造函数,从而能将父类的实例的属性和函数绑定到子类的this上。

function Boy(name, age) {
    // 继承 Man 实例的属性和方法,并不能继承父类原型函数,子类没有通过某种方式来调用父类原型对象的函数
    Man.call(this,name) // 向父类构造函数传参数
    this.age = age
}

优点:

  1. 可以解决子类共享父类属性的问题,每个子类都生成了自己继承自父类的属性和方法。
  2. 创建子类实例时,可以向父类传递参数
  3. 可以实现多继承,在子类的构造函数多次调用call()函数来继承多个父类对象。

缺点:

  1. 实例只是子类的实例,并不是父类的实例。因为并为通过原型对象将子父类串联,所以生成的实例跟父类没有关系,这也失去了继承的意义。
  2. 只能继承父类实例的属性和方法,并不能继承原型对上的属性和方法。
  3. 无法复用父类的实例函数,导致子类实例都拥有父类实例函数的引用,造成内存消耗,影响性能。

3. 复制继承

首先生成父类的实例,然后通过遍历父类实例的属性和函数,并依次设置为子类实例的属性和函数或者原型上的属性和函数。

function Boy(name, age) {
    let man = new Man(name)
    // 父类的属性和方法,全部添加到子类
    for (let p in man) {
        if (man.hasOwnProperty(p)) { // 实例的属性和方法,返回 true
            this. [p] = man[p]
        } else {
            Boy.prototype[p] = man[p]
        }
    }
    // 子类自己的属性
    this.age = age
}

优点:

  1. 支持多继承
  2. 能同时继承父类实例的属性和函数以及原型对象上的属性和函数
  3. 可以向父类构造函数传参

缺点:

  1. 父类所有的属性都要复制,消耗内存
  2. 实例只是子类的实例,并不是父类的实例,并没有通过原型链串联起父子类

4. 组合继承

【推荐】组合了构造继承和原型链继承两种方法。一方面在子类构造函数通过call()函数调用父类构造函数,将父类实例的属性和方法绑定到子类的this上;另一方面,通过改变子类的prototype属性,继承父类原型对象上的属性和方法。

function Boy(name,age){
  // 通过构造函数继承父类实例的属性和方法
  Man.call(this,name)
  this.age = age
}
// 通过原型继承父类原型上的属性和方法
Boy.prototype = new Man()
Boy.prototype.constructor = Boy

优点:

  1. 既能继承父类实例的属性和方法,也能继承原型对象上的属性和方法
  2. 既是子类的实例,也是父类的实例
  3. 不存在引用共享的问题
  4. 可以向父类的构造函数参数

缺点: 父类的实例属性会被绑定两次,一次是在子类构造函数中,通过call()函数调用父类构造函数,另一次是在子类prototyoe属性改写时,调用了一次父类构造函数。

5. 寄生组合

【最优】在子类进行子类的 prototype 设置时,去掉父类实例的属性和方法

function Boy(name,age){
  Man.call(this,name)
  this.age = age
}
(function(){
  let S = function(){}
  // S 函数的原型指向父类 Man 的原型,去掉父类的实例属性,从而避免父类实例属性的 2 次绑定
  S.prototype = Man.prototype
  Boy.prototype = new S()
  Boy.prototype.constructor = Boy
})()

4. instanceof 运算符

target instanceof constructor

表示:target对象是不是构造函数constructor的实例。

先来看段instanceof运算符实现原理比较经典的 JS 代码解释。

/*
 * instanceof 运算符实现原理
 * L: 表示左表达式  R: 表示右表达式
 */
function instanceof(L,R){
  let O = R.prototype
  L = L.__proto__
  while(true){
    if(L===null) 
      return false
    if(L === O)
      return true
    L = L.__proto__  // 递归 L 的 __proto__ 属性
  }
}

以下看些例子:

// 基础用法
function Man(){}
let m = new Man() 
m instanceof Man  // true  m.__proto__ === Man.prototype

// 继承判断
function Boy(){}
Boy.prototype = new Man()
let b = new Boy()
b instanceof Man  // true ,通过集成,Man.prototype 出现在 Boy 的原型链上

// 复杂用法
Object instanceof Object // true
Function instanceof Function // true
String instanceof String // false

// 解释下 String instanceof String 返回 false 的判断过程
取值: L = String.__proto__ = Function.prototype ; R = String.prototype
第一次判断:L !== R,返回 false
继续取 L.__proto__: L = Function.prototype.__proto__ = Object.prototype
第二次判断:L !== R, 返回 false
继续取 L.__proto__: L = Object.prototype.__proto__ = null
再次判断:L === null ,返回 false

5. Object 的一些函数

1. hasOwnProperty()

判断对象自身是否拥有指定名称的实例属性,不会检查实例对象原型上的属性。

function Man(name){
  this.name = name
}
Man.prototype.say = function(){}

const boy = new Man('张三')
boy.hasOwnProperty('name')    // 实例上的属性:true   
boy.hasOwnProperty('toString')  // 原型上的属性:false

2. Object.create()

创建并返回一个指定原型和指令属性的对象。语法如下:

Object.create(prototype,propertyDescriptor)

prototype 属性为对象的原型,可以为 null,若未 null,则对象的原型为 undefined。

propertyDescriptor 属性描述符格式如下:

propertyName:{
  // 属性值
  value:'',
  // 是否可写,若为 false,则值读
  writable:true,
  // 是否可枚举,默认 false
  enumerable:false,
  // 是否可配置,如:修改属性的特性,是否可以删除属性,默认 false
  configurable:true
}

举个例子深入理解下:

let boy = {name:'张三'}
let obj = Object.create(boy) // 输出: obj {}
console.log(obj.name)  // 输出:张三,可以看出 boy 被挂载到原型上了

// 通过 polyfill 下 Object.create()的实现
Object.create = function(proto,propertiesObj){
  function F(){}
  F.prototype = proto
  // 其他代码省略
  return new F()
}

let boy = {name:'张三'}
let obj = Object.create(boy)
obj.__proto__name === boy.name  // true

3. Object.defineProperties()

添加或者修改对象的属性值。

let boy = {}
Object.defineProperties(boy,{
    name:{  // 跟 Object.create()的属性描述符一样
    value:18,
    writable:true
  }
})

4. Object.getOwnPropertyNames()

获取对象的所有实例属性和函数,不包含原型链继承的属性和函数。

function Man(name){
  this.name = name
  this.getName = function(){return this.name}
}
Man.prototype.say = function(){}

let boy = new Man('张三')
Object.getOwnPropertyNames(boy)  // ['name','getName']

5. Object.keys()

获取对象可枚举的实例属性,不包含原型链继承的属性。

let obj = {
  name:'张三',
  getName:function(){}
}
Object.keys(obj)  // ['name','getName']

// 设置 name 属性不可枚举
Object.defineProperty(obj,'name',{enumerable:false})
Object.keys(obj)   // ['getName']

6. 总结

至此我们总结了 Object 对象的知识点,值得收藏,工作面试必备!

以上关于JS 深入理解Object,必会知识点的文章就介绍到这了,更多相关内容请搜索码云笔记以前的文章或继续浏览下面的相关文章,希望大家以后多多支持码云笔记。

「点点赞赏,手留余香」

0

给作者打赏,鼓励TA抓紧创作!

微信微信 支付宝支付宝

还没有人赞赏,快来当第一个赞赏的人吧!

声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 admin@mybj123.com 进行投诉反馈,一经查实,立即处理!
重要:如软件存在付费、会员、充值等,均属软件开发者或所属公司行为,与本站无关,网友需自行判断
码云笔记 » JS 深入理解Object,必会知识点

发表回复