MJ系列之JS汇总

这篇博客全面总结了JavaScript的核心知识点,包括数据类型、类型转换、类型检测、数组判断方法、深拷贝与浅拷贝、原型链与原型、new、call、apply、bind、this的用法、事件循环机制、事件(冒泡、捕获、委托)以及内存管理和防抖节流策略。深入理解这些概念对于提升JavaScript编程能力至关重要。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JS汇总

数据类型

基本数据类型:字符串、数字、布尔、null、undefined、Symbol、bigInt
引用数据类型:Object,包括函数、数组、对象等
Symbol这种类型的对象永不相等,即便创建的时候传入相同的值也不相等。
BigInt数据类型的目的是比Number数据类型支持的范围更大的整数值。
引用数据类型存储在堆内存中,但是会在栈内存中存一个指针。引用发图数据类型占据空间大、大小不固定
基本数据类型变量保存在栈内存中,因为基本数据类型占用空间小、大小固定,通过值来访问
引用数据类型会在栈中存储一个指针,这个指针指向堆内存空间中该实体的起始地址。
当解释器寻找引用值时,会先检索其在栈中的地址,取得地址后,从堆中获得实体。

类型转换

类型转换
隐式转换:if括号里变量会转换成布尔
遇到宽松相等开启隐式转换:布尔会转换成数字,字符串遇到数字会变成数字,对象遇到基本数据类型会依照ToPrimitive规则转换为原始类型
ToPrimitive:对象的ToPrimitive操作会先调用valueOf方法,返回一个原始类型的值,如果valueOf不存在或者返回的不是原始类型值,就用toString方法返回一个原始类型。如果不是,就抛出异常。

一些常见的转换:

String(null) //'null' 同undefined,true,false
String([null][]) // '',同undefined
String({}) //[Object Object]
Number(null) // 0
Number(undefined{}) // NaN
Boolean([]) // true,假只有null、undefined、false、0、NaN
Boolean({}) // true

一些常见的类型相等:

[] == ![] // true
[null] == 0false // 同undefined
[]['0']'0' == false0
null == undefined

一些常见的坑:(下面全是错的)

[] == []
{}=={}
{}==!{}
null==0
undefined==0

对象比较的是引用值在内存中是否指向同一对象,上面的两个对象互不相干。
加减

console.log("20" + 6) // "206" 字符串拼接 string + number = string
console.log("16" - 6) // 10 减法运算 string - number = number

类型检测

类型检测
typeof:字符串、数字、布尔、undefined、object(null)、function
instanceof:某个对象的原型链是否包含某个构造函数的 prototype 属性,适用于任何 object 的类型检查之外,也可以用来检测内置对象,比如:Array、RegExp、Object、Function,对基本数据类型不起作用因为没有原型链
constructor:确定当前对象的构造函数
hasOwnProperty:判断属性是否存在于当前对象实例中(而不是原型对象中)

var arr = [1,2,3];
console.log(typeof arr);  // object
console.log([1, 2, 3] instanceof Array); // true
var arr = new Array();
console.log(arr.constructor == Array); // true
const info = { title: "书", name: "大白" };
console.log(info.hasOwnProperty("title")); // true

判断数组的方法

instanceof
.isArray
Object.prototype.toString借用Object原型的call或者apply方法,调用toString()是否为[object Array]
Object.prototype.isPrototypeOf测试一个对象是否存在于另一个对象的原型链上。
Object.getPrototypeOf返回指定对象的原型

Object.getPrototypeOf(arr) === Array.prototype // true
bject.prototype.isPrototypeOf(arr, Array.prototype) // true
Object.getPrototypeOf(arr) === Array.prototype // true
const arr = []
Object.prototype.toString.call(arr) === '[object Array]' // true
const arr = []
Object.prototype.toString.call(arr) === '[object Array]' // true

深拷贝和浅拷贝

