💦 原型链
- 原型对象也有原型,这样就构成了一条原型链,根据对象的复杂程度不同,原型链的长度也不同,p对象的原型链:p对象 --> 原型 --> 原型 --> null,obj对象的原型链:obj对象 --> 原型 --> null。
- 读取对象属性时,会优先对象自身属性,如果对象中有,则使用,没有则去对象的原型中寻找,如果原型中有,则使用,没有则去原型的原型中寻找,直到找到Object对象的原型(Object的原型没有原型(为null)),如果依然没有找到,则返回undefined
- 作用域链,是找变量的链,找不到会报错
- 原型链,是找属性的链,找不到会返回undefined
💦 原型的作用
所有的同类型对象它们的原型对象都是同一个,也就意味着,同类型对象的原型链是一样的。
class Person { name = "孙悟空" age = 18 sayHello() { console.log("Hello,我是", this.name) } } class Dog {} const p = new Person() const p2 = new Person() const d = new Dog() const d2 = new Dog() console.log(p === p2) console.log(p.__proto__ === p2.__proto__) console.log(d.__proto__ === d2.__proto__) console.log(p.__proto__ === d.__proto__)
原型就相当于是一个公共的区域,可以被所有该类实例访问,可以将该类实例中,所有的公共属性(方法)统一存储到原型中,这样我们只需要创建一个属性,即可被所有实例访问。
在对象中有些值是对象独有的,像属性(name,age,gender)每个对象都应该有自己值,对于这些放在对象上;但是有些值对于每个对象来说都是一样的,像各种方法,对于一样的值没必要重复的创建,所以放在对象的原型对象中
const p = new Person() const p2 = new Person() console.log(p.__proto__) console.log(p2.__proto__) console.log(p.sayHello === p2.sayHello)
JS中继承就是通过原型来实现的,当继承时,子类的原型就是一个父类的实例。
class Animal{} class Cat extends Animal{} const cat = new Cat() // cat --> Animal实例 --> object --> Object原型 --> null console.log(cat.__proto__) console.log(cat.__proto__.__proto__) console.log(cat.__proto__.__proto__.__proto__) console.log(cat.__proto__.__proto__.__proto__.__proto__)
function fn() {} console.log(fn.__proto__) console.log(fn.__proto__.__proto__) console.log(fn.__proto__.__proto__.__proto__)
💦 修改原型
- 大部分情况下,我们是不需要修改原型对象
- 注意:千万不要通过类的实例去修改原型
- 通过一个对象影响所有同类对象,这么做不合适
- 修改原型先得创建实例,麻烦
- 危险,如果将对象的原型对象进行整个替换,该操作很危险
- 除了通过__proto__能访问对象的原型外,还可以通过类的prototype属性,来访问实例的原型
class Person { name = "孙悟空" age = 18 sayHello() { console.log("Hello,我是", this.name) } } const p = new Person() console.log(p.__proto__) console.log(Person.prototype) console.log(Person.prototype === p.__proto__)
- 修改原型时,最好通过通过类去修改
- 好处:
- 一修改就是修改所有实例的原型
- 无需创建实例即可完成对类的修改
- 原则:
- 原型尽量不要手动改
- 要改也不要通过实例对象去改
- 通过 类.prototype 属性去修改,
类.prototype.属性 = 值
- 最好不要直接给prototype去赋值,即
类.prototype = 值
💦 instanceof
- instanceof 用来检查一个对象是否是一个类的实例
- instanceof检查的是对象的原型链上是否有该类实例,只要原型链上有该类实例,就会返回true
- Object是所有对象的原型,所以任何和对象和Object进行instanceof运算都会返回true
class Animal {} class Dog extends Animal {} const dog = new Dog() // dog -> Animal的实例 -> Object实例 -> Object原型 console.log(dog instanceof Dog) // true console.log(dog instanceof Animal) // true console.log(dog instanceof Object) // true
💦 判断对象是否具有指定属性
- in
- 使用in运算符检查属性时,无论属性在对象自身还是在原型中,都会返回true
- 对象.hasOwnProperty(属性名) (不推荐使用)
- 用来检查一个对象的自身是否含有某个属性,该方法位于object原型中
- Object.hasOwn(对象, 属性名)
- 用来检查一个对象的自身是否含有某个属性
class Person { name = "孙悟空" age = 18 sayHello() { console.log("Hello,我是", this.name) } } const p = new Person() cconsole.log("'name' in p", 'name' in p) console.log("'address' in p", 'address' in p) console.log('"sayHello" in p', "sayHello" in p) console.log('-----------------------------------') console.log('p.hasOwnProperty("name")', p.hasOwnProperty("name")) console.log('p.hasOwnProperty("sayHello")', p.hasOwnProperty("sayHello")) console.log('-----------------------------------') console.log('Object.hasOwn(p, "name")', Object.hasOwn(p, "name")) console.log('Object.hasOwn(p, "sayHello")', Object.hasOwn(p, "sayHello"))
🌊 旧类(早期JS中定义类)
早期JS中,直接通过函数来定义类,一个函数如果直接调用 xxx() 那么这个函数就是一个普通函数,一个函数如果通过new调用 new xxx() 那么这个函数就是一个构造函数
// 立即执行函数,防止类的代码还未完成书写就使用类进行对象的创建 var Person = (function () { // 相当于class中的constructor构造函数 function Person(name, age) { // 在构造函数中,this表示新建的对象 this.name = name this.age = age // 采用该方式添加方法会造成每个对象都有自己的方法,无法实现方法的共享 // this.sayHello = function(){ // console.log(this.name) // } } // 向原型中添加属性(方法) Person.prototype.sayHello = function () { console.log(this.name) } // 类添加静态属性 Person.staticProperty = "xxx" // 类添加静态方法 Person.staticMethod = function () {} // 返回类 return Person })() const p = new Person("孙悟空", 18) console.log(p) var Animal = (function(){ function Animal(){ } return Animal })() var Cat = (function(){ function Cat(){ } // 继承Animal // 继承之后,该类创建出来的对象的原型对象为父类的实例对象 Cat.prototype = new Animal() return Cat })() var cat = new Cat() console.log(cat)
🌊 new运算符
- new运算符是创建对象时要使用的运算符
- 使用new时,到底发生了哪些事情:【https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new】
- 当使用new去调用一个函数时,这个函数将会作为构造函数调用,类创建对象必须使用new关键字,使用new调用函数时,将会发生这些事:
- 创建一个普通的JS对象(Object对象 {}), 为了方便,称其为新对象
- 将构造函数的prototype属性设置为新对象的原型
- 使用实参来执行构造函数,并且将新对象设置为函数中的this
- 如果构造函数返回的是一个非原始值,则该值会作为new运算的返回值返回(千万不要这么做),如果构造函数的返回值是一个原始值或者没有指定返回值,则新的对象将会作为返回值返回,通常不会为构造函数指定返回值
function MyClass(){ // 1. 创建一个普通的JS对象(Object对象 {}), 为了方便,称其为新对象 var newInstance = {} // 2. 将构造函数的prototype属性设置为新对象的原型 newInstance.__proto__ = MyClass.prototype // 3. 使用实参来执行构造函数,并且将新对象设置为函数中的this this = newInstance // 4. // 如果构造函数返回的是一个非原始值,则该值会作为new运算的返回值返回 // return {name: 'Tom'} // 则新创建的对象为{name: 'Tom'} // 如果构造函数的返回值是一个原始值或者没有指定返回值,则新的对象将会作为返回值返回 return 1 // 新创建的对象为newInstance } var mc = new MyClass() console.log(mc)
🥽 数组(Array)
🌊 简介
- 数组也是一种复合数据类型,在数组可以存储多个不同类型的数据
- 数组中存储的是有序的数据,数组中的每个数据都有一个唯一的索引,可以通过索引来操作获取数据,索引(index)是一组大于0的整数
- 数组中存储的数据叫做元素
- 创建数组
- 通过Array()来创建数组,也可以通过[]来创建数组
const arr = new Array() const arr2 = [1, 2, 3, 4, 5] // 数组字面量 console.log(arr) console.log(arr2)
- 向数组中添加元素
- 语法:
数组[索引] = 元素
const arr = new Array() console.log(arr) arr[0] = 10 // 向数组的第一个位置添加一个数据 arr[1] = 22 // 向数组的第二个位置添加一个数据 arr[2] = 44 // 向数组的第三个位置添加一个数据 arr[3] = 88 arr[4] = 99 console.log(arr)
- 读取数组中的元素
- 语法:
数组[索引]
- 如果读取了一个不存在的元素,不会报错而是返回undefined
const arr = [11, 22, 33, 44, 55] console.log(arr[0]) console.log(arr[1]) console.log(arr[10])
- length
- 获取数组的长度
- 获取的实际值就是数组的最大索引 + 1
- 向数组最后添加元素:
数组[数组.length] = 元素
- length是可以修改的
const arr = [11, 22, 33, 44, 55] console.log(arr) console.log(arr.length) // 获取数组的长度, 数组的最大索引 + 1 arr[arr.length] = 66 // 向数组最后添加元素 console.log(arr) console.log(arr.length) arr.length = 12 // 修改length console.log(arr) console.log(arr.length)
- 任何类型的值都可以成为数组中的元素
// 任何类型的值都可以成为数组中的元素 let arr = [1, "hello", true, null, { name: "孙悟空" }, () => {}] console.log(arr)
🌊 数组的遍历
遍历数组简单理解,就是获取到数组中的每一个元素
💦 for循环
//任何类型的值都可以成为数组中的元素 let arr = [1, "hello", true, null, { name: "孙悟空" }, () => {}] console.log(arr) // 遍历数组 for (let index = 0; index < arr.length; index++) { const element = arr[index]; console.log('第' + (index+1) + '个数组元素: ', element) }
💦 for-of语句
- for-of语句可以用来遍历可迭代对象
- 语法:
for(变量 of 可迭代的对象){ 语句... }
- 执行流程:for-of的循环体会执行多次,数组中有几个元素就会执行几次,每次执行时都会将一个元素赋值给变量
const arr = ["孙悟空", "猪八戒", "沙和尚", "唐僧"] // 每次执行时都会将一个元素赋值给变量 for(let value of arr){ console.log(value) } // for-of语句可以用来遍历可迭代对象 for(let value of "hello"){ console.log(value) }
🥽 Map
- Map用来存储键值对结构的数据(key-value)
- Object中存储的数据就可以认为是一种键值对结构
- Map和Object的主要区别:
- Object中的属性名只能是字符串或符号,如果传递了一个其他类型的属性名,JS解释器会自动将其转换为字符串
const obj = { "name":"孙悟空", 'age':18, [Symbol()]:"哈哈", [obj2]:"嘻嘻" // JS解释器会自动将其转换为字符串 object Object } console.log(obj)
- Map中任何类型的值都可以成为数据的key
🌊 创建Map
语法:new Map()
// 创建一个Map const map = new Map() console.log(map) // Map中任何类型的值都可以成为数据的key // map.set(key, value) 向map中添加键值对 map.set("name", "孙悟空") const obj2 = {} map.set(obj2, "呵呵") map.set(NaN, "哈哈哈") console.log(map)
🌊 Map的属性和方法
💦 map.set(key, value) 向map中添加键值对
// 创建一个Map const map = new Map() console.log(map) // Map中任何类型的值都可以成为数据的key // map.set(key, value) 向map中添加键值对 map.set("name", "孙悟空") const obj2 = {} map.set(obj2, "呵呵") map.set(NaN, "哈哈哈") console.log(map)
💦 map.get(key) 根据key获取值
console.log(map.get("name")) console.log(map.get(NaN)) console.log(map.get(obj2))
💦 map.size 获取map中键值对的数量
console.log(map) console.log(map.size)
💦 map.delete(key) 删除指定数据
console.log(map) console.log(map.size) map.delete(NaN) console.log(map) console.log(map.size)
💦 map.has(key) 检查map中是否包含指定键
console.log(map.has("name")) map.delete(NaN) console.log(map.has(NaN))
💦 map.clear() 删除全部的键值对
console.log(map) console.log(map.size) map.clear() console.log(map) console.log(map.size)
🌊 map转换为数组
💦 Array.form()
const map = new Map() map.set("name", "孙悟空") map.set("age", 18) // 将map转换为数组 // 转换的结果为二维数组 // Map的每个键值对的键和值组成一个数组作为转换结果数组的元素 const arr = Array.from(map) // [["name","孙悟空"],["age",18]] console.log(map) console.log(arr)
💦 扩展转换
const map = new Map() map.set("name", "孙悟空") map.set("age", 18) // 将map转换为数组 // const arr = Array.from(map) // [["name","孙悟空"],["age",18]] // 转换的结果为二维数组 // Map的每个键值对的键和值组成一个数组作为转换结果数组的元素 const arr = [...map] console.log(map) console.log(arr)
🌊 通过二维数组创建Map
const map2 = new Map([ ["name", "猪八戒"], ["age", 18], [{}, () => {}], ]) // 数组中的每个一维数组为一个键值对 // 每个一维数组的第一个元素为键,第二个元素为值 console.log(map2)
🌊 遍历map
💦 for-of
// 遍历map for (const entry of map) { // 获取的entry为map中的每个键值对 // entry是一个一维数组 // entry中第一个元素为键值对的键,第二个元素为键值对的值 console.log(entry) const [key, value] = entry // 对entry解构赋值 console.log(key, value) } console.log('------------------------------------') // 在获取到map的每个键值对时直接进行解构赋值 for (const [key, value] of map) { console.log(key, value) }
💦 Map.forEach()
// Map.forEach()遍历map // 需要传递一个回调函数 // map中有几个键值对就会执行几次回调函数 // 回调函数的第一个参数为键值对的键,第二个参数为键值对的值 // 回调函数的第三个参数为map本身 map.forEach((key, value, map)=>{ console.log(key, value, map) })
💦 map.keys() 获取map的所有的key
console.log(map.keys()) // 获取map的所有的key for (const key of map.keys()) { console.log(key) }
💦 map.values() 获取map的所有的value
console.log(map.values()) // 获取map的所有的value for (const value of map.values()) { console.log(value) }
💦 map.entries() 获取map的所有的键值对
console.log(map.entries()) // 获取map的所有的键值对 for (const entry of map.entries()) { console.log(entry) }