深入学习JavaScript系列(六)——对象/继承

本篇为此系列第六篇

第一篇:# 深入学习JavaScript系列(一)——ES6中的JS执行上下文

第二篇:# 深入学习JavaScript系列(二)——作用域和作用域链

第三篇:# 深入学习JavaScript系列(三)——this

第四篇:# 深入学习JavaScript系列(四)——JS闭包

第五篇:# 深入学习JavaScript系列(五)——原型/原型链

一 什么是对象

js中万物皆对象,这是我们刚刚接触前端便开始了解的概念,那么在js中对象到底有什么玩法呢,敢自称万物皆对象,这句话到底对不对呢?下面就通过这篇文章来探讨一下,本文只写关于对象与继承的知识点,其他的放到本系列中的其他文章。

JS中的对象是:无序属性的集合,其属性可以包含基本值、对象或者函数

这句话很抽象是不是,没关系,那我就具体展开来说:

先上总结:JavaScript中,除了原始值,都是对象

那么原始值有哪些呢?
原始值指的是没有属性或者方法的值;原始数据类型指的是拥有原始值的数据。
js定义了五种原始数据类型:

  • string
  • number
  • boolean
  • null
  • undefined

根据上面的结论,除了原始值都是对象,这里的对象我也列举出来

  • 布尔是对象(如果用 new 关键词定义)
  • 数字是对象(如果用 new 关键词定义)
  • 字符串是对象(如果用 new 关键词定义)
  • 日期永远都是对象
  • 算术永远都是对象
  • 正则表达式永远都是对象
  • 数组永远都是对象
  • 函数永远都是对象
  • 对象永远都是对象

这样看是不是就清楚很多了,然后就我们来验证一下:

var str = 'hello';
var num = 123;
var bool = true;

console.log(typeof str); // 输出 "string"
console.log(typeof num); // 输出 "number"
console.log(typeof bool); // 输出 "boolean"

str.prop = 'value';
num.prop = 'value';
bool.prop = 'value';

console.log(str.prop); // 输出 "undefined"
console.log(num.prop); // 输出 "undefined"
console.log(bool.prop); // 输出 "undefined"

使用 typeof 运算符来检查变量的类型,并尝试在原始值上添加属性。由于原始值是不可变的,因此无法为它们添加属性或方法。当我们尝试访问这些属性时,会得到 undefined 的结果

有的同学可能会说 那为什么原始值会有一些固定的方法来处理呢?是因为这些方法是js内置的,JavaScript 在处理原始值时会自动将其转换为对应的对象类型,使得我们可以像处理对象一样来处理原始值。

所以回到开头的问题:js万物皆对象 这句话是错误的,只是一种比喻,正确的应该是js千物皆对象(因为要除去原始值)

二 对象的分类

  1. 内置对象:这些对象由 JavaScript 的运行环境提供,如全局对象(如 windowglobal)、日期对象、数学对象、正则表达式对象等。
  2. 宿主对象:这些对象由宿主环境(如浏览器或 Node.js)提供,例如 DOM 对象和 BOM 对象。
  3. 自定义对象:这些对象由开发人员自己定义和创建。自定义对象可以使用构造函数、字面量表示法或者 Object.create() 方法来创建。
  4. 函数对象:在 JavaScript 中,函数也被看作是一个对象,因此也属于对象的一种。函数对象可以包含属性和方法,还可以接收参数并返回值。
  5. 原生对象:这些对象是 JavaScript 中固有的对象,如 ArrayFunctionObject 等。原生对象可以通过 new 关键字或者字面量表示法来创建。

关于对象的分类,我在学习的过程中也出现了五花八门的说法,这里只是列举出其中一种分类,就不细说了

三 对象的特点

上面我们知道了js中对象有哪些,那这些对象都有什么特点呢?

  1. 对象由若干个属性和方法组成。属性是一种用于存储值的“容器”,而方法是一种可以执行操作的函数。

有两种方式进行访问和操作对象属性和方法:

  • 点运算符:使用 . 运算符来访问对象的属性和方法。
  • 方括号表示法:使用方括号 [] 来访问对象的属性和方法。
var obj = { 
    name: 'John',
    age: 30,
    sayHi: function() {
      console.log('Hi, my name is ' + this.name + ', and I am ' + this.age + ' years old.');
    }
};
console.log(obj.name) // John
console.log(obj['age']) // 30
obj.sayHi() // Hi, my name is John, and I am 30 years old.
obj['sayHi']() // Hi, my name is John, and I am 30 years old.
// 注意看具体的使用方法,稍微有一点点不一样

2、对象可以进行增删改查操作属性或者方法。 并且属性值可以是任意js数据类型


var obj = { 
    name: 'John',
    age: 30,
    sayHi: function() {
      console.log('Hi, my name is ' + this.name + ', and I am ' + this.age + ' years old.');
    }
};
obj.name = 'Brian' // 改
obj.height = '174' // 增
delete obj.sayHi // 删
console.log(obj) // { name: 'Brian', age: 30, height: '174' }