引用数据类型:引用类型复制到新的变量后,二者一个变另一个跟着变。叫浅拷贝,深拷贝是引用类型复制到新的变量后,二者是独立的
深拷贝,实际上就是重新在堆内存中开辟一块新的空间,把原对象的数据拷贝到这个新地址空间
两方法
转一遍JSON再转回来 ,但是这个办法有一个问题,这只能转化一般常见数据,function,undefined等类型都无法通过这种变回来
手动去写循环遍历

const data = [{ name: "大白" }];
let obj = data.map(item => item);
obj.push({ name: "神奇的程序员" });
console.log("data = ", data);
console.log("obj = ", obj);

原型链与原型

原型:引用类型,隐式原型 proto 的属性值指向它的构造函数的显式原型 prototype 属性值

const obj = {};
const arr = [];
const fn = function() {}

obj.__proto__ == Object.prototype // true
arr.__proto__ === Array.prototype // true
fn.__proto__ == Function.prototype // true

当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么它会去它的隐式原型 proto(也就是它的构造函数的显式原型 prototype)中寻找

在这里插入图片描述
原型对象是在构造函数被声明时一同创建的,然后挂载到构造函数的 prototype 属性上,自动生成一个 constructor 属性,指向创建它的构造函数

1. Person.prototype.constructor == Person // **准则1:原型对象(即Person.prototype)的constructor指向构造函数本身**
2. person01.__proto__ == Person.prototype // **准则2:实例(即person01)的__proto__和原型对象指向同一个地方**

在这里插入图片描述
构造函数:用 new 关键字来创建它的实例对象
js的原型链
神链
在这里插入图片描述
原型链:被 proto 链接起来的链式关系,就称为原型链。

new call apply bind this

new:
新建一个对象obj
把obj的和构造函数通过原型链连接起来
将构造函数的this指向obj
如果该函数没有返回对象,则返回this
在这里插入图片描述

call、apply:改变某个函数运行时的上下文而存在的(就是为了改变函数内部的 this 指向),立即调用

var a1 = add.call(sub, 4, 2);
var a1 = add.apply(sub, [4, 2]);

apply:新 this 对象和一个数组 argArray
bind(),它不会立即调用

继承

继承
继承
构造函数继承:借用call 来改变this的指向。这种只能继承父类的实例属性和方法,不能继承原型属性/方法。
原型链继承:原型链继承,就是让对象实例通过原型链的方式串联起来,当访问目标对象的某一属性时,能顺着原型链进行查找,从而达到类似继承的效果。s1.pro 和 s2.__pro__指向同一个地址即 父类的prototype,会跟着变。
组合继承:构造函数继承+原型链继承。但是实例一个对象的时, 父类 new 了两次。
寄生组合继承:Object.create(Parent.prototype) 创建一个新的原型对象赋予子类

闭包 与 变量提升

变量提升是当栈内存作用域形成时,JS代码执行前,浏览器会将带有var, function关键字的变量提前进行声明 declare(值默认就是 undefined),定义 defined(就是赋值操作),这种预先处理的机制就叫做变量提升机制也叫预定义。
闭包就是指有权访问另一个函数作用域中的变量的函数。
闭包的作用域链包含着它自己的作用域,以及包含它的函数的作用域和全局作用域。
创建闭包最常见方式,就是在一个函数内部创建另一个函数。
通常,函数的作用域及其所有变量都会在函数执行结束后被销毁。但是,在创建了一个闭包以后,这个函数的作用域就会一直保存到闭包不存在为止。
匿名函数最大的用途是创建闭包
闭包的缺点就是常驻内存会增大内存使用量,并且使用不当很容易造成内存泄露。

垃圾清理机制

标记清除法

标记阶段:把所有活动对象做上标记。从全局作用域的变量,沿作用域逐层往里遍历(对,是深度遍历),当遍历到堆中对象时,说明该对象被引用着,则打上一个标记,继续递归遍历(因为肯定存在堆中对象引用另一个堆中对象),直到遍历到最后一个(最深的一层作用域)节点。
清除阶段:把没有标记(也就是非活动对象)销毁。遍历整个堆,回收没有打上标记的对象。
实现简单,打标记也就是打或者不打两种可能,所以就一位二进制位就可以表示
解决了循环引用问题
造成碎片化(有点类似磁盘的碎片化)

