JavaScript学习笔记(7)——补充

本章对以前的笔记做补充。包含变量提升、函数提升、类、原型、浅拷贝深拷贝、闭包、JSON、Map、Set、包装类。


一、变量提升

var 用来声明变量,作用和let相同,但是var不具有块作用域

        在全局中使用var声明的变量,都会作为window对象的属性保存

        使用function声明的函数,都会作为window的方法保存

        使用let声明的变量不会存储在window对象中,而存在一个秘密的小地方(无法访问)

        var虽然没有块作用域,但有函数作用域

        在局部作用域中,如果没有使用var或let声明变量,则变量会自动成为window对象的属性,也就是全局变量

变量的提升

        使用var声明的变量,它会在所有代码执行前被声明(提升是声明,不是赋值) 所以我们可以在变量声明前就访问变量

函数的提升

        使用函数声明创建的函数,会在其他代码执行前被创建,所以我们可以在函数声明前调用函数

let声明的变量实际也会提升,但是在赋值之前解释器禁止对该变量的访问

函数的创建方式

1.函数声明

function 函数名(){
语句...
}

2.函数表达式

const 变量 = function(){
语句...
}

3.箭头函数

() => {
语句...
}

        只有一个语句,箭头函数的大括号可以省略不写

        只有一个参数,箭头函数的参数括号可省略

        没有自己的this和arguments,不能作为构造函数调用

        无法通过call、apply、bind指定函数的this

箭头函数的返回值可以直接写在箭头后

        如果直接在箭头后设置对象字面量为返回值时,对象字面量必须使用()括起来,不然会当成代码块

const sum = (a.b) => a + b
const fn = () => ({name:"孙悟空"})

箭头函数没有自己的this,它的this由外层作用域决定,箭头函数的this和它的调用方式无关,比较稳定

function fn() {
	console.log("fn -->", this)
}

const fn2 = () => {
	console.log("fn2 -->", this) // 总是window
}

const obj = {
	name:"孙悟空",
	fn, // 等价于fn:fn   变量名和函数名相同可以省略
	fn2, // 等价于fn2:fn2
	sayHello(){  //等价于sayHello:function()
		console.log(this.name)
		// function t(){
		//     console.log("t -->", this)
		// }
		// t()

		const t2 = () => {
			console.log("t2 -->", this) //箭头函数this不看调用方式,即使下面是用函数调用
		}
		t2()
	}
}

obj.fn() // obj
obj.fn2() // window
obj.sayHello()  // obj

二、JS运行代码的模式

JS运行代码的模式有两种:
正常模式
        默认情况下代码都运行在正常模式中,在正常模式,语法检查并不严格
        它的原则是:能不报错的地方尽量不报错,这种处理方式导致代码的运行性能较差

严格模式
        在严格模式下,语法检查变得严格,禁止一些语法,更容易报错,提升了性能

在开发中,应该尽量使用严格模式,
    这样可以将一些隐藏的问题消灭在萌芽阶段,同时也能提升代码的运行性能

"use strict" // 全局的严格模式
function fn(){
    "use strict" // 函数的严格的模式
        }

三、类

class 类名 {} // 类名要使用大驼峰命名
const 类名 = class {}  

通过类创建对象
        new 类()

class Person{

}
const p1 = new Person()
console.log(p1 instanceof Person) // true

通过同一个类创建的对象,我们称为同类对象
        可以使用instanceof来检查一个对象是否是由某个类创建
        如果某个对象是由某个类所创建,则我们称该对象是这个类的实例

使用static声明的属性,是静态属性(类属性),静态属性只能通过类去访问

class Person{
    static test = "test静态属性"  //Person.test
}

方法

sayHello = function(){}
sayHello(){}

//静态方法(类方法) 通过类来调用静态方法中this指向的是当前类
static sayHello(){}

在类中可以添加一个特殊的方法constructor,该方法我们称为构造函数(构造方法),构造函数会在我们调用类创建对象时执行

class Person{
    constructor(name, age, gender){
	    this.name = name
	    this.age = age
	    this.gender = gender
    }
}
const p1 = new Person("孙悟空", 18, "男")
const p2 = new Person("猪八戒", 28, "男")

如何确保对象的数据的安全:
        1.私有化数据
                将需要保护的数据设置为私有,只能在类内部使用
        2.提供setter和getter方法来开放对数据的操作
                属性设置私有,通过getter setter方法操作属性带来的好处
                        1. 可以控制属性的读写权限
                        2. 可以在方法中对属性的值进行验证

封装主要用来保证数据的安全
实现封装的方式:
        1.属性私有化 加#
        2.通过getter和setter方法来操作属性
 

