面向对象——数据处理

前言

面向对象相信对你们来讲确定不陌生,基本上现代web开发不会这玩意以及无法写东西了html

可是呢,用归用,相信你们日常工做中须要写原生对象的地方并无多少,本篇文章来对面向对象进行一个总结和梳理vue

概念

我们先来讲一下概念,由于不一样人叫面向对象名字也不一样,这里属性变量意思都是同样的,我们不去纠结他们具体叫什么java

对象组成能够分为两大类:node

  1. 属性、变量、状态、数据
  2. 过程、方法、函数

  • 类(class): 类自己,通常没什么功能,除非是Math这种基本全是静态方法的类
  • 实例(instance):类被实例化以后的东西,就叫实例
  • 成员(meber):包括(属性+方法)
    • 实例成员(实例上才能用的属性和方法,相似str.length不能直接写成String.length
    • 类成员(相似Math.xxx),无需实例化就能够用

打个比方let oDate = new Date() 这个Date就是类,而这个oDate 就是实例react

而绝大部分人再说对象的时候都是在说实例ios


抽象

抽象这个词相信你们已经快听吐了,可是又不是很好理解,这里大概说一下es6

  1. 提取过程——设计类、设计程序
    • 这个其实很好理解,在写一个程序的时候,好比咱们要作一个聊天的功能,整理需求,而后定义好本身须要的类,好比这个用户须要有名字,和头像等等这个过程,就叫抽象
  2. 抽象类
    • 这个可能稍微复杂一点,有那么一种类,是不实现任何功能的
    • 可能有人看到这就有点不理解,为啥我要写一个不实现任何功能的类呢,这里为了方便用react 举个例子
    • 我们都知道react中写组件须要class App extnds React.Component... 而这里面必需要有一个render方法,若是没有,它就会给你报错
    • 固然了,这里主要为了说明例子,不包括hook什么的,别杠我,我惧怕
    • 而抽象类能够实现这个功能,给全部的类加一个公共的基类(也就是父类)统一处理
    • react中的这个Component就是实现了一个抽象类

面向对象思想

这个词儿也是被用烂了,你们无论是从别的文章看过也好,仍是找过什么资料,看了一大堆,仍是不明白啥叫面向对象思想web

封装

封装的最基本的目的是——为了保护我们或者实例当中的各类成员面试

事实上来讲我们无论写什么面向对象也好,面向过程也好,面向切片等等等等,最终的目的就是为了让这个程序出错少,效率高,易于调试和拓展数据库

而接下来我们就能想到,程序都是人写的,人确定就会犯错,容易偷懒或者侥幸心理

而我们封装的类,每每会有不少的数据须要内部处理,好比有一个请求队列的列表,你在你内部声明好了,而后若是想取消须要用户直接调你的方法,可是若是不保护的化,可能有人犯懒直接就看到这是个数组直接就操做了,这时候若是出错了或者这个类有其余的地方没作相应的操做就很难查找

因此这个封装大概有四重目的

  1. 保护成员
  2. 数据隐藏——方法
  3. 强制访问权限
  4. 便于理解

继承

任何一个类,能够不用从零开始写,能够在一个原有的类的基础之上作一些修改等等这就叫继承

  1. 重用代码
  2. 无需修改父类
  3. 能够抽象类

这个继承相信你们仍是没问题的,这里很少赘述

多态

多态也属于一种抽象,也是一种比较经常使用的思想,好比如今公司有一个转帐的功能,支持多个国家互相转的,那这个钱不同汇率也就不同,以及其余的一些问题

固然这时候直接用if判断确定也是能够的,可是这不是感受不够装* 么,这时候就能够用多态,把这个钱统一处理,至于具体汇率什么的细节,分别交给他们本身来处理,这个思想,就是多态

总结

相信看到这你们应该已经明白大概的意思了,不过对于初学者来讲仍是不太明白到底具体到项目能怎么写,别慌,一步一步来

有一点须要确认,你们可千万别觉得面向对象是语言所独有的东西,好比java是面向对象的,c是面向过程的,其实也不是,面向对象思想几乎在全部的地方都有用

好比说数据库,也有面向对象数据库——ORM,它里面存的就不是像excel那样的数据了,而是一个对象,有相应匹配的操做等等一系列的

写法

首先我们要知道,任何一种类,都须要构造函数,什么叫构造函数呢,很简单,就是当你这个类实例化的时候,须要作一些初始化的工做

es6以前

es6以前,类和构造函数是不分的,这也是很很差的一点,如今若是想实现一个类,直接拽一个function 在这很难分辨就是是一个函数仍是,因此,这个函数既是构造函数也是类

我们来直接写一个看看

function A() {
  console.log('hello');
}

var a = new A();
复制代码

能够看到,在我们实例化的过程中,就会运行函数内的代码

以及,我们若是相加属性和方法也是很简单的

属性和es6版本同样,直接加this就行

方法放在prototype上,这个没什么可说的

继承

es6以前没给我们提供直接继承的方法,因此得我们手动操做,因此我们得明白继承是干什么 继承就是把父类的方法+属性拿过来就能够了

首先,我们先来随便写一个类出来,像这样

而后搞一个子类,而且新增一个参数,而后我们把参数先拿过来,怎么拿呢? 如今我们的类是一个函数,是函数就能执行,我们只须要把A拿过来执行一下而且call到本身身上,再把A须要的参数传过去,这样就能拿到全部的参数了,像这样

好的,属性我们如今已经能够拿过来了,那么方法怎么拿过来了,方法在哪,在Aprototype上,那能直接B.prototype = A.prototype么?首先确定一点,这么写,东西确定能出来,不过问题你们也知道,js里存在引用,给子类添加方法父类也被修改了这确定不行

不啰嗦了

我们能够直接B.prototype = new A()

我们能够看到,东西是能出来的,父类的方法也没有被污染到,这是为何呢,这里就不得不搞出来原型链这个概念了

有大白话讲就是 找一个实例要东西,它会先从本身实例身上找,找不到的话再找本身的prototype,可是我们如今这个prototype指向的就是A的实例,因此从B实例找不到后去找prototype的时候,找不到就回去找A的实例,A的实例找不到就回去找Aprototype 而后就能够了

好玩不

相信看到这,你们应该已经明白es6以前的写法缺陷了,功能确定是都能实现,可是太乱了,一个团队好几十人,你搞你的,我搞个人就乱套了

es6

我们es6class就简单多了,直接提供了一个关键字class,直接写就好了,不过本质上来讲只是语法糖而已,因此建议你们仍是看看es6以前的写法

写法也是很简单的,注意一点,这里面的方法并非函数的简写,写一个function的话,反而会报错

继承

继承es6也有专门的关键字来讲明,就叫extends

很简单对不对?

注意⚠️ 在子类什么都不写的状况下,默认会给你加一个constructor,若是你本身写了constructor,就至关于你须要本身来了

主要要干的就是须要把父类的属性拿过来,es6有一个super关键字,就至关于直接把父类constructor直接在子类里执行了一遍,不用像以前同样要么apply,要么call的,很乱

注意⚠️ 在我们构建子类的时候,须要完成父类的构建,也就是那个super,若是先用thissuper的时候会报错


this

this相信你们确定常常用,而且一会变成这个,一会变成那个,特别的乱 this取决于谁在调用,this就是谁 而this是用于函数/方法内部,而谁在调用这个函数,this就指向谁

js自己又是全局的东西都属于window,因此我们这么写

function fn() {
    console.log(this);
}

fn();
复制代码

就彻底等价于这么写

function fn() {
    console.log(this);
}

window.fn();
复制代码

因此console出来的this也是window

我们可能看过不少面试题都有相似这样的题目

总的来讲就是一个函数给这个对象那个对象的,而后打印this.xxx,其实想明白这件事,一切都会变得很简单了

如今这个arr.fn = fn了,而调用的时候,是arr在调用,因此这个this就是arr

固然了,事件也是同样的

很简单对不对?

顺便一说,js的this这么乱是由于做者自己想让它变得更简单,谁调就是谁,多好呀~ 不过这每每在写一些大项目的时候会有一堆问题

严格模式

js做者后来又出了一个严格模式,由于全局的东西都属于window这事儿原本也不靠谱,毕竟js运行场景已经不少了,好比nodejs,就没有window的概念

而用严格模式也很简单,直接在script里加一个"use strict" 就能够,像这样

这时候console出来的就是undefined

定时器

不过这个this还受一点影响,也就是定时器

能够看到我如今是开着严格模式的状况下,可是console的仍是window

不过这却是也好解释,毕竟这个定时器window的,是浏览器调的,也是经过window间接的来执行到了这个函数


操做this

js中有两种操做this的方法

  1. 函数的方法

    • 我们都知道直接写一个函数也就至关于new了一个Function类,因此这么写
    function fn() {
        alert(this);
    }
    复制代码

    彻底等价于

    var fn = new Function('alert(this)');
    复制代码
    • 因此函数也是个对象,既然是对象,那么也就有方法
    1. call

      • 我们正常运行函数多是直接fn();,用了这个直接在括号前加一个call就能够,像这样
      • 这个你传什么,它的this就是什么,随便传,想这个例子,consolethis就是"aaa"
      • 至于参数,直接日后堆就能够了
    2. apply

      • 这个apply就很简单了,和call基本一摸同样,区别在于call跟参数是直接堆在后面,apply的其余参数是放在一个数组里的,像这样
    3. bind

      • 这个bind跟前二者不同,callapply都是直接运行了,bind的做用是返回一个新函数
      • 能够看到,无论生成的新函数在哪运行,谁调用this都是不变的
  2. 箭头函数

    • 箭头函数内部的this永远跟箭头函数外部的this一致,也就是上下文
    • 正常状况下这个document.onclick里的函数的this指向HTMLDocument,因此找不到aaa,会consoleundefined,这个很好理解
    • 改为箭头函数以后,这回就对了,由于它的上下文环境在class A里,这就没问题了
    • 固然了,用bind也是能够的,这不是方便么

类型检测

typeof

typeof更适合检测基本类型:number、boolean、string、function、Object、undefined

这个相信你们也都用过,也就很少说了,检测基本类型表明我们分不清一个对象究竟是数组仍是json或者是map等等 全部对象全都是object,那这个确定知足不了需求

instanceof

这个instanceof是能够检测具体类型的,并且不光能够检测子类,对子类的父类也能检测到

其实这不能算是问题,由于正常来讲子类原本就>=父类

constructor

可是有时候咱们就是不想对父类也有反应,就是想检测是否是属于我这个类,这是有直接用constructor来判断就很方便 constructor是能够用于精确匹配的

这个constructor极少的状况会用到,自己并非用来判断类型的,而是返回实例的构造器,不过正好我们能够间接作一个类型判断而已 正常状况用typeofinstanceof就已经足够了

固然了,可能有人看到es6以前的class写法已经要喷我了,由于那么写用constructor判断又有问题,由于那么写子类constructor就会变成父类了,因此还须要重置一下

等等等等把,es6以前的写法等等须要处理细节方面还不少,由于如今实在是不经常使用,因此本文就很少赘述了


高阶类-HOC

高阶类这个词可能有人没听过,这里简单描述一下
通常状况下,都是子类继承父类,而后子类可使用父类当中的东西,而这个高阶类,能够反过来,父类使用子类的东西

顺便一说,不是只有js才有高阶类的概念,几乎全部语言都有,只是方不方便的问题

我们直接来写一个

能够看到,直接在class A 上,并无bbb这个东西,而直接子类去继承的时候,子类身上有,间接着就能够调用了

这就是所谓的高阶类,贼简单吧

不过直接这么说你们可能感受不到有什么应用场景,其实也不太经常使用,在写一些工具或者框架的时候可能会用到,这里简单写个小例子,多个类之间如何用高阶类共享数据 固然了,高阶类的用途有不少,这只是其中一种而已

class Store {
    constructor() {
      this.state = {};
    }
    get(key) {
      return this.state[key];
    }
    set(key, value) {
      this.state[key] = value;
    }
    conect(cls) {
      let _this = this;
      return new (class extends cls {
        constructor(...args) {
          super(args);
          this.get = _this.get.bind(_this);
          this.set = _this.set.bind(_this);
        }
      })();
    }
  }

  let store = new Store();

  let a = store.conect(class {});

  a.set('aaa', '我是aaa');

  let b = store.conect(class {});

  console.log(b.get('aaa'));
复制代码

这个例子很简单,主要就是要分清谁是父类,我们把数据全存到store类里,而后接受一个类参数,内部继承一个新的类,而后添加出新的方法,而在使用者来讲,也就是a类和b类,这些都不用管,他们本身就是父类,来调用子类的方法

是否是很简单?

大概就是这种感受,至于工做当中怎么用,就看你们本身的业务场景了

可相应对象

所谓的可相应对象就是你操做这个对象的时候能够收到通知,好比vue,你们应该都知道vue页面,就算直接在浏览器f12控制台中,直接vm.xxx=xxx对某一项数据修改,页面中就会发生变化,这就是可相应对象

访问器

这个访问器就是在原型的方法前加get或者set,至于内部要干什么,你本身决定,像这样

用过ts 版的vue2.x的你们都知道,computed 就是用这玩意的原生语法

访问器仍是比较简单的,在一些小的场景中用的比较多

defineProperty

defineProperty接受三个参数(data,key, option)

这个data就是你的数据源,这个key就是你要监听哪一个属性,剩下的具体的操做放在option里,好比getset等等,我们直接来试试

var json = {
    _num: 0,
};
Object.defineProperty(json, 'num', {
get() {
  console.log('get');
  return this._num;
},
set(val) {
  console.log('set');
  this._num = val;
  return this._num;
},
});

console.log(json.num);
json.num++;
console.log(json.num);
复制代码

是否是很简单,固然了,细节上还有不少,好比delete,我们知道正常状况下js中的json是能够直接delete某个key的,可是我们如今直接这么写是不行的

我们能够给一个参数configurable: true,默认的状况是false

更多参数介绍你们能够看MDN文档中的defineProperty,这里就很少赘述

我们这里用的只是json,你们可能会感受跟访问器区别不大,并且,平时我们也不是直接对json用,而是像vue 2.x同样又能操做实例,又能this直接访问或修改属性

类中的可相应对象

我们都知道 vue2.x中能够这么修改属性

知道了这点以后我们就能够干不少事儿了,我们能够直接监听这个来进行return

这样我们就能够监听到变化了,能监听到变化了天然就能够紧接着干其余的事了,好比从新渲染页面等等

固然了,真正Vue中可彻底不是这么写的,由于它东西不少,并且全部数据都须要监听,我们如今只是单单监听一个对象里的key,方法我们也知道,循环+递归么,不过要把这些东西整理出来其实也是很复杂的,好比页面属性怎么跟你的data绑定,组件间的传值,渲染、虚拟dom等等

等等吧,若是真拓展成一个方便的框架或者小工具仍是有不少工做的, 这里只是说明方法,有什么更方便的用法和场景还须要你们本身钻研

defineProperty缺陷

用过vue的你们都知道vue中是不能直接用下标修改数组中的某一条数据的,vue做者也推出了$set解决这个问题,这个就是defineProperty的问题

  1. 对数组下标直接赋值
  2. json对象没有的key赋值

你们能够看到其实还好,虽然问题不大,不过没有确定比有强,proxy没这个问题,为啥不直接用proxy

proxy

defineProperty有点不一样,defineProperty是操做监听的原始对象,而proxy是操做返回出来的新对象

有两个参数,一个是数据,一个是对应的操做,这里面有几个经常使用的参数

  • has 这个对应的就是 js 中的in操做
    • 我们知道js里能够直接"a" in {a: 12}这么判断
    • 而这个就会触发has,像这样
    • 这个data就是原始数据,基本上每一个方法里都会把原始数据给你,很方便
  • get 获取,这个相信不用多说,都差很少
  • set 设置
  • deleteProperty
    • 这个其实就是delete,只不过delete是关键字,因此取了个这个名字
  • apply 这个apply其实颇有用,它能够监听一个函数,在编写一个axios这样的库 我也用到了,有兴趣你们能够看一下
    • apply有三个参数,第一个仍是data,可是因为我们是监听函数,因此就是那个函数自己,第二个就是谁在调用的那个this,上文讲过谁调用函数this就指向谁,因此这个第二个参数就是那个this,第三个参数就是我们运行这个函数的时候传进来的参数
  • construct 监听类
    • 它有两个参数,一个是监听的那个class,一个是参数,其实用起来感受跟高阶类有点类似
    • 不过通常用起来,我们还须要再配合一个proxy,由于要监听我们通常都是要监听这个类上的属性有没有改变之类的,因此还要单独再监听一遍实例返回出去

剩下的参数你们感兴趣的话也能够去MDN中的Proxy去了解,也很少赘述

完整的监听例子

看到这你们应该都对js中的可监听对象了解的差很少了,这里附上一个相对完成一点的监听例子

相似vue中的datadata能够是值,也能够是json,也能够是数组,数组里套jsonjson套数组等等,我们能够分别判断一下而后套一个递归就搞定了

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <script> function createProxy(data, cb) { let res; if (data instanceof Array) { res = []; for (let i = 0; i < data.length; i++) { if (typeof data[i] == 'object') { res[i] = createProxy(data[i], cb); } else { res[i] = data[i]; } } } else { res = {}; for (let key in data) { if (typeof data[key] == 'object') { res[key] = createProxy(data[key], cb); } else { res[key] = data[key]; } } } return new Proxy(res, { get(data, name) { return data[name]; }, set(data, name, val) { data[name] = val; cb(name); return true; }, }); } let _json = { arr: [ { a: [1, 2, 3], }, 321, ], json: { aaa: { bbb: { ccc: 111, }, }, }, name: 'name', }; var p = createProxy(_json, function(name) { console.log('set'); }); </script>
  </body>
</html>

复制代码

篇幅较长

感谢观看

若是有问题的话能够直接在评论区留言或者加我微信一块儿沟通

相关文章
相关标签/搜索