JavaScript面向对象基础总结

吐槽君 分类:javascript

面向对象和类的定义

:所谓“类”就是对象的模板,对象就是“类”的实例。对象通常需要一个模板,表示某一类实物的共同特征,然后对象根据这个模板生成

那么,“对象”(object)到底是什么?我们从两个层次来理解。
(1)对象是单个实物的抽象。
一本书、一辆汽车、一个人都可以是对象,一个数据库、一张网页、一个远程服务器连接也可以是对象。当实物被抽象成对象,实物之间的关系就变成了对象之间的关系,从而就可以模拟现实情况,针对对象进行编程。
(2)对象是一个容器,封装了属性(property)和方法(method)。
属性是对象的状态,方法是对象的行为(完成某种任务)。比如,我们可以把动物抽象为animal对象,使用“属性”记录具体是哪一种动物,使用“方法”表示动物的某种行为(奔跑、捕猎、休息等等)。

JavaScript 语言的对象体系,不是基于“类”的,而是基于构造函数(constructor)原型链(prototype)

以上概念来自阮一峰JavaScript教程-面向对象编程

开发示例:如果一个页面有两个轮播图,我想让这两个轮播图各自单独运行,但是核心的代码逻辑不变,且两个实例互不影响,他们的核心运行逻辑和表现都一样,但是需要各自单独运行他们自己的逻辑,且有自己的数据和状态,这时候就要用到面向对象的开发方法。插件封装组件封装,都是面向对象的思想的应用,都是对私有化和公有化的合理管控。

JS本身就是面向对象的编程语言,而他本身也是基于面向对象的思想构建出来的语言。

image。png

面向对象思想:

  • 对象:万物皆对象
  • 类:对“对象”的归类和细分
  • 实例:以类为模板创造出来的具体成员

内置类

JS中的类:内置类、自定义类

内置类:

  • 每一种数据类型有自己对应的内置类:NumberStringBooleanSymbolBigIntObjectArrayRegExpDateSetMapArrayBuffer...)、Function
  • 我们平时接触的具体值就是对应的类的实例,例如:10就是Number类的一个实例,[10,20] -> Array -> Object ...
  • 每一个HTML元素标签「包含window/document等」在JS中都有自己的内置类,例如:div -> HTMLDivElement -> HTMLElement -> Element -> Node -> EventTarget -> Object

image。png
一个DOM元素对象

image。png

在封装,抽象的时候,要始终记得使用面向对象的思想

自己创造类

普通函数执行

function Fn(x, y) {
    let total = x + y,
        flag = false;
    this.total = total;
    this.say = function say() {
        console.log(this.total);
    };
}
let result = Fn(10, 20);
console.log(result);//undefined
 

普通函数执行,函数中的this要根据函数运行时所在的环境决定

image。png

构造函数执行

let result1 = new Fn(10, 20);
let result2 = new Fn(30, 40);
console.log(result1.say === result2.say); //->false
 

使用new代表把函数当作构造函数执行,这个构造函数就是自定义的类

  • Fn被称为或者构造函数
  • result被称为当前类的一个实例

image。png

使用new关键字执行构造函数比普通函数执行做了如下操作:

  1. 在初始化this之前,默认创建一个空对象(作为返回的实例对象),在初始化this的时候,将this指向创建的实例对象
  2. 所以在代码执行阶段,函数体中遇到this.xxx = xxx的赋值操作,都是给实例对象设置私有的属性或方法(上下文中的私有变量和实例对象没有直接关系,只有遇到类似this.xxx = xxx的赋值,才有关系)
  3. 如果函数不设置返回值或者返回的是原始值类型,则默认返回的结果是创建的实例对象,只有手动返回对象类型值,才以用户自己返回的为准

image。png

所以返回的result实例对象的私有属性或方法,和函数上下文中的私有变量不存在直接关系。

这样new也行成了闭包,因为返回了实例对象,被外部变量所引用

所以类在JavaScript中实际上是function类型,本质是函数(构造函数),实例对象是Object类型
image。png

instanceof

instanceof:检测某个实例是否属于这个类

console.log(result instanceof Fn); //->true
 

区分字面量和构造函数创建的值

创建值有两种办法:

  • 字面量方案
  • 构造函数方案
 let obj1 = {};
 let obj2 = new Object();
 

对于对象来说两种方案没有区别,但对于原始值来说:

 let n1 = 10; //Number类的一个实例「原始值」
 let n2 = new Number(10); //Number类的一个实例「对象」
 

一个是原始值类型,一个是对象类型
image。png

console.log(n2.toFixed(2)); //->10.00
console.log(n1.toFixed(2)); //->10.00 
 

原始值类型没有属性和方法,那么是如何做成员访问的?

js在内部有自己的处理机制:装箱和拆箱

装箱:

10->Object(10) 将原始值类型转化为「对象实例」,然后再做成员访问

image。png

拆箱:

 console.log(n1 + 10); //->20
 console.log(n2 + 10); //->20 
 

