JavaScript高级程序设计--数据类型(2)_Object

文章较长,建议收藏以便浏览

《JavaScript高级程序设计(第三版)》学习总结

Object

  Object类型即对象类型,在ECMAScript中,对象其实就是一组数据和功能的集合。我们可以使用new操作符来创建:

let obj = new Object()

单单这一行代码,就可以引申出一系列疑问。创建的对象有什么属性和方法?new操作符干了啥?创建对象有哪些方式?既然是对象怎么实现继承?怎么实现对象深拷贝?and so on.如果一下子全都能说明白透彻的话,我斑愿称你为最强 。接下来我们挨个来剖析一下这些问题。

创建的对象有什么属性和方法?

我们在控制台打印一下刚才声明的obj实例,得到以下结果,在这里就又得引入原型和原型链的概念,我就不做过多阐述了,可以参考JS中的原型和原型链(图解)。下面说一下里面的具体属性。
JavaScript高级程序设计--数据类型(2)_Object_第1张图片

  • constructor:保存着用于创建当前对象的函数。在这里及是Object()。
  • hasOwnProperty(propertyName):用于检查给定的属性在当前对象实例中是否存在。其中,作为参数的属性名必须以字符串的形式指定(eg: obj.hasOwnProperty("name"))。
  • isPrototypeOf(object):用于检查传入的对象是否是另一个对象的原型。
  • propertyIsEnumerable(propertyName):用于检查给定的属性在当前对象实例中是否能够使用for-in来枚举出来,可以用来区分实例属性和原型属性。
  • toLocaleString():返回对象的字符串表示,该字符串与执行环境的地区对应。
  • toString():返回对象的字符串表示。
  • valueOf():返回对象的字符串、数值或布尔值表示。通常与toString()方法的返回值相同。JS中toString()、toLocaleString()、valueOf()的区别

以上是官方介绍Object的每个实例都具有的属性和方法,但是控制台打印的不仅仅只有这些,还有下面的,我们继续看:

  • _ _defineGetter _ _:给对象添加getter方法
  • _ _defineSetter _ _:给对象添加setter方法
  • _ _lookupGetter _ _:返回getter的定义函数
  • _ _lookupSetter _ _:返回setter的定义函数
let obj =  new Object()
// name添加getter
obj.__defineGetter__('name', ()=>this.name)
console.log(obj.name) // null
obj.name = "luffy"
// 此时name还没有setter方法,因此不能赋值
console.log(obj.name) // null
// name添加setter
obj.__defineSetter__('name',(name)=>(this.name = name))
obj.name = "luffy"
// 此时可以看到name赋值成功
console.log(obj.name) // luffy
// 返回相应的定义函数
console.log(obj.__lookupGetter__('name')) // ()=>this.name
console.log(obj.__lookupSetter__('name')) // (name)=>(this.name = name)
  • get _ _ proto _ _:获取_ _proto _ _指向,用法就是obj. __ proto __
  • set _ _ proto _ _:设置_ _proto _ _指向,用法就是obj. __ proto __ = 对象

new操作符干了啥?

  1. 创建一个空对象
    let obj = {
           }
    
  2. 将空对象的__proto__指向构造函数的prototype,让空对象继承构造函数的原型
    obj.__proto__ = Object.prototype
    
  3. 将构造函数的this指向新对象,并执行构造函数为新对象添加属性
    Object.call(obj)
    
  4. 返回新对象
    return obj
    

创建对象有哪些方式?

创建单个对象的话采用以下两个方式即可:

  • new操作符+Object构造函数

    let obj = new Object()
    obj.name = "luffy"
    
  • 对象字面量

    let obj = {
           
    	name: "luffy"
    }
    

但是平常敲代码的业务中,往往需要创建很多对象,如果使用以上两种方法的话,会产生大量重复代码,产生代码冗余问题,因此以下几种方法我们也需要掌握:

  • 工厂模式:用函数来封装以特定的接口创建对象的细节
    • 缺点:没有解决对象识别的问题(即没办法知道一个对象的类型)
function createObj(name){
     
	let obj = new Object()
	obj.name = name
	obj.sayName = () =>{
     
		console.log(this.name)
	}
	return obj
}
let obj = createObj("luffy")
  • 构造函数模式
    • 和工厂模式的不同之处:没有显示地创建对象;直接将属性和方法赋给了this对象;没有return语句。
    • 缺点:每个方法都要在每个实例上重新创建一遍,即下面的sayName(),可以通过把函数定义转移到构造函数外部来解决这个问题。
function ObjFn(name){
     
	this.name = name
	this.sayName = ()=>{
     
		console.log(this.name)
	}
}
let obj = new ObjFn("luffy")
//不通过new调用的话,它就是一个普通函数,
//那么执行的属性和方法在浏览器中就会指向window对象,
//就能得到window.sayName()的结果为"who"
ObjFn("who") 

// 优化版
function ObjFn(name){
     
	this.name = name
	this.sayName = sayName
}
const sayName = ()=>{
     
	console.log(this.name)
}
  • class语法糖(需要与时俱进不是(っ•̀ω•́)っ✎⁾⁾ )
class Obj {
      // 注意命名规范
	constructor(name){
     
		this.name = name
	}
	sayName(){
     
		console.log(this.name)
	}
}
let obj = new Obj("luffy")
  • 原型模式
    • 优点:不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到对象原型中。
// 初始写法
function Obj() {
     
	Obj.prototype.name = "luffy"
	Obj.prototype.sayName = () =>{
     
		console.log(this.name)
	}
}
let obj1 = new Obj()
let obj2 = new Obj()
obj1.name = "Tom" // 不会修改原型对象的name属性,而是实例对象添加了一个同名属性
obj1.sayName() // Tom,访问实例对象的name属性,屏蔽原型对象中的同名属性
obj2.sayName() // luffy,访问原型对象的name属性
// 进阶写法
function Obj(){
     }
