志英舅 發表於 2025-7-10 23:53:00

JavaScript

<h1 id="数据类型">数据类型</h1>
<p><strong>基础类型</strong>: String、Number、Boolean、null、undefined、Symbol 存储在栈中,赋值变量和比较均为数据本身。<br>
<strong>引用类型</strong>:Object、Array、Map、Function 存储在堆中,使用new创建,赋值变量和比较均是内存地址。</p>
<h2 id="检测数据类型">检测数据类型</h2>
<h3 id="1typeof---基本数据类型">1.typeof   基本数据类型</h3>
<p>不能区分null,数组,对象,正则,因为返回的都是”object”</p>
<pre><code>typeof 2 输出 'number'
typeof NaN 输出 'number'
typeof null 输出 'object'
typeof {} 输出 'object'
typeof [] 输出 'object.
typeof (function(){}) 输出 'function'
typeof undefined 输出 'undefined'
typeof '222' 输出 'string'
typeof true 输出 'boolean'
</code></pre>
<h3 id="2instanceof----引用数据类型检测一个实例是否属于某个类">2.instanceof    引用数据类型;检测一个实例是否属于某个类</h3>
<pre><code>    var c= ;
    var d = new Date();
    var e = function(){alert(111);};
    var f = function(){this.name="22";};
    console.log(c instanceof Array) //true
    console.log(d instanceof Date)//true
    console.log(e instanceof Function) //true
    // console.log(f instanceof function ) //false
// 注意左侧必须是对象(object),如果不是,直接返回false,具体见基础类型。
    let num = 1
    num instanceof Number // false
    num = new Number(1)
    num instanceof Number // true
</code></pre>
<h3 id="3constructor">3.constructor</h3>
<p>根据对象的constructor判断,返回对创建此对象的数组函数的引用。<br>
几乎可以判断基本数据类型和引用数据类型</p>
<pre><code>var c= ;
var d = new Date();
var e = function(){alert(111);};
alert(c.constructor === Array) ----------&gt; true
alert(d.constructor === Date) -----------&gt; true
alert(e.constructor === Function) -------&gt; true
//注意: constructor 在类继承时会出错
</code></pre>
<p>4.Object.prototype.toString.call() 最准确的判断方式<br>
所有数据类型均可判断:Object.prototype.toString.call()<br>
这是对象的一个原生原型扩展函数,用来更精确的区分数据类型(类)。<br>
原理</p>
<pre><code>var gettype = Object.prototype.toString
gettype.call('aaaa') 输出
gettype.call(2222) 输出
gettype.call(true) 输出
gettype.call(undefined) 输出
gettype.call(null) 输出
gettype.call({}) 输出
gettype.call([]) 输出
gettype.call(function(){}) 输出
</code></pre>
<h1 id="let-const-var">let const var</h1>
<h2 id="区别">区别</h2>
<ul>
<li>var声明的变量存在变量提升,即变量可以在声明之前调用,值为undefined<br>
let和const不存在变量提升,即它们所声明的变量一定要在声明后使用,否则报错</li>
<li>var不存在暂时性死区<br>
let和const存在暂时性死区,只有等到声明变量的那一行代码出现,才可以获取和使用该变量</li>
</ul>
<pre><code>// var
console.log(a) // undefined
var a = 10
// let
console.log(b) // Cannot access ‘b’ before initialization
let b = 10
// const
console.log© // Cannot access ‘c’ before initialization
const c = 10
</code></pre>
<ul>
<li>var不存在块级作用域<br>
let和const存在块级作用域</li>
</ul>
<pre><code>// 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© // Uncaught ReferenceError: c is not defined
</code></pre>
<ul>
<li>var允许重复声明变量<br>
let和const在同一作用域不允许重复声明变量</li>
</ul>
<pre><code>// 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
</code></pre>
<h2 id="例子">例子</h2>
<pre><code>    for (var i = 0; i &lt; 5; i++) {
      setTimeout(() =&gt; console.log('i======', i))
    }//输出5 5 5 5 5
    for (let j = 0; j &lt; 5; j++) {
      setTimeout(() =&gt; console.log('j======', j))
    }//输出0 1 2 3 4