n2对象会依次调用Symbol.toPrimitive/valueOf()n2valueOf()的返回原始值10,这个过程就是"拆箱"

instanceof不能识别原始值

注意:instanceof它的局限性:不能识别原始值

 console.log(n2 instanceof Number); //->true
 console.log(n1 instanceof Number); //->false  
 

注意:

每个实例对象都是独立的内存地址:

let result1 = new Fn(10, 20);
let result2 = new Fn(30, 40);
console.log(result1 === result2); //->false
console.log(result1.say === result2.say); //->false
 

hasOwnProperty in obj

  • Object为每一个对象提供hasOwnProperty方法,obj.hasOwnProperty()检测attr是否是obj对象的私有属性
  • in obj:检测attr是否为obj的一个属性「不论私有还是公有
console.log(result1.hasOwnProperty('say')); //->true
console.log(result1.hasOwnProperty('hasOwnProperty')); //->false  result1可以调用hasOwnProperty,说明hasOwnProperty是result1的一个成员「属性」,共有
console.log('say' in result1); //->true
console.log('hasOwnProperty' in result1); //->true 
 

面试题:自己编写一个方法 hasPubProperty 检测某个属性是否为对象的公有属性

function hasPubProperty(obj, attr) {
    // 思路:基于in检测结果是TRUE「是它的一个属性」 & 基于hasOwnProperty检测结果是false「不是私有的」,那么一定是公有的属性
    return (attr in obj) && !obj.hasOwnProperty(attr);
}
console.log(hasPubProperty(result1, 'hasOwnProperty')); //->true
console.log(hasPubProperty(result1, 'say')); //->false 
 

这个方法的弊端:只能是这种情况“某个属性不是私有的而是公有的”,但是如果这个属性 私有中也有且公有中也有 ,基于这个方法结果是false,但是这个属性确实是他的公有属性

for in 循环

for in循环可以用来迭代对象

let arr = [10, 20, 30];

for (let i = 0; i < arr.length; i++) {
    console.log(arr[i], i);
}
for (let key in arr) {
    console.log(arr[key], key);//注意key为字符串形式
}
 

for循环本质不是遍历数组,是自己控制一个数字索引的循环逻辑,例如i=0 i<3 i++,自己控制循环数字三次, 每一轮循环i的值,恰好是我们想获取当前数组中这一项的索引「i从零开始,数组索引也是从零开始」,所以再基于成员访问获取即可。即for是循环索引,然后按照索引拿值

for in本质是迭代对象,按照本身的结构(键值对)去一一迭代的

所以for循环比for in循环性能稍微好一点

for in内置的缺陷

  • 不能迭代Symbol属性
  • 迭代的时候不一定按照自己编写的键值对顺序迭代「优先迭代数字属性{小->大},再去迭代非数字属性{按自己编写顺序}」
  • 不仅会迭代对象的私有属性,对于一些自己扩展的公有属性也会迭代到「迭代可枚举的{一般自己设定的都是可枚举的,内置的是不可枚举的}」
Object.prototype.sum = function sum() {};
let obj = {
    name: 'xxx',
    age: 12,
    0: 100,
    1: 200,
    teacher: 'mtt',
    [Symbol('AA')]: 300
};
for (let key in obj) {
    console.log(key); //'0' '1' 'name' 'age' 'teacher' 'sum'
}
 

上面的顺序是数字属性,非数字属性,公有属性

如果不想迭代公有的,即使内部机制找到了,我们也不让其做任何的处理,这样做即可:

for (let key in obj) {
    // 先找所有私有,一但发现这个是公有的,说明私有的都找完了,停止循环「注意不含Symbol」
    if (!obj.hasOwnProperty(key)) break;
    console.log(key);
} 
 

Object.getOwnPropertySymbols(obj)

Object.getOwnPropertySymbols(obj)获取对象所有私有的Symbol属性「数组」

Object.keys(obj)Object.getOwnPropertyNames(obj)

Object.keys(obj)Object.getOwnPropertyNames(obj)获取对象所有非Symbol的私有属性「数组」

两个合在一起就能拿到所有的私有属性
image。png

题目:写一个函数,遍历所有的私有成员,包括symbol属性的成员:

function each(obj, callback) {
    let keys = Object.keys(obj),
        key = null,
        value = null,
        i = 0,
        len = 0;
    if (typeof Symbol !== "undefined") {
        // 支持Symbol
        keys = keys.concat(Object.getOwnPropertySymbols(obj));
    }
    len = keys.length;
    if (typeof callback !== "function") callback = function () {};
    for (; i < len; i++) {
        key = keys[i];
        value = obj[key];
        callback(value, key);
    }
}
each(obj, (value, key) => {
    console.log(value, key);
});
 

image。png

注意,对象属性的类型只可能是String或者Symbol

obj[1]等同于obj['1']

image.png

每一个Symbol类型的值都是独一无二的

image.png

会把属性自动转化成String类型
image。png

回复

我来回复
  • 暂无回复内容