时间:2025-07-10 23:53
人气:
作者:admin
基础类型: String、Number、Boolean、null、undefined、Symbol 存储在栈中,赋值变量和比较均为数据本身。
引用类型:Object、Array、Map、Function 存储在堆中,使用new创建,赋值变量和比较均是内存地址。
不能区分null,数组,对象,正则,因为返回的都是”object”
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'
var c= [1,2,3];
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
根据对象的constructor判断,返回对创建此对象的数组函数的引用。
几乎可以判断基本数据类型和引用数据类型
var c= [1,2,3];
var d = new Date();
var e = function(){alert(111);};
alert(c.constructor === Array) ----------> true
alert(d.constructor === Date) -----------> true
alert(e.constructor === Function) -------> true
//注意: constructor 在类继承时会出错
4.Object.prototype.toString.call() 最准确的判断方式
所有数据类型均可判断:Object.prototype.toString.call()
这是对象的一个原生原型扩展函数,用来更精确的区分数据类型(类)。
原理
var gettype = Object.prototype.toString
gettype.call('aaaa') 输出 [object String]
gettype.call(2222) 输出 [object Number]
gettype.call(true) 输出 [object Boolean]
gettype.call(undefined) 输出 [object Undefined]
gettype.call(null) 输出 [object Null]
gettype.call({}) 输出 [object Object]
gettype.call([]) 输出 [object Array]
gettype.call(function(){}) 输出 [object Function]
// 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
// 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
// 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
for (var i = 0; i < 5; i++) {
setTimeout(() => console.log('i======', i))
}//输出5 5 5 5 5
for (let j = 0; j < 5; j++) {
setTimeout(() => console.log('j======', j))
}//输出0 1 2 3 4
var a = 10;
console.log(window.a) // 10
console.log(a) // undefined
var a = 20
在编译阶段,编译器会将其变成以下执行
var a
console.log(a)
a = 20
var a = 20
function change(){
var a = 30
}
change()
console.log(a) // 20
而如果在函数内不使用var,该变量是全局的
var a = 20
function change(){
a = 30
}
change()
console.log(a) // 30
let是ES6新增的命令,用来声明变量
{
let a = 20
}
console.log(a) // ReferenceError: a is not defined.
console.log(a) // 报错ReferenceError
let a = 2
这表示在声明它之前,变量a是不存在的,这时如果用到它,就会抛出一个错误
var a = 123
if (true) {
a = ‘abc’ // ReferenceError
let a;
}
使用let声明变量前,该变量都不可用,也就是大家常说的“暂时性死区”
let a = 20
let a = 30
// Uncaught SyntaxError: Identifier ‘a’ has already been declared
注意的是相同作用域,下面这种情况是不会报错的
let a = 20
{
let a = 30
}
const声明一个只读的常量,一旦声明,常量的值就不能改变
const a = 1
a = 3
// TypeError: Assignment to constant variable.
这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值
const a;
// SyntaxError: Missing initializer in const declaration
如果之前用var或let声明过变量,再用const声明同样会报错
var a = 20
let b = 20
const a = 30
const b = 30
// 都会报错
const实际上保证的并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动
对于简单类型的数据,值就保存在变量指向的那个内存地址,因此等同于常量
对于复杂类型的数据,变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的,并不能确保改变量的结构不变
const foo = {};
// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123
// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: “foo” is read-only
其它情况,const与let一致
事件循环练习
由于js是单线程,为防止代码阻塞,将代码分为同步和异步,同步代码会直接放入执行栈中执行,异步代码(如setTimeout)放入宿主环境(浏览器,Node)中,时机到了(点击事件即点击后,setTimeout即时间结束后)以后将回调函数放入任务队列中,执行栈中的代码执行完后就会去任务队列中查看有无异步代码要执行。反复循环查看执行,这个过程就是事件循环。