</code></pre>
<h3 id="settimeout-的执行机制">setTimeout 的执行机制</h3>
<ul>
<li>即使延迟时间为 0 毫秒,setTimeout 的回调函数也会被放入任务队列(Task Queue),而非立即执行。<br>
JavaScript 会先执行完主线程中的所有同步代码(即整个 for 循环),再从任务队列中取出回调函数执行。</li>
<li>var 的函数作用域<br>
var 声明的变量属于函数作用域(或全局作用域),而非块级作用域。因此,整个循环中只有一个 i 变量。<br>
当循环结束时,i 的值已经变为 5(因为 i 从 0 递增到 4 后,再次执行 i++ 变为 5,此时循环条件 i &lt; 5 不成立,循环终止)。</li>
<li>闭包捕获变量引用<br>
每个 setTimeout 的回调函数形成闭包,捕获的是 i 的引用,而非值。<br>
当主线程执行完循环后,任务队列中的回调函数开始执行,但此时 i 的值已固定为 5,因此所有回调都打印 5。</li>
</ul>
<h3 id="let-的作用域特性">let 的作用域特性:</h3>
<ul>
<li>let 声明的变量属于块级作用域(即 {} 内的区域)。在 for 循环中,每次迭代都会创建一个新的变量副本,这些副本相互独立。</li>
<li>循环中的闭包捕获<br>
每个 setTimeout 的回调函数捕获的是当前迭代的 j 变量副本,而非共享同一个变量。当回调函数在延迟后执行时,它们访问的是捕获时的变量值(即 0 到 4)。</li>
</ul>
<h2 id="1var">1.var</h2>
<ul>
<li>在ES5中,顶层对象的属性和全局变量是等价的,用var声明的变量既是全局变量,也是顶层变量<br>
注意:顶层对象,在浏览器环境指的是window对象,在 Node 指的是global对象</li>
</ul>
<pre><code>var a = 10;
console.log(window.a) // 10
</code></pre>
<ul>
<li>使用var声明的变量存在变量提升的情况</li>
</ul>
<pre><code>console.log(a) // undefined
var a = 20
在编译阶段,编译器会将其变成以下执行
var a
console.log(a)
a = 20
</code></pre>
<ul>
<li>使用var,我们能够对一个变量进行多次声明,后面声明的变量会覆盖前面的变量声明</li>
<li>在函数中使用使用var声明变量时候,该变量是局部的</li>
</ul>
<pre><code>var a = 20
function change(){
var a = 30
}
change()
console.log(a) // 20
</code></pre>
<p>而如果在函数内不使用var,该变量是全局的</p>
<pre><code>var a = 20
function change(){
a = 30
}
change()
console.log(a) // 30
</code></pre>
<h2 id="2let">2.let</h2>
<p>let是ES6新增的命令,用来声明变量</p>
<ul>
<li>用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效</li>
</ul>
<pre><code>{
let a = 20
}
console.log(a) // ReferenceError: a is not defined.
</code></pre>
<ul>
<li>不存在变量提升</li>
</ul>
<pre><code>console.log(a) // 报错ReferenceError
let a = 2
</code></pre>
<p>这表示在声明它之前,变量a是不存在的,这时如果用到它,就会抛出一个错误</p>
<ul>
<li>只要块级作用域内存在let命令,这个区域就不再受外部影响</li>
</ul>
<pre><code>var a = 123
if (true) {
a = ‘abc’ // ReferenceError
let a;
}
</code></pre>
<p>使用let声明变量前,该变量都不可用,也就是大家常说的“暂时性死区”</p>
<ul>
<li>let不允许在相同作用域中重复声明</li>
</ul>
<pre><code>let a = 20
let a = 30
// Uncaught SyntaxError: Identifier ‘a’ has already been declared
注意的是相同作用域,下面这种情况是不会报错的
let a = 20
{
let a = 30
}
</code></pre>
<h2 id="3const">3.const</h2>
<p>const声明一个只读的常量,一旦声明,常量的值就不能改变</p>
<pre><code>const a = 1
a = 3
// TypeError: Assignment to constant variable.
</code></pre>
<p>这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值</p>
<pre><code>const a;
// SyntaxError: Missing initializer in const declaration
</code></pre>
<p>如果之前用var或let声明过变量,再用const声明同样会报错</p>
<pre><code>var a = 20
let b = 20
const a = 30
const b = 30
// 都会报错
</code></pre>
<p>const实际上保证的并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动</p>
<blockquote>
<p>对于简单类型的数据,值就保存在变量指向的那个内存地址,因此等同于常量<br>
对于复杂类型的数据,变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的,并不能确保改变量的结构不变</p>
</blockquote>
<pre><code>const foo = {};
// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123
// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: “foo” is read-only
</code></pre>
<p>其它情况,const与let一致</p>
<h1 id="事件循环机制">事件循环机制</h1>
<p>事件循环练习<br>
由于js是单线程,为防止代码阻塞,将代码分为同步和异步,同步代码会直接放入执行栈中执行,异步代码(如setTimeout)放入宿主环境(浏览器,Node)中,时机到了(点击事件即点击后,setTimeout即时间结束后)以后将回调函数放入任务队列中,执行栈中的代码执行完后就会去任务队列中查看有无异步代码要执行。反复循环查看执行,这个过程就是事件循环。<br>
<img src="https://img2024.cnblogs.com/blog/3390382/202507/3390382-20250710231513760-232140412.png" alt="image" loading="lazy"><br>
js又把异步任务分为宏任务(由宿主环境发起,如script,事件,网络请求Ajax/Fetch,setTimeout()/setInterval())和微任务(由JS引擎发起,如Promise,Promise本身同步,then/catch回调函数异步)<br>
<img src="https://img2024.cnblogs.com/blog/3390382/202507/3390382-20250710231543392-596173098.png" alt="image" loading="lazy"></p>
<h2 id="注意️">注意⚠️</h2>
<ul>
<li>排前面的 script 先执行,执行其内部的【同】,再执行其【微】,接着就轮到下一个大的宏,也就是执行下一个 script,【同】、【微】......顺序执行完后,再从头开始,看第一个 script 是否有需要执行的【宏】,再去下一个 script 中找 【宏】,等大家宏结束后,进入下一轮循环。<br>
<img src="https://img2024.cnblogs.com/blog/3390382/202507/3390382-20250710231820269-408097562.png" alt="image" loading="lazy"></li>
<li>async函数里面的属于同步代码,await后的代码属于异步微任务<br>
<img src="https://img2024.cnblogs.com/blog/3390382/202507/3390382-20250710231842588-1856885379.png" alt="image" loading="lazy"></li>
</ul>
<h2 id="practice">Practice</h2>
<h3 id="1️⃣">1️⃣</h3>
<pre><code>console.log('A');·
setTimeout(() =&gt; console.log('B'), 0);
Promise.resolve().then(() =&gt; console.log('C'));
Promise.resolve().then(() =&gt; setTimeout(() =&gt; console.log('D'), 0));
console.log('E');
</code></pre>
<details>
<summary>点击查看答案</summary>
<pre><code>A → E → C → B → D
</code></pre>
</details>
<h3 id="2️⃣">2️⃣</h3>
<pre><code>const promise = new Promise((resolve, reject) =&gt; {
console.log(1);
console.log(2);
});
promise.then(() =&gt; {
console.log(3);
});
console.log(4);
</code></pre>
<details>
<summary>点击查看答案</summary>
<pre><code> 1 → 2 → 4
</code></pre>
</details>
<h3 id="3️⃣">3️⃣</h3>
<pre><code>async function async1() {
console.log('1');
await async2();
console.log('2');
}
async function async2() { console.log('3'); }
setTimeout(() =&gt; console.log('4'), 0);
async1();
new Promise(resolve =&gt; {
console.log('5');
resolve();
}).then(() =&gt; console.log('6'));
console.log('7');
</code></pre>
<details>
<summary>点击查看答案</summary>
<pre><code> 1 → 3 → 5 → 7 → 2 → 6 → 4
</code></pre>
</details>
<h3 id="4️⃣">4️⃣</h3>
<pre><code>new Promise((resolve) =&gt; {
console.log(1);
resolve(3);
Promise.resolve().then(() =&gt; {
    console.log(4);
});
}).then((num) =&gt; {
console.log(num);
});

