# 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 对象;它是运行时动态分配内存的,因此存取速度较慢。就像仓库, 需要索引 image-20200713144353620 ## 分支语法 ### 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 属性是从它的原型继承来的 ![image-20201107122039097|655x801](https://img.081024.xyz/20230321055400.png) ### 使用 把属性或者方法挂到构造函数的原型上, 使得所有实例都能共享该属性或方法 ```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 ``` * 模块脚本默认是延迟执行的,类似于 `