js又把异步任务分为宏任务(由宿主环境发起,如script,事件,网络请求Ajax/Fetch,setTimeout()/setInterval())和微任务(由JS引擎发起,如Promise,Promise本身同步,then/catch回调函数异步)



console.log('A');·
setTimeout(() => console.log('B'), 0);
Promise.resolve().then(() => console.log('C'));
Promise.resolve().then(() => setTimeout(() => console.log('D'), 0));
console.log('E');
A → E → C → B → D
const promise = new Promise((resolve, reject) => {
console.log(1);
console.log(2);
});
promise.then(() => {
console.log(3);
});
console.log(4);
1 → 2 → 4
async function async1() {
console.log('1');
await async2();
console.log('2');
}
async function async2() { console.log('3'); }
setTimeout(() => console.log('4'), 0);
async1();
new Promise(resolve => {
console.log('5');
resolve();
}).then(() => console.log('6'));
console.log('7');
1 → 3 → 5 → 7 → 2 → 6 → 4
new Promise((resolve) => {
console.log(1);
resolve(3);
Promise.resolve().then(() => {
console.log(4);
});
}).then((num) => {
console.log(num);
});
setTimeout(() => {
console.log(6);
});
Promise.resolve().then(() => {
console.log(5);
});
console.log(2);
1 → 2 → 4 → 3 → 5 → 6
console.log('start');
setTimeout(() => {
console.log('Timeout1');
}, 1000);
Promise.resolve().then(() => {
console.log('Promise1');
});
Promise.resolve().then(() => {
console.log('Promise2');
setTimeout(() => {
Promise.resolve().then(() => {
console.log('Promise3');
})
console.log('Timeout2');
}, 0);
});
console.log('end');
start → end → Promise1 → Promise2 → Timeout2 → Promise3 → Timeout1
function app() {
setTimeout(() => {
console.log("1-1");3
Promise.resolve().then(() => {
console.log("2-1");5
});
});
console.log("1-2"); 1
Promise.resolve().then(() => {
console.log("1-3"); 2
setTimeout(() => {
console.log("3-1"); 4
});
});
}
JS有如下数据类型:
原始数据类型:String, Number, Boolean, Null, Undefined, Symbol
引用数据类型:Object
而存放这些数据的内存又可以分为两部分:栈内存(Stack)和堆内存(Heap)。原始数据类型存在栈中,引用类型存在堆中。
栈是一种只能一端进出的数据结构,先进后出,后进先出。

JS中原始数据类型的内存大小是固定的,由系统自动分配内存。但是引用数据类型,比如Object, Array,他们的大小不是固定的,所以是存在堆内存的。JS不允许直接操作堆内存,我们在操作对象时,操作的实际是对象的引用,而不是实际的对象。可以理解为对象在栈里面存了一个内存地址,这个地址指向了堆里面实际的对象。所以引用类型的值是一个指向堆内存的引用地址。

函数也是引用类型,当我们定义一个函数时,会在堆内存中开辟一块内存空间,将函数体代码以字符串的形式存进去。然后将这块内存的地址赋值给函数名,函数名和引用地址会存在栈上。