setTimeout(() =&gt; {
console.log(6);
});

Promise.resolve().then(() =&gt; {
console.log(5);
});
console.log(2);
</code></pre>
<details>
<summary>点击查看答案</summary>
<pre><code> 1 → 2 → 4 → 3 → 5 → 6
</code></pre>
</details>
<h3 id="5️⃣">5️⃣</h3>
<pre><code>console.log('start');
setTimeout(() =&gt; {
console.log('Timeout1');
}, 1000);

Promise.resolve().then(() =&gt; {
console.log('Promise1');
});

Promise.resolve().then(() =&gt; {
console.log('Promise2');
setTimeout(() =&gt; {
    Promise.resolve().then(() =&gt; {
      console.log('Promise3');
    })
    console.log('Timeout2');
}, 0);
});
console.log('end');
</code></pre>
<details>
<summary>点击查看答案</summary>
<pre><code> start → end → Promise1 → Promise2 → Timeout2 → Promise3 → Timeout1
</code></pre>
</details>
<pre><code>function app() {
setTimeout(() =&gt; {
    console.log("1-1");3
    Promise.resolve().then(() =&gt; {
      console.log("2-1");5
    });
});
console.log("1-2"); 1
Promise.resolve().then(() =&gt; {
    console.log("1-3"); 2
    setTimeout(() =&gt; {
      console.log("3-1"); 4
    });
});
}
</code></pre>
<details>
<summary>点击查看答案</summary>
```
1-2 → 1-3 → 1-1 → 3-1 → 2-1
```
</details>
<h1 id="内存管理">内存管理</h1>
<p>JS有如下数据类型:<br>
<strong>原始数据类型</strong>:String, Number, Boolean, Null, Undefined, Symbol<br>
<strong>引用数据类型</strong>:Object<br>
而存放这些数据的内存又可以分为两部分:栈内存(Stack)和堆内存(Heap)。原始数据类型存在栈中,引用类型存在堆中。</p>
<h2 id="栈内存">栈内存</h2>
<p>栈是一种只能一端进出的数据结构,先进后出,后进先出。<br>
<img src="https://img2024.cnblogs.com/blog/3390382/202507/3390382-20250713215310983-1673529642.png" alt="image" loading="lazy"></p>
<h2 id="堆内存">堆内存</h2>
<p>JS中原始数据类型的内存大小是固定的,由系统自动分配内存。但是引用数据类型,比如Object, Array,他们的大小不是固定的,所以是存在堆内存的。JS不允许直接操作堆内存,我们在操作对象时,操作的实际是对象的引用,而不是实际的对象。可以理解为对象在栈里面存了一个内存地址,这个地址指向了堆里面实际的对象。所以引用类型的值是一个指向堆内存的引用地址。<br>
<img src="https://img2024.cnblogs.com/blog/3390382/202507/3390382-20250713215355803-1044334403.png" alt="image" loading="lazy"><br>
函数也是引用类型,当我们定义一个函数时,会在堆内存中开辟一块内存空间,将函数体代码以字符串的形式存进去。然后将这块内存的地址赋值给函数名,函数名和引用地址会存在栈上。<br>
<img src="https://img2024.cnblogs.com/blog/3390382/202507/3390382-20250713215418278-2065198501.png" alt="image" loading="lazy"></p>
<h2 id="垃圾回收">垃圾回收</h2>
<p>垃圾回收就是找出那些不再继续使用的变量,然后释放其占用的内存,垃圾回收器会按照固定的时间间隔周期性执行这一操作。JS使用垃圾回收机制来自动管理内存,但是他是一把双刃剑:<br>
<strong>优势</strong>: 可以大幅简化程序的内存管理代码,降低程序员负担,减少因为长时间运行而带来的内存泄漏问题。<br>
<strong>劣势</strong>:程序员无法掌控内存,JS没有暴露任何关于内存的API,我们无法进行强制垃圾回收,更无法干预内存管理。</p>
<h3 id="引用计数">引用计数</h3>
<p>引用计数是一种回收策略,它跟踪记录每个值被引用的次数,每次引用的时候加一,被释放时减一,如果一个值的引用次数变成0了,就可以将其内存空间回收。<br>
使用引用计数会有一个很严重的问题:循环引用。循环引用指的是对象A中包含一个指向对象B的指针,而对象B中也包含一个指向对象A的引用。</p>
<pre><code>function problem(){
    var objectA = {};
    var objectB = {};
    objectA.a = objectB;
    objectB.b = objectA;
}
</code></pre>
<p>在这个例子中,objectA 和 objectB 通过各自的属性相互引用;也就是说,这两个对象的引用次数都是 2。当函数执行完毕后,objectA 和 objectB 还将继续存在,因为它们的引用次数永远不会是 0。</p>
<h3 id="标记清除算法">标记—清除算法</h3>
<p>算法分为 <strong>“标记”和“清除”</strong> 两个阶段,最终目的是识别并释放 “不再需要” 的内存。</p>
<ol>
<li>标记阶段:区分 “有用” 和 “无用” 的变量<br>
标记规则:<br>
垃圾回收器会从根对象(Roots) 开始遍历所有变量(根对象通常是全局对象,如浏览器中的window、Node.js 中的global)。</li>
</ol>
<ul>
<li>所有能被根对象直接或间接访问到的变量,标记为 <strong>“有用”</strong>(处于 “进入环境” 状态,即仍在执行环境中被使用)。</li>
<li>无法被根对象访问到的变量,标记为 <strong>“无用”</strong>(处于 “离开环境” 状态,即已脱离执行环境,不再被使用)。</li>
</ul>
<ol start="2">
<li>清除逻辑:<br>
垃圾回收器会遍历内存中所有变量,将标记为 “无用” 的变量占用的内存释放,并将这些内存空间归还给操作系统,供后续使用。</li>
</ol>
<h1 id="闭包">闭包</h1>
<p>当一个内部函数引用了外部函数的变量时,就形成了闭包。</p>
<h2 id="闭包的优点特点">闭包的优点/特点</h2>
<ul>
<li>通过闭包可以让外部环境访问到函数内部的局部变量</li>
<li>通过闭包可以让全局变量持续保存下来,不随着它的上下文一起销毁</li>
</ul>
<hr>
<p>通过此特性,我们可以解决一个全局变量污染的问题, 早期在 JavaScript 还无法进行模块化的时候,在多人协作时,如果定义过多的全局变量有可能造成全局变量命名冲突,使用闭包来解决功能对变量的调用将变量写到一个独立的空间里面,从而能够一定程度上解决全局变量污染的问题。</p>
<h2 id="闭包经典面试题一">闭包经典面试题一</h2>
<pre><code>for (var i = 1; i &lt;= 3; i++) {
    setTimeout(function () {
      console.log(i);
    }, 1000);
}
</code></pre>
<h3 id="var-的作用域特性">var 的作用域特性:</h3>
<p>var 声明的变量没有块级作用域(仅函数作用域或全局作用域),因此整个循环共享同一个 i 变量。</p>
<ul>
<li>循环执行时:<br>
三次迭代都会将 setTimeout 放入任务队列,延迟 1 秒执行。<br>
但每次迭代的回调函数引用的是同一个 i 变量。</li>
<li>定时器触发时:<br>
由于循环执行速度极快,1 秒后所有定时器触发时,循环早已结束,此时 i 的值已经变为 4(循环终止条件是 i &gt; 3)。<br>
因此所有回调函数打印的都是 4。</li>
</ul>
<h3 id="解决方法">解决方法</h3>
<pre><code>for (var i = 1; i &lt;= 3; i++) {
    (function (index) {
      setTimeout(function () {
            console.log(index);
      }, 1000);
    })(i)
}
</code></pre>
<h3 id="let-的作用域特性-1">let 的作用域特性:</h3>
<p>let 声明的变量具有块级作用域(每个循环迭代都会创建独立的变量副本)。</p>
<ul>
<li>循环执行时:<br>
每次迭代都会创建一个新的 i 变量,并将其值(1、2、3)绑定到对应的定时器回调函数中。<br>
每个回调函数形成的闭包捕获的是当前迭代的 i 值。</li>
<li>定时器触发时:<br>
每个回调函数访问的是自己闭包中的 i 副本(分别为 1、2、3),因此依次打印:1 2 3<br>
闭包面试题</li>
</ul>
<h1 id="this指向">this指向</h1>
<p>函数中的this指向取决于函数如何调用,它指向当前函数的运行环境<br>
判断this的唯一依据就是此刻函数的执行由哪个对象调用</p>
<ul>
<li>如果函数以参数形式传递,则this会指向windows,函数是一个局部变量,找不到他的调用者</li>
</ul>
<h1 id="原型链">原型链</h1>
<h2 id="1原型">1.原型</h2>
<p>在js中,每一个对象(函数也是对象)都有一个特殊的属性叫做原型(prototype),它指向另一个对象,这个对象(Test.prototype)被称为原型对象, 原型对象是用来共享属性和方法的</p>
<h2 id="2原型对象">2.原型对象:</h2>
<p>(1)原型对象有一个constructor属性指向构造函数本身(Test)。<br>
(2)原型对象是一个普通的对象,它包含属性和方法。<br>
(3)原型对象的属性和方法会被继承到所有通过原型链与它相连的对象。</p>
<pre><code>function Test(name, age){
    this.name = name
    this.age = age
}

