Skip to content

面向对象

1. 概念

首先面向对象不是一种新的语法,而是一种编程思维,是一种编程范式(方式)

一般我们可以把编程思想分为两种面向过程

1.1 面向过程:

面向过程是一种以事件为中心的编程思想,编程的时候把解决问题的步骤分析出来,然后用函数把这些步骤实现,在一步一步的具体步骤中再按顺序调用函数。 以过程为核心,强调事件的流程、顺序面向对象

1.2 面向对象:

面向对象是一种以对象为中心的编程思想,把要解决的问题分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个对象在整个解决问题的步骤中的属性和行为。 以对象为核心,强调事件的角色、主体

1.3 举个栗子

小明每天早上起床上班的事情包含:起床、穿衣、洗漱、开车上班

从面向过程的角度看就是:

  • 1、起床;2、穿衣服;3、刷牙洗脸;4、开车上班。

从面向对象的角度看就是:

  • 主人公:小明;

  • 拥有的财产:床、衣服、洗漱用品、车;

  • 小明要做的事:从床上起来、把衣服穿上、去洗漱,开车上班。

在日常生活或编程中,简单的问题可以用面向过程的思路来解决,直接有效 ,但是当问题的规模变得更大时,用面向过程的思想是远远不够的。所以慢慢就出现了面向对象的编程思想

1.4 优缺点比较

面向过程:

优点:

  • 流程化使得编程任务明确,在开发之前基本考虑了实现方式和最终结果,具体步骤清楚,便于节点分析。

  • 效率高,面向过程强调代码的短小精悍,善于结合数据结构来开发高效率的程序。

缺点:

  • 需要深入的思考,耗费精力,代码重用性低,扩展能力差,后期维护难度比较大。面向对象

面向对象:

  • 优点: 结构清晰,程序是模块化和结构化,更加符合人类的思维方式;

  • 缺点:开销大,当要修改对象内部时,对象的 直接存取,消耗的内存会加大

2. 创建对象的方式

  • 因为面向对象就是一个找到对象的过程

  • 所以我们先要了解如何创建一个对象

2.1 Object 构造函数创建

  • js 给我们内置了一个 Object 构造函数

  • 这个构造函数就是用来创造对象的

  • 当 构造函数 和 new 关键字连用的时候,就可以为我们创造出一个对象

  • 因为 js 是一个动态的语言,那么我们就可以动态的向对象中添加成员了

js
var o1 = new Object() 

// 正常操作对象
o1.name = 'Jack'
o1.age = 18
o1.gender = '男'

2.2 字面量创建

  • 直接使用字面量的形式,也就是直接写

  • 可以在写的时候就添加好成员,也可以动态的添加

js
// 添加默认成员
var o1 = {
  name: 'Jack',
  age: 18,
  gender: '男'
}

// 动态添加成员
var o2 = {}
o2.name = 'Rose'
o2.age = 20
o2.gender = '女'

2.4 工厂函数创建

  • 先书写一个工厂函数

  • 这个工厂函数里面可以创造出一个对象,并且给对象添加一些属性,还能把对象返回

  • 使用这个工厂函数创造对象

js
// 1. 先创建一个工厂函数
function createObj() {
  // 手动创建一个对象
  var obj = new Object()

  // 手动的向对象中添加成员
  obj.name = 'Jack'
  obj.age = 18
  obj.gender = '男'

  // 手动返回一个对象
  return obj
}

// 2. 使用这个工厂函数创建对象
var o1 = createObj()
var o2 = createObj()

2.5 自定义构造函数创建

工厂函数需要经历三个步骤

  • 手动创建对象

  • 手动添加成员

  • 手动返回对象

构造函数会比工厂函数简单一下

  • 自动创建对象
  • 手动添加成员
  • 自动返回对象

构造函数创建对象过程

  • 先书写一个构造函数
  • 在构造函数内向对象添加一些成员
  • 使用这个构造函数创造一个对象(和 new 连用)
  • 构造函数可以创建对象,并且创建一个带有属性和方法的对象
  • 面向对象就是要想办法找到一个有属性和方法的对象
  • 面向对象就是我们自己制造

构造函数 的过程

js
// 1. 先创造一个构造函数
function Person(name, gender) {
  this.age = 18
  this.name = name
  this.gender = gender
}

// 2. 使用构造函数创建对象
var p1 = new Person('Jack', 'man')
var p2 = new Person('Rose', 'woman')

3.关于原型

我们从一个例子展开

