JS查漏补缺——面向对象中与对象相关的知识

简介: JS查漏补缺系列是我在学习JS高级语法时做的笔记,通过实践费曼学习法进一步加深自己对其的理解,也希望别人能通过我的笔记能学习到相关的知识点。这一次我们来了解面向对象中与对象相关的知识
我们经常调侃:”没有对象,自己new一个“ 。 而在JavaScript中对象是什么呢?

面向对象

面向对象是相对于面向过程来讲的,面向对象方法,把相关的数据和方法组织为一个整体来看待,从更高的层次来进行系统建模,更贴近事物的自然运行模式(面向对象就是现实的抽象方式)
一句话来说:
面向对象是以对象功能来划分问题,而不是步骤。

面向对象对于我们编程是很重要的一个东西:因为我们在大部分情况下描述一个事物都是从整体来描述的

对象

对象可以将多个相关联的数据封装到一起,更好的描述一个事物:

var girlFriend ={
  // key: value
  name: '巴咔巴咔',
  height: 170,
  age: 18
  ...
}

JavaScript中的对象被设计成一组属性的无序集合,由key和value组成;

  • key是一个标识符名称,
  • value可以是任意类型,也可以是其他对象或者函数类型,如果值是一个函数,那么我们可以称之为是对象的方法;

创建对象的方法

new Object

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例
// new一个对象本质是创建一个空对象,然后往里面添加属性和方法
var girlFriend = new Object()
girlFriend。name: '巴咔巴咔'
girlFriend.height = 170
girlFriend.age = 18

字面量创建

var girlFriend ={
  name: '巴咔巴咔',
  height: 170,
  age: 18
  ...
}
创建单个对象的方法弊端:需要编写的代码重复的地方很多,代码冗杂

创建多个对象的方法

工厂模式 —— 一种常见的设计模式

工厂中的代码写一次便可以复用无数次,能够减少代码量
先写一个工厂函数接收方法,返回对象,接下来就只用传入不同的参数就好了
function createObj(name, age) {
  var obj = {}
  obj.name = name
  obj.age = age

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

var p1 = createObj("张三", 18)
var p2 = createObj("李四", 20)

缺点:工厂模式创建的对象属于Object,无法区分对象类型,这也是工厂模式没有广泛使用的原因

构造函数(constructor)

普通的函数被使用new操作符来调用了,那么这个函数就可以被称之为构造函数
规范: 构造函数定义时首字母大写 (因为是通过调用的方式来确定是否为构造函数,单纯看函数区分不了,所以要通过首字母大写来区分这个函数为构造函数)
function foo() {
    ...
}
// foo是普通函数
foo()


function Foo() {
    ...
}
// Foo是构造函数
new Foo()

foo被new调用之后会执行:

  1. 申请内存,创建对象
  2. 这个对象内部的[[prototype]](浏览器中提供的__proto__)会被赋值为该构造函数的prototype属性;
  3. 构造函数内部的this,会指向创建出来的新对象;(new绑定)
  4. 执行函数的内部代码(函数体代码);
  5. 如果构造函数没有返回非空对象,则返回创建出来的新对象;(无论如何new 构造函数之后都会返回一个对象值,而普通函数没有返回值则返回undefined)
//手写构造函数  
function  Test (constructFunction){
  let obj = {};
  obj.__proto__ = constructFunction.prototype;
  return function(){
    constructFunction.apply(obj,arguments);
    return obj;
  }
}

使用:

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

  this.eating = function() {
    console.log(this.name + "在吃东西~")
  }
  // 不用返回对象,构造函数自动返回
}

var p1 = new Person("张三", 18)
var p2 = new Person("李四", 20)

Snipaste_2022-09-18_21-19-28.png
缺点: 两次构造的对象是不一样的(就算你的操作是一样的,他们构建的对象也是不一样的),因为这个特性每次构造都是一个新的函数对象,就会造成不必要的空间浪费

如何优化上面的构造函数——就要用到“原型”

对象的原型/隐式原型(不会直接使用)

我们每个对象中都有一个 [[prototype]], 这个属性可以称之为 对象的原型(隐式原型)

怎么查看:

  1. __proto__(浏览器提供):console.log(obj.__proto__)
  2. Object.getPrototypeOf (ES5之后提供) :console.log(Object.getPrototypeOf (obj))

用途:我们找一个对象里面获取一个属性,在当前对象中找不到就会去原型里面查找。(方便实现继承,在多个对象里面要使用同一个属性或方法时就可以将其放到原型中,而不是放到构造函数中去浪费空间)

函数的原型/显示原型