Test.prototype.say = function(){
    console.log('我能说话')
}
var obj3 = new Test('Jack', 26)
var obj4 = new Test('Rose', 25)

obj3.say()    // 我能说话
obj4.say()    // 我能说话
console.log(obj3.say === obj4.say)    // true

</code></pre>
<p>构造函数和实例之间就初步构成了这样一个关系,如图:<br>
<img src="https://img2024.cnblogs.com/blog/3390382/202507/3390382-20250713234503812-732457334.png" alt="image" loading="lazy"></p>
<h2 id="3隐式原型__proto__">3.隐式原型__proto__</h2>
<p>在js中,每个对象都有一个__proto__ 属性(左右两边两个短下划线),这个__proto__就被称为隐式原型。</p>
<pre><code>console.log(obj3.__proto__ === Test.prototype)
// true
</code></pre>
<p>(1)每个js对象都有一个隐藏的原型对象属性__proto__,它指向创建它的构造函数的原型对象(Test.prototype)<br>
(2)<strong>proto__存在的意义在于为原型链查找提供方向,原型链查找靠的是__proto</strong>,而不是prototype</p>
<pre><code>function Test(name, age){
    this.name = name
    this.age = age
}

Test.prototype.say = function(){
    console.log('我能说话')
}
var obj3 = new Test('Jack', 26)