一个构造函数可以有多个属性,每次实例化都会创造一个新的引用对象,属性也是一样的

js
function Person() {
    this.name = '55开'
    this.age = 25
    this.hobby = "吃鸡"
}

let p1 = new Person;

let p2 = new Person;

let p3 = new Person;

p2.sayHi = function (){
    console.log('大家好 我是55开');
}

p3.nickname = "我开哥没有开挂"

console.log(p1);
console.log(p2);
// console.log(p1==p2);
console.log(p3);

//问题:
//3个实例有着相同的属性,但是这些相同的属性分别在不同的内存区域中,造成了大量的内存消耗

//为何不将这些共享的属性放到一个对象中?放到一个内存中?
//原型:
// 原型的本质:原型就是一个对象,里面放着各个实例共有的属性(想象成它就是一个仓库)
// 原型的作用:减少内存消耗

3.1 原型的本质

就是一个对象,我们可以理解为它就是一个仓库所有的实例都可以去这个仓库中拿属性

3.2 原型的作用

用来存放实例中共有的那部份属性、方法,可以大大减少内存消耗。

3.3 prototype

每一个函数自带一个成员,叫做 prototype,是一个对象空间

即然每一个函数都有,构造函数也是函数,构造函数也有这个对象空间

js
function Person() {}
console.log(Person.prototype) // 是一个对象

即然是个对象,那么我们就可以向里面添加数据

js
function Person() {}
Person.prototype.name = 'prototype'
Person.prototype.sayHi = function () {}

重点: 在函数的 prototype 里面存储的内容,不是给函数使用的,是给函数的每一个实例化对象使用的

3.4 proto

  • 每一个对象都天生自带一个成员,叫做 __proto__,是一个对象空间

  • __proto__是对构造函数prototype的引用,也就是说__proto__prototype指向同一个地址

  • 访问对象成员的时候,先查找自身对象成员,如果没有,就查找__proto__

  • __proto__只能通过实例访问

js
function Person() {}
var p1 = new Person()
// __proto__和prototype 地址相同
console.log(p1.__proto__ === Person.prototype) // true
  • 实例化对象的 __proto__ 和所属的构造函数的 prototype同一个对象空间
  • 可以通过构造函数名称来向 prototype 中添加成员

注意:当通过obj.key使用对象的属性或方法时,若对象本身没有,则会在实例化对象的proto中找

js
function Person() {}
// 向prototype中添加成员
Person.prototype.sayHi = function () {
  console.log('hello Person')
}

// 实例化对象
var p1 = new Person()

p1.sayHi()
  • 以上代码中,p1的构造函数Person自己没有 sayHi 方法,就会去自己的 proto 中查找
  • p1.__proto__就是 Person.prototype
  • 我们又向 Person.prototype 中添加了 sayHi 方法
  • 所以 p1.sayHi 就可以执行了

每个实例化对象的__proto__都指向一个其构造函数的prototype

js
function Person() {}
Person.prototype.sayHi = function () {
  console.log('hello')
}

var p1 = new Person()
var p2 = new Person()

console.log(p1.sayHi === p2.sayHi)
  • p1是 Person 的一个实例
  • p2是 Person 的一个实例
  • 也就是说 p1.proto 和 p2.proto 指向的都是 Person.prototype
  • 当 p1 去调用 sayHi 方法的时候是去 Person.prototype 中找
  • 当 p2 去调用 sayHi 方法的时候是去 Person.prototype 中找
  • 那么两个实例化对象就是找到的一个方法,也是执行的一个方法

所以:当我们写构造函数的时候,属性我们直接写在构造函数体内,方法我们写在原型上

4.原型链

我们说构造函数的 prototype 是一个对象 又说了每一个对象都天生自带一个 __proto__ 属性,那么构造函数的 prototype 里面的 __proto__ 属性又指向哪里呢?

首先我们要清楚明白两个概念:

  1. js分为函数对象和普通对象,每个对象都有__proto__属性,但是只有函数对象才有prototype属性

  2. Object、Function都是js内置的函数, 类似的还有我们常用到的Array、RegExp、Date、Boolean、Number、String

  3. 全局对象有哪些?navigator history location window

那么__proto__prototype到底是什么,两个概念理解它们

  1. 函数都有一个属性叫做prototype 里面有一个constructor属性指向构造函数, 记录了实例的创建者是谁

  2. 对象都有一个属性叫做__proto__ 指向构造函数的prototype

  3. prototype本质是一个对象

  4. function本质也是一个对象