添加属性或者方法也可以使用 Object.defineProperty() 看到这里应该很熟悉了吧,没错 响应式原理用的也是这个。例子如下:

let Person = {}
Object.defineProperty(Person, 'name', {
   value: 'jack',
   writable: true // 是否可以改变
})
console.log(Preson)
Person.name = 'Brian'
console.log(Person) //writable为true 时{name: "Brian"} ;
// writable为false 时{name: "jack"} ;

Object.defineProperty(obj, prop, desc) 一共有三个参数, obj为需要定义属性或者方法的对象;prop为需要定义的属性名或者方法名; desc为属性值(属性描述符)

这里有个writable参数为定义后的属性或者方法是否可更改
我们熟悉的响应式写法:

get:一个给属性提供getter的方法,如果没有getter则为undefined。该方法返回值被用作属性值。默认为undefined
set:一个给属性提供setter的方法,如果没有setter则为undefined。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认值为undefined

let Person = {}
let temp = null
Object.defineProperty(Person, 'name', {
  get: function () {
    return temp
  },
  set: function (val) {
    temp = val
  }
})
// **setter和getter函数中可以做任意复杂操作。**

这里就不展开说 我觉得Object.defineProperty()可以单独写一篇文章了 。插眼

3、查找对象的属性(方法)时,如果当前对象没有此属性(方法),会通过对象的原型和原型链依次往上查找

详情参考:

第二篇:# 深入学习JavaScript系列(二)——作用域和作用域链

第五篇:# 深入学习JavaScript系列(五)——原型/原型链

四 对象的创建

创建对象方法:
在 JavaScript 中,有多种方法可以创建对象。以下是一些常见的 JavaScript 对象创建方法:

1. 字面量表示法:使用花括号 {} 来创建对象,并在花括号中指定对象属性和属性值,这是最简单也是最常见的:

var obj = { 
  prop1: 'value1',
  prop2: 123,
  prop3: true
};

2. 构造函数:使用构造函数来创建对象,构造函数内部可以定义对象的属性和方法:

function Person(name, age) {
  this.name = name;
  this.age = age;
  this.sayHello = function() {
    console.log('Hello, my name is ' + this.name);
  }
}

var person = new Person('John', 30);
person.sayHello(); // 输出 "Hello, my name is John"

关于构造函数,我会单独开一节来讲,这里插个眼!

3. Object.create() 方法:使用 Object.create() 方法来创建对象,该方法接收一个参数,为新对象的原型对象,

var protoObj = {
  prop1: 'value1',
  prop2: 123,
  sayHello: function() {
    console.log('Hello!');
  }
};

var newObj = Object.create(protoObj);
console.log(newObj.prop1); // 输出 "value1"
newObj.sayHello(); // 输出 "Hello!"

4. 工厂函数方法:使用工厂函数来创建对象,返回一个新的对象实例,

function createPerson(name, age) {
  return {
    name: name,
    age: age,
    sayHello: function() {
      console.log('Hello, my name is ' + this.name);
    }
  };
}

var person = createPerson('John', 30);
person.sayHello(); // 输出 "Hello, my name is John"

5. 箭头函数:关于箭头函数的this指向问题,看本系列文章第三篇。

const person = () => ({ 
  name: 'John',
  age: 30,
  sayHello() {
    console.log(`Hello, my name is ${this.name}`);
  }
});

person().sayHello(); // 输出 "Hello, my name is John"

6. Object.assign() 方法:使用 Object.assign() 方法来创建对象,该方法可以将多个对象合并成一个新的对象,如下所示:

const obj1 = { 
  prop1: 'value1',
  prop2: 123,
};

const obj2 = { 
  prop3: true,
  prop4: ['a', 'b', 'c']
};

const newObj = Object.assign({}, obj1, obj2);
console.log(newObj); // 输出 {prop1: "value1", prop2: 123, prop3: true, prop4: Array(3)}

使用 Object.assign() 方法来合并 obj1obj2 对象,并将结果赋值给 newObj。由于 Object.assign() 方法返回一个新的对象,因此原始对象不会被修改。

7. new Object() 构造函数

const obj = new Object();
obj.prop1 = 'value1';
obj.prop2 = 123;
obj.sayHello = function() {
  console.log('Hello!');
};

console.log(obj.prop1); // 输出 "value1"
obj.sayHello(); // 输出 "Hello!"

使用 new Object() 构造函数来创建了一个空对象,然后向对象添加了属性和方法。这种方式与字面量表示法创建对象的方式相似,只是使用了构造函数来创建对象。

通过new关键字来调用构造函数生产对象会经历四个步骤:

  1. 创建一个新对象。
  2. 将新创建的对象设置为构造函数中的this,因此构造函数中的this就指向了新创建的对象。
  3. 逐行执行构造函数中的代码。
  4. 返回新创建的对象。

