【Javascript】面向对象编程,this,原型与原型链,类与实例,class,实现Map,stack,Queue ,Set,二维向量

❤️ Author: 老九
☕️ 个人博客:老九的CSDN博客
🙏 个人名言:不可控之事 乐观面对
😍 系列专栏:

对象中的方法/this

  • 面向对象:核心思想就是可以为每种事务(东西),在编程语言中建立一个对应的类型,对应到语言里就是一个class
  • 反引号:中间可以插入${}作为动态字符串
  • 其实对象中函数的调用就是方法,foo和bar就当作obj对象的方法调用
  • 其中foo的this就是指代的obj对象,bar的this指代的也是obj这个对象
<script>
  obj = {
    foo: function () { },
    bar: function () { },
  }

  let rabbit = {}
  rabbit.speak = function (line) {
    console.log(`the rabbit says ${line}`)
  }
</script>

this使用

在全局环境下面,this指向window;
通过对象调用,this指向调用的对象

  • this相当于函数的一个隐藏参数,其值不是传入的,而是取决于该函数的调用方式,不取决于函数在哪定义和在哪调用
  • f() = obj.foo
    f() // 当函数调用,里面的this指向window
    obj.foo() //当obj的方法调用,里面的this指向obj
    他们两个的this是不同的
  • 这个this指代的是添加的属性对应的对象
  • js语言中,函数从来不属于一个对象,this也不会因为这种属于而一成不变
  • 在js中,如果两个对象分别有一个属性指向同一个函数,则该函数和这两个对象之间的关系是对等的
<script>
  function speak(line) {
    console.log(`the ${this.type} rabbit says ${line}`)
  }

  let whiteRabbit = {
    type: "white",
    speak
  }
  let hungryRabbit = {
  	type : "hungry",
  	speak
  }
  speak.call(hungryRabbit,"woshizhu")
</script>
  • 在call方法和apply方法中,第一个参数传的就是this,call和apply唯一的区别就是第二个参数apply传的是数组,而call后面传的是一个一个的参数,由此来看apply更好用一些,因为如果数组可以是一个变化的值,而call的话只能是传固定的参数
  • 如果需要立即执行一个函数并改变其执行上下文,可以使用call或apply函数;如果需要返回一个新的函数,并在稍后的某个时刻执行该函数并改变其执行上下文,可以使用bind函数。
    在这里插入图片描述
  • 所以,如果一个函数中创建了另一个函数,那么内层函数是拿不到外层函数的this的,除非是外层函数通过call方法,把外层函数的this和内层函数指向同一个函数
    function waiceng() {
      neiceng.call(waiceng);
      console.log("waiceng" + this);
      function neiceng() {
        console.log("neiceng" + this);
      }
    }
  • 箭头函数是没有this的,在箭头函数里读this,就跟在读一个箭头函数内部没有声明的变量一样。读箭头函数的this,就是读箭头函数外面函数的this
  • 由于call/apply并不能改变箭头函数里的this,所以一般都不会使用call和apply来调用箭头函数。apply传不定数量参数的功能由展开运算符。。。来提供

bind函数

  • bind函数的第一个参数是绑定this,如果bind的this已经被绑定了,那么就不能再次更改绑定了。即f2 = f.bind(obj2) , f2.call(obj3),f2的调用还是相当于f的this为obj2.但当把f2当构造函数使用时,this的绑定会失效,new f2时,f2的内部的this为一个新的对象,且其原型为f.prototype

构造函数

  • 构造函数是一个用于创建对象的特殊函数,通过new关键字进行调用,并通过将属性和方法添加到this对象来初始化对象,要和普通函数进行区分
    在这里插入图片描述
    第五步补充一下,如果构造函数没有返回非空对象,则返回创建出来的新对象
function Person(name, age) {
  this.name = name;
  this.age = age;
  this.sayHello = function() {
    console.log("Hello, my name is " + this.name + " and I am " + this.age + " years old.");
  };
}

var john = new Person("John", 30);
john.sayHello(); // 输出 "Hello, my name is John and I am 30 years old."