引用计数法

让所有对象实现记录下有多少“程序”在引用自己,让各对象都知道自己的“人气指数”
可即刻回收垃圾,计数器需要占很大的位置,无法解决循环引用无法回收的问题,
当对象 1 中的某个属性指向对象 2,对象 2 中的某个属性指向对象 1 就会出现循环引用,他们无法被标记,他们将会被垃圾回收器回收
所谓"引用计数"是指语言引擎有一张"引用表",保存了内存里面所有的资源(通常是各种值)的引用次数。如果一个值的引用次数是0,就表示这个值不再用到了,因此可以将这块内存释放。

事件循环机制

在这里插入图片描述
就是遇到宏任务,先执行宏任务,将宏任务放入eventqueue,然后在执行微任务,将微任务放入eventqueue最骚的是,这两个queue不是一个queue。当你往外拿的时候先从微任务里拿这个回掉函数,然后再从宏任务的queue上拿宏任务的回掉函数。
步任务会进入所谓的 “任务队列” 中了。如果在执行的过程中突然有重要的数据需要获取,或是说有事件突然需要处理一下,按照队列的先进先出顺序这些是无法得到及时处理的。这个时候就催生了宏任务和微任务,微任务使得一些异步任务得到及时的处理。
所有同步任务都在主线程上执行,形成一个执行栈。
主线程之外,还存在一个 “任务队列”,它是存放异步任务运行后的回调函数的,也就是异步任务有了运行结果,就在"任务队列"之中放置一个事件。
一旦 “执行栈” 中的所有同步任务执行完毕,主线程就会读取 “任务队列”,看看里面有哪些事件。然后把那些对应的异步任务,压入执行栈中,开始执行。
EventLoop
进程: 进程是cpu分配资源的最小单位;(是能拥有资源和独立运行的最小单位)
线程: 线程是cpu调度的最小单位;(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)
浏览器是多进程的: 在浏览器中,每打开一个tab页面,其实就是新开了一个进程,在这个进程中,还有ui渲染线程,js引擎线程,http请求线程等。 所以,浏览器是一个多进程的。
js是单线程的: js是作为浏览器的脚本语言,主要是实现用户与浏览器的交互,以及操作dom;这决定了它只能是单线程,否则会带来很复杂的同步问题。
由于js是单线程,js设计者把任务分为同步任务和异步任务,同步任务都在主线程上排队执行,前面任务没有执行完成,后面的任务会一直等待;异步任务则是挂在在一个任务队列里,等待主线程所有任务执行完成后,通知任务队列可以把可执行的任务放到主线程执行。异步任务放到主线程执行完后,又通知任务队列把下一个异步任务放到主线程中执行。这个过程一直持续,直到异步任务执行完成,这个持续重复的过程就叫Event loop。而一次循环就是一次tick 。

分类

在任务队列中的异步任务又可以分为两种microtast(微任务) 和 macrotask(宏任务)
microtast(微任务):Promise, process.nextTick, Object.observe, MutationObserver
macrotask(宏任务):script整体代码、setTimeout、 setInterval等

规则

执行优先级上,先执行宏任务macrotask,再执行微任务mincrotask。
在一次event loop中,microtask在这一次循环中是一直取一直取,直到清空microtask队列,而macrotask则是一次循环取一次。
如果执行事件循环的过程中又加入了异步任务,如果是macrotask,则放到macrotask末尾,等待下一轮循环再执行。如果是microtask,则放到本次event loop中的microtask任务末尾继续执行。直到microtask队列清空。

this指向

如果 new 关键词出现在被调用函数的前面,那么JavaScript引擎会创建一个新的对象,被调用函数中的this指向的就是这个新创建的函数。
如果 new 关键词出现在被调用函数的前面,那么JavaScript引擎会创建一个新的对象,被调用函数中的this指向的就是这个新创建的实例。
如果一个函数是某个对象的方法,并且对象使用句点符号触发函数,那么this指向的就是该函数作为那个对象的属性的对象,也就是,this指向句点左边的对象。函数数执行时,this总是指向调用该函数的对象
如果一个函数函数没有所属对象时,就指向全局对象