1, 构造函数是? 实例是?
2, obj3.constructor === Test   true or false?
3, obj3.__proto__ === Test ?
4, Test.prototype === obj3.__proto__ ?
5, obj3.__proto__.constructor === Test ?

// 1, Testobj32,true3,false4,true5,true
</code></pre>
<h2 id="4原型链">4.原型链</h2>
<p>Test的原型对象Test.prototype会不会也有一个隐式原型__proto__</p>
<pre><code>Test.prototype.__proto__ === Object.prototype
// true
</code></pre>
<p>(1) Test.prototype的隐式原型(<strong>proto</strong>)就是Object.prototype<br>
(2) 所有的对象,包括构造函数的原型对象,最终都继承自 Object.prototype,这是js原型链的顶点<br>
此时的关系图:<br>
<img src="https://img2024.cnblogs.com/blog/3390382/202507/3390382-20250713235231095-1409817950.png" alt="image" loading="lazy"></p>
<h3 id="链">链</h3>
<p>Object.prototype作为原型链的顶端,位于原型链的最末端。因此,它不再有自己的原型,所以Object.prototype.<strong>proto</strong> 指向null,表示原型链的终点</p>
<ul>
<li>原型链的终点是null</li>
<li>Object.prototype.<strong>proto</strong> === null<br>
<img src="https://img2024.cnblogs.com/blog/3390382/202507/3390382-20250713235410669-713860297.png" alt="image" loading="lazy"><br>
每个对象都有一个原型(prototype),它指向另外一个对象,而指向的对象又存在属性(<em>proto</em>)指向另外一个对象。当我们访问对象(obj3)的属性时,会先在对象定义的属性中进行查找,没找到就会沿着__proto__一路向上查找,最终形成一个链式结构,这整个链式结构就叫做<strong>原型链</strong><br>
如果在原型链中找到了这个属性,就返回找到的属性值;如果整个原型链都没找到这个属性值,则返回 undefined,没找到方法直接报错</li>
</ul>
<pre><code>function Test(name, age){
    this.name = name
    this.age = age
}
Test.prototype.say = function(){
    console.log('我能说话')
}
var obj3 = new Test('Jack', 26)
var obj4 = new Test('Rose', 24)