4.2 举个栗子

有以下构造函数Person,他的原型上有所属国属性motherland='china'

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

Person.prototype.motherland = 'china'

通过new Person()创建的person01实例

js
 let person01 = new Person('小明', 18);

js之父在设计js原型、原型链的时候遵从以下两个准则

js
// 准则1:原型对象(即Person.prototype)的constructor指向构造函数本身
Person.prototype.constructor == Person 
// 准则2:实例(即person01)的__proto__和原型对象指向同一个地方
person01.__proto__ == Person.prototype

4.3 一个对象所属的构造函数

  • 每一个对象都有一个自己所属的构造函数

  • 比如: 数组

js
// 数组本身也是一个对象
var arr = []
var arr2 = new Array()
  • 以上两种方式都是创造一个数组

  • 我们就说数组所属的构造函数就是 Array

  • 比如: 函数

js
// 函数本身也是一个对象
var fn = function () {}
var fun = new Function()
  • 以上两种方式都是创造一个函数

  • 我们就说函数所属的构造函数就是 Function

4.4 constructor

  • 对象的 __proto__ 里面也有一个成员叫做 constructor

  • 这个属性就是指向当前这个对象所属的构造函数

4.5 链状结构

  • 当一个对象我们不知道准确的是谁构造的时候,我们呢就把它看成 Object 的实例化对象

  • 也就是说,我们的 构造函数 的 prototype 的 proto 指向的是 Object.prototype

  • 那么 Object.prototype 也是个对象,那么它的 proto 又指向谁呢?

  • 因为 Object 的 js 中的顶级构造函数,我们有一句话叫 万物皆对象

  • 所以 Object.prototype 就到顶了,Object.prototype 的 proto 就是 null

4.6 顶级构造函数

我们 js内置了一个构造函数Object用来创建对象

作为一个函数,就会拥有 prototype成员

js
console.log(Object.prototype);
// 通过 Object构造函数 实例的对象 的 __proto__ 指向的就是 Object.prototype
let o1 = new Object();
console.log(o1.__proto__);
console.log(o1.__proto__ === Object.prototype);  // true

我们 自定义的构造函数的原型对象的原型对象 指向的是 内置构造函数Object的原型对象

js
console.log(Person.prototype.__proto__ === Object.prototype);  // true
console.log(p1.__proto__.__proto__ === Object.prototype);  // true
console.log(p2.__proto__.__proto__ === Object.prototype);  // true

在 js中 Object构造函数 是用于创建对象的 顶级构造函数

所以我们 可以认为 所有的对象都是 Object构造函数创建

当一个对象我们不知道准确的是谁构造的时候,我们呢就把它看成 Object 的实例化对象

也就是说,我们的 构造函数 的 prototype 的 proto 指向的是 Object.prototype

那么 Object.prototype 也是个对象,那么它的 __proto__ 又指向谁呢?

因为 Object 的 js 中的顶级构造函数,我们有一句话叫 万物皆对象

所以 Object.prototype 就到顶了,Object.prototype 的__proto__ 就是 null

js
console.log(Object.prototype.__proto__);  // null
//在正常情况下,非Object构造函数创建的对象的 __proto__ 的 __proto__ 都是指向 Object.prototype

// 沿着 一个对象的 __proto__ 一直向上 去查找 就最终构成了 原型链

4.7 原型链的访问原则

  • 我们之前说过,访问一个对象的成员的时候,自己没有就会去 proto 中找

  • 接下来就是,如果 __proto__ 里面没有就再去 __proto__ 里面找

  • 一直找到 Object.prototype 里面都没有,那么就会返回 undefiend

注意:

  1. 如果 针对一个对象的属性进行赋值,是 不会 按照原型链查找赋值的

  2. 执行的逻辑是

  3. 如果对象 本身就有这个属性 直接 修改

  4. 如果对象 本上就没有这个属性 直接 添加

5. 关于原型的一些方法

5.1 Object.create(obj)

该方法接受一个对象作为参数, 返回一个全新的对象, 新对象会将参数中的对象作为自己的原型链(作为自己原型对象)

语法:

js
obj2=Object.create(obj)

obj为obj2的原型对象

js
let obj1={
    name:'对象1',
    say:function(){
        console.log(`我是${name}`);
    }
}
let obj2=Object.create(obj1)
obj2.name='对象2'
// obj1时obj2的原型对象
console.log(obj2.__proto__);    //obj1
console.log(obj2.__proto__===obj1);    //true
console.log(obj2.__proto__.__proto__);    //Object.prototype

