面向对象相信对你们来讲确定不陌生,基本上现代web
开发不会这玩意以及无法写东西了html
可是呢,用归用,相信你们日常工做中须要写原生对象的地方并无多少,本篇文章来对面向对象
进行一个总结和梳理vue
我们先来讲一下概念
,由于不一样人叫面向对象名字也不一样,这里属性
和变量
意思都是同样的,我们不去纠结他们具体叫什么java
对象组成能够分为两大类:node
Math
这种基本全是静态方法的类str.length
不能直接写成String.length
)Math.xxx
),无需实例化就能够用打个比方let oDate = new Date()
这个Date
就是类,而这个oDate
就是实例react
而绝大部分人再说对象的时候都是在说
实例
ios
抽象
这个词相信你们已经快听吐了,可是又不是很好理解,这里大概说一下es6
抽象
react
举个例子react
中写组件须要class App extnds React.Component...
而这里面必需要有一个render
方法,若是没有,它就会给你报错hook
什么的,别杠我,我惧怕react
中的这个Component
就是实现了一个抽象类
这个词儿也是被用烂了,你们无论是从别的文章看过也好,仍是找过什么资料,看了一大堆,仍是不明白啥叫面向对象思想
web
封装的最基本的目的是——为了保护我们类
或者实例
当中的各类成员
面试
事实上来讲我们无论写什么
面向对象
也好,面向过程
也好,面向切片
等等等等,最终的目的就是为了让这个程序出错少,效率高,易于调试和拓展数据库
而接下来我们就能想到,程序都是人写的,人确定就会犯错,容易偷懒或者侥幸心理
而我们封装的类,每每会有不少的数据须要内部处理,好比有一个请求队列的列表,你在你内部声明好了,而后若是想取消须要用户直接调你的方法,可是若是不保护的化,可能有人犯懒直接就看到这是个数组直接就操做了,这时候若是出错了或者这个类有其余的地方没作相应的操做就很难查找
因此这个封装大概有四重目的
任何一个类,能够不用从零开始写,能够在一个原有的类的基础之上作一些修改等等这就叫继承
这个继承相信你们仍是没问题的,这里很少赘述
多态也属于一种抽象,也是一种比较经常使用的思想,好比如今公司有一个转帐的功能,支持多个国家互相转的,那这个钱不同汇率也就不同,以及其余的一些问题
固然这时候直接用if
判断确定也是能够的,可是这不是感受不够装*
么,这时候就能够用多态,把这个钱统一处理,至于具体汇率什么的细节,分别交给他们本身来处理,这个思想,就是多态
相信看到这你们应该已经明白大概的意思了,不过对于初学者来讲仍是不太明白到底具体到项目能怎么写,别慌,一步一步来
有一点须要确认,你们可千万别觉得
面向对象是语言所独有的东西
,好比java
是面向对象的,c
是面向过程的,其实也不是,面向对象思想几乎在全部的地方都有用
好比说数据库,也有面向对象数据库——ORM
,它里面存的就不是像excel
那样的数据了,而是一个对象,有相应匹配的操做等等一系列的
首先我们要知道,任何一种类,都须要构造函数
,什么叫构造函数
呢,很简单,就是当你这个类实例化的时候,须要作一些初始化
的工做
在es6
以前,类和构造函数是不分的,这也是很很差的一点,如今若是想实现一个类,直接拽一个function
在这很难分辨就是是一个函数
仍是类
,因此,这个函数既是构造函数也是类
我们来直接写一个看看
function A() { console.log('hello'); } var a = new A(); 复制代码
能够看到,在我们实例化的过程中,就会运行函数内的代码
以及,我们若是相加属性和方法也是很简单的
属性和es6
版本同样,直接加this
就行
方法放在prototype
上,这个没什么可说的
es6
以前没给我们提供直接继承的方法,因此得我们手动操做,因此我们得明白继承是干什么 继承就是把父类的方法+属性拿过来就能够了
首先,我们先来随便写一个类出来,像这样
而后搞一个子类,而且新增一个参数,而后我们把参数先拿过来,怎么拿呢? 如今我们的类是一个函数,是函数就能执行,我们只须要把A
拿过来执行一下而且call
到本身身上,再把A
须要的参数传过去,这样就能拿到全部的参数了,像这样
好的,属性我们如今已经能够拿过来了,那么方法怎么拿过来了,方法在哪,在A
的prototype
上,那能直接B.prototype = A.prototype
么?首先确定一点,这么写,东西确定能出来,不过问题你们也知道,js
里存在引用
,给子类添加方法父类也被修改了这确定不行
不啰嗦了
我们能够直接B.prototype = new A()
我们能够看到,东西是能出来的,父类的方法也没有被污染到,这是为何呢,这里就不得不搞出来原型链这个概念了
有大白话讲就是 找一个实例要东西,它会先从本身实例身上找,找不到的话再找本身的prototype
,可是我们如今这个prototype
指向的就是A
的实例,因此从B
实例找不到后去找prototype
的时候,找不到就回去找A
的实例,A
的实例找不到就回去找A
的prototype
而后就能够了
好玩不
相信看到这,你们应该已经明白es6
以前的写法缺陷了,功能确定是都能实现,可是太乱了,一个团队好几十人,你搞你的,我搞个人就乱套了
我们es6
写class
就简单多了,直接提供了一个关键字class
,直接写就好了,不过本质上来讲只是语法糖而已,因此建议你们仍是看看es6
以前的写法
写法也是很简单的,注意一点,这里面的方法并非函数的简写,写一个function
的话,反而会报错
继承es6
也有专门的关键字来讲明,就叫extends
很简单对不对?
注意⚠️ 在子类什么都不写的状况下,默认会给你加一个
constructor
,若是你本身写了constructor
,就至关于你须要本身来了
主要要干的就是须要把父类
的属性拿过来,es6
有一个super
关键字,就至关于直接把父类
的constructor
直接在子类里执行了一遍,不用像以前同样要么apply
,要么call
的,很乱
注意⚠️ 在我们构建子类的时候,须要完成父类的构建,也就是那个
super
,若是先用this
后super
的时候会报错
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
间接的来执行到了这个函数
js
中有两种操做this
的方法
函数的方法
new
了一个Function
类,因此这么写function fn() { alert(this); } 复制代码
彻底等价于
var fn = new Function('alert(this)'); 复制代码
call
fn();
,用了这个直接在括号前加一个call
就能够,像这样
this
就是什么,随便传,想这个例子,console
的this
就是"aaa"
apply
apply
就很简单了,和call
基本一摸同样,区别在于call
跟参数是直接堆在后面,apply
的其余参数是放在一个数组里的,像这样
bind
bind
跟前二者不同,call
和apply
都是直接运行了,bind
的做用是返回一个新函数
this
都是不变的箭头函数
this
永远跟箭头函数外部的this
一致,也就是上下文
document.onclick
里的函数的this
指向HTMLDocument
,因此找不到aaa
,会console
出undefined
,这个很好理解
class A
里,这就没问题了bind
也是能够的,这不是方便么typeof更适合检测基本类型:number、boolean、string、function、Object、undefined
这个相信你们也都用过,也就很少说了,检测基本类型表明我们分不清一个对象究竟是数组仍是json
或者是map
等等 全部对象全都是object
,那这个确定知足不了需求
这个instanceof
是能够检测具体类型的,并且不光能够检测子类,对子类的父类也能检测到
其实这不能算是问题,由于正常来讲子类原本就>=
父类
可是有时候咱们就是不想对父类也有反应,就是想检测是否是属于我这个类,这是有直接用constructor
来判断就很方便 constructor
是能够用于精确匹配的
这个constructor
极少的状况会用到,自己并非用来判断类型的,而是返回实例的构造器
,不过正好我们能够间接作一个类型判断而已 正常状况用typeof
和instanceof
就已经足够了
固然了,可能有人看到es6
以前的class
写法已经要喷我了,由于那么写用constructor
判断又有问题,由于那么写子类
的constructor
就会变成父类
了,因此还须要重置一下
等等等等把,es6
以前的写法等等须要处理细节方面还不少,由于如今实在是不经常使用,因此本文就很少赘述了
高阶类这个词可能有人没听过,这里简单描述一下
通常状况下,都是子类继承父类,而后子类可使用父类当中的东西,而这个高阶类,能够反过来,父类使用子类的东西
顺便一说,不是只有
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
接受三个参数(data,key, option)
这个data
就是你的数据源,这个key
就是你要监听哪一个属性,剩下的具体的操做放在option
里,好比get
和set
等等,我们直接来试试
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等等
等等吧,若是真拓展成一个方便的框架或者小工具仍是有不少工做的, 这里只是说明方法,有什么更方便的用法和场景还须要你们本身钻研
用过vue
的你们都知道vue
中是不能直接用下标修改数组中的某一条数据的,vue
做者也推出了$set
解决这个问题,这个就是defineProperty
的问题
json
对象没有的key
赋值你们能够看到其实还好,虽然问题不大,不过没有确定比有强,proxy
没这个问题,为啥不直接用proxy
呢
和defineProperty
有点不一样,defineProperty
是操做监听的原始对象,而proxy
是操做返回出来的新对象
有两个参数,一个是数据,一个是对应的操做,这里面有几个经常使用的参数
js
中的in
操做
"a" in {a: 12}
这么判断data
就是原始数据,基本上每一个方法里都会把原始数据给你,很方便delete
,只不过delete
是关键字,因此取了个这个名字
apply
其实颇有用,它能够监听一个函数,在编写一个axios这样的库 我也用到了,有兴趣你们能够看一下
apply
有三个参数,第一个仍是data
,可是因为我们是监听函数,因此就是那个函数自己,第二个就是谁在调用的那个this
,上文讲过谁调用函数this
就指向谁,因此这个第二个参数就是那个this
,第三个参数就是我们运行这个函数的时候传进来的参数
class
,一个是参数,其实用起来感受跟高阶类有点类似proxy
,由于要监听我们通常都是要监听这个类上的属性有没有改变之类的,因此还要单独再监听一遍实例返回出去剩下的参数你们感兴趣的话也能够去MDN中的Proxy去了解,也很少赘述
看到这你们应该都对js
中的可监听对象了解的差很少了,这里附上一个相对完成一点的监听例子
相似vue
中的data
,data
能够是值,也能够是json
,也能够是数组,数组里套json
,json
套数组等等,我们能够分别判断一下而后套一个递归就搞定了
<!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> 复制代码
若是有问题的话能够直接在评论区留言或者加我微信一块儿沟通