原型

  • 每一个对象都有自己的原型[[Prototype]],如果想看对象的原型,有两种方式,第一种是obj._ _ proto _ _,这个是浏览器自己加的属性,但是有的浏览器不兼容,第二种是Object.getPrototypeOf(obj)

  • 原型的作用就是当我们通过[[get]]方式获取一个属性对应的value的时候,首先会在自己的对象中查找,如果找到就直接返回,如果没有找到,那么就会在原型对象中查找

  • 函数的原型属性prototype,注意这个不是_proto_,函数和对象都有_proto_但是当是函数的时候,函数还有另外一个原型属性是prototype,当用new创建对象的时候,会先在内存中创建一个新对象(空对象),然后将这个对象的_proto_指向构造函数的prototype属性,所以说,通过Person构造函数创建出来的所有对象的_proto_都指向Person.prototype,即p._ _ proto _ _ = Person.prototype
    在这里插入图片描述
    右面那两个结果都是true

  • prototype属性中有一个constructor属性,这个属性又指向了我们本身的函数
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • 函数的原型属性本身也是一个对象 ,它自身也会有原型,直到Object.prototype
    在这里插入图片描述

  • 从这里我们可以看到,js从一个空对象里取出了一个属性;如果你在一个对象上找不到对应的属性,就会在这个原型对象上找,原型就相当于是一个后备源;所以任何一个对象都有toString的方法,这个方法就是在原型上的
    在这里插入图片描述

  • Person是一个构造函数,用于创建对象实例,而a是通过调用Person构造函数创建的一个具体的对象实例。此外,Person定义了属性和方法,而a只是具有这些属性和方法的特定对象实例。

如果想要看到函数的内部状态,用console.dir()
在这里插入图片描述

重写原型对象

如果我们需要在原型上添加过多的属性,通常我们会重写整个原型对象:
在这里插入图片描述
我们这里相当于给prototype重新赋值了一个对象, 那么这个新对象的constructor属性, 会指向Object构造函数, 而不是Person构造函数了
首先我们可以通过给Person.prototype直接手动添加一个constructor,但是这样的constructor的枚举属性设置成了true,而默认的原生constructor属性是不可枚举的
在这里插入图片描述
通过Object.defineProperty()函数就可以实现了
在这里插入图片描述

构造函数喝原型组合

当我们把函数写在构造函数中,会创建出重复的函数,如果我们将函数放到Person.prototype的对象上就可以了
在这里插入图片描述

原型链(继承)

  • js就是通过原型链的方式实现了面向对象的继承
  • 原型链在js中其实也只是指针(对象的指向关系 )
  • 原型链存在的作用一方面是为了继承,另一方面是为了节省空间;如果一组对象都想有某些方法/属性,则把这些共用的方法/属性放到它们的原型上,可以节省存储空间
  • Object.getPrototypeOf([]) === Array.prototype
  • Object.getPrototypeOf(function(){}) === Function.prototype
  • Array.prototype和Function.prototype都以Object.prototype为原型
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    Object是所有类的父类,原型链最顶层的原型对象就是Object的原型对象

补充一个语法:当在对象的属性中,一个属性指向一个函数(必须是function函数),可以直接将冒号和function删除 在这里插入图片描述

  • 在前面我们说过对于实例的方法,我们是希望放到原型上的,这样可以被多个实例来共享;
    在这里插入图片描述

类的访问器方法

在这里插入图片描述

类的静态方法/类方法

  • 区分类方法和实例方法,类方法是直接可以从类中调用的方法(static关键字实现),Person.randomPerson(),实例方法是具体的实例创建的方法
    在这里插入图片描述

自己创建原型对象

  • 怎样自己创建原型对象呢?通过Object.create(xxx)
    在这里插入图片描述

ES6继承extends,class

  • ES6的新标准中使用了class关键字来直接定义类,通过extends关键字实现继承
  • 除了undifined和null,任何值都有原型,即_ proto _

super

  • 通过super.method(…),来调用一个父类的方法
  • 如果想更改或者初始化父类构造函数传来的参数,使用super关键字,必须放在this的前面,放在子类的构造函数中
  • 在这里插入图片描述
  • super还可以调用 父对象/父类上的方法
  • 在这里插入图片描述