垃圾回收就是找出那些不再继续使用的变量,然后释放其占用的内存,垃圾回收器会按照固定的时间间隔周期性执行这一操作。JS使用垃圾回收机制来自动管理内存,但是他是一把双刃剑:
优势: 可以大幅简化程序的内存管理代码,降低程序员负担,减少因为长时间运行而带来的内存泄漏问题。
劣势:程序员无法掌控内存,JS没有暴露任何关于内存的API,我们无法进行强制垃圾回收,更无法干预内存管理。
引用计数是一种回收策略,它跟踪记录每个值被引用的次数,每次引用的时候加一,被释放时减一,如果一个值的引用次数变成0了,就可以将其内存空间回收。
使用引用计数会有一个很严重的问题:循环引用。循环引用指的是对象A中包含一个指向对象B的指针,而对象B中也包含一个指向对象A的引用。
function problem(){
var objectA = {};
var objectB = {};
objectA.a = objectB;
objectB.b = objectA;
}
在这个例子中,objectA 和 objectB 通过各自的属性相互引用;也就是说,这两个对象的引用次数都是 2。当函数执行完毕后,objectA 和 objectB 还将继续存在,因为它们的引用次数永远不会是 0。
算法分为 “标记”和“清除” 两个阶段,最终目的是识别并释放 “不再需要” 的内存。
当一个内部函数引用了外部函数的变量时,就形成了闭包。
通过此特性,我们可以解决一个全局变量污染的问题, 早期在 JavaScript 还无法进行模块化的时候,在多人协作时,如果定义过多的全局变量有可能造成全局变量命名冲突,使用闭包来解决功能对变量的调用将变量写到一个独立的空间里面,从而能够一定程度上解决全局变量污染的问题。
for (var i = 1; i <= 3; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}
var 声明的变量没有块级作用域(仅函数作用域或全局作用域),因此整个循环共享同一个 i 变量。
for (var i = 1; i <= 3; i++) {
(function (index) {
setTimeout(function () {
console.log(index);
}, 1000);
})(i)
}
let 声明的变量具有块级作用域(每个循环迭代都会创建独立的变量副本)。
函数中的this指向取决于函数如何调用,它指向当前函数的运行环境
判断this的唯一依据就是此刻函数的执行由哪个对象调用
在js中,每一个对象(函数也是对象)都有一个特殊的属性叫做原型(prototype),它指向另一个对象,这个对象(Test.prototype)被称为原型对象, 原型对象是用来共享属性和方法的
(1)原型对象有一个constructor属性指向构造函数本身(Test)。
(2)原型对象是一个普通的对象,它包含属性和方法。
(3)原型对象的属性和方法会被继承到所有通过原型链与它相连的对象。
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
构造函数和实例之间就初步构成了这样一个关系,如图:

在js中,每个对象都有一个__proto__ 属性(左右两边两个短下划线),这个__proto__就被称为隐式原型。
console.log(obj3.__proto__ === Test.prototype)
// true
(1)每个js对象都有一个隐藏的原型对象属性__proto__,它指向创建它的构造函数的原型对象(Test.prototype)
(2)proto__存在的意义在于为原型链查找提供方向,原型链查找靠的是__proto,而不是prototype
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, Test obj3 2,true 3,false 4,true 5,true
Test的原型对象Test.prototype会不会也有一个隐式原型__proto__
Test.prototype.__proto__ === Object.prototype
// true
(1) Test.prototype的隐式原型(proto)就是Object.prototype
(2) 所有的对象,包括构造函数的原型对象,最终都继承自 Object.prototype,这是js原型链的顶点
此时的关系图:

