Skip to content

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 的区别

sh
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 元素解析完成后才会执行.

image-20240311214122995

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()返回要查找的元素在数组中的位置,返回trueflase
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()

      js
      let obj1 = { a: 1, b: { c: 2 } };
      let obj2 = Object.assign({}, obj1);
    • 扩展运算符

      js
      let obj1 = { a: 1, b: { c: 2 } };
      let obj2 = { ...obj1 };
  • 深拷贝

    • 手写递归函数

      js
      const 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;
      };

什么是闭包

闭包是指一个函数可以访问另一个函数作用域内的变量。当一个函数嵌套在另一个函数中时,内部函数可以访问外部函数的变量,即使外部函数已经返回了。这种情况下,内部函数形成了一个闭包,它保留了外部函数的作用域链并可以继续访问这些变量。闭包常常用于实现函数的封装和私有化,以及在回调和事件处理等场景下的数据共享与传递。

闭包的应用场景包括但不限于:

  1. 保护变量:通过使用闭包,可以确保变量不会被外部直接修改,增加了数据的安全性。

  2. 计数器和累加器:通过闭包,可以在函数外部保存一个内部状态,并在每次调用内部函数时修改该状态。这一特性可用于实现计数器、累加器等功能。

  3. 延迟执行和回调函数:将函数作为返回值,可以实现延迟执行或者在特定条件满足时回调执行。

  4. 实现数据私有化:通过使用闭包封装一些私有数据和方法,可以避免外部访问和修改,实现数据的私有化和保护。

  5. 函数颗粒化

    延长作用域链

    创建私有作用域,延长变量生命周期

判断数据类型的方式

  • 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取消对事件监听
  • 变量的循环引用

事件循环

sh
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 事件捕获

示例

html
<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

html
<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 操作符做了什么

  1. 先创建了一个空对象
  2. 把空对象的** proto **,指向构造函数的 prototype
  3. 将构造函数的 this,绑定到新的空对象身上
  4. 根据构造函数返回的类型判断,如果是值类型,则返对象本身,如果是引用类型,则返回这个引用类型

手写 new 的功能

html
<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>

实例

js
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 进行的继承

js
//让一个构造函数的原型是另一个构造函数的实例,
Son.prototype = new Father();

优点

  • 写法简单方便,容易理解

不足

  • 对象的实例共享所有继承的属性和方法

  • 因为是原型是父类的实例,无法向父类构造函数传参

    js
    function Father(name) {
      this.name = name;
    }
    let name = '王小明';
    // 父可以传
    Son.prototype = new Father(name);
    // 但是 Son不行 无法向父类构造函数传参
    let son = new Son('子言');
    console.log(son.name); // 王小明

构造函数继承

使用 Call 进行实现

优点

  • 借用构造函数可以进行传参。

不足

  • 无法使用父类的原型对象中的属性和方法

组合式继承

使用 原型继承 和 借用构造函数继承 两个一起使用

js
function Child(age) {
  // 第一次调用父类的构造函数
  Parent.call(this, age);
}

// 实例化对象  第二次调用父类的构造函数
let child = new Child(24);

优点

  • 解决了原型链继承和 借用构造函数继承造成的影响

不足

  • 会调用两次父类构造函数,一次是在创建子类原型的时候,一次是子类构造函数内部进行借用的时候

ES6 的 Class 继承

使用 Class 关键字,进行继承

优点

  • 语法简单易懂,操作更方便

不足

  • 并不是所有浏览器都支持 Class 关键字

实例

原型继承

html
<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>

借用构造函数继承

html
<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>

组合式继承

html
<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 继承

js
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

介绍

sh
JSONP是通过添加Script标签实现跨域的方法。通过Script标签的src不受同源策略的限制,可以跨域请求数据,

缺点:只能是进行Get请求。不安全,不容易维护

实现原理

  • 后端返回的是一个函数,但是这个函数是定义在前端的,他会把值注入到这个函数的参数中。

实例

js
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);
});

后端

js
// 测试接口
router.get('/', function (req, res, next) {
  let callback = req.query.callback;
  console.log(callback);
  console.log(123);
  res.send(`${callback}(123)`);
});

CORS

sh
1. CORS通过服务器设置响应头,允许客户端进行跨域进行跨域跨域

WebSocket

双向数据绑定

sh
1. 数据劫持
2. 模版编译
3. 依赖收集

通过Object.definePropoty,对数据进行劫持。 在Observer方法进行数据的劫持,

判断元素是否在可视区域内

数组去重方式

八种

ES6 new Set

双重for循环+splice

indexOf去重

includes

filter+indexOf

Map数据结构

对象属性特征

reduce与includes方法