函数作为对象来说,他也有隐式原型[[prototype]],但函数因为是一个函数,所以他还有一个显示原型prototype
function Obj() {
  
}

var obj1 = new Obj()
var obj2 = new Obj()

构造函数创建对象的时候会把显示原型赋值给隐式原型
就是Obj中有prototype且prototype存着原型对象的地址,指向原型对象的地址;构建对象的时候将prototype赋值给obj1和obj2的__proto__,这样obj1和obj2的__proto__也指向原型对象(obj2.__proto__=== Obj.prototype)

🚨注意: Obj.prototype !== Obj.prototype (一个构造函数的显式原型和自己的隐式原型是不相等的,注意与上面区分)
因为 Obj.prototype = { constructor: Obj }
Obj.__proto__ = Function.prototype
Function.prototype = { constructor: Function }

原型关系:
2868782068.jpg

原型对象中有一个constructor属性,而这个constructor属性指向当前的函数对象(Obj)
Snipaste_2022-09-19_15-32-46.png
现在我们了解了原型的概念,知道了构造函数的弊端是会创建重复的函数,那么就来尝试优化一下构造函数吧:

场景

将重复的函数方法/属性放进原型中再进行调用(注意:得是重复的才能放进原型,不重复的放入会被后来的覆盖,所以一般来说是函数方法放入原型,属性很少)

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

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

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

var p1 = new Person("张三", 18)
var p2 = new Person("李四", 20)

p1.eating()
p2.eating()

操作对象的方法

var obj ={
  name: '巴咔巴咔',
  height: 170,
  age: 18
  ...
}
// 获取属性
console.log(obj.name)
// 给属性赋值
obj.height = 172
// 删除属性
delete obj.name
// 遍历属性
for (var key in obj) {
  console.log(key)
}
但有时候我们不想让别人这么轻易地对 ‘对象中的属性’ 进行操作,对别人的操作进行限制,就要用到 Object.defineProperty

Object.defineProperty()

Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

**Object.defineProperty(obj,prop,descriptor) 中的参数分别对应 '要定义属性的对象'、'要定义或修改的属性/Symbol'、'属性描述符'** ,返回值:被传递给的函数的对象

而其中的属性描述符又分为数据描述符和存取描述符

configurable enumerable value writable get set
数据描述符 T T T T F F
存取描述符 T T F F T T
  • [configurable]:表示属性是否可配置(删除、修改特性、改为存取属性描述符)
  • [enumerable]:有可枚举的意思(能否通过for-in或Object.keys( )来返回属性)
  • [value]:属性的value值,读取属性时会返回该值,修改属性时,会对其进行修改; 默认情况下这个值是undefined;
  • [writable]:表示能否修改属性的值
  • [get]:获取属性时会执行的函数。默认为undefined
  • [set]:设置属性时会执行的函数。默认为undefined

数据属性描述符:

var obj ={
  name: '巴咔巴咔',
  height: 170,
  age: 18
}
// 数据属性描述符
Object.defineProperty(obj, "address", {
   value: "北京市", // 默认值undefined
  // 该特性不可删除/也不可以重新定义属性描述符
   configurable: false, // 默认值false
  // 该特性是配置对应的属性(address)是否是可以枚举
  enumerable: true, // 默认值false
  // 该特性是属性是否是可以赋值(写入值) 
  writable: false // 默认值false
})

// 测试configurable的作用
delete obj.address // 报错

// 测试enumerable的作用
for (var key in obj) {
  console.log(key)  // name height age address
}

// 测试writable的作用
 obj.address = "上海市"
console.log(obj.address) // 北京市 (这是一个静默错误)

存取属性符使用场景:
1.隐藏某一个私有属性不希望直接被外界使用和赋值(如下面_address就被隐藏起来了,别人使用的是address,虽然说_address是私有属性,但JS里没有严格意义的私有属性,就是说console.log还是可以看到,但程序员看到了以下划线开头的就知道是私有属性/方法而不会在外面直接使用它,只能说是默认的规范)

var obj ={
  name: '巴咔巴咔',
  height: 170,
  age: 18,
  _address: "北京市"
}
// 存取属性描述符
Object.defineProperty(obj, "address", {
  enumerable: true,
  configurable: true,
  get: function() {
    return this._address
  },
  set: function(value) {
    this._address = value
  }
})

console.log(obj.address)

obj.address = "上海市"
console.log(obj.address)

2.如果我们希望截获某一个属性它访问和设置值的过程时, 也会使用存储属性描述符(vue中响应式的原理)

var obj ={
  name: '巴咔巴咔',
  height: '170',
  age: '18',
  _address: "北京市"
}

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)

obj.address = "上海市"
console.log(obj.address)

