JS 的组成
ECMAScript
JavaScript 的语法和核心部分
定义了语言的基本结构、语法规范、数据类型、操作符、语句、内置对象以及基本的程序设计概念
DOM
DOM 是一种接口,通过 DOM API ,JavaScript 可以与网页内容进行交互,操作节点元素
BOM
主要处理浏览器窗口及其相关功能,BOM 的核心是 widow 对象,提供了与浏览器交互的方法和属性
异步编程机制
事件循环、回调函数、Promise、Generator 函数,async/await 等
库和框架
JQuery、React、Vue.js 等
Web API
除了 DOM、BOM、浏览器还提供了其他 API 如 Fetch API 用于网络请求、Web Storage API 进行本地存储等。
Void 0 和 Undefined
JavaScript 中 undefined 能作为变量名, Undefined 不再此列 它可以作为变量名被重新定义
void 0 可以让我们获得一个 纯正的 Undefined
Script 标签的 async 和 defer 的区别
script 标签 src 加载文件的时候 可以添加 async和defer
当 Script 没有 async 和 defer 这俩个属性时
- 读到 script, 浏览器会立即执行并加载指定的脚本,这个过程会阻塞 HTML 的解析
script 添加 async
- 异步执行 script 标签 加载和渲染 HTML 的过程和 script 的加载并行进行,但执行脚本时依旧会阻塞 HTML 的解析和渲染,async 不适合引入与 dom 交互的 js
script 添加 defer
- 加载和渲染 HTML 的过程和 script 的加载并行进行, 但与 async 不同的是 script 脚本的执行是在,所有 HTML 元素解析完成后才会执行.
JS 的数据类型
- 基本数据类型
- 复杂数据类型
两钟类型的区别是,基本数据类型存储于栈空间,复杂类型存储在堆空间,栈空间存储引用地址
基本数据类型
String
Number
Boolean
Symbol
Null
Null 的类型为 object 是 bug
Undefined
BigInt
复杂数据类型
- Object
- Array
- Function
- Date
- RegExp:【】
- Map
- Set
总结
- 有 8 中数据类型
- 简单类型的值存放在栈中,在栈中存放的是对应的值
- 引用类型对应的值放在堆中,在栈中存放的是指向堆内存的地址
数组中的常用方法
增
方法 | 说明 |
---|---|
push() | 接收任意数量的参数,添加到数组末尾,返回新的数组长度 |
unshift() | 接收任意数量的参数,添加到数组开头,返回新的数组长度 |
splice() | 传入三个参数,分别是开始位置、0(要删除的元素数量)、插入的元素,返回空数组 |
concat() | 进行数组的拼接 |
删除
方法 | 说明 |
---|---|
pop | 删除数组的最后一项,返回被删除的项 |
shift | 删除数组的开头一项,返回被删除的项 |
splice | 传入两个参数,分别是开始位置,删除元素的数量,返回删除元素的数组 |
slice | 创建一个包含原有数组中一个或多个元素的新数组,不会影响原始数组 |
改
方法 | 说明 |
---|---|
splice | 传入三个参数,分别是开始位置,要删除元素的数量,要插入的任意多个元素,返回删除元素的数 |
查
方法 | 说明 |
---|---|
indexOf() | 返回要查找元素在数组中的位置,没有返回-1 |
includes() | 返回要查找的元素在数组中的位置,返回true 和flase |
find() | 返回第一个匹配的元素 |
排序方法
方法 | 说明 |
---|---|
reverse | 将数组进行反转 |
sort | 接收一个比较函数,判断哪个值应该排在前面 |
转换方法
方法 | 说明 |
---|---|
join | 接受一个参数作为分隔符,返回包含所有项的字符串 |
迭代方法
方法 | 说明 |
---|---|
some | 遍历数组,一个为 ture 结果为 ture |
every | 遍历数组,一个为 false 结果为 false |
forEach | 遍历数组每一项,没有返回 |
filter | 遍历数组,所有结果为 true 的项,返回为一个新数组 |
map | 遍历数组,返回每次函数调用的结果构成的数组 |
字符串的常用方法
方法 | 说明 |
---|---|
增 | —————————————— |
concat | 将一个或多个字符串拼接成一个新字符串 |
删除 | —————————————— |
slice | 返回调用它们的字符串的一个子字符串,而且都接收一或两个参数。 |
substr | |
substring | |
改 | —————————————— |
trim、trimLeft、trimRight | 删除前、后或前后所有空格符,再返回新的字符串 |
repeat | 将字符串复制。 .repeat(2)进行复制两次 |
padEnd、padStart | 小于指定长度,进行后或前的补全。.padStart(6, "a")) ‘0000xx’, 数字类型记得使用 new String()包裹 |
toLowerCase、 toUpperCase | 大小写转化 |
查 | —————————————— |
chatAt | 返回指定位置的字符,接收整数参数 |
indexOf() | 索引给定的值,返回下标,没有返回-1 |
startWith()、includes() | 从字符串中搜索传入的字符串,返回布尔值 |
转换方法 | —————————————— |
split | 按照给定的,分隔符,拆分成数组 |
模板匹配方法 | —————————————— |
replace | 替换,接收两个参数,第一个参数为匹配的内容,第二个参数为替换的元素 |
search | 找到则返回匹配索引,否则返回 -1 |
match | 接收一个参数,可以是一个正则表达式字符串,也可以是一个RegExp 对象,返回数组 |
JavaScript 中类型转换机制
- 强制转换(显示转换)
- 自动转换(隐式转换)
强制转换
- Number
- String
- Boolean
- parseInt
自动转换
- 比较运算符:大于、小于、等等于、不等于
- 算数运算符:加、减、乘、除、%
== 和 ===区别,分别在什么情况使用
- ==具有隐式转化,在比较中会先进行类型转换,再确定操作数是否相等,会带来一些违反直觉的结果
- === 全等: 进行严格比较,不会做类型转换
深浅拷贝,如何实现深拷贝
浅拷贝
- 浅拷贝拷贝对象,只会拷贝对象的最外层,内部的引用类型拷贝的是引用地址,复杂类型依旧共享引用
深拷贝
- 深拷贝拷贝对象,会对对象进行完全拷贝,内部的引用类型也会进行深层的递归拷贝。
深浅拷贝方式
浅拷贝
Object.assign()
jslet obj1 = { a: 1, b: { c: 2 } }; let obj2 = Object.assign({}, obj1);
扩展运算符
jslet obj1 = { a: 1, b: { c: 2 } }; let obj2 = { ...obj1 };
深拷贝
手写递归函数
jsconst myDeep = (obj) => { if (obj === 'null' || typeof obj !== 'object') { return obj; } // 接收返回的引用类型的数据 let newObj = Array.isArray(obj) ? [] : {}; for (let key in obj) { if (obj.hasOwnProperty(key)) { // 递归调用自身 newObj[key] = myDeep(obj[key]); } } return obj; };
什么是闭包
闭包是指一个函数可以访问另一个函数作用域内的变量。当一个函数嵌套在另一个函数中时,内部函数可以访问外部函数的变量,即使外部函数已经返回了。这种情况下,内部函数形成了一个闭包,它保留了外部函数的作用域链并可以继续访问这些变量。闭包常常用于实现函数的封装和私有化,以及在回调和事件处理等场景下的数据共享与传递。
闭包的应用场景包括但不限于:
保护变量:通过使用闭包,可以确保变量不会被外部直接修改,增加了数据的安全性。
计数器和累加器:通过闭包,可以在函数外部保存一个内部状态,并在每次调用内部函数时修改该状态。这一特性可用于实现计数器、累加器等功能。
延迟执行和回调函数:将函数作为返回值,可以实现延迟执行或者在特定条件满足时回调执行。
实现数据私有化:通过使用闭包封装一些私有数据和方法,可以避免外部访问和修改,实现数据的私有化和保护。
函数颗粒化
延长作用域链
创建私有作用域,延长变量生命周期
判断数据类型的方式
instanceof
typeof
constanct
Object.definePropoty.toString.call()
isArray()
this 关键字
在 JavaScript 中 this 是一个关键字,用于表示执行当前函数的当前对象,也称为执行上下文。不同长江 this 有着不同的值和含义
- 全局环境下:this 指向 window
- 普通函数中:this 指向全局对象 window
- 作为对象的方法调用时,this 表示该对象
- 在构造函数中,this 指向实例化的对象
- 箭头函数:箭头函数没有自己的 this,箭头函数的 this 指向的是创建时所在的作用域的 this
原型和原型链
每个函数都有一个特殊的属性 prototype 也就是我们常说的原型对象
当我们访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾
作用域链
JavaScript 中作用域
- 全局作用域
- 函数作用域
- 块级作用域
JS 垃圾回收机制
垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存,腾出所占用的内存
垃圾回收机制策略:
引用计数法(以前)
原理
- 引用一次对象的引用数+1 取消引用一次,引用数-1 引用为 0 被垃圾回收
不足
- 循环引用,引用次数永远不可能为 0,无法回收,内存无法释放。
标记清除法(现代)
原理
标记阶段即为所有活动对象做上标记,清除阶段则把没有标记(也就是非活动对象)销毁
V8 引擎用的标记清除,做了一些优化
- 新生代(新,小,存活时间短):针对新生区采用并行回收
- 老生代(老,大,存活时间长):采用增量标记与惰性回收
不足
- 空闲内存块是不连续的,容易出现很多空闲内存块
- 可以使用 标记整理算法将不需要清理的对象合并起来
JS 内存泄漏
内存泄漏:由于疏忽或错误造成程序未能释放已经不再使用的内存(垃圾回收机制,回收不了,或者不知道什么时候进行回收)
1. 意外的全局变量
2. 未清除的定时器、
3. 没有清理对DOM元素的引用童谣造成内存泄漏
4. 闭包
5. addEvelistener监听的时候,没有进行removeEventListener注销事件监听
6. 变量的循环引用
- 意外的全局变量
- 未清除的定时器
- 没有清理对
DOM
元素的引用同样造成内存泄露 - 闭包
addEventListener
监听的时候,在不监听的情况下使用removeEventListener
取消对事件监听- 变量的循环引用
事件循环
1. 事件循环是JavaScript运行时环境中的一个核心概念,负责协调同步任务和异步任务的执行。
2. JavaScript是单线程的,一次只能执行一个任务。
3. JavaScript任务分为同步任务和异步任务,
4. 异步任务可以分为微任务(Promise"中的回调函数"、Observer)和宏任务(定时器延时器,网络请求,I/O 操作),微任务优先级高于宏任务。
5. JavaScript执行全局代码,同步任务直接在执行栈中执行,遇到异步任务,会将它们放入相应的微任务或宏任务队列等待时机成熟(由avaScript引擎会管理这些队列)
6. 同步代码执行完成后,会执行微任务队列中的任务,直到为空
7. 如果微任务队列为空,会执行宏任务,执行完一个宏任务,会去微任务队列查看是否有任务,没有继续执行下一个宏任务,循环执行。
8. 当所有任务执行完毕时,JavaScript 引擎会再次开始执行同步任务,如此循环往复。
事件委托
利用了冒泡机制
- 事件委托又叫事件代理,原理就是利用了事件冒泡的机制来实现,
- 把子元素的事件绑定到父元素上
好处
- 提升性能
- 减少事件的绑定,
- 减少了内存的占用
怎么阻止事件冒泡
- event.stopPropagation()
- addEventListener()事件绑定,有三个参数
- addEventListener('click',函数名,true/false) 是 false 事件冒泡 ,true 事件捕获
示例
<body>
<div>我是钟明楼</div>
<div class="box">
<p>1</p>
<p>2</p>
<p>3</p>
<p>4</p>
</div>
</body>
<script>
let box = document.querySelector('.box');
box.addEventListener('click', function (e) {
// 判断生效的是不是P标签
// 转字符串小写是因为tagName是大写的
if (e.target.tagName.toLowerCase() === 'p') {
console.log(e.target.innerText);
}
});
</script>
原型
什么是原型
函数有个 prototype 属性,这就是构造函数的原型,也被称为原型对象,每个对象有隐式原型** proto ** 它与构造函数的 prototype 指向同一个对象。
作用
原型的存在是为了实现面向对象,原型的存在避免了类型的丢失
原型链
什么是原型链
每个对象都有个一个隐式原型 _ _ proto _ _ 指向他的构造函数的 prototype,这个构造函数的 prototype 也是一个对象也有自己的_ _ proto _ _ 层层向上 直到达到 Object.prototype,Object.prototype.** proto ** 指向 null,作为作用域链的最后一层
例子
当在访问一个属性的时候,会到对象本身去找,没有会到原型中去查找,原型中没有会到原型的原型中查找,直到找到该属性或是到原型链的最后一个层,Object.prototype.** proto ** 指向的 null
<script>
// 实例化的对象有隐式原型 __proto__ 指向的是 ML的原型对象(prototype)
console.log(ml.__proto__); // {age: 24, eat: ƒ}
// 构造函数的 原型 | 原型对象 (是对象类型的也叫原型对象)
console.log(ML.prototype); // {age: 24, eat: ƒ}
// 构造函数原型 与 实例化的对象的隐式原型,__proto__是指向的同一个对象
console.log(ml.__proto__ === ML.prototype); // true
console.log(ML.prototype.__proto__ === Object.prototype); // true
// Object.prototype 的原型对象,原型链的结尾,它的__proto__为null。作为原型链的最后一层
console.log(Object.prototype.__proto__ === null); // true
// 构造函数的原型对象,也是一个对象,也有隐式的__proto__,指Object.prototype,Object.prototype 的__proto__指向null 是原型链的结尾
</script>
New 操作符做了什么
new 操作符做了什么
- 先创建了一个空对象
- 把空对象的** proto **,指向构造函数的 prototype
- 将构造函数的 this,绑定到新的空对象身上
- 根据构造函数返回的类型判断,如果是值类型,则返对象本身,如果是引用类型,则返回这个引用类型
手写 new 的功能
<script>
function Parent() {
this.name = '明楼';
return null;
}
Parent.prototype.age = 26;
const myNew = function (fn) {
if (fn === null || !fn instanceof Function) return;
// 创建一个空对象
let obj = Object.create(constructor.prototype);
// 将构造函数的this绑定到空对象上
const result = fn.apply(obj);
// 将空对象的__proto__ 指向 构造函数的prototype
obj.__proto__ = fn.prototype;
// 构造函数返回为空或值类型,返回对象本身, 返回引用类型,则返回这个引用类型
return typeof result === 'object' && result !== null ? result : obj;
};
// 自定义函数
let obj = myNew(Parent);
console.log('obj', myNew(Parent));
// 类型判断为 true
console.log('instanceof', obj instanceof Parent); // ture
// new 关键字
let obj2 = new Parent();
console.log('obj2', obj2);
// 两个实例化的对象的 proto
console.log(obj2.__proto__ === obj.__proto__); // ture
</script>
实例
function ML() {
this.name = '明楼';
this.gender = '男';
}
// 在构造函数中添加 属性和方法付
ML.prototype = {
age: 24,
};
// 先创建了一个空对象ml,进行实例化
let ml = new ML();
// 把空对象的__ proto __,指向构造函数的prototype
console.log(ml.__proto__ === ML.prototype); // true
// 将构造函数的this,绑定到新的空对象身上
console.log(ml.name); // 明楼
// 返回为空或者为值类型,则返回对象本身,如果返回类型为引用类型,则返回这个引用类型
console.log(new ML()); // 没有返回对象类型 返回{name: '明楼', gender: '男'}
// 如果return返回了 [1,2,3] 打印的就是 [1,2,3] 不再是{name: '明楼', gender: '男'}
JS 是如何实现继承的
原型链继承
通过 prototype 进行的继承
//让一个构造函数的原型是另一个构造函数的实例,
Son.prototype = new Father();
优点
- 写法简单方便,容易理解
不足
对象的实例共享所有继承的属性和方法
因为是原型是父类的实例,无法向父类构造函数传参
jsfunction Father(name) { this.name = name; } let name = '王小明'; // 父可以传 Son.prototype = new Father(name); // 但是 Son不行 无法向父类构造函数传参 let son = new Son('子言'); console.log(son.name); // 王小明
构造函数继承
使用 Call 进行实现
优点
- 借用构造函数可以进行传参。
不足
- 无法使用父类的原型对象中的属性和方法
组合式继承
使用 原型继承 和 借用构造函数继承 两个一起使用
function Child(age) {
// 第一次调用父类的构造函数
Parent.call(this, age);
}
// 实例化对象 第二次调用父类的构造函数
let child = new Child(24);
优点
- 解决了原型链继承和 借用构造函数继承造成的影响
不足
- 会调用两次父类构造函数,一次是在创建子类原型的时候,一次是子类构造函数内部进行借用的时候
ES6 的 Class 继承
使用 Class 关键字,进行继承
优点
- 语法简单易懂,操作更方便
不足
- 并不是所有浏览器都支持 Class 关键字
实例
原型继承
<script>
function Father() {
this.name = '明楼';
this.age = 24;
this.obj = {
like: 'women',
};
}
Father.prototype.behavior = function () {
return '吃饭';
};
function Son() {}
// 原型链继承 通过 prototype
Son.prototype = new Father();
let son = new Son();
console.log(son.behavior);
</script>
借用构造函数继承
<script>
function Father(age) {
this.name = '明楼';
this.age = age;
this.obj = {
like: 'women',
};
}
function Son(age) {
Father.Call(this, age);
}
let son = new Son(24);
console.log(son.age);
</script>
组合式继承
<script>
function Parent(age) {
this.name = '明楼';
this.age = age;
this.hobby = '友情';
}
// 定义一个原型中的属性和方法
Parent.prototype = {
gender: '男',
getList: function () {
return 'CSS';
},
};
// 组合式继承会调用两次 父类构造函数
function Child(age) {
// 第一次调用父类的构造函数
Parent.call(this, age);
}
// 第二次调用父类的构造函数
Child.prototype = new Parent();
// 实例化对象 第二次调用父类的构造函数
let child = new Child(24);
// 调用父类的构造函数中的内容 可以调用到
console.log(child.age); // 24
// 调用原型中的属性和方法
console.log(child.gender); // 男
console.log(child.getList()); // CSS
</script>
Class 继承
class Parent {
constructor(age) {
this.name = '子言';
this.age = age;
this.gender = '男';
}
}
//在原型中添加属性和方法
Parent.prototype.hobby = '可爱';
Parent.prototype.List = function () {
return 'CSS';
};
// 通过 extends 继承父类 需要写 constructor(实例化给的参数) && super(给父的参数)
class Child extends Parent {
constructor(age) {
// 先写super 有参数在其中填写参数 ,没有可以空着
super(age);
this.name = '明楼';
}
}
// 实例化 并传递参数
let child = new Child(24);
console.log(child.name);
console.log(child.gender);
console.log(child.hobby);
console.log(child.List());
console.log(child);
// 优点:语法简单易懂,操作更方便
// 不足:并不是所有浏览器都致辞class关键字
setInterval、setTimeout 时间是有误差的,为什么
在事件循环中,setInterval 和 setTimeout 是异步的宏任务,如果同步代码执行时间过长,甚至超过了计时器的时间,那么计时器的回调函数会被迫延后执行,在同步代码执行完毕之后(让出主线程)会立即执行。
什么是 Promise?
- 当有一个符合 Promise A+规范的 then 方法的对象,它就是 Promise
- 在 ES6 新增的 Promise 构造函数,可以通过这个 Promise 构造函数创建一个满足 Promise A+规范的 Promise 对象
Promise 是用来处理异步操作的对象,可以解决回调地狱问题,让异步代码更加优雅和可控。
Promise 有三种状态,pending、fulfilled、rejected
Promise 和 async/await 的区别是什么?
避免了回调函数的嵌套和链式调用的复杂性
Promise 是一种用于处理异步操作的对象,通过链式调用让回调变得可控, async/await 是基于 Promise 的语法糖,比起 Promise 的链式调用,async 和 await 语法糖的使用可以使异步代码更加简洁和易读,看起来像同步代码
Promise 是 ES6、async/await 是 ES7 对 Promise 的再次封装
Promise 可以通过.catch 捕获异常,async 需要通过 try().catch()
Promise 的优缺点是什么?
- 简化了异步操作、避免回调地狱,更好的错误处理等。
- 缺点是内部状态不可变,无法取消 Promise
在实际项目中,你是如何使用 Promise 的?
跨域的四种方式
JSONP
介绍
JSONP是通过添加Script标签实现跨域的方法。通过Script标签的src不受同源策略的限制,可以跨域请求数据,
缺点:只能是进行Get请求。不安全,不容易维护
实现原理
- 后端返回的是一个函数,但是这个函数是定义在前端的,他会把值注入到这个函数的参数中。
实例
const jsonp = (name) => {
let script = document.createElement('script');
script.src = 'http://127.0.0.1:3000/?callback=' + name;
document.body.appendChild(script);
return new Promise((resolve) => {
window[name] = (data) => {
resolve(data);
};
});
};
jsonp(`callback_${new Date().getTime()}`).then((data) => {
console.log(data);
});
后端
// 测试接口
router.get('/', function (req, res, next) {
let callback = req.query.callback;
console.log(callback);
console.log(123);
res.send(`${callback}(123)`);
});
CORS
1. CORS通过服务器设置响应头,允许客户端进行跨域进行跨域跨域
WebSocket
双向数据绑定
1. 数据劫持
2. 模版编译
3. 依赖收集
通过Object.definePropoty,对数据进行劫持。 在Observer方法进行数据的劫持,
判断元素是否在可视区域内
数组去重方式
八种