5.2 Object.getPrototypeOf()

  • 该方法接受一个对象作为参数

  • ES5, 返回参数对象的原型链对象(原型对象)

  • 此方法和使用 对象.proto 返回的结果是一样的

js
let abc = Object.getPrototypeOf(obj1); 
console.log(abc.__proto__);  // obj
console.log(abc===obj1.__proto__);  // true
console.log(Object.getPrototypeOf(abc)===abc.__proto__); //true

5.3 isPrototypeOf()

语法:

js
obj1.isPrototypeOf(obj2)

该方法用于 检测 ob1 是否 存在于 ob2 的 原型链上,返回布尔值

js
let test = function () { }  //构造函数
let testObject = new test();        //实例对象
test.prototype.isPrototypeOf(testObject) // true test.prototype在testObject的原型链上
Object.prototype.isPrototypeOf(testObject)// true Object.prototype在testObject的原型链上

5.4 instanceof

用于测试构造函数的prototype属性是否出现在对象的原型链中的任何位置

1.检查某个实例对象是不是由某个构造函数创建的

js
let arr=[1,2]
let o=22
console.log(arr instanceof Array);  // true
console.log(arr instanceof Object);  // true
console.log(o instanceof Array);  // false

2.用于检查某个数据是不是一个数组类型

语法:obj instanceof constructor

js
let arr=[1,2]
let o=22
console.log(arr instanceof Array);  // true
console.log(o instanceof Array);  // false

5.5 hasOwnProperty()

指示对象自身属性中是否具有指定的属性,但是不会查找原型链上的属性

语法:obj.hasOwnProperty(prop)

参数: prop 要查找的属性

返回值: 用来判断某个对象是否含有指定的属性的Boolean。

js
let test ={ 'food': '嗯呢' }
test.hasOwnProperty('food');  // true
test.hasOwnProperty('toString'); // false test本身没查找到toString

6.new一个对象发生了什么

new一个构造函数,发生了什么

  1. 创建一个对象(开辟一个内存区域)

  2. 绑定this(构造函数内的this指向创建的对象实例)

  3. 会执行构造函数中的代码(为这个新对象添加属性)

  4. 实例constructor(构造器)指向构造函数

  5. 创建出来的对象,它的__proto__会指向构造函数的prototype

  6. 当函数没有返回新对象的时候,那么会自动获得通过new构建出来的对象

7.构造函数

7.1 构造函数基本使用

7.1.1 创建构造函数

  • 和普通函数创建一样,区别是调用的方式不同
  • 一名合格的程序员构造函数名首字母应该大写
js
function Test(){
    // ***
}

7.1.2 实例化构造函数

  • 不同于普通函数,调用的时候必须要和 new 连用
  • 在实例化new一个对象时,如果不需要传递参数,那么可以不写 (),如果传递参数就必须写
js
var t = new Test()
  • 每次 new 的时候,函数内部的 this 都是指向当前这次的实例化对象
  • 每次new一个新对象,都会创建一个内存空间,所以每个实例化对象的数据互不影响
  • 构造函数会自动返回一个对象,所以构造函数内部不要写 return
  • 你如果 return 一个基本数据类型,那么写了没有意义
  • 如果你 return 一个引用数据类型,那么构造函数本身的意义就没有了
js
function Person() {
  console.log(this)
}
var o1 = new Person()  // 本次调用的时候,this => o1
var o2 = new Person()  // 本次调用的时候,this => o2

7.2 构造函数的普通属性

一个构造函数可以有普通属性,原型属性,静态属性

通过在构造函数中使用this.属性名=值来定义数据

js
function Person() {	
    // 定义普通属性
  this.name = 'Jack'
    // 定义普通方法
  this.sayHi = function () {
    console.log('hello')
  }
    
}
// 创建实例对象
var p = new Person()
// 使用普通属性
console.log(p.name)		// 'Jack'
// 使用普通方法
p.sayHi()

普通属性占内存,因为每当我们new一个实例对象,都会开辟一个新的内存控件,但这些空间的属性和方法都是一样的,占用了两个空间地址,因此就会引起不必要的内存消耗

7.3 构造函数的原型属性

7.3.1 定义

通过构造函数.prototype.属性来添加

js
function Person() {}

// 给构造函数添加原型属性
Person.prototype.name='xiazhi'

// 给构造函数添加原型方法
Person.prototype.sayHi=function(){
	console.log('hello')
}