function foo() {
  console.log("获取了一次address的值")
}

function bar() {
  console.log("设置了addres的值")
}

Snipaste_2022-09-17_21-18-30.png

注意:有writable和value就不能有get和set

Object.defineProperties()

上面的Object.defineProperty()只能定义一个属性,而Object.defineProperties()可以定义多个属性

Object.defineProperties(obj, {
  name: {
    configurable: true,
    enumerable: true,
    writable: true,
    value: "玛卡巴卡"
  },
  age: {
    configurable: true,
    enumerable: true,
    get: function() {
      return this._age
    },
    set: function(value) {
      this._age = value
    }
  }
})

补充

  1. 获取某一个特性属性的属性描述符 :console.log(Object.getOwnPropertyDescriptor(obj,"name"))
  2. 获取对象的所有属性描述符:

console.log(Object.getOwnPropertyDescriptors(obj))

  1. 禁止对象继续添加新的属性:

Object.preventExtensions(obj)

  1. 让属性不可以修改(writable: false)

Object.freeze(obj)
......

  1. hasOwnProperty方法判断对象中是否含有某个属性(不是原型上的属性)

console.log(info.hasOwnProperty("address"))
console.log(info.hasOwnProperty("name"))
6. in 操作符: 判断对象中是否含有某个属性(不管在当前对象还是原型中返回的都是true)
console.log("address" in info)
console.log("name" in info)

  1. instanceof 用于判断构造函数的pototype,是否出现在某个实例对象的原型链上

console.log(stu instanceof Student)// 判断Student的原型是否出现在stu的原型链上

目录
相关文章
|
1月前
|
JavaScript 前端开发
如何在 JavaScript 中使用 __proto__ 实现对象的继承?
使用`__proto__`实现对象继承时需要注意原型链的完整性和属性方法的正确继承,避免出现意外的行为和错误。同时,在现代JavaScript中,也可以使用`class`和`extends`关键字来实现更简洁和直观的继承语法,但理解基于`__proto__`的继承方式对于深入理解JavaScript的面向对象编程和原型链机制仍然具有重要意义。
|
1月前
|
Web App开发 JavaScript 前端开发
如何确保 Math 对象的方法在不同的 JavaScript 环境中具有一致的精度?
【10月更文挑战第29天】通过遵循标准和最佳实践、采用固定精度计算、进行全面的测试与验证、避免隐式类型转换以及持续关注和更新等方法,可以在很大程度上确保Math对象的方法在不同的JavaScript环境中具有一致的精度,从而提高代码的可靠性和可移植性。
|
1月前
|
JSON 前端开发 JavaScript
JavaScript中对象的数据拷贝
本文介绍了JavaScript中对象数据拷贝的问题及解决方案。作者首先解释了对象赋值时地址共享导致的值同步变化现象,随后提供了五种解决方法:手动复制、`Object.assign`、扩展运算符、`JSON.stringify`与`JSON.parse`组合以及自定义深拷贝函数。每种方法都有其适用场景和局限性,文章最后鼓励读者关注作者以获取更多前端知识分享。
25 1
JavaScript中对象的数据拷贝
|
1月前
|
JavaScript 前端开发 图形学
JavaScript 中 Math 对象常用方法
【10月更文挑战第29天】JavaScript中的Math对象提供了丰富多样的数学方法,涵盖了基本数学运算、幂运算、开方、随机数生成、极值获取以及三角函数等多个方面,为各种数学相关的计算和处理提供了强大的支持,是JavaScript编程中不可或缺的一部分。
|
2月前
|
存储 JavaScript 前端开发
JavaScript 对象的概念
JavaScript 对象的概念
49 4
|
2月前
|
缓存 JavaScript 前端开发
JavaScript中数组、对象等循环遍历的常用方法介绍(二)
JavaScript中数组、对象等循环遍历的常用方法介绍(二)
52 1
|
2月前
|
存储 JavaScript 前端开发
js中函数、方法、对象的区别
js中函数、方法、对象的区别
23 2
|
2月前
|
JavaScript 前端开发 Unix
Node.js 全局对象
10月更文挑战第5天
40 2
|
2月前
|
JavaScript 前端开发 大数据
在JavaScript中,Object.assign()方法或展开语法(...)来合并对象,Object.freeze()方法来冻结对象,防止对象被修改
在JavaScript中,Object.assign()方法或展开语法(...)来合并对象,Object.freeze()方法来冻结对象,防止对象被修改
46 0
|
2月前
|
JavaScript 前端开发 索引
JavaScript中数组、对象等循环遍历的常用方法介绍(一)
JavaScript中数组、对象等循环遍历的常用方法介绍(一)
41 0