1, Test.prototype === ( ) ?
2, obj3.__proto__.__proto__ === ( ) ?
3, obj3.__proto__ === obj4.__proto__ ?
4, Test.prototype.__proto__ === ( ) ?
5, obj4.__proto__.constructor === ( ) ?
6, Object.prototype.__proto__ === ( ) ?
7, obj3.say === obj4.say ?


// 1, obj3.__proto__ 或 obj4.__proto    2,Object.prototype    3, true (二者都由Test new出来,在原型链上都指向 Test.prototype)
// 4, Object.prototype    5, Test    6, null (终点)    7,true (同问题3)
</code></pre>
<p>原型链面试题</p>
<h1 id="promise">Promise</h1>
<h2 id="1-promise是什么">1. Promise是什么?</h2>
<blockquote>
<p>Promise 是异步编程的一种解决方案,比传统的解决方案回调函数,更合理和更强大。ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象 。</p>
</blockquote>
<ul>
<li>指定回调函数方式更灵活易懂。</li>
<li>解决异步 <strong>回调地狱</strong> 的问题。</li>
</ul>
<h3 id="1-回调地狱">(1). 回调地狱</h3>
<ul>
<li>当一个回调函数嵌套一个回调函数的时候</li>
<li>就会出现一个嵌套结构</li>
<li>当嵌套的多了就会出现回调地狱的情况</li>
<li>比如我们发送三个 ajax 请求
<ul>
<li>第一个正常发送</li>
<li>第二个请求需要第一个请求的结果中的某一个值作为参数</li>
<li>第三个请求需要第二个请求的结果中的某一个值作为参数</li>
</ul>
<pre><code class="language-javascript">ajax({
url: '我是第一个请求',
success (res) {
    // 现在发送第二个请求
    ajax({
      url: '我是第二个请求',
      data: { a: res.a, b: res.b },
      success (res2) {
      // 进行第三个请求
      ajax({
          url: '我是第三个请求',
          data: { a: res2.a, b: res2.b },
                                success (res3) {
            console.log(res3)
          }
      })
      }
    })
}
})
</code></pre>
</li>
<li><strong>回调地狱,其实就是回调函数嵌套过多导致的</strong></li>
<li>当代码成为这个结构以后,已经没有维护的可能了</li>
</ul>
<h2 id="2-promise使用">2. Promise使用</h2>
<ul>
<li>语法:<pre><code class="language-javascript">new Promise(function (resolve, reject) {
// resolve 表示成功的回调
// reject 表示失败的回调
}).then(function (res) {
// 成功的函数
}).catch(function (err) {
// 失败的函数
})
</code></pre>
</li>
</ul>
<h2 id="3-promise-对象的状态">3. Promise 对象的状态</h2>
<p>Promise 对象通过自身的状态,来控制异步操作。Promise 实例具有三种状态。</p>
<pre><code>异步操作未完成(pending)
异步操作成功(fulfilled)
异步操作失败(rejected)
</code></pre>
<p>这三种的状态的变化途径只有两种。</p>
<pre><code>从“未完成”到“成功”
从“未完成”到“失败”
</code></pre>
<p>一旦状态发生变化,就凝固了,不会再有新的状态变化。这也是 Promise 这个名字的由来,它的英语意思是“承诺”,一旦承诺成效,就不得再改变了。这也意味着,Promise 实例的状态变化只可能发生一次。<br>
因此,Promise 的最终结果只有两种。</p>
<pre><code>异步操作成功,Promise 实例传回一个值(value),状态变为fulfilled。
异步操作失败,Promise 实例抛出一个错误(error),状态变为rejected。
</code></pre>
<h2 id="4promise对象方法">4.Promise对象方法</h2>
<blockquote>
<p>Promise 是一个对象,也是一个构造函数。</p>
</blockquote>
<h3 id="1promiseresolve">(1).Promise.resolve</h3>
<p>将现有对象转为 Promise 对象</p>
<pre><code class="language-javascript">Promise.resolve('kerwin')
// 等价于
new Promise(resolve =&gt; resolve('kerwin'))
</code></pre>
<h3 id="2promisereject">(2).Promise.reject</h3>
<p><code>Promise.reject(reason)</code>方法也会返回一个新的 Promise 实例,该实例的状态为<code>rejected</code>。</p>
<pre><code class="language-javascript">const p = Promise.reject('error');
// 等同于
const p = new Promise((resolve, reject) =&gt; reject('error'))
</code></pre>
<h3 id="3promiseall">(3).Promise.all</h3>
<p><code>Promise.all()</code>方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。</p>
<pre><code class="language-javascript">const p = Promise.all();
</code></pre>
<p>p的状态由p1,p2,p3 决定,分成两种情况。<br>
1️⃣只有<code>p1</code>、<code>p2</code>、<code>p3</code>的状态都变成<code>fulfilled</code>,<code>p</code>的状态才会变成<code>fulfilled</code>,此时<code>p1</code>、<code>p2</code>、<code>p3</code>的返回值组成一个数组,传递给<code>p</code>的回调函数。<br>
2️⃣只要<code>p1</code>、<code>p2</code>、<code>p3</code>之中有一个被<code>rejected</code>,<code>p</code>的状态就变成<code>rejected</code>,此时第一个被<code>reject</code>的实例的返回值,会传递给<code>p</code>的回调函数。</p>
<h3 id="4promiserace">(4).Promise.race</h3>
<p><code>Promise.race()</code>方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。</p>
<pre><code class="language-javascript">const p = Promise.race();
</code></pre>
<p>上面代码中,只要<code>p1</code>、<code>p2</code>、<code>p3</code>之中有一个实例率先改变状态,<code>p</code>的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给<code>p</code>的回调函数。</p>
<h3 id="5promiseallsettled">(5).Promise.allSettled</h3>
<p><code>Promise.allSettled()</code>方法,用来确定一组异步操作是否都结束了(不管成功或失败)。所以,它的名字叫做”Settled“,包含了”fulfilled“和”rejected“两种情况。</p>
<pre><code class="language-js">const promises = [ ajax('/200接口'), ajax('/401接口') ];
Promise.allSettled(promises).then(results=&gt;{
    // 过滤出成功的请求
    results.filter(item =&gt;item.status === 'fulfilled');
    过滤出失败的请求
    results.filter(item=&gt; item.status === 'rejected');
})
</code></pre>
<h3 id="6promiseany">(6).Promise.any</h3>
<p>只要参数实例有一个变成<code>fulfilled</code>状态,包装实例就会变成<code>fulfilled</code>状态;如果所有参数实例都变成<code>rejected</code>状态,包装实例就会变成<code>rejected</code>状态。</p>
<blockquote>
<p><code>Promise.any()</code>跟<code>Promise.race()</code>方法很像,只有一点不同,就是<code>Promise.any()</code>不会因为某个 Promise 变成<code>rejected</code>状态而结束,必须等到所有参数 Promise 变成<code>rejected</code>状态才会结束。</p>
</blockquote>
<h2 id="5手写promise">5.手写Promise</h2>
<pre><code class="language-js">function KerwinPromise(executor) {
    this.status = "pending";
    this.result = undefined;
    this.cb = []
    var _this = this;

    function resolve(res) {
      if (_this.status !== "pending") return;
      // console.log(_this)
      _this.status = "fulfilled"
      _this.result = res;

      _this.cb.forEach(item =&gt; {
            item.successCB &amp;&amp; item.successCB(_this.result)
      });
    }

    function reject(res) {
      if (_this.status !== "pending") return;
      // console.log("reject")
      _this.status = "rejected"
      _this.result = res;
      _this.cb.forEach(item =&gt; {
            item.failCB &amp;&amp; item.failCB(_this.result)
      });
    }
    executor(resolve, reject)
}