Object.prototype作为原型链的顶端,位于原型链的最末端。因此,它不再有自己的原型,所以Object.prototype.proto 指向null,表示原型链的终点

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)
Promise 是异步编程的一种解决方案,比传统的解决方案回调函数, 更合理和更强大。ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象 。
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)
}
})
}
})
}
})
new Promise(function (resolve, reject) {
// resolve 表示成功的回调
// reject 表示失败的回调
}).then(function (res) {
// 成功的函数
}).catch(function (err) {
// 失败的函数
})
Promise 对象通过自身的状态,来控制异步操作。Promise 实例具有三种状态。
异步操作未完成(pending)
异步操作成功(fulfilled)
异步操作失败(rejected)
这三种的状态的变化途径只有两种。
从“未完成”到“成功”
从“未完成”到“失败”
一旦状态发生变化,就凝固了,不会再有新的状态变化。这也是 Promise 这个名字的由来,它的英语意思是“承诺”,一旦承诺成效,就不得再改变了。这也意味着,Promise 实例的状态变化只可能发生一次。
因此,Promise 的最终结果只有两种。
异步操作成功,Promise 实例传回一个值(value),状态变为fulfilled。
异步操作失败,Promise 实例抛出一个错误(error),状态变为rejected。
Promise 是一个对象,也是一个构造函数。
将现有对象转为 Promise 对象
Promise.resolve('kerwin')
// 等价于
new Promise(resolve => resolve('kerwin'))
Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。
const p = Promise.reject('error');
// 等同于
const p = new Promise((resolve, reject) => reject('error'))
Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.all([p1, p2, p3]);
p的状态由p1,p2,p3 决定,分成两种情况。
1️⃣只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
2️⃣只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.race([p1, p2, p3]);
上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
Promise.allSettled()方法,用来确定一组异步操作是否都结束了(不管成功或失败)。所以,它的名字叫做”Settled“,包含了”fulfilled“和”rejected“两种情况。
const promises = [ ajax('/200接口'), ajax('/401接口') ];
Promise.allSettled(promises).then(results=>{
// 过滤出成功的请求
results.filter(item =>item.status === 'fulfilled');
过滤出失败的请求
results.filter(item=> item.status === 'rejected');
})
只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。
Promise.any()跟Promise.race()方法很像,只有一点不同,就是Promise.any()不会因为某个 Promise 变成rejected状态而结束,必须等到所有参数 Promise 变成rejected状态才会结束。
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 => {
item.successCB && 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 => {
item.failCB && item.failCB(_this.result)
});
}
executor(resolve, reject)
}
KerwinPromise.prototype.then = function (successCB, failCB) {
if(!successCB){
successCB = value=>value
}
if(!failCB){
failCB = error=>error
}
// successCB()
return new KerwinPromise((resolve, reject) => {
if (this.status === "fulfilled") {
var result = successCB && successCB(this.result)
// console.log(result);
if (result instanceof KerwinPromise) {
result.then(res => {
// console.log(res)
resolve(res);
}, err => {
// console.log(err)
reject(err)
})
} else {
resolve(result);
}
}
if (this.status === "rejected") {
var result = failCB && failCB(this.result)
if (result instanceof KerwinPromise) {
result.then(res => {
// console.log(res)
resolve(res);
}, err => {
// console.log(err)
reject(err)
})
} else {
reject(result);
}
}
if (this.status === "pending") {
//收集回调
this.cb.push({
successCB: () => {
var result = successCB && successCB(this.result)
if (result instanceof KerwinPromise) {
result.then(res => {
// console.log(res)
resolve(res);
}, err => {
// console.log(err)
reject(err)
})
} else {
resolve(result);
}
},
failCB: () => {
var result = failCB && failCB(this.result)
if (result instanceof KerwinPromise) {
result.then(res => {
// console.log(res)
resolve(res);
}, err => {
// console.log(err)
reject(err)
})
} else {
reject(result);
}
}
})
}
})
}
KerwinPromise.prototype.catch= function(failCB){
this.then(undefined,failCB)
}
async 函数,使得异步操作变得更加方便。
async function test(){
}
test()
await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。
async function test(){
var res1 = await ajax("http://localhost:3000/news1")
var res2 = await ajax("http://localhost:3000/news2")
return res2
}
test().then(res=>{
console.log("返回结果",res)
}).catch(err=>{
console.log("err",err)
})
try{
var res1 = await ajax("http://localhost:3000/news1")
var res2 = await ajax("http://localhost:3000/news2")
}catch(err){
console.log("err",err)
}

当你调用 setTimeout 并传入一个回调函数时,JavaScript 引擎会将这个回调函数添加到一个任务队列中。当指定的时间到达时,引擎会从任务队列中取出这个回调函数并执行它。任务队列处于全局环境中。
箭头函数的 this 绑定是在定义时就确定的,因此在箭头函数被添加到任务队列之前,this 的值已经被捕获并保存。当箭头函数在任务队列中被执行时,它会使用这个已经确定的 this 值。