class Person {
	//实例使用#开头就变成了私有属性,私有属性只能在类内部访问,需要先声明
	#name
	#age
	#gender

	constructor(name, age, gender) {
		this.#name = name
		this.#age = age
		this.#gender = gender
	}

	sayHello() {
		console.log(this.#name)
	}

	// getter方法,用来读取属性
	getName(){
		return this.#name
	}

	// setter方法,用来设置属性
	setName(name){
		this.#name = name
	}

	getAge(){
		return this.#age
	}

	setAge(age){

		if(age >= 0){
			this.#age = age
		}
	}

	get gender(){
		return this.#gender
	}

	set gender(gender){
		this.#gender = gender
	}
}

多态

在JS中不会检查参数的类型,所以这就意味着任何数据都可以作为参数传递

要调用某个函数,无需指定的类型,只要对象满足某些条件即可

四、继承

        可以通过extends完成继承
        当一个类继承另一个类时,就相当于将另一个类中的代码复制到了当前类中
        继承发生时,被继承的类称为父类(超类),继承的类称为子类
        通过继承可以减少重复的代码,并且可以在不修改一个类的前提对其进行扩展

在子类中,可以通过创建同名方法来重写父类的方法

重写构造函数
重写构造函数时,构造函数的第一行代码必须为super()
 

class Cat extends Animal{
    // 重写构造函数
    constructor(name, age){
        // 重写构造函数时,构造函数的第一行代码必须为super()
        super(name) // 调用父类的构造函数
        this.age = age
    }
            
    sayHello(){
        // 调用一下父类的sayHello
        super.sayHello() // 在方法中可以使用super来引用父类的方法
        console.log("喵喵喵")
    }
}

继承
        通过继承可以在不修改一个类的情况下对其进行扩展
        OCP 开闭原则
                程序应该对修改关闭,对扩展开放

封装 —— 安全性
继承 —— 扩展性
多态 —— 灵活性
 

五、原型

访问一个对象的原型对象
    对象.__proto__
    Object.getPrototypeOf(对象)   推荐这个方法

原型对象中的数据:
    1. 对象中的数据(属性、方法等)
    2. constructor (对象的构造函数)

注意:
    原型对象也有原型,这样就构成了一条原型链,根据对象的复杂程度不同,原型链的长度也不同
        p对象的原型链:p对象 --> 原型 --> 原型 --> null
        obj对象的原型链:obj对象 --> 原型 --> null

作用域链,是找变量的链,找不到会报错
原型链,是找属性的链,找不到会返回undefined

所有的同类型对象它们的原型对象都是同一个,也就意味着,同类型对象的原型链是一样的

JS中继承就是通过原型来实现的,当继承时,子类的原型就是一个父类的实例

Cat继承Animal,cat --> Animal实例 --> object --> Object原型 --> null
                           p对象 --> object --> Object原型 --> null

修改原型

大部分情况下,我们是不需要修改原型对象
        注意:
                千万不要通过类的实例去修改原型
                        1. 通过一个对象影响所有同类对象,这么做不合适
                        2. 修改原型先得创建实例,麻烦
                        3. 危险

应该采取如下做法   通过类的prototype属性,来访问实例的原型

Person.prototype.fly = () => {
    console.log("我在飞!")
}

好处:
        1. 一修改就是修改所有实例的原型
        2. 无需创建实例即可完成对类的修改

原则:
        1. 原型尽量不要手动改
        2. 要改也不要通过实例对象去改
        3. 通过 类.prototype 属性去修改
        4. 最好不要直接给prototype去赋值

Object.hasOwn(对象, 属性名)    静态方法,通过类调用
        用来检查一个对象的自身是否含有某个属性

console.log(Object.hasOwn(p, "sayHello"))

数组

复制必须要产生新的对象

原始值在内存中没有重复值

六、浅拷贝和深拷贝

浅拷贝(shallow copy)
通常对对象的拷贝都是浅拷贝
浅拷贝只对对象的浅层进行复制(只复制一层)
如果对象中存储的数据是原始值,那么拷贝的深浅是不重要
浅拷贝只会对对象本身进行复制,不会复制对象中的属性(或元素)
深拷贝(deep copy)将一个对象从内存中完整地拷贝一份出来,从堆内存中开辟一个新的区域存放新对象
深拷贝指不仅复制对象本身,还复制对象中的属性和元素
因为性能问题,通常情况不太使用深拷贝

浅拷贝基本类型之间互不影响,引用类型其中一个对象改了地址,就会影响另一个对象。深拷贝改变新对象不会影响原对象,他们之间互不影响

const arr3 = structuredClone(arr) // 专门用来深拷贝的方法
const arr2 = arr.slice() // 浅拷贝

... (展开运算符)
        可以将一个数组中的元素展开到另一个数组中或者作为函数的参数传递
        通过它也可以对数组进行浅拷贝

const arr3 = [...arr]
const arr3 = ["唐僧", ...arr, "白骨精"]
function sum(a, b, c) {
	return a + b + c
}

const arr4 = [10, 20, 30]

let result = sum(arr4[0], arr4[1], arr4[2])
result = sum(...arr4)  //这样方便

对象的复制
        Object.assign(目标对象, 被复制的对象)
        将被复制对象中的属性复制到目标对象里,并将目标对象返回

const obj = { name: "孙悟空", age: 18 }
const obj2 = Object.assign({}, obj)  //方法一
const obj2 = { address: "花果山", age: 28 }  
Object.assign(obj2, obj)  //方法二 //这age变为18

也可以使用展开运算符对对象进行复制

const obj3 = { address: "高老庄", ...obj, age: 48 } //这age仍为48,因为在后面

回调函数