KerwinPromise.prototype.then = function (successCB, failCB) {

    if(!successCB){
      successCB = value=&gt;value
    }
    if(!failCB){
      failCB = error=&gt;error
    }

    // successCB()
    return new KerwinPromise((resolve, reject) =&gt; {
      if (this.status === "fulfilled") {
            var result = successCB &amp;&amp; successCB(this.result)
            // console.log(result);

            if (result instanceof KerwinPromise) {
                result.then(res =&gt; {
                  // console.log(res)
                  resolve(res);
                }, err =&gt; {
                  // console.log(err)
                  reject(err)
                })
            } else {
                resolve(result);
            }
      }
      if (this.status === "rejected") {
            var result = failCB &amp;&amp; failCB(this.result)

            if (result instanceof KerwinPromise) {
                result.then(res =&gt; {
                  // console.log(res)
                  resolve(res);
                }, err =&gt; {
                  // console.log(err)
                  reject(err)
                })
            } else {
                reject(result);
            }
      }

      if (this.status === "pending") {
            //收集回调
            this.cb.push({
                successCB: () =&gt; {
                  var result = successCB &amp;&amp; successCB(this.result)

                  if (result instanceof KerwinPromise) {
                        result.then(res =&gt; {
                            // console.log(res)
                            resolve(res);
                        }, err =&gt; {
                            // console.log(err)
                            reject(err)
                        })
                  } else {
                        resolve(result);
                  }
                },
                failCB: () =&gt; {
                  var result = failCB &amp;&amp; failCB(this.result)
                  if (result instanceof KerwinPromise) {
                        result.then(res =&gt; {
                            // console.log(res)
                            resolve(res);
                        }, err =&gt; {
                            // console.log(err)
                            reject(err)
                        })
                  } else {
                        reject(result);
                  }
                }
            })
      }
    })
}