事件(冒泡、捕获、委托)

事件流

当一个html元素上产生事件时,该事件会在dom树元素节点之间按照特定的顺序去传播。传播路径的每一个节点,都会收到这个事件,这就是dom事件流。

顺序

事件捕获阶段:反
处于目标阶段:当到达目标元素之后,执行目标元素该事件相应的处理函数。
事件冒泡阶段:子元素嵌套在父元素内部,点击子元素的时候一定同时表示点击了父元素,这个时候,先触发子元素的事件处理器,然后再触发父元素的事件处理器,如果父元素的父元素还有处理器,就一直向上触发,一直到 body 元素。

常用冒泡

1、浏览器兼容问题
2、addEventListener (type, listener[, useCapture]) 第三个参数如果是true,表示在事件捕获阶段调用事件处理程序;如果是false(不写默认就是false),表示在事件冒泡阶段处理程序。
3、onclick 和 attachEvent 只能得到冒泡阶段

阻止冒泡

event.stopPropagation(); // 一般浏览器停止冒泡
event.cancelBubble; // IE 6 7 8 的停止冒泡

阻止默认行为(冒泡+本身)

event.preventDefault( )
return false

事件委托

不给每个子节点单独设置事件监听器,而是设置在其父节点上,然后利用冒泡原理设置每个子节点。可以做到只操作了一次 DOM ,提高了程序的性能。

例子

给 ul 注册点击事件,然后利用事件对象的 target 来找到当前点击的 li ,然后事件冒泡到 ul 上, ul 有注册事件,就会触发事件监听器。
可以通过判断target的类型来确定是哪一类的子元素对象执行事件

顺序

给父元素绑定事件,监听子元素冒泡事件,用e.target确定事件目标

(function(){
var color_list = document.getElementById('color-list');
color_list.addEventListener('click',showColor,false);
function showColor(e){
    var x = e.target;
    if(x.nodeName.toLowerCase() === 'li'){
        console.log('The color is ' + x.innerHTML);
    }
}
})();

内存泄漏

虽然JavaScript会自动垃圾收集,但是如果我们的代码写法不当,会让变量一直处于“进入环境”的状态,无法被回收。
当变量进入执行环境是,就标记这个变量为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到他们。当变量离开环境时,则将其标记为“离开环境”。
1、意外的全局变量
2、被遗忘的计时器或回调函数
3、闭包
4、有清理的DOM元素引用:在对象里保存 DOM 节点内部数据结构,removeChild后还在

防抖节流

防抖:有个输入框,输入之后会调用接口,获取联想词。
节流:鼠标不断点击触发,懒加载要监听计算滚动条的位置,提交按钮指定时间内智能触发一次
防抖

function debounce(fn) {
      // 4、创建一个标记用来存放定时器的返回值
      let timeout = null;
      return function() {
        // 5、每次当用户点击/输入的时候,把前一个定时器清除
        clearTimeout(timeout);
        // 6、然后创建一个新的 setTimeout,
        // 这样就能保证点击按钮后的 interval 间隔内
        // 如果用户还点击了的话,就不会执行 fn 函数
        timeout = setTimeout(() => {
          fn.call(this, arguments);
        }, 1000);
      };
    }

节流

function throttle(fn) {
      // 4、通过闭包保存一个标记
      let canRun = true;
      return function() {
        // 5、在函数开头判断标志是否为 true,不为 true 则中断函数
        if(!canRun) {
          return;
        }
        // 6、将 canRun 设置为 false,防止执行之前再被执行
        canRun = false;
        // 7、定时器
        setTimeout( () => {
          fn.call(this, arguments);
          // 8、执行完事件(比如调用完接口)之后,重新将这个标志设置为 true
          canRun = true;
        }, 1000);
      };
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值