Obj.prototype = {
     
	// constructor: Obj, // 设置constructor属性的指向
	name: "luffy",
	sayName: ()=>{
     
		console.log(this.name)
	}
}
  • 组合使用构造函数模式和原型模式
    • 构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性。最大限度节省了内存,还支持向构造函数传递参数。
function Obj(name){
     
	this.name = name
}
Obj.prototype = {
     
	constructor: Obj,
	sayName: ()=>{
     
		console.log(this.name)
	}
}
let obj = new Obj("luffy")
  • 寄生构造函数模式
    • 就是相当于使用new来调用了工厂模式,返回的对象与构造函数以及构造函数的原型属性之间没有关系,酌情使用!
function createObj(name){
     
	let obj = new Object()
	obj.name = name
	obj.sayName = () =>{
     
		console.log(this.name)
	}
	return obj
}
let obj = new createObj("luffy")
  • 稳妥构造函数模式
    • 只能调用构造函数内的方法去访问其数据成员。
function createObj(name){
     
	let obj = new Object()
	obj.sayName = () =>{
     
		console.log(name)
	}
	return obj
}
let obj = new createObj("luffy")
obj.sayName() // luffy

既然是对象怎么实现继承?

  • 原型链
    • 缺点:包含引用类型值的原型会被所有实例共享;在创建子类型的实例时,不能向超类型的构造函数中传递参数。
function Animal(){
     
    this.ages = [1,2,3]
}
function Cat(){
     
}
Cat.prototype = new Animal()
let cat1 = new Cat()
let cat2 = new Cat()
console.log(cat1.ages) //[ 1, 2, 3 ]
console.log(cat2.ages)//[ 1, 2, 3 ]
cat1.age.push(4) // 修改cat1会影响cat2
console.log(cat1.ages)//[ 1, 2, 3, 4 ]
console.log(cat2.ages)//[ 1, 2, 3, 4 ]
  • 借用构造函数
    • 缺点:函数复用问题
function Animal(name){
     
	this.name = name
    this.ages = [1,2,3]
}
function Cat(){
     
	Cat.call(this, "yoyo")
}
  • 组合继承
    • 缺点:无论什么时候都会调用两次超类型的构造函数,一次是创建子类型原型的时候,一次是在构造函数内部。
function Animal(name){
     
	this.name = name
    this.ages = [1,2,3]
    this.sayName = () =>{
     
		console.log(this.name)
	}
}
function Cat(){
     
	Cat.call(this, "yoyo") // 继承属性
}
Cat.prototype = new Animal() // 继承方法
  • 原型式继承
    • ES5新增的Object.create()方法规范化了原型式继承。
let Obj = {
     
	name: "luffy"
} 
let obj1 = Object.create(Obj) // 一个参数
let obj2 = Object.create(Obj,{
     age:{
     value: 23}}) // 两个参数
  • 寄生式继承
let Obj = {
     
	name: "luffy"
} 
function createObj(origin){
     
	let clone = new Object(origin)
	clone.sayName = (name) =>{
      // 可以增强对象
		console.log(name)
    }
    return clone
}
let obj = createObj(Obj)
obj.sayName(obj.name) // luffy
  • 寄生组合式继承
function inheritPrototype(subType,superType){
     
	let prototype1 = new Object(superType.prototype) // 创建对象
	prototype1.constructor = subType // 增强对象
	subType.prototype = prototype1 // 绑定对象
}
function Animal(name){
     
	this.name = name
    this.ages = [1,2,3]
}
Animal.prototype.sayName = () =>{
     
	console.log(this.name)
}
function Cat(){
     
	Cat.call(this, "yoyo") // 继承属性
}
inheritPrototype(Cat, Animal)
  • class对应的extends继承
    • 不可继承常规对象(eg: let Animal={...})
class Animal {
     
	constructor(){
     
		this.ages = [1,2,3]
	}
}
class Cat extends Animal{
     
	constructor(){
     
		super()
	}
}

怎么实现对象深拷贝?

默认情况下对象之间的直接赋值都是浅拷贝,一个对象的属性如果是基本数据类型, 那么也都是深拷贝,如果对象的属性包含了引用数据类型, 才真正的区分深拷贝和浅拷贝。

  • 对象的拷贝:将一个对象赋值给另外一个对象
  • 浅拷贝:将A对象赋值给B对象,修改B对象的属性和方法会影响到A对象的属性和方法
let obj = {
     a:1,b:2,c:[1,2]}
let obj1 = {
     }
for(let key in obj){
     
    obj1[key] = obj[key]
} // 等同于 Object.assign(obj1,obj)
obj1.c.push(3);
console.log(obj.c) // [1,2,3]
  • 深拷贝:将A对象赋值给B对象,修改B对象的属性和方法不会影响到A对象的属性和方法(附上一个大佬实现的包含所有类型的深拷贝方法)
const deepClone = (obj, hash = new WeakMap())=> {
     
    if (obj === null) return obj
    if (obj instanceof Date) return new Date(obj)
    if (obj instanceof RegExp) return new RegExp(obj)
    if (typeof obj !== 'object') return obj
    if (hash.get(obj)) return hash.get(obj)
    let cloneObj = new obj.constructor()
    hash.set(obj, cloneObj)
    for (let key in obj) {
     
      if (obj.hasOwnProperty(key)) {
     
        cloneObj[key] = this.deepClone(obj[key], hash)
      }
    }
    return cloneObj
}

关于Object的知识点,真是三天三夜都聊不完,就到这里吧。

你可能感兴趣的:(JavaScript,js,object,继承,创建对象,深拷贝)