面向对象是一种“编程思想 [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(){} 帕斯卡尔命名「大驼峰」
  1. hungarian_case 例如:let _index = null 匈牙利命名法
    :::

    实例(所有的实例都是对象数据类型的)

  • 创建实例的方式
    • 字面量方式[含基于特定API创建]
    • 基于new创建[构造函数方式]
      1. 对于对象数据类型来讲,即便基于不同方式,创造出来的结果都一样:都是某个类的实例、都是对象数据类型的值
      2. let arr1=[10,20,30]
      3. let arr2=Array.of(10,20,30)
      4. let arr3=new Array(10,20,30)
      5. console.log(arr1,arr2,arr3)
      1. 但是对于原始类型来讲,基于字面量方式创造的是原始值,不是所属类的实例;基于构造函数方式(或者基于Object([value])这种方式)创造出来的是“非标准特殊对象”,这些才是所属类的实例
      2. let num1=10
      3. let num2=new Number(10)-->非标准特殊对象
      4. let num3=Object(10)-->非标准特殊对象
      5. console.log(num1,num2num3)
      6. console.log(num2.toFixed(2))//'10.00'
      7. console.log(num1.toFixed(2))//'10.00' 浏览器默认会进行“装箱”:把原始值转为对象类型的值(基于Object([value])方法处理的)
      8. console.log(num1+10)//20
      9. console.log(num2+10)//20 浏览器默认会进行“拆箱”:把对象类型的值转为原始值

      new执行和普通函数执行的区别

      1. function Fn(x, y) {
      2. let sum = 10
      3. this.total = x + y
      4. this.say = function () {
      5. console.log(`我计算的和是:${this.total}`)
      6. }
      7. }
      8. let res = Fn(10, 20)//作为普通函数执行
      9. let f1 = new Fn(10, 20)
      10. let f2 = new Fn
      11. console.log(f1.sum)
      12. console.log(f1.total)
      13. console.log(f1.say === f2.say)
      6I0~6B7@{G}}FW~EJPI1FB0_tmb.jpg
      1. function Foo() {
      2. getName = function () {
      3. console.log(1)
      4. }
      5. return this
      6. }
      7. Foo.getName = function () {
      8. console.log(2)
      9. }
      10. Foo.prototype.getName = function () {
      11. console.log(3)
      12. }
      13. var getName = function () {
      14. console.log(4)
      15. }
      16. function getName() {
      17. console.log(5)
      18. }
      19. Foo.getName()
      20. getName()
      21. Foo().getName()
      22. getName()
      23. new Foo.getName()
      24. new Foo().getName()
      25. new new Foo().getName()
      ~4PQCO)PJOM337}WOJXOJA6.jpg
      函数具备的角色
  1. 普通对象「操作的是函数堆中,存储的静态私有属性方法」
    1. Foo.xxx=xxx
    2. console.log(Foo.xxx)
  2. 函数
    1. 普通函数「闭包作用域」
    2. 构造函数「即拥有闭包作用域、还具备实例、proto、prototype」

      重写new方法

      ![PLMCSP5[J_YQ`V8YC%BTFB.jpg
      1. const _new = function _new(Ctor, ...params) {
      2. //Ctor:我们需要操作的构造函数[就是创造它的一个实例]-->Dog
      3. //params:数组,存储给Ctor传递的实参信息-->['三毛']
      4. //4.对Ctor要做校验[不能是null/undefined、不能是Symbol/BigInt、必须具备prototype]
      5. if(Ctor==null||Ctor===Symbol||Ctor===BigInt||!Ctor.prototype)throw new TypeError('Ctor is not a constructor')
      6. //1.创建一个空的实例对象(空对象、__proto__指向类的prototype)
      7. let instance = Object.create(Ctor.prototype)
      8. //2.在构造函数作为普通函数执行的时候,让函数中的this指向创建的实例对象
      9. let result = Ctor.call(instance, ...params)
      10. //3.监测函数执行的返回值[返回的是对象,则以函数自己返回的为主,否则返回创建的实例对象]
      11. if(result!==null&&/^(object|function)$/.test(typeof result))return result
      12. return instance
      13. }

      this情况梳理

      函数的执行主体(通俗来讲,就是谁把这个函数执行的),不是函数执行所处的上下文;我们都是去研究函数中的this,全局上下文中的this是window「前提:在浏览器环境下运行」;块级私有上下文中没有自己的this,用到的this都还是宿主环境中的

(JZZ%_QM@VVS41$%$A(@_Q8_tmb.jpg

关于函数中的this到底是谁,有以下五条规律:

  1. 给元素进行事件绑定「DOM0/DOM2」

    当事件触发,绑定的方法执行,方法中的this是当前被操作的元素「给谁绑定的,this就是谁」

  2. 普通函数执行,看函数前面是否有“点”

  • 有:“点”前面是谁,函数中的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
  1. 构造函数执行(NEW执行),函数体中的this指向创建的实例对象
  2. 我们可以基于 call/apply/bind 强制改变函数中this的指向
  • call/apply:把函数立即执行,让函数中的this指向自己指定的值
    • 函数.call(THIS,实参1,实参2,…)
    • 函数.apply(THIS.[实参1,实参2,…])
  • bind:bind语法和call一致,只不过其不是把函数立即执行,而是基于”柯里化函数思想”,创建一个闭包,把需要改变的THIS及传递的实参保存起来,供后续使用
  1. 箭头函数/块级私有上下文中没有this,所用到的this都是其“宿主环境(或上级上下文)”中的

    箭头函数 VS 普通函数

  • 最核心的区别:this「箭头函数中没有this」
  • 箭头函数没有 arguments 「可以基于“…”剩余运算符获取传递的实参
  • 箭头函数没有 prototype 「不能被NEW执行」
  • 语法上的区别 「箭头函数写起来更方便/简单」

    项目中用谁?
  • 不涉及到this的问题,用谁都可以「推荐使用箭头函数」

  • 想作为一个构造函数,只能用普通函数
  • 涉及this问题,用谁处理起来方便,就选择用谁「一般都是外层普通函数,内层箭头函数」

    call/apply/bind

    在Function.prototype上具备call/apply/bind三个方法;每一个函数都是Function类的实例,所以都可以调用这三个方法,其目的是:改变函数执行中的this指向

  1. const fn = function fn(x, y) {
  2. console.log(this, x, y)
  3. return x + y
  4. }
  5. const obj = {
  6. name: 'obj',
  7. fn:1000
  8. }
  9. 需求:把fn执行,让this指向obj,传递10/20
  10. obj.fn(10,20)//Uncaught TypeError: obj.fn is not a function[obj和fn没有关联]
  11. 不使用call方法,我们只需要让objfn建立关系(把函数作为obj的一个成员),再基于obj.fn执行即可

重写内置的call方法

  1. console.log(
  2. /*步骤:
  3. @1 fn函数首先基于__proto__,找到Function.prototype上的call方法,并且把找到的call方法执行;
  4. @2 给call方法传递了obj/10/20三个实参
  5. @3 call方法执行做了以下的事情[方法内部实现的]:
  6. +把fn(call中的this)执行
  7. +把fn中的this改为obj
  8. +把第二个及以后传递给call方法的实参,作为参数传递给fn
  9. +接收fn执行的返回值,作为call执行的返回值
  10. */
  11. fn.call(obj, 10, 20)
  12. )
  1. /* 重写内置的call方法 */
  2. //给对象新增一个不可枚举的成员(可删除可修改)
  3. const define=function define(obj,key,value){
  4. Object.defineProperty(obj,key,{
  5. value,//"value":value | value:value
  6. writable:true,
  7. configurable:true,
  8. enumerable:false
  9. })
  10. }
  11. const call=function call(context,...params){
  12. //context:最后fn执行需要改变的this指向-->obj
  13. //params:最后fn执行需要传递的实参信息-->[10,20]
  14. //call方法中的this是:fn
  15. //最终目的:把this(fn) 执行,传递实参params([10,20]),并且让函数中的"this指向"指向context(obj),并且接收函数执行的返回值,作为call执行的返回值
  16. //context是null/undefined,我们让其默认为window
  17. if(context==null)context=window
  18. //给除null/undefined以外的原始值设置成员,不会报错,但是也不会生效[也就是要去context 必须是对象类型,如果不是,则转换为非标准特殊对象(装箱)]
  19. if(!/^(object|function)$/.test(typeof context))context=Object(context)
  20. if(context==null)context=window
  21. const sym=Symbol('insert-key')
  22. context[sym]=this
  23. const result=context[sym](...params)
  24. delete context[sym]
  25. return result
  26. }
  27. define(Function.prototype,'call',call)

image.png

重写bind方法

需求:点击Body的时候执行fn,让fn中的this是obj,让x/y是10/20,让ev是事件对象

  1. 点击Body执行fnfn->this:body x:事件对象 y:undefined
  2. document.body.onclick = fn
  3. call方法会把fn立即执行,不等点击Body就执行了
  4. document.body.onclick = fn.call(obj, 10, 20)
  5. 点击Body才会执行匿名函数
  6. document.body.onclick = function (ev) {
  7. // this:body ev:事件对象
  8. // 在匿名函数执行的时候,再基于call方法,把真正要执行的函数fn执行,改变其this和参数即可
  9. fn.call(obj, 10, 20, ev)
  10. }

![19P`S)%PJ)D60_C@U6)(_U_tmb.jpg

  1. const bind = function bind(context, ...params) {
  2. // this->fn context->obj params->[10,20]
  3. let that = this
  4. return function anonymous(...args) {
  5. // this->body args->[ev]
  6. // 在返回的匿名函数执行中,把真正需要执行的函数执行「改变THIS & 传递实参」
  7. params = params.concat(args)
  8. return that.call(context, ...params)
  9. }
  10. }
  11. define(Function.prototype, 'bind', bind)
  1. 基于bind可以解决我们的需求:预先改变函数中的this指向(及参数),但是不会把函数立即执行
  2. document.body.onclick = fn.bind(obj, 10, 20)
  3. document.body.onclick = anonymous 点击的时候执行的是bind返回的小函数anonymous

总结call/apply/bind方法重写

  1. const call = function call(context, ...params) {
  2. if (context == null) context = window
  3. if (!/^(object|function)$/.test(typeof context)) context = Object(context)
  4. const sym = Symbol('insert-key')
  5. context[sym] = this
  6. const result = context[sym](...params)
  7. delete context[sym]
  8. return result
  9. }
  10. const apply = function apply(context, params) {
  11. if (context == null) context = window
  12. if (!/^(object|function)$/.test(typeof context)) context = Object(context)
  13. const sym = Symbol('insert-key')
  14. context[sym] = this
  15. const result = context[sym](...params)
  16. delete context[sym]
  17. return result
  18. }
  19. const bind = function bind(context, ...params) {
  20. let that = this
  21. return function anonymous(...args) {
  22. params = params.concat(args)
  23. return that.call(context, ...params)
  24. }
  25. }
  26. define(Function.prototype, 'call', call)
  27. define(Function.prototype, 'apply', apply)
  28. define(Function.prototype, 'bind', bind)