class和Function的区别

  • Function定义的类,可以作为普通函数进行调用,Class定义的类,不能作为一个普通函数进行调用

List item

静态方法(类方法)

  • static randomPerson(){},通过类点的方式就可以直接调用了。
    在这里插入图片描述

class语法

  • 有一个原型属性的构造函数就可以称为一个类,比上面的写法更简单 ,上面的方法是在2015年之前写的,也就是es6之前
  • class里面写的就是把一份数据,和能对这份数据进行操作的函数放在一起
<script>
  class Rabbit{
    //构造函数:往往是构造需要实现的东西,比如需要类型type,需要数组key等等
    constructor(type){
      this.type = type
    }
    speak(line){
      console.log(`ths ${this.type} rabbit says ${line}`)
    }
  }
  let killerRabbit = new Rabbit("killer")
  //相当于,class就相当于函数
  //let killerRabbit = new class{}
</script>
  • 如果把Rabbit typeof一下,那它就是一个function。,上面的speak方法就是放在Rabbit.prototype上面的

补充

  • obj自己的x会覆盖原型上的x
    在这里插入图片描述

  • 如果想代替Array.prototype的toString方法,使用Object.prototype的toString方法,可以通过Object.prototype.toString.call(ary)来实现,这个输出的是’[object Array]',因此Object.prototype.toString这个函数,给他的this传入什么,它就将该值转换为[object xxx]的字符串,其中xxxx是this缩指向的对象的类型名。所以这个函数可以用来做类型判断,还有typeof也可以做类型判断;但是这个toString无法判断用户自定义的类型(toString.call(rabbit)),这个值是[object,object],本来输出的应该是[object, rabbit];

<script>
//注意第一个object要小写
	function isArray(val){
	return Object.prototype.toString.call(val) === '[object Array]'
	}
</script>

Map

  • 通过一个值映射到另一个值,虽然对象上可以实现map的功能,但是如果读出的一个key是不存在的,但是原型上存在,这样就会出现歧义了。一个方法是创建一个没有原型的对象(obj = Object.create(null));
<script>
  let ages = new Map()
  ages.set("Boris", 39)
  ages.set("Liang", 22)
  ages.set("Julia", 62) // obj['Julia'] = 62
  ages.get('Liang') //obj['Liang']
  ages.has('Liang') //'Liang' in obj
</script>
<script>
  const val = 'hello world !!'
  function lmp(val) {
    let map = new Map()
    for (let i = 0; i < val.length; i++) {
      if (!map.has(val[i])) {
        map.set(val[i], 1)
      } else {
        map.set(val[i], map.get(val[i]) + 1)
      }
    }
    let res = []
    let max = 0
    map.forEach((value, key) => {
      if (value > max) {
        res = [key]
        max = value
      } else if (value == max) {
        res.push(key)
      }
    })
    return [max, res]
  }
  let a = lmp(val)
</script>
  • set,get,has是map的其中一些接口,map这些操作的性能都是O(1)的,是非常快的。
  • ages.clear():清除所有的映射对;ages.delete(key):删除一组映射对; ages.forEach((val,key) => {xxx}):遍历这个映射,可以类比于数组,传入的值是数组的值和键,这个顺序别搞错了;ages.size:这是一个属性,不是一个方法,可以拿到map里有几组;
  • Object.keys(obj):这个可以返回一个对象的自有属性有哪些,原型属性是无法返回的,这些自有属性组成了一个数组返回。
    在这里插入图片描述
<script>
  let obj = {}
  obj.foo = 1
  obj.bar = 2

  function keys(obj) {
    var res = []
    for (let key in obj) {
    if(Object.prototype.hasOwnProperty.call(obj,key)){
      res.push(key)
      console.log(key, obj[key])
    	}
    }
    return res
  }
</script>
  • 有一个方法是obj.hasOwnProperty()方法是继承来的,可以判断该对象有没有对应的自有属性;但是in运算符不同,只要obj上有这个属性,不管是不是原型属性都算上

在这里插入图片描述
在这里插入图片描述

实现Map