        如果将函数作为参数传递,那么我们就称这个函数为回调函数(callback)

高阶函数
        如果一个函数的参数或返回值是函数,则这个函数就称为高阶函数
        为什么要将函数作为参数传递?(回调函数有什么作用?)
                将函数作为参数,意味着可以对另一个函数动态的传递代码

七、闭包

        能访问到外部函数作用域中变量的函数

什么时候使用:当我们需要隐藏一些不希望被别人访问的内容时就可以使用闭包

构成闭包的要件:

        函数的嵌套

        内部函数要引用外部函数中的变量

        内部函数要作为返回值返回

函数的作用域

        在函数创建时就已经确定的(词法作用域)和调用的位置无关

        函数在全局作用域创建,那么它的外层作用域就是全局作用域

        闭包利用的就是词法作用域

闭包的生命周期:

        闭包在外部函数调用时产生,外部函数每次调用都会产生一个全新的闭包

        在内部函数丢失时销毁(内部函数被垃圾回收了,闭包才会消失)

注意事项:

        闭包主要用来隐藏一些不希望被外部访问的内容,这就意味着闭包需要占用一定的内存空间

        相较于类来说,闭包比较浪费内存空间(类可以使用原型而闭包不能),

        需要执行次数较少时,使用闭包

        需要大量创建实例时,使用类

可变参数

        在定义函数时可以将参数指定为可变参数

可变参数可以接收任意数量实参,并将他们统一存储到一个数组中返回

可变参数的作用和arguments基本是一致,但是也具有一些不同点:

        可变参数的名字可以自己指定

        可变参数就是一个数组,可以直接使用数组的方法

        可变参数可以配合其他参数一起使用

        当可变参数和普通参数一起使用时,需要将可变参数写到最后

function fn3(a, b, ...args) {
    console.log(args)
}
fn3(123, 456, "hello", true, "1111")

bind() 是函数的方法,可以用来创建一个新的函数并返回

        bind可以为新函数绑定this

        bind可以为新函数绑定参数,参数排列非数组

箭头函数没有自身的this,它的this由外层作用域决定,也无法通过call、apply和bind修改它的this 箭头函数中没有arguments

解构

注意;很重要,否则认为是数组了

const arr = ["孙悟空", "猪八戒", "沙和尚"]
let a,b,c
;[a, b, c] = arr // 解构赋值
let [d, e, f, g] = ["唐僧", "白骨精", "蜘蛛精", "玉兔精"] // 声明同时解构
let [n1, n2, ...n3] = [4, 5, 6, 7] // 解构数组时,可以使用...来设置获取多余的元素
//n1=4,n2=5,n3=[6,7]
function fn(){
	return ["二郎神", "猪八戒"]
}
let [name1, name2] = fn()

//对象的解构
const obj = { name: "孙悟空", age: 18, gender: "男" }
let {name:a, age:b, gender:c, address:d="花果山"} = obj
console.log(a, b, c, d)   //修改变量名字为a,b,c

八、对象的序列化(JSON)

        JS中的对象使用时都是存在于计算机的内存中的
        序列化指将对象转换为一个可以存储的格式
在JS中对象的序列化通常是将一个对象转换为字符串(JSON字符串)
序列化的用途(对象转换为字符串有什么用):
        对象转换为字符串后,可以将字符串在不同的语言之间进行传递
        甚至人可以直接对字符串进行读写操作,使得JS对象可以不同的语言之间传递
用途:
        1. 作为数据交换的格式
        2. 用来编写配置文字
如何进行序列化:
        在JS中有一个工具类 JSON (JavaScript Object Notation) JS对象表示法
        JS对象序列化后会转换为一个字符串,这个字符串我们称其为JSON字符串  

