我正在参加「掘金·启航计划」
前言
请先理解 「原型」 和 「new 操作符」在看此篇文章
概念
推理
// 当我们声明一个对象的时候
let obj = {
name: 'hone',
age: 18
}
// 再次声明一个的时候
let obj2 = {
name: 'dl',
age: 19
}
// 以上两个对象是不是很像,很像的对象把它们归为一类
// obj 和 obj2 叫做同一类对象
let obj3 = { // 显然它们和以上两个不是同一类
xxx: 1,
yyy: 2
}
// 如果同一类对象我们表示一类,我们怎么能用代码表示它呢?
// 写一篇文档
// 同志们 如果要创建一个人类对象请一定要这样写
let xxx = { // 你写的类必须要有这两个 key 属性 name age
name: xxx,
age: 18
}
// 你提供一个函数来创建这一类的对象
function createPerson(name, age) {
let obj = {}
obj.name = name || '' // 有就给没有就是 ‘’
obj.age = age || 18
return obj
}
createPerson() // {name:'', age: 18}
createPerson('hone') // {name: 'hone', age: 18}
// 用 createPerson 创建的对象都具有相同的特征
// 这个 createPerson 只能创建一类对象,它是拥有 name 和 age 的对象
// 拥有 name 和 age 就叫做这个类的特征
创建拥有共同特征的对象就是创建这个类,那么类就是拥有共同特征的对象
类是一个抽象的东西,它并不存在,没有一个实体叫做人类, 人类是所有人的分类,所有人都是人类,但是不存在一个具体的人类
人和人之间有共同的属性,这个共有属性就叫做原型
// 假设人 必须有 4 个属性 name age walk species
// 我们发现每个人都有自己的自有name 自己的名字
// 每个人都有自己的 自有age
// 人类走路的动作 共有walk
// 所有的人都是人类 共有species
// 如何通过代码来区分 自有的 和 公有的
// 把所有共有属性放到 原型里面
// 把所有的非共有属性 放到 createPerson 里面
let 人类共有属性 = {
walk(){ console.log('走两步') },
species: '人类'
}
function createPerson(name, age) {
let obj = {}
obj.name = name || ''
obj.age = age || 18
obj.__proto__ = 人类共有属性 // 需要给一个这个对象的原型
return obj
}
// 在 JS 里的类,没有这么严格, 只要拥有相同属性的就叫做一类
结论
- 类: 拥有相同属性的对象
- 构造函数:用来创建某个类的对象的函数
- 自有属性:对象自身的属性
- 共有属性:对象原型 (链) 里的属性
简单语法
class、new、extends、super()
构造函数的由来:我想生成一类的东西,我不想每次都写一遍。
所以需要一个函数来帮助我们创建符合某种特征的对象,符合某种特征就叫做类,用函数来创建某一类的对象,那这个函数就叫做构造函数。
class Person {
constructor(name) {
this.name = name
this.age = 18
}
}
let p1 = new Person('hone')
每一个 class 都有一个构造函数 constructor ,它用来构造自有属性
class Person {
// constructor 里面写所有的自有属性
constructor(name) {
this.name = name
this.age = 18
}
// 所有的共有属性 写成 xxx(){}
// walk 在原型上,也就是存在共有属性上
walk(){
console.log('走两步')
}
}
let p1 = new Person('hone')
let p2 = new Person('h')
p1.walk === p2.walk
当你要声明对象的时候就用 new ****, 继承就要用 extends
class Animal {
constructor(){
this.body = '身体' // 假设 Animal 有一个自有属性 body
}
move(){
console.log('我能动')
}
}
// Person 继承了 Animal
class Person extends Animal { // extends 后面的叫做 基类/超类
constructor(name){
super() // 相当于把这句话 this.body = '身体' 给弄过来
this.name = name
this.age = 18
}
// 人类自己的共有属性
walk(){
console.log('走')
}
}
let p1 = new Person('hone')
打印的是 p2 为啥显示的是大写的 Person, 这个东西是没有任何意义的不要去管它
extends 的意思是如果一个类 Person 继承另外一个类 Animal, 那么 Person 的对象就拥有 Animal 的属性
super() 的意思是 执行你继承的那个类的 constructor
class Person extends Animal { // extends 后面的叫做 基类/超类
constructor(name){
super() // 在使用 this 之前必须先调用 super()
this.name = name
this.age = 18
this.body = this.body + '手脚'
}
// 人类自己的共有属性
walk(){
console.log('走')
}
}
get、set
如果共有属性是一个数字呢?
// 1
class Animal {
constructor(){
this.body = '身体' // 假设 Animal 有一个自有属性 body
}
move(){
console.log('我能动')
}
race(){
return '动物'
}
}
class Person extends Animal {
constructor(name){
super()
this.name = name
this.age = 18
}
walk(){
console.log('走')
}
}
let p1 = new Person('hone')
p1.race() // '动物'
// 可以不写 () ? 使用 get 就可以
// 2
// ES6
class Animal {
// #race = '动物'
constructor(){
this.body = '身体' // 假设 Animal 有一个自有属性 body
}
move(){
console.log('我能动')
}
get race(){ // 在前面加一个 get
return '动物'
}
}
p1.race // 这样就可以调用
p1.race = 'animal'
p1.race // '动物' 改不动,为什么? 因为它始终在调用 race() 这个函数, 这个函数永远会返回 ‘动物’
// 3
// 如果你想赋值的话 需要用到 set
class Animal {
constructor(){
this.body = '身体' // 假设 Animal 有一个自有属性 body
// 隐藏的属性 this._race 去保留真正的值
this._race = '动物' // 在这里开始存
}
move(){
console.log('我能动')
}
// 用两个函数去操作这个隐藏的属性
get race(){ // 在前面加一个 get
return this._race
}
set race(value){ // 这个 set 接收用户赋的值
this._race = value
}
// 属性封装
}
p1.race // '动物' 在运行这个的时候调用了 get 方法
p1._race // '动物' 奇怪吧, 后续会有修复语法
p1.race = 'animal'
p1.race // 'animal'
// 还可以用 #
class Animal {
#race = '动物' // 于是所有人不可以通过 this._race 来得到这个 _race
constructor(){
this.body = '身体' // 假设 Animal 有一个自有属性 body
}
}
以上代码, 用一个隐藏的属性 this._race 去保留真正的值,然后用两个函数去操作这个隐藏的属性,这个叫做属性封装
// 设置人类不能改自己的年龄,如何设置不允许更改年龄
class Animal {
constructor(){
this.body = '身体' // 假设 Animal 有一个自有属性 body
}
move(){
console.log('我能动')
}
race(){
return '动物'
}
}
class Person extends Animal {
constructor(name){
super()
this.name = name
this._age = 18 // 在这里给她隐藏起来
this.body = this.body + '手脚'
}
walk(){
console.log('走')
}
// 不设置 set 就无法更改了
get age(){
return this._age
}
}
let p1 = new Person('hone')
// 对 name 进行封装
class Person extends Animal {
constructor(name){
super()
this._name = name
this._age = 18 // 在这里给她隐藏起来
this.body = this.body + '手脚'
}
walk(){
console.log('走')
}
// 不设置 set 就无法更改了
get age(){
return this._age
}
get name(){
return this._name
}
set name(v){
if(v.length > 4 || v.length < 2) {
console.log('请重新输入 name ')
}else{
this._name = v
}
}
}
get 是用来控制读的, set 是用来控制写的,这就是面向对象发明 get 和 set 的用意
static
static 静态方法: 你可以直接通过类名访问到的方法(可以通过 class 名字直接 点 出来的方法)
class Animal {
constructor(){
this.body = '身体'
}
move(){
console.log('我能动')
}
race(){
return '动物'
}
}
class Person extends Animal {
constructor(name){
super()
this._name = name
this._age = 18
this.body = this.body + '手脚'
}
// static 让 p1 访问不了
// 人类灭亡不可以通过 p1.die 因为这只表示是p1死亡,不代表人类
// 需要 Person.die 这个类 灭亡才是人类灭亡
static die(){
console.log('地球炸了')
}
walk(){
console.log('走')
}
get age(){
return this._age
}
get name(){
return this._name
}
set name(v){
if(v.length > 4 || v.length < 2) {
console.log('请重新输入 name ')
}else{
this._name = v
}
}
}
let p1 = new Person('hone')
// 以上都可以通过 p1.age、p1.name、p1.walk 来访问
// 我们可以写一个东西让 p1 访问不了
p1.die // 函数 没有返回值 undefined
Preson.die() // 地球炸了