❤️ 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定义的类,不能作为一个普通函数进行调用
静态方法(类方法)
- 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名」的原创文章