KerwinPromise.prototype.catch= function(failCB){
    this.then(undefined,failCB)
}
</code></pre>
<h2 id="6async与await">6.Async与Await</h2>
<h3 id="1async">(1).Async</h3>
<p>async 函数,使得异步操作变得更加方便。</p>
<ul>
<li>更好的语义。</li>
<li>返回值是 Promise。</li>
</ul>
<pre><code class="language-js">async function test(){
       
}
test()
</code></pre>
<h3 id="2await">(2).Await</h3>
<p><code>await</code>命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。</p>
<pre><code>async function test(){
    var res1 =await ajax("http://localhost:3000/news1")
    var res2 =await ajax("http://localhost:3000/news2")
    return res2
}

test().then(res=&gt;{
        console.log("返回结果",res)
}).catch(err=&gt;{
        console.log("err",err)
})
</code></pre>
<h3 id="3错误处理">(3)错误处理</h3>
<pre><code class="language-js">try{
    var res1 =await ajax("http://localhost:3000/news1")
    var res2 =await ajax("http://localhost:3000/news2")
}catch(err){
        console.log("err",err)
}
</code></pre>
<h1 id="箭头函数和普通函数">箭头函数和普通函数</h1>
<h2 id="1语法上">1.语法上</h2>
<ul>
<li>普通函数,使用 function 关键字定义,语法相对冗长。</li>
<li>箭头函数 使用 =&gt; 语法定义,语法简洁,适合单行函数</li>
</ul>
<h2 id="2this-的绑定">2.this 的绑定</h2>
<ul>
<li>普通函数的 this 是动态绑定的,取决于函数的调用方式。<br>
在<strong>全局作用域</strong>中,this 指向 window(浏览器)或 global(Node.js)。<br>
在<strong>对象方法</strong>中,this 指向调用该方法的对象。</li>
<li>箭头函数的 this 是词法绑定的,继承自外层作用域的 this。<br>
箭头函数没有自己的 this,因此不能通过 call、apply 或 bind 改变 this。<br>
<img src="https://img2024.cnblogs.com/blog/3390382/202507/3390382-20250715003941663-126103899.png" alt="image" loading="lazy"></li>
</ul>
<h4 id="普通函数中">普通函数中</h4>
<ul>
<li>在 setTimeout 的回调函数中,使用了普通函数 function() {... }。普通函数的 this 指向是动态的,它的值取决于函数的调用方式。</li>
<li>在 setTimeout 中,回调函数是在全局作用域下被调用的(严格模式下为 undefined),而不是在 normal 对象的上下文中被调用的。因此,在这个回调函数中,this 并不指向 normal 对象,而是指向全局对象(在浏览器环境中是 window 对象)。</li>
</ul>
<blockquote>
<p>当你调用 setTimeout 并传入一个回调函数时,JavaScript 引擎会将这个回调函数添加到一个任务队列中。当指定的时间到达时,引擎会从任务队列中取出这个回调函数并执行它。<strong>任务队列处于全局环境中。</strong></p>
</blockquote>
<ul>
<li>由于全局对象中没有定义 bibi 属性,所以 this.bibi 的值是 undefined,导致最终输出 普通函数: undefined。</li>
</ul>
<h4 id="箭头函数中">箭头函数中</h4>
<ul>
<li>在 setTimeout 的回调函数中,使用了箭头函数 () =&gt; {... }。箭头函数的 this 指向是静态的,它继承自外层作用域的 this。</li>
</ul>
<blockquote>
<p>箭头函数的 this 绑定是在<strong>定义时</strong>就确定的,因此在箭头函数被添加到任务队列之前,this 的值已经被捕获并保存。当箭头函数在任务队列中被执行时,它会使用这个已经确定的 this 值。</p>
</blockquote>
<ul>
<li>在 arrow 对象的 biubiu 方法中,this 指向 arrow 对象。箭头函数的 this 继承了外层作用域(即 biubiu 方法)的 this,因此在箭头函数中,this 仍然指向 arrow 对象。</li>
</ul>
<h2 id="3构造函数">3.构造函数</h2>
<ul>
<li>普通函数可以作为构造函数使用,通过 new 关键字创建实例。构造函数内部的 this 指向新创建的实例。</li>
<li>箭头函数不能作为构造函数使用,使用 new 调用箭头函数会抛出错误。箭头函数没有 prototype 属性。</li>
</ul>
<h1 id="this指向-1">This指向</h1><br><br>
来源:https://www.cnblogs.com/L178/p/18977971/JavaScript
頁: [1]
查看完整版本: JavaScript