        也可以手动的编写JSON字符串,在很多程序的配置文件就是使用JSON编写的
编写JSON的注意事项:
        1. JSON字符串有两种类型:
                JSON对象 {}
                JSON数组 []
        2. JSON字符串的属性名必须使用双引号引起来
        3. JSON中可以使用的属性值(元素)
                数字(Number)
                字符串(String) 必须使用双引号
                布尔值(Boolean)
                空值(Null)
                对象(Object {})
                数组(Array [])
        4. JSON的格式和JS对象的格式基本上一致的
        注意:JSON字符串如果属性是最后一个,则不要再加,

利用JSON可以完成深复制

const obj = {
	name: "孙悟空",
	age: 18,
}
const str = JSON.stringify(obj) //JSON.stringify() 可以将一个对象转换为JSON字符串
const obj2 = JSON.parse(str) // JSON.parse() 可以将一个JSON格式的字符串转换为JS对象
console.log(str)  //{"name":"孙悟空","age":18}
const str4 = '["hello", true, []]' //也可以是JSON数组
const arr4 = JSON.parse(str4)  //这个是数组

// 利用JSON来完成深复制
const str = JSON.stringify(obj)
const obj4 = JSON.parse(str)
const obj5 = JSON.parse(JSON.stringify(obj))

九、Map

        Map用来存储键值对结构的数据(key-value)
        Object中存储的数据就可以认为是一种键值对结构
        Map和Object的主要区别:
                Object中的属性名只能是字符串或符号,如果传递了一个其他类型的属性名,JS解释器会自动将其转换为字符串
                Map中任何类型的值都可以称为数据的key

创建:
    new Map()

属性和方法:
    map.size() 获取map中键值对的数量
    map.set(key, value) 向map中添加键值对
    map.get(key) 根据key获取值 
    map.delete(key) 删除指定数据
    map.has(key) 检查map中是否包含指定键
    map.clear() 删除全部的键值对

const map = new Map()
map.set("name", "孙悟空")
map.set(obj2, "呵呵")
map.set(NaN, "哈哈哈")
console.log(map.get("name"))
console.log(map.get(obj2))
console.log(map.has(NaN))
map.clear()

map.keys() - 获取map的所有的key
map.values() - 获取map的所有的value

// 将map转换为数组
const arr = Array.from(map) // [["name","孙悟空"],["age",18]]
const arr = [...map]  (推荐用这个)

const map2 = new Map([    //创建
	["name", "猪八戒"],
	["age", 18],
	[{}, () => {}],
])
onsole.log(map2)
//遍历map
for (const [key, value] of map) {
    console.log(key, value)  ///解构遍历
}
map.forEach((key, value)=>{
	console.log(key, value)
})
for(const key of map.keys()){
    console.log(key)
}

十、Set

        Set用来创建一个集合
        它的功能和数组类似,不同点在于Set中不能存储重复的数据

使用方式:
创建
        new Set()
        new Set([...])

方法
    size 获取数量
    add() 添加元素
    has() 检查元素
    delete() 删除元素

const set = new Set()

// 向set中添加数据
set.add(10)
set.add("孙悟空")
set.add(10)  //加不进去
console.log(set)

for(const item of set){
    console.log(item)
}

const arr = [...set]  //转为数组
console.log(arr)
//数组去重可以这样简便做
const arr2 = [1,2,3,2,1,3,4,5,4,6,7,7,8,9,10]
const set2 = new Set(arr2)
const arr = [...set2]
console.log(arr)

日期的格式化

十一、包装类

在JS中,除了直接创建原始值外,也可以创建原始值的对象,不建议用,这是JS自己用的

        通过 new String() 可以创建String类型的对象

        通过 new Number() 可以创建Number类型的对象

        通过 new Boolean() 可以创建Boolean类型的对象

        但是千万不要这么做,这么做不便于随时随地类型转换,抹杀了js语法的最大特点

JS中一共有5个包装类:  我们应该用下面的

        String --> 字符串包装为String对象

        Number --> 数值包装为Number对象

        Boolean --> 布尔值包装为Boolean对象

        BigInt --> 大整数包装为BigInt对象

        Symbol --> 符号包装为Symbol对象

        通过包装类可以将一个原始值包装为一个对象,当我们对一个原始值调用方法或属性时,JS解释器会临时将原始值包装为对应的对象然后调用这个对象的属性或方法 (自动装箱)

        由于原始值会被临时转换为对应的对象,这就意味着对象中的方法都可以直接通过原始值来调用


总结

本节是对以前笔记的补充。
 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值