js基础文档
数据类型
数据分为基本数据类型(String, Number, Boolean, Null, Undefined,Symbol)和对象数据类型。
基本数据类型的特点:直接存储在栈(stack)中的数据
引用数据类型的特点:存储的是该对象在栈中引用,真实的数据存放在堆内存里
引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
深拷贝和浅拷贝的区别
1.浅拷贝只复制对象的顶层属性(顶层属性"通常指的是对象直接拥有的那些属性,而不是通过引用指向的其他对象或数组,对于一个包含多个层级的对象,浅拷贝会复制这个对象本身的属性(第一层属性),但如果是属性值为引用类型(比如对象或数组),它只会复制这些引用类型值的引用,而不是它们的实际内容。这意味着原始对象和拷贝对象将共享这些引用类型值。)
2.深拷贝会复制对象的所有顶层属性
3.浅拷贝的速度通常比深拷贝快
=和区别
==
(相等运算符) :
- 进行值的比较,但在比较前会进行类型转换,也被称为“抽象相等比较”。
- 如果比较的两个值类型不同,JavaScript会尝试将它们转换成相同的类型再进行比较。
- 例如,数字5和字符串"5"用
==
比较会返回true
,因为JavaScript会尝试将字符串"5"转换成数字5再进行比较。、
===
(恒等运算符) :
- 同时进行值和类型的比较,也被称为“严格相等比较”。
- 如果比较的两个值类型不同,
===
会直接返回false
,不会进行类型转换。 - 例如,数字5和字符串"5"用
===
比较会返回false
,因为它们的类型不同。
闭包
方法里返回一个方法
存在的意义:1.延长变量的生命周期,2.创造私有环境
应用场景:
1.防抖和节流
防抖(多次触发,只执行最后一次)
高频率触发的事件,在指定的单位时间内,只响应最后一次,如果在指定的时间内再次触发,则重新计算时间
防抖类似于英雄联盟回城6秒,如果回城中被打断,再次回城需要再等6秒。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>防抖函数</title>
</head>
<body>
<div>
<label for="inp"></label><input type="text" id="inp">
</div>
<script>
//封装防抖函数
function debounce(fn,time){
// 创建一个标记用来存放定时器的返回值
let timeout=null
return function(){
// 每当用户触发input事件 把前一个 setTimeout 清楚掉
clearTimeout(timeout)
// 然后又创建一个新的 setTimeout, 这样就能保证输入字符后等待的间隔内 还有字符输入的话,就不会执行 setTimeout里面的内容
timeout=setTimeout(()=>{
// 这里进行防抖的内容
fn()
},time)
}
}
// 获取inpt元素
const inp=document.getElementById('inp');
// 测试防抖临时使用的函数
function sayHi(){
console.log('防抖成功')
}
// 给inp绑定input事件 调用封装的防抖函数 传入要执行的内容与间隔事件
inp.addEventListener('input',debounce(sayHi,1000))
</script>
</body>
</html>
节流(规定时间内,只触发一次)
高频率触发的事件,在指定的单位时间内,只响应第一次
节流类似于英雄联盟里的英雄平A 一定是内点击多次只进行攻击一次
原型和原型链
异步函数
setTimeout
setTimeout() 是属于 window 的方法,该方法用于在指定的毫秒数后调用函数或计算表达式。
setTimeout(要执行的代码, 等待的毫秒数)
clearTimeout
clearTimeout() 方法可取消由setTimeout方法设置的定时操作。
clearTimeout() 方法的参数必须是由 setTimeout() 返回的 ID 值。
const myVar;
function myFunction() {
myVar = setTimeout(function(){ alert("Hello"); }, 3000);
}
function myStopFunction() {
clearTimeout(myVar);
}
ES6新特性
块级作用域的变量声明:let、const
let a=10;
const b=20;
箭头函数
const add=(x,y)=>x+y;
模板字符串
使用反引号(```)创建多行字符串和内嵌表达式
const name=`Alice`
const greeting=`hello,${name}!`
解构赋值
允许从数组或对象中提取数据,并将其赋值给变量
let [x,y]=[1,2]
let { name , age } = { name: 'Alice',age: 25}
默认参数
允许为函数参数指定默认值。
function greet(name = 'Guest') {
console.log(`Hello, ${name}!`);
}
展开运算符
用于展开数组或对象的元素
let arr=[1,2,...[3,,4,5]] //[1, 2, 3, 4, 5]
// 对象展开
let obj1 = { a: 1, b: 2 }
let obj2 = { ...obj1, c: 3 }
简写对象属性和方法
可以简洁地定义对象的属性和方法
let x = 1, y = 2
let obj = { x, y, greet() { console.log('Hello'); } }
模块化
使用 export
和 import
关键字支持模块化开发
// 导出模块
export const pi = 3.14;
export function add(a, b) { return a + b; }
// 导入模块
import { pi, add } from './math';
类
引入了基于类的面向对象编程语法
class Person {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hello, ${this.name}!`);
}
}
let person = new Person('Alice');
person.greet();
Promise
用于处理异步操作的更强大和灵活的方式
then函数异步执行
let promise = new Promise((resolve, reject) => {
// 异步操作
if (/* 成功 */) {
resolve('Success');
} else {
reject('Error');
}
});
promise.then(result => console.log(result)).catch(error => console.log(error));
交换两值变量(不借助第三变量)
let a=1
let b=1
[a,b]=[b,a] //解构知识点
快速去重
let arr=[1,2,2,3,4,5]
let item=[...new Set(arr)]
console.log(item)
面试题
同步和异步区别
基础面试题
字符串拼接问题
当使用加号(+)操作符连接一个数字和一个字符串时,JavaScript会将数字转换为字符串,然后进行字符串拼接。
当使用减号(-)操作符时,JavaScript尝试从两个操作数中计算数值结果。如果其中一个操作数是字符串,JavaScript会尝试将该字符串转换为一个数字。
console.log(1+'2') // 12
console.log(1-'2') // -1
var、let、const区别
var
、let
、const
三者区别可以围绕下面五点展开:
(1)变量声明提升
🐖:变量提升(Hoisting)是JavaScript中一个常见的行为,它会将变量和函数声明提升到其所在作用域的顶部。
var声明的变量会被提升到函数或全局作用域的顶部,但其赋值不会被提升
,即变量可以在声明之前调用,值为 undefined。
let和const声明的变量也会被提升,但它们在实际赋值前不能访问,处于“暂时性死区”
,即它们所声明的变量一定要在声明后使用,否则报错。
console.log(a) //undefined
var a = 10
console.log(b) // Cannot access 'b' before initialization
let b=10
console.log(c) // Cannot access 'c' before initialization
const c=10
(2)暂时性死区
var
不存在暂时性死区
let
和 const
存在暂时性死区,只有等到声明变量的那一行代码出现,才可以获取和使用该变量
// var
console.log(a) // undefined
var a = 10
// let
console.log(b) // Cannot access 'b' before initialization
let b = 10
// const
console.log(c) // Cannot access 'c' before initialization
const c = 10
(3)块级作用域
var
不存在块级作用域
let
和 const
存在块级作用域
// var
{
var a = 20
}
console.log(a) // 20
// let
{
let b = 20
}
console.log(b) // Uncaught ReferenceError: b is not defined
// const
{
const c = 20
}
console.log(c) // Uncaught ReferenceError: c is not defined
(4)重复声明
var
允许重复声明变量
let
和 const
在同一作用域不允许重复声明变量
// var
var a = 10
var a = 20 // 20
// let
let b = 10
let b = 20 // Identifier 'b' has already been declared
// const
const c = 10
const c = 20 // Identifier 'c' has already been declared
(5)修改声明的变量
var
和 let
可以
const
声明一个只读的常量。一旦声明,常量的值就不能改变
// var
var a = 10
a = 20
console.log(a) // 20
//let
let b = 10
b = 20
console.log(b) // 20
// const
const c = 10
c = 20
console.log(c) // Uncaught TypeError: Assignment to constant variable
如何选择哪种声明
能用const的情况尽量使用const,其他情况下大多数使用let,避免使用var
箭头函数和普通函数的区别
1.语法区别
function add(a,b){
return a+b;
}
const add=(a,b)=>{return a+b;}
2.this绑定
// 普通函数
const obj1 = {
name: 'john',
greet: function () {
console.log("hello, "+this.name)
}
}
obj1.greet() // hello, john
// 箭头函数
const obj2={
name: 'john',
greet:()=>{
console.log(this.name)
}
}
obj2.greet() // 空
3.arguments对象、args剩余参数区别
function sum(){
let res=0;
for(let i=0;i<arguments.length;i++){
res+=arguments[i];
}
return res;
}
sumArray=(...args)=>{ // 剩余函数
let res=0;
/*
* for(let arg of args){
* res+=arg;
* }
**/
for(let i=0;i<args.length;i++){
res+=args[i];
}
return res;
}
console.log(sum(1,2,3,4,5))
console.log(sumArray(1,2,3,4,5))
4.构造函数
箭头函数不能作为构造函数
function Person(name) {
this.name = name;
}
const john = new Person("John");
console.log(john.name); // 输出: John
const PersonArrow = (name) => {
this.name = name; // 这里会抛出错误
};
const alice = new PersonArrow("Alice");
null和underfined
null是对象类型,underfined是underfined类型
四种情况是underfined
1.已经声明,但为赋值
let a
console.log(a)
2.对象某个类型不存在
let obj={}
console.log(obj.a)
3.函数调用,参数空缺
function get(a,b){
console.log(a,b)
}
4.常规函数(不包括构造函数)默认返回值是undefined
function get(){
console.log(a,b)
}
a=get()
mvvm和mvc
v-if和v-show的区别
v-if:不满足条件,不会渲染dom,单次判断
v-show:隐藏dom,多次切换,不能用于权限操作
nextTick
单页与多页的区别及优缺点
单页应用:只有一个主页面的应用
组件=>页面片段
跳转=>刷新局部资源
场景=>pc端
优点
a.体验好,快
b.改变内容,不用加载整个页面
c.前后端分离
d.效果可以很酷炫
缺点:
a.不利于SEO
b.初次加载比较慢
c.页面复杂度很高
v-if和v-for
不能同时使用,vue2中v-for的优先级比v-if高,vue3中v-if的优先级高于v-if
Vue-router与location.href的区别
location.href:简单方便,刷新页面(跳外链)
vue-router:实现了按需加载,减少了dom消耗(内部页面),js原生history
事件委托
事件的三个阶段:1.事件捕获阶段,2.事件目标阶段,3.事件泡阶段
- 事件捕获:当某个元素触发某个事件(如onclick),顶层对象document就会发出一个事件流,随着DOM树的节点向目标元素节点流去,直到到达事件真正发生的目标元素。在这个过程中,事件相应的监听函数是不会被触发的。
- 事件目标:当到达目标元素之后,执行目标元素该事件相应的处理函数。如果没有绑定监听函数,那就不执行。
- 事件冒泡:从目标元素开始,往顶层元素传播。途中如果有节点绑定了相应的事件处理函数,这些函数都会被触发。
优点
使用事件委托对于web应用程序带来的几个优点:
1.管理的函数变少了。不用为每个元素都添加监听函数。同一个父节点下面类似的子元素,可以通过委托给父元素的监听函数来处理事件。
2.可以方便地动态添加和修改元素,不需要因为元素的改动而修改事件绑定。
3.JavaScript和DOM节点之间的关联变少了,这样也就减少了因循环引用而带来的内存泄漏发生的概率。
事件循环
1.JS是单线程,防止代码阻塞,我们把代码(任务)分为:同步和异步
2.同步代码给js引擎执行,异步代码交给宿主环境
3.同步代码放入执行栈中,异步代码等待时机成熟送入任务队列排队
4.执行栈执行完毕,会去任务队列看是否有异步任务,有就送到执行栈执行,反复循环查看执行,这个过程是事件循环(eventloop)
执行顺序:
- 一开始整个脚本作为一个宏任务执行
- 执行过程中同步代码直接执行,宏任务进入宏任务队列,微任务进入微任务队列
- 当前宏任务执行完出队,检查微任务列表,有则依次执行,直到全部执行完
- 执行浏览器UI线程的渲染工作
- 检查是否有Web Worker任务,有则执行
- 执行完本轮的宏任务,回到2,依次循环,直到宏任务和微服务队列都为空
微任务包括: MutationObserver
、Promise.then()或reject()
、Promise为基础开发的其它技术,比如fetch API
、V8
的垃圾回收过程、Node独有的process.nextTick
。
宏任务包括 :script
、script
、setTimeout
、setInterval
、setImmediate
、I/O
、UI rendering
理解 JavaScript 的事件循环往往伴随着宏任务和微任务、JavaScript 单线程执行过程及浏览器异步机制等相关问题,而浏览器和 NodeJS 中的事件循环实现也是有很大差别。熟悉事件循环,了解浏览器运行机制将对我们理解 JavaScript 的执行过程和排查运行问题有很大帮助。
注释:
在所有任务开始的时候,由于宏任务中包括了 script
,所以浏览器会先执行一个宏任务,在这个过程中你看到的延迟任务(例如 setTimeout
)将被放到下一轮宏任务中来执行。
Promise本身是同步的,then、catch的回调函数是异步的微任务
执行顺序是:1.同步任务,2.微任务的异步任务(promise),3.宏任务的异步代码(settimeout,setinterval)
事件 | 宏任务 | 微任务 |
---|---|---|
谁发起的 | 宿主(Node、浏览器) | JS引擎 |
具体事件 | 1.script 2.setTimeout 3.setInterval 4.UI rendering(UI事件) 5.postMessage 6.MessageChannel 7.setImmediate 8.I/O(Node.js) |
1.Promise.then() / catch() 2.MutaionObserver 3.Proxy 4.process.nextTick(Node.js) 5.Async / Await |
谁先执行 | 后运行 | 先运行 |
会触发新一轮Tick吗 | 会 | 不会 |
改变this的函数
call、bind、apply函数