[JS]14.面向对象&自定义类

173 阅读5分钟

1. 基本概念

  • 编程语言

    • OOP 面向对象
      • Java/Python/cpp/JS...
    • POP 面向过程
      • C语言
  • HTML和CSS是标记语言

    • less/sass/stylus叫CSS预编译语言,让CSS具备面向对象编程的特点
      • 写完的代码无法被浏览器直接识别,需要编译成普通CSS后再浏览器中渲染
  • 什么是面向对象编程

    • 对象:泛指,万物皆对象(JS中所有学习研究和开发的都是对象)
    • 类:对对象的一个细分,按照对应的功能特点分成大类和小类
    • 实例:某个类别中具体的事物
    • 关于类的封装继承和多态
      • 封装:封装函数,低耦合高内聚
      • 继承:子类及子类实例,继承了父类中的属性和方法
      • 多态:函数的重载(方法名相同,但是传参个数或者类型不同,识别为两个不同的方法 -> js中无)和重写(子类重写父类的方法)
// 重载:方法名相同,根据传参个数不同调用不同方法
// 后台语言中基于函数重载可以减轻一个业务逻辑的复杂度和抗压能力(拆分)
public void sum(int x, int y, Boolean flag) {}
public void sum(int x, int y) {}
public void sum(int y, Boolean flag) {}
sum(10, 20, true)  //调用第一个
sum(10, 20)  //调用第二个
sum(20, true)  //调用第三个
// js无论参数如何,只会执行最后一个sum
  • JS就是基于面向对象设计的变成语言
    • 本身存在很多`内置类
      • 每一个数据类型都有一个自己所属的内置类
      • 获取的元素集合或者节点集合也是有自己的类 HTMLCollection / NodeList
      • 每一个元素标签,都有自己所属的类
    • 当前实例所属类下的其它实例拥有相同特性

image.png

2. 自定义类 new Fn()/Fn

  • 所有的类,内置类和自定义类都是函数数据类型值
    • 函数执行的时候基于new执行即可,构造函数执行

2.1 普通函数执行流程

function Fn(x, y) {
  let total = x + y;
  this.x = x;
  this.y = y;
  return total;
}
// 作为普通函数执行
Fn();
  • Fn变量提升,声明+定义(赋值)一个函数
    • 创建函数堆 0x000, 其中存储代码字符串
  • Fn(10, 20)执行,Fn中形成私有上下文EC(FN)
    • 方法执行前没有.,所以this是window
    • 函数内部执行步骤
        1. 初始化作用域链<EC(fn), EC(g)>
        1. 初始this:window
        1. 形参赋值:x=10, y=20
        1. 变量提升:无
        1. 代码执行:有一个私有变量total
  • 返回的不是堆内存地址,外部未占用,执行完出栈释放

image.png

2.2 构造函数执行

function Fn(x, y) {
  let total = x + y;
  this.x = x;
  this.y = y;
  return total;
}
// 作为普通函数执行
Fn();

// 构造函数执行
let f1 = new Fn(10, 20);  // f1是fn的一个实例=> {x: 10, y: 20}
// total 只是上下文一个实例变量,和实例f1没有关系
// 如果函数没有return,或者返回一个基本数据类型,则浏览器默认会把创建的实例对象返回

// 如果构造函数体返回的是一个引用类型,而不是total,那么f1就是返回的对象,此时f1不是Fn的实例
console.log(f1 instanceof Fn); // false
  • 构造函数执行,也会先形成一个私有上下文EC(Fn)
  • 紧接着,在当前上下文中,创建一个实例对象 0x001
  • this是当前实例对象

image.png

function Fn(x, y) {
  let total = x + y;
  this.x = x;
  this.y = y;
  this.say = function say() {
    console.log(`SAY:${total}`)
  }
}
let f1 = new Fn(10, 20);   // {x: 10, y: 20, say: ƒ}
let f2 = new Fn;           // {x: undefined, y: undefined, say: ƒ}
console.log(f1 === f2);   // false
/*
- Fn Vs Fn()
  - Fn代表函数本身(堆内存 -> f Fn(x, y) {...})
  - Fn() 代表执行函数
- new Fn Vs new Fn()
  - 都是把Fn执行,第一个无参数传递,第二个传递了实参
  - new Fn 无参数列表new 运算优先级 高于 有参数列表 new Fn() 

image.png

  • 检测一个属性是否为当前对象的成员
    • 属性名 in 对象 : 无论是私有属性还是公有属性,只要有就是true
    • 对象.hasOWnProperty(属性名) : 必须是对象的私有属性结果才是true
console.log('say' in f1)  // true
console.log('toString' in f1) // true
console.log('total' in f1) // false

console.log(f1.hasOwnProperty('say')) // true
console.log(f1.hasOwnProperty('toString')) // false 不是私有属性
console.log(f1.hasOwnProperty('total'))// false

2.3 构造函数执行和普通函数执行区别

  • 相同点:

    • 构造函数执行,最开始也会像普通函数执行一样,形成私有上下文
      • AO
      • Scope-chain
      • 形参赋值
      • 变量提升
      • 代码执行
  • 不同点:

    • 构造函数执行,创建上下文之后,浏览器默认帮助我们创建一个对象,实例对象
      • 把当前fn函数当成一个类,叫构造函数
      • 创建的对象就是这个类的一个实例
    • 构造函数执行,初始化this时,让this指向当前创建的实例
    • 构造函数执行,代码执行完,返回值的时候
      • 如果函数没有return,或者返回一个基本数据类型,则浏览器默认会把创建的实例对象返回
      • 如果函数本身返回一个引用数据类型值,还是以自己返回的为主,且不是实例
        • f1 instanceof Fn -> false

2.4 属性的遍历

Object.prototype.AA = 'AA'  // 写在公有上了,非私有
let obj = {
  name: 'xxx',
  age: 11,
  0: 100,
  [Symbol('AA')]: 200,
  [Symbol.toPrimitive]: function() {
    return 0;
  }
}
console.log(obj);

image.png

// 基于 for...in遍历对象
//  - 优先遍历数字属性
//  - 不会遍历Symbol属性
//  - 会把自己扩展到类原型上的公共属性方法也遍历到
for(let key in obj) {
  // 遍历过程中,遍历到非公共属性,跳出,下句可以筛除AA
  if( !obj.hasOwnProperty(key)) break;   // 0 name age
  console.log(key)
}
// 0 name age AA
  • 想拿到包括Symbol的私有属性
// Object.keys(obj) 用来获取当前对象所有非Symbol的私有属性,输出数组
console.log(Object.keys(obj));  //  ["0", "name", "age"]
console.log(Object.getOwnPropertyNames(obj));  // ["0", "name", "age"] 获取私有的
// 获取所有私有的Symbol属性
console.log(Object.getOwnPropertySymbols(obj));  // [Symbol(AA), Symbol(Symbol.toPrimitive)]
let keys = [
  ...Object.keys(obj),
  ...Object.getOwnPropertySymbols(obj),
]
// 所有属性