面向对象是一种“编程思想 [OOP:Object-Oriented Programming]”,在此思想中,最主要的就是:对象、类、实例
基本概念
对象:一种泛指,JS中所有的内容都是我们要学习、研究和使用的对象
类:把所有的内容,按照功能特征进行划分,分为一些大类(父类)和小类(子类)
实例:类中具体的实物,其具备一些自己私有的属性和方法,也拥有类赋予其的公有的属性和方法
类(构造函数:所有的类本身都是函数数据类型)
内置类
- 数据类型内置类:Number、String、Boolean、Symbol、BigInt、Object、Array、RegExp、Date、Error、Function…(每一种数据类型值,都是自己所属类的实例)
DOM相关的内置类
- 元素集合类:HTMLCollection
- 节点集合类:NodeList
- 每一个元素标签(或元素对象)都有自己所属的类:HTMLBodyElement、HTMLDivElement、HTMLParagraphElement、HTMLAnchorElement…
详细的大类分小类的链条HTMLXxxElement—>HTMLElement—>Element—>Node—>EventTarget—>Object
IntersectionObserver
- Promise
- …
- 自定义类
- 创建一个函数[约定的规范:函数名遵循PascalCase规范]
- 基于new来执行这个函数
- 此时这个函数就变成了”构造函数(类)”
- 返回值一般都是这个类的实例
:::info
前端的命名方式:
1. kebab-case 例如:class=”index-box”
2. camelCase 例如:let indexBox = null 小驼峰命名法
3. PascalCase 例如:function IndexBox(){} 帕斯卡尔命名「大驼峰」
- 创建实例的方式
- 字面量方式[含基于特定API创建]
- 基于new创建[构造函数方式]
对于对象数据类型来讲,即便基于不同方式,创造出来的结果都一样:都是某个类的实例、都是对象数据类型的值let arr1=[10,20,30]let arr2=Array.of(10,20,30)let arr3=new Array(10,20,30)console.log(arr1,arr2,arr3)
但是对于原始类型来讲,基于字面量方式创造的是原始值,不是所属类的实例;基于构造函数方式(或者基于Object([value])这种方式)创造出来的是“非标准特殊对象”,这些才是所属类的实例let num1=10let num2=new Number(10)-->非标准特殊对象let num3=Object(10)-->非标准特殊对象console.log(num1,num2,num3)console.log(num2.toFixed(2))//'10.00'console.log(num1.toFixed(2))//'10.00' 浏览器默认会进行“装箱”:把原始值转为对象类型的值(基于Object([value])方法处理的)console.log(num1+10)//20console.log(num2+10)//20 浏览器默认会进行“拆箱”:把对象类型的值转为原始值
new执行和普通函数执行的区别
function Fn(x, y) {let sum = 10this.total = x + ythis.say = function () {console.log(`我计算的和是:${this.total}`)}}let res = Fn(10, 20)//作为普通函数执行let f1 = new Fn(10, 20)let f2 = new Fnconsole.log(f1.sum)console.log(f1.total)console.log(f1.say === f2.say)