TIP

原型属性占用内存较少

为了避免普通属性占内存的问题,我们可以把属性和方法放在一个公共的空间里,那就是构造函数的prototype

原型属性是属于一个类,而不是属于每个实例对象

每次实例化一个对象时,对象身上的属性都需要开辟空间进行存储,而原型属性只需要开辟一次空间存储

7.3.2 使用

依然是直接通过.操作符进行访问

js
// 创建实例对象
var o1 = new Person()

// 使用原型数据
console.log(o1.name)		// 'xiazhi'
o1.sayHi()					// 'hello'

TIP

在访问一个对象的属性时,会先在对象本身上查找,找不到则会去对象的原型上找,一直往上找到Object对象的原型中,若一直没找到则返回undefined

js
class Person {
    age = 18
}
const person = new Person()
// person对象身上有age属性,值为18
console.log(person.age) // 18

// person对象身上没有score属性,为undefined
console.log(person.score) // undefined

Object.prototype.score = 100
// 给所有对象添加score属性,值为100
console.log(person.score) // 100

7.4 构造函数的静态属性

我们给构造函数添加原型方法虽然不占内存,但每次使用都需要创建实例对象

使用静态属性不用创建实例对象,比如Array.isArray()

7.4.1 定义

需要通过构造函数名来添加

js
function Person() {}

Person.age=18
Person.sayHi=function(){
    console.log("hello")
}

7.4.2 使用

通过构造函数.属性使用

js
console.log(Person.age)  //18
Person.sayHi()   // "hello"

8.class类

8.1 概述

在ES6之前都没有类的概念, 其实 ES6 的 类 只是一个 语法糖

从底层来说,还是构造函数

定义类: class 名称{ }

8.2 动态成员

类实例化才能够使用的成员,就是动态成员,也就是普通属性

什么是动态方法:在类实例化之后,内存中已经开辟了空间,用于保存当前的对象,那么这个方法就会添加到实例上,也就是说,只有实例化类之后,才能调用的方法,就是动态方法

8.2.1 constructor内添加

  • constructor构造器 === 和构造函数是一样的

  • 给 实例对象 添加的**普通属性(动态成员)**书写在 constructor ( ) { } 构造器 中,( ) 可以写参数

  • constructor构造器 的 this 指向 实例对象

  • 和构造函数一样,给实例对象添加成员是通过 this添加

  • 在 constructor构造器中 给 实例对象 添加成员 都是 动态成员

javascript
class Test {
    constructor(num) {
        this.num = num;
        this.name = "xiazhi";
        this.speak = function () {
            console.log("hello");
        };
    }
}
// 实例化
const t = new Test(66);

// 使用普通属性
console.log(t.num);  //66 
console.log(t.name);  // xiazhi
t.speak();  // hello

8.2.2 constructor外添加

  • 如果 在 constructor构造器外 定义 动态成员就直接书写,不需要使用 this

  • 在 constructor构造器外定义动态属性不能使用参数赋值,如果使用就会报错,不建议将 动态属性 定义在 constructor构造器外

  • 在constructor外用this.属性访问对象成员

  • constructor外定义动态方法用 fn=function(){}或箭头函数fn=()=>{}

  • fn ( ) 是给原型添加方法,相当于 Person.prototype.fn = function(){}

  • 只能在class里添加原型方法,不能添加原型属性,原型属性需要在class外面添加

js
class Person {
    constructor() {
        this.name = '张三';
    }
    //动态属性
    age=18   
    //动态方法,普通函数
    hello=function(){     
        console.log(`我是${this.name},今年${this.age}`);
    }
    //动态方法,箭头函数
    hello1=()=>{
        console.log(`我是${this.name},今年${this.age}`);
    }
}

// 实例化
let person1=new Person('男',22)
person1.hello()
person1.hello1()

8.3 类的原型属性

8.3.1 class里定义原型属性

constructor外定义,不需要=

js
class Test {
    speak() {
        console.log('hello');
    }
}

const t = new Test();
t.speak(); // hello

8.3.2 class外定义原型属性

通过类名.prototype.函数名定义

js
class Test {}
Test.prototype.speak = () => {
    console.log('hello')
}
// 实例化
const t = new Test();
t.speak(); // hello

