# 1-js
JavaScript 的组成:
* ECMAScript: 核心, 语言规范
* DOM: 文档对象模型
* BOM: 浏览器对象模型
## 数据类型
* 基本数据类型: string number Boolean null undefined symble bigInt
* 复杂数据类型: array object function date regexp set map Promise
null 和 undefined
* null 表示一个值被定义了,定义为"空值"
* undefined 表示根本不存在定义
### 基本数据类型是按值传递
> 传递的是===副本===, 不会修改原始数据
```js
function modifyValue(x) {
x = 10; // 修改副本
console.log(x); // 10
}
let a = 5;
modifyValue(a);
console.log(a); // 5,原始值未改变
```
* 函数中不会修改传递进来的基本类型
* 需要修改, 要么全局变量
* 函数返回修改后值, 调用函数重新赋值
* 用对象/数组包装
### 复杂数据类型是按共享传递
> 传递的是内存地址的===副本===
> 如果是修改对象的属性, 会影响原始数据
> 如果是赋值, 不会影响原始数据
* 修改属性
```js
function modifyProperty(obj) {
obj.value = 10; // 修改对象的属性
console.log(obj.value); // 10
}
let myObj = { value: 5 };
modifyProperty(myObj);
console.log(myObj.value); // 10,原始对象被修改
```
* 重新赋值
```js
function reassignReference(obj) {
obj = { value: 10 }; // 重新赋值引用副本
console.log(obj.value); // 10
}
let myObj = { value: 5 };
reassignReference(myObj);
console.log(myObj.value); // 5,原始对象未被修改
```
## 栈, 堆
**栈**(stack)中主要存放一些**基本类型的变量和对象的引用**, 其优势是存取速度比堆要快,但缺点是存在栈中的数据大小与生存期必须是确定的,缺乏灵活性,**后进先出**, 就像盘子
**堆**(heap )多用于复杂数据类型(**引用类型**)分配空间,例如数组对象、object 对象;它是运行时动态分配内存的,因此存取速度较慢。就像仓库, 需要索引
## 分支语法
### if else
### switch-case
```js
switch (expression) {
case value1:
// 当 expression 的结果与 value1 匹配时,执行此处语句
[break;]
case value2:
// 当 expression 的结果与 value2 匹配时,执行此处语句
[break;]
...
case valueN:
// 当 expression 的结果与 valueN 匹配时,执行此处语句
[break;]
[default:
// 如果 expression 与上面的 value 值都不匹配,执行此处语句
[break;]]
}
```
* break 作用:结束该 switch 语句,所以一般情况下要加上,如果不加上则会发生穿透
* 穿透:从上一个 case 代码快执行到下一个 case 代码快
* 使用场景: 多中值执行同个代码块, 例如:
```js
case 1:
case 2:
console.log("1或2都打印")
break
```
### 三元表达式
```js
// 如果表达式成立则执行代码1,否则执行代码2
表达式?代码1:代码2
```
* 一元运算符:只能操作一个值 `++` `--` `!`
* 二元运算符:操作两个值 `1 + 1` `1 > 0`
* 三元运算符:操作三个值
## 循环语法
### for
```js
for(i; i<10; i++){
}
```
### while
```js
while(条件 true/false){
循环体/需要重复执行的代码;
}
```
### do-while
```js
do{
循环体
}while(条件)
```
* do-while 和 while 实现的循环其实是一样的,只有一个不同点:do-while 循环不管怎样先执行一次循环体代码,然后再判断条件
## 创建对象的三种方式
### 字面量
> 直接赋值
```js
const obj = {name:"悟空",height:100,age:5000}
```
* 简单粗暴
* 不适合创建多个同样类型的对象的场景
### 工厂函数
> 函数返回
```js
function createPerson(name, age, height) {
return {
name: name,
age: age,
height: height
}
}
```
1. 容易理解
2. 失去`血缘关系`,无法简单分辨对象的特征
### 构造函数
> new
```markdown
构造函数的工作原理:
1. 开辟内存空间(堆)
2. 产生内部对象:this
3. 初始化属性(执行构造函数)
4. 返回对象的内存地址
```
```js
// 1 声明函数
function CreateStudent(name, age) {
// 2 通过 this 赋值
this.name = name;
this.age = age;
}
// 3 通过 new 来创建对象
const obj = new CreateStudent("悟能", 83);
console.log(obj);
```
优点:
1. 可以方便的创建对象
2. 拥有血缘关系
3. 还有后续更多的优势
缺点:
1. 可能会浪费内存空间:在构造函数中给属性增加方法,会导致每个对象都会保存一份方法
解决: 将方法单独存储一份,让对象中的属性指向方法
```js
// 但造成了污染全局变量的问题
function myLog(){
console.log(1)
}
function Test(){
this.log= myLog
}
```
构造函数的本质就是函数, 但与普通函数不同:
1. 构造函数名字是大驼峰法(**首字母大写**)
2. 会在函数体中直接使用this(this就代表对象自己)
3. 构造函数通常不需要给返回值:默认有返回值,是一个对象
## 定义函数的三种方式
### 声明式
> function 函数名 (){}
```js
fn()
function fn(参数..){
console.log("这是函数声明")
return 返回值
}
```
* ==函数声明可以先调用,再声明==
### 表达式
> const 函数名=function (){}
```js
const fn = function() {
console.log("这是函数表达式")
}
fn()
```
* ==函数表达式必须先声明,再调用==
### 构造函数
> new Function ()
```js
// Function是系统内置的一个构造函数
// 创建函数:new Function(动态参数,都需要使用引号,最后一个参数代表函数体)
```
```js
const fn1 = new Function("a1", "a2", "alert(a1+a2)")
fn1(1,2)
```
## 高阶函数
### arguments 关键字
> 在函数内部使用, 获取所有的实参
```js
fucntion add(){
let sum = 0
for (let i = 0; i < arguments.length; i++){
let value = arguments[i]
if(isNaN(value)){
return false
}
sum += Number(value)
}
return sum
}
```
## 原型 prototype
> 当函数定义完毕,系统就会自动创建原型
* 原型本质是一个对象,理解为 `定义构造函数` 的时候, `JavaScript` 自动帮我们添加的
* 所有构造函数的实例,共享一个原型
* 原型上一般用来挂载函数
### 实例的`__proto__`属性
* 实例的 `__proto__` 属性 等于 构造函数的 `prototype`
* 实例的 `__proto__` 是非标准属性, 只是为了方便我们开发的时候查看数据
实际用来获取原型的方法
```js
const obj = {}
const proto = Object.getPrototypeOf(obj)
```
### 原型链
* ==所有的构造函数都是Function的实例==
* Object的顶端是 null `console.log(Object.prototype.__proto__ === null)`
* student 实例的 constructor 属性是从它的原型继承来的