function Foo() {getName = function () {console.log(1)}return this}Foo.getName = function () {console.log(2)}Foo.prototype.getName = function () {console.log(3)}var getName = function () {console.log(4)}function getName() {console.log(5)}Foo.getName()getName()Foo().getName()getName()new Foo.getName()new Foo().getName()new new Foo().getName()
函数具备的角色
- 普通对象「操作的是函数堆中,存储的静态私有属性方法」
- Foo.xxx=xxx
- console.log(Foo.xxx)
- 函数
- 普通函数「闭包作用域」
- 构造函数「即拥有闭包作用域、还具备实例、proto、prototype」
重写new方法
![PLMCSP5[J_YQ`V8YC%BTFB.jpgconst _new = function _new(Ctor, ...params) {//Ctor:我们需要操作的构造函数[就是创造它的一个实例]-->Dog//params:数组,存储给Ctor传递的实参信息-->['三毛']//4.对Ctor要做校验[不能是null/undefined、不能是Symbol/BigInt、必须具备prototype]if(Ctor==null||Ctor===Symbol||Ctor===BigInt||!Ctor.prototype)throw new TypeError('Ctor is not a constructor')//1.创建一个空的实例对象(空对象、__proto__指向类的prototype)let instance = Object.create(Ctor.prototype)//2.在构造函数作为普通函数执行的时候,让函数中的this指向创建的实例对象let result = Ctor.call(instance, ...params)//3.监测函数执行的返回值[返回的是对象,则以函数自己返回的为主,否则返回创建的实例对象]if(result!==null&&/^(object|function)$/.test(typeof result))return resultreturn instance}
this情况梳理
函数的执行主体(通俗来讲,就是谁把这个函数执行的),不是函数执行所处的上下文;我们都是去研究函数中的this,全局上下文中的this是window「前提:在浏览器环境下运行」;块级私有上下文中没有自己的this,用到的this都还是宿主环境中的
关于函数中的this到底是谁,有以下五条规律:
给元素进行事件绑定「DOM0/DOM2」
当事件触发,绑定的方法执行,方法中的this是当前被操作的元素「给谁绑定的,this就是谁」
普通函数执行,看函数前面是否有“点”
- 有:“点”前面是谁,函数中的this就是谁
- 例如:
- arr.push(100) -> this:arr
- arr.proto.push(100) -> this:arr.proto
- Array.prototype.push(100) -> this:Array.prototype
- const push=Array.prototype.push
- push(100) -> this:undefined/window
- …
- 没有:函数中的this是window(非严格模式)或者undefined(严格模式)
- 像 自执行函数 或者 回调函数 等匿名函数,如果没有经过特殊的处理,那么函数中的this,一般都是window/undefined
- 构造函数执行(NEW执行),函数体中的this指向创建的实例对象
- 我们可以基于 call/apply/bind 强制改变函数中this的指向
- call/apply:把函数立即执行,让函数中的this指向自己指定的值
- 函数.call(THIS,实参1,实参2,…)
- 函数.apply(THIS.[实参1,实参2,…])
- bind:bind语法和call一致,只不过其不是把函数立即执行,而是基于”柯里化函数思想”,创建一个闭包,把需要改变的THIS及传递的实参保存起来,供后续使用
- 最核心的区别:this「箭头函数中没有this」
- 箭头函数没有 arguments 「可以基于“…”剩余运算符获取传递的实参
- 箭头函数没有 prototype 「不能被NEW执行」
-
项目中用谁?
不涉及到this的问题,用谁都可以「推荐使用箭头函数」
- 想作为一个构造函数,只能用普通函数
- 涉及this问题,用谁处理起来方便,就选择用谁「一般都是外层普通函数,内层箭头函数」
call/apply/bind
在Function.prototype上具备call/apply/bind三个方法;每一个函数都是Function类的实例,所以都可以调用这三个方法,其目的是:改变函数执行中的this指向
const fn = function fn(x, y) {console.log(this, x, y)return x + y}const obj = {name: 'obj',fn:1000}需求:把fn执行,让this指向obj,传递10/20obj.fn(10,20)//Uncaught TypeError: obj.fn is not a function[obj和fn没有关联]不使用call方法,我们只需要让obj和fn建立关系(把函数作为obj的一个成员),再基于obj.fn执行即可
重写内置的call方法
console.log(/*步骤:@1 fn函数首先基于__proto__,找到Function.prototype上的call方法,并且把找到的call方法执行;@2 给call方法传递了obj/10/20三个实参@3 call方法执行做了以下的事情[方法内部实现的]:+把fn(call中的this)执行+把fn中的this改为obj+把第二个及以后传递给call方法的实参,作为参数传递给fn+接收fn执行的返回值,作为call执行的返回值*/fn.call(obj, 10, 20))
/* 重写内置的call方法 *///给对象新增一个不可枚举的成员(可删除可修改)const define=function define(obj,key,value){Object.defineProperty(obj,key,{value,//"value":value | value:valuewritable:true,configurable:true,enumerable:false})}const call=function call(context,...params){//context:最后fn执行需要改变的this指向-->obj//params:最后fn执行需要传递的实参信息-->[10,20]//call方法中的this是:fn//最终目的:把this(fn) 执行,传递实参params([10,20]),并且让函数中的"this指向"指向context(obj),并且接收函数执行的返回值,作为call执行的返回值//context是null/undefined,我们让其默认为windowif(context==null)context=window//给除null/undefined以外的原始值设置成员,不会报错,但是也不会生效[也就是要去context 必须是对象类型,如果不是,则转换为非标准特殊对象(装箱)]if(!/^(object|function)$/.test(typeof context))context=Object(context)if(context==null)context=windowconst sym=Symbol('insert-key')context[sym]=thisconst result=context[sym](...params)delete context[sym]return result}define(Function.prototype,'call',call)
重写bind方法
需求:点击Body的时候执行fn,让fn中的this是obj,让x/y是10/20,让ev是事件对象
点击Body执行fn,fn->this:body x:事件对象 y:undefineddocument.body.onclick = fncall方法会把fn立即执行,不等点击Body就执行了document.body.onclick = fn.call(obj, 10, 20)点击Body才会执行匿名函数document.body.onclick = function (ev) {// this:body ev:事件对象// 在匿名函数执行的时候,再基于call方法,把真正要执行的函数fn执行,改变其this和参数即可fn.call(obj, 10, 20, ev)}
![19P`S)%PJ)D60_C@U6)(_U_tmb.jpg
const bind = function bind(context, ...params) {// this->fn context->obj params->[10,20]let that = thisreturn function anonymous(...args) {// this->body args->[ev]// 在返回的匿名函数执行中,把真正需要执行的函数执行「改变THIS & 传递实参」params = params.concat(args)return that.call(context, ...params)}}define(Function.prototype, 'bind', bind)
基于bind可以解决我们的需求:预先改变函数中的this指向(及参数),但是不会把函数立即执行document.body.onclick = fn.bind(obj, 10, 20)document.body.onclick = anonymous 点击的时候执行的是bind返回的小函数anonymous
总结call/apply/bind方法重写
const call = function call(context, ...params) {if (context == null) context = windowif (!/^(object|function)$/.test(typeof context)) context = Object(context)const sym = Symbol('insert-key')context[sym] = thisconst result = context[sym](...params)delete context[sym]return result}const apply = function apply(context, params) {if (context == null) context = windowif (!/^(object|function)$/.test(typeof context)) context = Object(context)const sym = Symbol('insert-key')context[sym] = thisconst result = context[sym](...params)delete context[sym]return result}const bind = function bind(context, ...params) {let that = thisreturn function anonymous(...args) {params = params.concat(args)return that.call(context, ...params)}}define(Function.prototype, 'call', call)define(Function.prototype, 'apply', apply)define(Function.prototype, 'bind', bind)