8.3 类的静态成员

  1. 静态成员 是和类一起生成的 如果实例化,那么会把这个成员标记为'封闭',也就意味着,实例化之后是无法通过 实例对象 访问到静态成员

  2. 给类定义静态成员需要通过 static关键字

  3. 定义静态方法:static fn ( ) =function ( ) { }

  4. 定义静态属性:static name='张三'

  5. 静态成员 只能通过类名.键实现访问,不能通过实例对象调用

  6. 访问静态方法:Person.fn ( ) Person为类名,fn()为静态方法,name为静态属性

  7. 访问静态属性:Person.name

  8. 在静态方法中,不好使用this,因为this指向的是当前的类,而不是当前的实例

  9. 静态方法中可以通过this访问静态成员,无法访问动态成员

js
class Person {
    constructor() {
        this.name = '小明';
    }
    hello() {
        console.log('我是' + this.name);
    }

    // 静态成员 -- 方法
    static fn = function () {
        console.log('我是类的静态方法');
        // 在静态方法中,不好使用this,因为this指向的是当前的类,而不是当前的实例
        console.log(this);
        // 在静态方法中可以访问静态成员
        console.log(this.age);  // 18
        //无法访问 动态成员
        console.log(this.name);  // undefined
    }
    // 静态成员 -- 属性
    static age= 18;
}
let p1=new Person()
Person1.fn()

在class外添加静态成员

js
class Person{
    constructor(){
        
    }
}

// 静态成员 -- 方法
Person.sayHi=function(){
    console.log("hello")
}

// 静态成员 -- 属性
Person.age=18

8.4 类的继承

  1. 继承,就是将父类的成员,继承到子类,如果子类没有这些成员,可以通过访问父类来获取这些成员,减少重复性逻辑的编写,复用代码逻辑

  2. 实现类的继承,需要子类使用到extends关键字

  3. super关键字用于访问和调用一个对象的父对象上的constructor函数。

javascript
class Father{
    constructor(arr1,arr2){
        ...    
    }
}
class Son extends Father{
    constructor(arr1,arr2){
        super(arr1,arr2) 
    }
}
  1. 当子类和父类中有相同的属性,则 优先使用子类的这个属性

  2. 如果子类中没有,才会按照父类中的执行

实例:

如果有需要传参的属性,也需要在super的()中传入参数

js
class Father {
    constructor(name) {
        this.name = name;
        this.house='别墅'
        this.say=function(){
            console.log("你好,我是张三");
        }
    }
}
class Son extends Father{
    constructor(name){
        super(name)
        this.name=name
        this.say=function(){
            console.log("你好,我是李四");
        }
    }
}
let f1=new Father('张三')
f1.say()        //你好,我是张三
let son=new Son('李四')     //你好,我是李四
son.say()
console.log(son.name);      //李四
console.log(son.house);       //别墅,子类没有定义属性,继承父亲的属性

8.5 属性访问器

属性访问器是js类中,当我们访问一个属性时,可以做一些其他操作

属性访问器是两个函数,分别是set()和get()

  • set()函数在属性被修改时被处触发
  • get()函数在属性被访问时被处触发

语法如下

js
class Person {
    get name() {
        return 'John'
    }
    set name(value) {
        console.log('Name cannot be changed')
    }
}

const p = new Person()
console.log(p.name) // John

上述代码并没有在类中定义name属性,所以不可修改,开发中可以作为实现私有属性的一个办法

若要实现修改访问器,需要单独定义一个方法

js
class Person {
    constructor() {
        this._name = 'John'
    }
    get name() {
        return this._name
    }
    set name(value) {
        this._name = value
    }
}

const p = new Person()
console.log(p.name) // John
p.name = 'Mary'
console.log(p.name) // Mary

注意:上述代码中属性访问器的名称和属性名并不相同,这是因为

8.6 私有成员

通过#关键字定义一个私有成员,并且私有属性只能在类内,construct外定义

js
class Person {
    #_name = ''
    #fn = () => {
        console.log('Hello')
    }
}

私有成员可以在类内访问,类外不可访问,并且访问时也要带上#

js
class Person {
	#_name
	#fn = () => {
		console.log('Hello')
	}
	constructor() {
		this.#_name = 'John'
		this.#fn()
	}
}

const p=new Person()
console.log(p.#_name)		// 报错

若想在类外访问类内的私有成员,可以单独写一个方法进行”间接访问“

js
class Person {
    #_name='John'
    #fn = () => {
        console.log('Hello')
    }
    
    get name() {
        return this.#_name
    }
    myFn = () => {
        this.#fn()
    }
}

const p = new Person()

console.log(p.name)  //John
p.myFn()

MIT Licensed