<script>
  class Map2 {
    constructor() {
      this.keys = [] // 用来存储每组映射的key
      this.vals = []// 用来存储每组映射的value
    }
    get(key) {
      var keyIdx = this.keys.indexOf(key)
      if (keyIdx >= 0) {
        return this.vals[keyIdx]
      }
    }
    set(key, val) {
      var keyIdx = this.keys.indexOf(key)
      if (keyIdx >= 0) {
        this.vals[keyIdx] = val
      } else {
        this.keys.push(key)
        this.vals.push(val)
      }
      return this
    }
    has(key) {
      var keyIdx = this.keys.indexOf(key)
      if (keyIdx >= 0) {
        return true
      }
      return false
    }
    delete(key) {
      var keyIdx = this.keys.indexOf(key)
      if (keyIdx >= 0) {
        //splice函数,从keyIdx位置,删除一项
        this.keys.splice(keyIdx, 1)
        this.vals.splice(keyIdx, 1)
        return true
      }
      return false
    }
    clear() {
      this.keys = []
      this.vals = []
    }
    size() {
      return this.keys.length
    }
  }
</script>

  • 我们不希望外界的人设置keys和vals,在构造函数外面写上#xxx,就变成私有类字段,外界就无法访问了
    在这里插入图片描述

实现stack

<script>
  class stack {
    //带警号的要在外面先声明
    #size = 0
    #top = null
    // constructor() {
    //   this.#top = null // 用于存储栈内元素的链表;由于头节点是栈顶,所以是top
    //   this.#size = 0
    // }
    push(val) {
      let Node = {
        val: val,
        next: null
      }
      Node.next = this.#top
      this.#top = Node
      this.#size++
      return this
    }
    //返回栈顶元素,并将其出栈
    pop() {
      if (this.#top) {
        let res = this.#top.val
        this.#top = this.#top.next
        this.#size--
        return res
      }
    }
    size() {
      return this.#size 
    }
    //查看栈顶元素的值,但不出栈
    peek() {
      if (this.#top) {
        return this.#top.val
      }
    }
  }

</script>

实现Queue

<script>
  class Queue {
    constructor() {
      this._head = null
      this._tail = null
      this._size = 0
    }
    //进队
    enqueue(val) {
      this._size++
      var node = {
        val: val,
        next: null
      }
      if (this._head == null) {
        this._head = this._tail = node
      } else {
        this._tail.next = node
        this._tail = node
      }
      return this
    }
    dequeue() {
      if (this._head) {
        this._size--
        var result = this._head.val
        if (this._head == this._tail) {
          this._head = this._tail = null
        } else {
          this._head = this._head.next
        }
        return result
      }
    }
    peek() {
      if (this._head) {
        return this._head.val
      }
    }
    size() {
      return this._size
    }
  }
</script>

实现Set

  • 数组是可重复的有序集合,set是不可重复的集合
  • let = new Set(),有add方法,has方法,delete方法,clear方法,size方法
<script>
  class Set {
    constructor() {
      this._elements = []
    }
    add(val) {
      if (!this.has(val)) {
        this._elements.push(val)
      }
      return this
    }
    has(val) {
      if (this._elements.indexOf(val) >= 0) {
        return true
      }
      return false
    }
    delete(val) {
      var idx = this._elements.indexOf(val)
      if (idx >= 0) {
        this._elements.splice(idx, 1)
        return true
      }
      return false
    }
    size() {
      return this._elements.length
    }
  }
</script>

实现二维向量

<script>
  class Vector{
    constructor(x,y){
      this.x = x
      this.y = y
    }
    //用当前向量加上一个向量v,返回新的结果向量
    plus(v){
      let x = this.x + v.x
      let y = this.y + v.y
      return new Vector(x,y)
    }
    //用当前向量减去一个向量v,返回新的结果向量
    minus(v){
      let x = this.x - v.x
      let y = this.y - v.y
      return new Vector(x,y)
    }

    length(){
      let res = Math.sqrt(this.x ** 2 + this.y ** 2)
      return res
    }
  }
</script>

————————————————————————
♥♥♥码字不易,大家的支持就是我坚持下去的动力♥♥♥
版权声明:本文为CSDN博主「亚太地区百大最帅面孔第101名」的原创文章

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李小浦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值