五 对象的继承

继承不止存在于js中,面向对象的编程语言中,都有一个对象(子类)可以从另外一个对象(父类)继承属性和方法,从而减少重复的代码。

在js中 继承是通过原型和原型链的方式来实现的,每个对象都有一个指向原型对象的链接,称为原型链。当我们访问对象的属性或者方法时,如果该对象没有定义这个属性或者方法时,js就会一直沿着原型链往上查找,直到找到为止。

常见的几种继承方式:
1 原型继承
2 构造函数继承
3 组合继承
4 寄生式继承
5 寄生组合继承

原型继承

直接在原型链上写继承

function Animal (){
    this.species = 'animal';
}
Animal.prototype.eat = function (){
    console.log('eating')
}

function Cat(name,color){
    this.name = name;
    this.color = color;
}
 Cat.prototype = new Animal();
  var cat1 = new Cat('Fluffy','white');
    console.log(cat1.species);
  cat1.eat();
  

构造函数继承

通过构造函数来实现

unction  Animal (){
    this.species = 'animal';
}
function Cat(name,color){
    Animal.call(this);
    this.name = name;
    this.color = color;
}
var cat1 = new Cat('fluffy','white');
console.log(cat1.species); // animal

组合继承

function Animal (){
    this.species = 'animal';
}
Animal.prototype.eat = function (){
    console.log('eating');
};
function Cat(name,color){
    Animal.call(this);
    this.name = name;
    this.color = color;
}
Cat.prototype = new Animal();
Cat.prototype.constrictor = Cat
var cat1 = new Cat('Fluffy','white')
console.log(cat1.species); // animal
cat1.eat();// eating

寄生式继承

function Animal (){
    this.species = 'animal';
}
Animal.prototype.eat = function (){
    console.log('eating');
};
function Cat(name,color){
    Animal.call(this);
    this.name = name;
    this.color = color;
}
Cat.prototype = new Animal();
Cat.prototype.constrictor = Cat
var cat1 = new Cat('Fluffy','white')
console.log(cat1.species); // animal
cat1.eat();// eating

寄生组合式继承

function Animal() {
  this.species = 'animal';
}

Animal.prototype.eat = function() {
  console.log('eating');
};

function Cat(name, color) {
  Animal.call(this);
  this.name = name;
  this.color = color;
}

(function() {
  var Super = function() {};
  Super.prototype = Animal.prototype;
  Cat.prototype = new Super();
})();

var cat1 = new Cat('Fluffy', 'white');
console.log(cat1.species); // 输出:animal
cat1.eat(); // 输出:eating

六 对象的深拷贝与浅拷贝

JS对象的深拷贝实现方式有很多,以下是其中一种实现方式:

function deepCopy(obj) {
  if (typeof obj !== "object") {
    return obj;
  }
  const newObj = Array.isArray(obj) ? [] : {};
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      newObj[key] = deepCopy(obj[key]);
    }
  }
  return newObj;
}

此函数接收一个对象参数 obj,它首先检查 obj 是否是一个对象,如果不是,则直接返回 obj。如果是一个对象,则创建一个新的空对象 newObj,如果 obj 是数组,则创建一个新的空数组;否则创建一个新的空对象。接着使用 for...in 循环遍历原对象的所有属性,如果属性是对象或数组,则递归调用 deepCopy 函数,将其复制到新对象中。最后返回新对象。 深拷贝与浅拷贝的区别在于,浅拷贝只是复制对象的引用,而深拷贝则是递归复制对象的所有属性和子属性,使得新对象与原对象完全独立,互不影响。深拷贝在处理复杂数据结构时非常有用,例如在处理嵌套的对象、数组、DOM节点等时常常需要使用深拷贝。

function shallowCopy(obj) {
  var newObj = {};
  for (var key in obj) {
    if (obj.hasOwnProperty(key)) {
      newObj[key] = obj[key];
    }
  }
  return newObj;
}

浅拷贝是指将原对象的属性值复制到新对象中,如果属性值为基本类型(number、string、boolean、null、undefined、symbol),则直接复制该属性值;如果属性值为引用类型(object、array、function),则复制该属性值的引用地址。 实现浅拷贝的方法有很多,以下是其中的一种:

该方法遍历原对象的属性,如果该属性是原对象自身的属性,则将该属性复制到新对象中。需要注意的是,该方法只能复制原对象的一层属性,如果原对象的属性值为引用类型,则新对象和原对象会共享该引用类型的属性值。

参考文章:

深入理解js对象

# JS学习—(8)JS对象

# 浅解析js中的对象

原文链接:https://juejin.cn/post/7214396380353347640 作者:十九万里

(0)
上一篇 2023年3月26日 上午10:31
下一篇 2023年3月26日 上午10:41

相关推荐

发表回复

登录后才能评论