### 使用
把属性或者方法挂到构造函数的原型上, 使得所有实例都能共享该属性或方法
```js
function CreateStu(name,age){
this.name=name
this.age=age
}
const stu1 = new CreateStu('张三', 18)
Object.getPrototypeOf(stu1).say = function(){
console.log('hello')
}
/**
//或者
stu1.__proro__.say = function(){
console.log('hello')
}
// 或者
stu1.constructor.prototype.say = function(){
console.log('hello')
}
*/
const stu2 = new CreateStu('李四', 20)
stu2.say()
```
## 作用域
> 作用域有三种: 全局作用域global, 局部作用域local, 块级作用域block
* 全局作用域: 函数外部的作用域
* 全局变量: 可以在任意地方使用, 如果页面不关闭,那么变量所占用的内存就不会释放,就会占空间,消耗内存
* 局部作用域: 函数内部的作用域
* 局部变量:在函数内部使用
* 块级作用域: 使用一对大括号包裹的一段代码
* es6中新增 let const
* ==var声明的变量会产生变量提升,没有块的概念, 可以跨块访问, 不能跨函数访问==
* let 和 const定义的变量是有块级作用域的,只能在当前块作用域访问
## 函数的四种调用模式与this
> 根据函数内部this的指向不同,可以将函数的调用模式分成4种
1. 函数调用模式
2. 方法调用模式
3. 构造函数调用模式
4. 上下文调用模式(借用方法模式)
### 函数调用模式
如果一个函数不是一个对象的属性时,就是被当做一个函数来进行调用的。此时this指向了window
```js
function fn(){
console.log(this) // 指向window
}
fn()
```
### 方法调用模式
当一个函数被保存为对象的一个属性时,我们称之为一个方法。当一个方法被调用时,this被绑定到当前对象
```js
const obj = {
sayHi:function(){
console.log(this) //在方法调用模式中,this指向调用当前方法的对象。
}
}
obj.sayHi()
```
* 简写
```js
const obj = {
sayHi(){
console.log(this)
}
}
obj.sayHi()
```
### 构造函数调用模式
如果函数是通过new关键字进行调用的,此时this被绑定到创建出来的新对象上
```js
function Person(){
console.log(this)
}
Person() //this指向window
let p = new Person() //this指向Person
```
### 方法借用模式/上下文模式
> apply, bind, call, 可以改变this
```js
/** apply 、bind、call 这三个函数的第一个参数都是 this 的指向对象,第二个参数差别就来了:
** apply 所有参数都必须放在一个数组里
** bind 参数用逗号隔开,返回是函数需要执行
** call 参数用逗号分隔
apply call 立刻调用, bind 返回函数可以延迟执行
**/
```
```js
const name = '小王', age=17
const obj = {
name: '小张',
objAge: this.age,
myFun: function(fm, t){
console.log(this.name + '年龄' + this.age, "来自" + fm + "去往" + t)
}
}
const db = {
name = '德玛',
age: 99
}
obj.myFun.apply(db,['成都','上海']); // 德玛 年龄 99 来自 成都去往上海
obj.myFun.bind(db,'成都','上海')(); // 德玛 年龄 99 来自 成都去往上海, bind返回的是个函数
obj.myFun.call(db,'成都','上海'); // 德玛 年龄 99 来自 成都去往上海
```
## this
* 单独使用,`this` 指向全局对象
```js
console.log(this) // window
```
* 在函数内部,`this` 的指向在函数定义的时候是不能确定的,**只有函数执行的时候才能确定**
* 谁调用它, 就指向谁
```js
function show(){
console.log(this) // window
}
show()
```
```js
const a = 18
const obj = {
a: 19,
b: {
a: 20,
c: function () {
console.log(this.a)
}
}
}
obj.b.c() // 20
```
* 在方法中,`this` 指代该调用方法的对象
```js
const obj ={
name:"小白",
say:function(){
console.log(this)
}
}
obj.say() // {name:"小白",say:f}
```
* 箭头函数: 箭头函数自己没有this, this向上级找
```js
//箭头函数没有funtion关键字, 所以里面没有this关键字
//浏览器断点调试,可以发现this是undefined
//箭头函数中非要用this, this向上级找
let obj = {
fn1: function () {
console.log('我是function函数输出的this: ', this)
},
fn2: () => console.log('我是箭头函数输出的this: ', this)
}
obj.fn1() //输出obj, function函数中, 谁调用函数, this指的就是谁
obj.fn2() //输出windows, 箭头函数不会产生this, 向上找, obj是个对象, 再向上就是window
```
* 借调函数不能改变箭头函数里面的this
* 要改变this, 可以通过改变它的上级
## 判断对象属于哪个构造函数
> instanceof, constructor, typeof
```js
let arr = [1, 2, 3]
console.log(arr instanceof Array) //true
```
```js
console.log(arr.constructor === Array) //true
//arr没有constructor, 就向上到它的原型找 arr.__proto__.constructor
```
```js
//typeof 可以找到基础数据类型, 数组,对象, 函数都不准确, 输出object
console.log(typeof (a)); //number
console.log(typeof (arr)); //object
```
## 对象的键使用变量
```js
//在键使用中括号的时候, 可以设为变量
const key = test
const obj = {[key]: 'a'}
console.log(obj) // {test: 'a'}
```
## 内置对象的方法
### Array
```js
let c = a.concat(b) // 连接两个或多个数组
let b = [1, 2, 3].join() // 拼接成字符串
a.shift() // 删除第一个元素, 返回第一个元素的值
a.unshift('the', 'start') // 往头部插入一个或多个, 返回新的长度
a.pop() // 删除最后一个元素, 返回被删除元素的值
a.push('the','end') // 往最后插入一个或多个, 返回新的长度
a.splice(index, howmany , item1 , ..... , itemX) // 删除或添加元素.
// 第一个参数是开始的位置
// 删除的数量, 不写则删除index到结尾, 可以是0
// 后面的是要插入的元素
// 如果删除了元素, 则返回的是含有被删除元素的数组
let b = a.slice(start, end) // 切片, 截取一部分, 浅拷贝
let value = a.find((value, index) => return value > 18) // 返回第一个满足条件的元素, 没有符合的返回undefined
let index = a.findIndex((value, index) => return value > 18) // 返回第应该满足条件的元素的下标, 没有符合的返回-1
let index = a.indexOf('test', 0) // 从头开始找, 是否能找到该元素, 找到返回第一次出现的下标, 没有返回-1. 第二个参数是开始找的位置, 默认是下标0可以不写
let index = a.lastIndexOf('test', a.length - 1) // 从结尾往前找, 是否能找到该元素, 找到返回第一次出现的下标, 没有返回-1. 第二个参数是开始找的位置, 默认是数组的长度减1可以不写
let boolean = a.some((value, index) => value > 18) // 满足条件返回true
a.sort() // 排序, 小到大
a.sort((a, b) => a - b) // 小到大
a.sort((a, b) => b - a) // 大到小
a.reverse() // 颠倒顺序
// 遍历
a.forEach((value, index) => {
console.log(`下标${index}, 值为${value}`)
})
// 过滤
let b = a.filter((value, index)=>{
return value > 0
})
// 映射
let b = a.map((value, index)=>{
return value + 1
})
```
### string 方法
```js
let len = str.length()
let newStr = str.replace('a', 'b') // 字符替换替换
let uStr = str.toUpperCase() // 全大写
let lStr = str.toLowerCase() // 全小写
let index = str.indexOf('a') // 某个字符首次出现的位置, 没有找到返回 -1
let arr = "a,b,c,d,e".split(","); // 转数组, 按逗号分隔
str.trim() // 移除两端空格
let str2 = str.substring(indexStart [, indexEnd]) // 截取部分
```
### math 方法
```js
Math.ceil(数字) // 向上取整
Math.floor(数字) // 向下取整
Math.max(数字1,数字2,...) // 求最大值
Math.min(数字1,数字2,...) // 求最小值
Math.random() // 返回 (0,1) 之间的小数
Math.abs(数字) // 绝对值
```
### regexp 方法
```js
// 创建
let re = new RegExp("\\w+");
let re = /\w+/;
let str="Is this all there is?";
let patt1=/is/gi; // 修饰符, g全局, i忽略大小写
part1.test(str) // test方法, 满足时返回true
```
## 编码解码
```js
encodeURI(URI) // 对整个 URI 进行编码, 不会编码 URI 中的保留字符(如 :、/、?、&、= 等)
decodeURI(encoded)
const uri = 'https://example.com/path?name=John Doe';
const encodedURI = encodeURI(uri);
console.log(encodedURI); // 输出: https://example.com/path?name=John%20Doe
```
```js
encodeURIComponent(str) // 对 URI 的组件(如查询参数)进行编码
decodeURIComponent(encodedURI)
const param = 'name=John Doe';
const encodedParam = encodeURIComponent(param);
console.log(encodedParam); // 输出: name%3DJohn%20Doe
```
## 闭包
> 函数能够记住并访问它被创建时的作用域,即使这个函数在作用域之外执行
```js
function outer() {
let x = 10; // outer 函数的局部变量
function inner() {
console.log(x); // inner 函数访问 outer 函数的局部变量
}
return inner;
}
const closureFunc = outer(); // outer 函数执行完毕,但 x 仍然被 inner 函数记住
closureFunc(); // 输出: 10
```
1. **函数**:闭包的核心是一个函数(如 `inner`)。
2. **作用域**:这个函数能够访问它被创建时的作用域(如 `outer` 函数中的 `x`)。
3. **记忆**:即使外部函数(如 `outer`)已经执行完毕,闭包仍然可以访问它的作用域。
闭包可以用于:
* 创建私有变量。
* 实现函数柯里化(Currying)。
* 柯里化: 一个函数 f(a, b, c) 转换为 f(a)(b)(c) 的形式
* 在异步编程中保留上下文。
缺点:
* 可能带来内存泄漏
* 额外的内存开销和性能损耗
解决:
* 及时释放 = null
使用:
* 防抖
* 下载任务
## reduce
会循环当前的数组, 侧重于 "滚雪球"
```js
// 语法
数组.reduce((val, item)=>{return ...}, 初始值)
// 把每次 函数体的结果, 返回到val里, 参与下次滚雪球
// 始始值只在第一次时使用
const arr = [1, 2, 3, 4, 5]
const total = arr.reduce((val, item)=>{return val + item}, 0)
console.log(total)
```
## DOM
### script 标签进阶
#### type=importmap 导入映射
```js
```
* **`
```
```js
```
* 模块脚本默认是延迟执行的,类似于 `