目录
1 值类型 vs 引用类型
-
值类型:undefined、string、number、boolean、symbol、bigInt
-
引用类型:Object、Array、Date、RegExp、特殊(Function:不用于存储数据、Null:指针指向为空地址)
值类型和引用类型的区别:值类型存储值,引用类型存储内存地址
* const不能声明undefied类型 必须声明有值的常量
深入分析 为什么?
因为值类型直接赋值内存比较小,对造成的性能不会有问题,所以可以直接赋值
而引用类型的json可以是上千上万行代码,一般造成内存比较大, 因此也需要将栈和堆严格分离出来,引用类型如果像值类型一样直接存储复制值 占内存 耗时
2 typeof能判断哪些类型
typeof 运算符
-
识别所有值类型
-
识别函数
-
判断是否是引用类型(不可再细分)
// 判断所有值类型
let a; typeof a // 'undefined'
const str = 'abc'; typeof str // 'string'
const n = 100; typeof n // 'number'
const b = true; typeof b // 'boolean'
const s = Symbol('s'); typeof s // 'symbol'
// 判断函数
typeof console.log // 'function'
typeof function () {} // 'function'
// 识别引用类型
typeof null // 'object'
typeof ['a', 'b'] // 'object'
typeof { x: 100 } // 'object'
3 何时使用===何时使用=2
=== 绝对相等,
== 存在隐式转化的相等值类型
除了 == null 之外,其它一律用 ===
100 == '100' // true
0 == '' // true
0 == false // true
false == '' // true
null == undefined // true
// 除了 == null 之外,其它一律用 ===,例如:
const obj = {x: 100 }
if (obj.a == null) {}
// 相当于:
if (obj.a === null || obj.a === undefined) {}
//字符串的+ 号
const a = 100 + 10 // 110
const b = 100 + '10' // '10010'
const c = true + '10' // 'true10'
const d = 100 + parseInt('10') // 110
4 值类型和引用类型的区别
5手写深拷贝
注意判断值类型和引用类型
eg.值类型是null直接返回,引用类型的话往下做一些递归遍历
注意判断是数组还是对象
递归
/**
* 深拷贝
* @param {Object} obj 要拷贝的对象
*/
function deepClone(obj) {
if (typeof obj !== 'object' || obj == null) {
// obj 是 null ,或者不是对象和数组,直接返回
return obj
}
// 初始化返回结果
let result
if (obj instanceof Array) {
result = []
} else {
result = {}
}
for (let key in obj) {
// 保证 key 不是原型的属性
if (obj.hasOwnProperty(key)) {
// 递归调用!!!
result[key] = deepClone(obj[key])
}
}
// 返回结果
return result
}
6 if 语句和逻辑运算
7 如何准确判断一个变量是不是数组?
var arr = []
arr instanceof Array // true
arr.constructor === Array // true
Object.prototype.toString.call(arr) === '[object Array]' // true
Array.isArray(arr) // true
8手写一个简易的jQuery ,考虑插件和扩展性
class jQuery {
constructor(selector) {
const result = document.querySelectorAll(selector)
const length = result.length
for (let i = 0; i < length; i++) {
this[i] = result[i]
}
this.length = length
this.selector = selector
}
get(index) {
return this[index]
}
each(fn) {
for (let i = 0; i < this.length; i++) {
const elem = this[i]
fn(elem)
}
}
on(type, fn) {
return this.each(elem => {
elem.addEventListener(type, fn, false)
})
}
// 扩展很多 DOM API
}
// 插件
jQuery.prototype.dialog = function (info) {
alert(info)
}
// “造轮子”
class myJQuery extends jQuery {
constructor(selector) {
super(selector)
}
// 扩展自己的方法
addClass(className) {
}
style(data) {
}
}
// const $p = new jQuery('p')
// $p.get(1)
// $p.each((elem) => console.log(elem.nodeName))
// $p.on('click', () => alert('clicked'))
9 class 的原型本质,怎么理解?
JS本身是基于原型继承的语言 问题的 本质————原型 和原型链
何用 class 实现继承
class
-
constructor
-
属性
-
方法
继承
-
extends(继承)
-
super(执行父类构造函数)
-
扩展或重写方法
class 继承(extends,super)
10 如何理解js原型
-
原型链本质是特殊的链表
-
链表用 next 连接,原型链用 __protp__ 连接
-
原型链最终指向 Object.prototype
-
instanceof 本质是遍历链表
每个class都是由显示原型的,每个实例都有隐式原型,隐式原型指向显示原型;
基于原型的执行规则:
1,获取实例的方法或者实例的属性时
2,现在自身属性和方法里寻找
3,找不到的时候则自动去__proto__里寻找
class People {
constructor (name, number) {
this.name = name
this.num = number
}
sayHi () {
console.log(`早上好${this.name}, 学号:${this.num}`)
}
}
const person1 = new People('刘备', 1212)
const person2 = new People('诸葛亮', 1213)
console.log(person1.__proto__ === People.prototype) // true
class Student extends People {
constructor (name, number) {
super(name, number)
}
eat () {
console.log('吃饭啦')
}
}
const p1 = new Student('张飞', 1209)
const p2 = new Student('赵云', 1200)
console.log(p1.__proto__ === Student.prototype, p1.prototype === Student.prototype) // false
console.log(p1.__proto__ === p2.__proto__) // true 两个实例的隐式原型同时指向Student的显示原型
console.log(p1.__proto__ === People.prototype, p1.__proto__ === Object.prototype) // false 实例的隐式原型指向的是父级类,没有指向父级以上的类原型
console.log(Student.prototype.__proto__ === People.prototype) // true 类的原型的隐式原型指向父级类的原型,这个可类比实例的隐式原型
p1.eat()
/**
* instanceof实现原理:
* 下面代码展示了instanceof实现判断数据类型的流程,
* instanceof沿着数据的原型链一级一级往上找,最后一个
* 判断是否为Array的返回false是由于再p1的原型链上没有Array
*/
console.log(p1 instanceof Student) // true
console.log(p1 instanceof People) // true
console.log(p1 instanceof Object) // true
console.log(p1 instanceof Array) // false
11 instanceof实现原理:
* 下面代码展示了instanceof实现判断数据类型的流程,
* instanceof沿着数据的原型链一级一级往上找,最后一个
* 判断是否为Array的返回false是由于再p1的原型链上没有Array
原型链:
-
每个构造函数都有 prototype(显式原型)
-
每个实例对象都有 __proto__ / [[prototype]](隐式原型)
-
实例对象的__proto__ (隐式原型) 指向构造函数的 prototype(显式原型)
12 作用域
作用域: 一个变量的合法使用范围
作用域种类:
-
全局作用域
-
函数作用域
-
块级作用域(es6的语法,在{ }内用let,const声明的变量),块级作用域:在if、while、for循环里面使用
// 花括号作用域,if,for
if(true){
let a = 100
}
for(let a=0; a<10; a++){
let b = 200
console.log(a)
console.log(b)
}
// 此时所有的a,b只能在块级作用域内使用,外部引用则报错
自由变量查找方式:
-
在函数当前的作用域开始查找
-
向上级作用域,一层一层依次往上查找,找到为止。没找到则报错
-
如果到全局作用域都没找到,则报错:xxx is not defined
13 this 的不同应用场景,如何取值
this
应用场景:
-
作为普通函数 --> window
-
使用bind、call、apply --> 传入的对象
-
作为对象方法被调用 --> 调用的对象
-
在 class 方法中调用 --> 实例对象
-
箭头函数 --> 上级作用域
注意:this的取值是在函数执行时确定的,不是函数定义的时候确定的
const zhangsan = {
name: '张三',
sayHi(){
console.log(this.name)
},
wait(){
// 定时器的回调是匿名函数,匿名函数默认指向window
setTimeout(function() {
console.log(this.name) // 打印undefind
}, 1000);
/*
// 箭头函数可以解决这个问题,让匿名函数指向当前对象
setTimeout(() => {
console.log(this.name)
}, 1000);
*/
}
}
// 执行函数的几种情况
zhangsan.sayHi() //打印张三
let ls = {name:'lisa'}
zhangsan.sayHi.call(ls) //打印lisa,调用call,bind,apply改变内部的this指向
zhangsan.wait() //打印undefind,定时器回调是匿名函数,匿名函数默认指向window
let say = zhangsan.sayHi //打印undefind意料之中,因为执行的时候let say变量定义在window
say() //所以say的this是window,window对象没有变量name
// 解决一个怪异的面试题
const obj = {
x:1,
// 创建obj对象时,这个箭头函数不受当前obj{}作用域限制,跳到window对象了,this属于window
print1:()=>{
console.log(this.x)
},
// 已下两种方式等价,是属于在obj内定义的对象(不是回调),this属于obj
print2(){
console.log(this.x)
},
print25: function (){
console.log(this.x)
},
// 定义时匿名函数被指向了window的作用域,所以内部this属于window
print3: function(){
console.log(this.x)
}.bind(this)
}
obj.print1() // undefind
obj.print2() // 1
obj.print25() // 1
obj.print3() // undefind
14 手写 bind 函数
bind()
方法创建一个新的函数,在bind()
被调用时,这个新函数的this
被指定为bind()
的第一个参数,而其余参数将作为新函数的参数,供调用时使用。 -
//手写bind:
function myBind() {
const content = [].shift.call(arguments);
content.fn = this;
const args = arguments;
return () => {
const result = content.fn(...args);
delete content.fn;
return result;
};
}
手写call、apply、bind
原理:
-
3个方法第一个参数为新的执行环境,所以是一个对象
-
将当前执行环境赋值给新环境的某个属性
-
使用新环境调用当前执行环境,相当于一个对象调用方法,方法的this就是这个对象
-
新环境是一个对象所以为引用类型,新增一个属性会改变自身,所以调用之后获得结果需要删除新增的属性
示例:
const obj = { a: "这是obj" };
function fn(arg1, arg2) {
console.log(this);
}
//手写bind:
function myBind() {
const content = [].shift.call(arguments);
content.fn = this;
const args = arguments;
return () => {
const result = content.fn(...args);
delete content.fn;
return result;
};
}
Function.prototype.myBind = myBind;
fn.myBind(obj, "arg1", "arg2")(); // {a: '这是obj'}
15什么是闭包 实际开发中闭包的应用场景,举例说明
闭包
作用域应用的特殊情况,指有权访问另一个函数作用域中的变量的函数
闭包实际上是一种函数,闭包就是也是函数技术的一种;闭包能做的事情函数几乎都能做。
闭包也可认为是一种作用域。
触发方式:
-
函数作为参数被传递
-
函数作为返回值被返回
2、闭包的作用:1、读取函数内部的变量;2、让这些变量始终保持在内存之中。
注意:
-
闭包的应用包括但不限于以下几个方面:
-
创建私有变量:闭包可以通过在函数内部定义变量,并将其作为返回值,实现创建私有变量的效果。这样可以避免全局变量的污染和冲突,提高代码的安全性和可维护性。
-
延长变量的生命周期:闭包可以使得变量在函数执行完成后仍然存在,并且可以在下一次调用函数时保持其值。这对于需要保留中间状态或者需要记住某些特定值的情况非常有用。
-
实现回调函数:闭包可以作为参数传递给其他函数,用于实现回调函数的功能。当某个条件满足时,调用该闭包函数,可以实现特定的逻辑或操作。这在事件处理、异步操作等场景中非常常见。
需要注意的是,闭包的使用需要谨慎。如果不是特定任务需要,过多地使用闭包可能导致性能下降和内存消耗增加。因此,在没有必要的情况下,最好避免在函数中创建闭包。
总之,闭包的应用包括创建私有变量、延长变量生命周期和实现回调函数等,能够提供更灵活和高效的编程方式
16 创建 10 个 a 标签,点击的时候弹出来相应的序号
for循环在很短的时间内就循环完了,i的值就变成了10,因为i的作用域是全局, 这个时候点击事件还没触发,所以再点击时所有 弹出来的都是 10
把i定义在for循环内就可以了,for循环内是一个块级作用域
JS中的 回调函数(callback)
回调函数:函数a有一个参数,这个参数是个函数b,当函数a执行完以后执行函数b。那么这个过程就叫回调。,这句话的意思是函数b以一个参数的形式传入函数a并执行,顺序是先执行a ,然后执行参数b,b就是所谓的回调函数。
注意在回调函数调用时this的执行上下文并不是回调函数定义时的那个上下文,而是调用它的函数所在的上下文。
为什么要用到回调函数:有一个非常重要的原因 —— JavaScript 是事件驱动的语言。这意味着,JavaScript 不会因为要等待一个响应而停止当前运行,而是在监听其他事件时继续执行
回调正是确保一段代码执行完毕之后再执行另一段代码的方式。
回调函数和异步操作的关系是什么?回调函数是异步么?
回调函数的本质是一种模式(一种解决常见问题的模式),因此回调函数也被称为回调模式。
简而言之:一个函数在另一个函数中被调用。而且可以当参数传给其他函数。
回调函数和异步操作是没有关系的!!!
callback 顾名思义 打电话回来的意思
eg1:你点外卖,刚好你要吃的食物没有了,于是你在店老板那里留下了你的电话,过了几天店里有了,店员就打了你的电话,然后你接到电话后就跑到店里买了。在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的事件,店员给你打电话叫做调用回调函数,你到店里去取货叫做响应回调事件。
eg2:再比如,你发送一个axios 请求,请求成功之后,触发成功的回调函数,请求失败触发失败的回调函数。这里面的回调函数更像是一个工具,后台通过这个工具告诉你,你成功了抑或是失败了。这里面的所有异步操作都和回调没关系,真正的异步是then方法。
JS中的 回调函数(callback)_前端小草籽的博客-CSDN博客_js回调函数
js中的回调函数_简单的小伙子的博客-CSDN博客_回调函数js
JS同步执行、异步执行、及同步中的异步执行
首先明确两点:
1.JS 执行机制是单线程。
2.JS的Event loop是JS的执行机制,深入了解Event loop,就等于深入了解JS引擎的执行。
3 浏览器是单线程执行JavaScript代码的,但是浏览器实际上是以多个线程协助操作来实现单线程异步模型的,具体线程组成如下:
1.GUI 渲染进程
2.JavaScript 引擎线程
3.事件触发线程
4.定时器触发线程
5.http 请求线程
6.其他线程
1.【主线程】:这个线程用来执行页面的渲染,JavaScript 代码的运行,事件的触发等等。
2.【工作线程】:这个线程是在幕后工作的,用来处理异步任务的执行来实现非阻塞的运行模式。
js的异步执行_理想和远方_在路上的博客-CSDN博客_js异步执行
JS的异步是怎么实现的?
四个线程参与了JS的执行,但是永远只有JS引擎线程在执行JS脚本程序,其他三个线程只负责将满足触发条件的处理函数推进任务队列,等待JS引擎线程执行。
所以JS异步的实现靠的就是浏览器的多线程,当他遇到异步API时,就将这个任务交给对应的线程,当这个异步API满足回调条件时,对应的线程又通过事件触发线程将这个事件放入任务队列,然后主线程从任务队列取出事件继续执行。
原文链接:https://blog.csdn.net/weixin_43845090/article/details/119727502
JS的异步是怎么实现的_程序员的脱发之路的博客-CSDN博客_js异步怎么实现
回调函数本身是同步代码。
JavaScript中的回 调函数结构,默认是同步的结构,由于JavaScript单线程异步模型的规则,如果想要编写异步的代码,必须 使用回调嵌套的形式才能实现,所以回调函数结构不一定是异步代码,但是异步代码一定是回调函数结构。
宏任务和微任务
宏任务: 宏任务是JavaScript中最原始的异步任务,包括setTimeout、setInterVal、AJAX等,在代码执⾏环境中按照同步代 码的顺序,逐个进⼊⼯作线程挂起,再按照异步任务到达的时间节点,逐个进⼊异步任务队列,最终按照队列中的 顺序进⼊函数执⾏栈进⾏执⾏。
微任务: 微任务是随着ECMA标准升级提出的新的异步任务,微任务在异步任务队列的基础上增加了【微任务】的概念,每 ⼀个宏任务执⾏前,程序会先检测中是否有当次事件循环未执⾏的微任务,优先清空本次的微任务后,再执⾏下⼀ 个宏任务,每⼀个宏任务内部可注册当次任务的微任务队列,再下⼀个宏任务执⾏前运⾏,微任务也是按照进⼊队 列的顺序执⾏的。
总结:在JavaScript的运⾏环境中,代码的执⾏流程是这样的:
1. 默认的同步代码按照顺序从上到下,从左到右运⾏,运⾏过程中注册本次的微任务和后续的宏任务:
2. 执⾏本次同步代码中注册的微任务,并向任务队列注册微任务中包含的宏任务和微任务
3. 将下⼀个宏任务开始前的所有微任务执⾏完毕
4. 执⾏最先进⼊队列的宏任务,并注册当次的微任务和后续的宏任务,宏任务会按照当前任务队列的队尾继续向 下排列
单线程的概念
JavaScript是一门单线程语言,因此,JavaScript在同一个时间只能做一件事,单线程意味着,如果在同个时间有多个任务的话,这些任务就需要进行排队,前一个任务执行完,才会执行下一个任务
为什么javascript是单线程
其实,JavaScript的单线程,与它的用途是有很大关系,我们都知道,JavaScript作为浏览器的脚本语言,主要用来实现与用户的交互,利用JavaScript,我们可以实现对DOM的各种各样的操作,如果JavaScript是多线程的话,一个线程在一个DOM节点中增加内容,另一个线程要删除这个DOM节点,那么这个DOM节点究竟是要增加内容还是删除呢?这会带来很复杂的同步问题,因此,JavaScript是单线程的。
为什么会有同步和异步
因为JavaScript的单线程,因此同个时间只能处理同个任务,所有任务都需要排队,前一个任务执行完,才能继续执行下一个任务,但是,如果前一个任务的执行时间很长,比如文件的读取操作或ajax操作,后一个任务就不得不等着,拿ajax来说,当用户向后台获取大量的数据时,不得不等到所有数据都获取完毕才能进行下一步操作,用户只能在那里干等着,严重影响用户体验
因此,JavaScript在设计的时候,就已经考虑到这个问题,主线程可以完全不用等待文件的读取完毕或ajax的加载成功,可以先挂起处于等待中的任务,先运行排在后面的任务,等到文件的读取或ajax有了结果后,再回过头执行挂起的任务,因此,任务就可以分为同步任务和异步任务
同步任务
同步任务是指在主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务,当我们打开网站时,网站的渲染过程,比如元素的渲染,其实就是一个同步任务
异步任务
异步任务是指**不进入主线程,而进入任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程,**当我们打开网站时,像图片的加载,音乐的加载,其实就是一个异步任务。
异步机制
那么,JavaScript中的异步是怎么实现的呢?那要需要说下回调和事件循环这两个概念啦
首先要先说下任务队列,我们在前面也介绍了,异步任务是不会进入主线程,而是会先进入任务队列,任务队列其实是一个先进先出的数据结构,也是一个事件队列,比如说文件读取操作,因为这是一个异步任务,因此该任务会被添加到任务队列中,等到IO完成后,就会在任务队列中添加一个事件,表示异步任务完成啦,可以进入执行栈啦~但是这时候呀,主线程不一定有空,当主线程处理完其它任务有空时,就会读取任务队列,读取里面有哪些事件,排在前面的事件会被优先进行处理,如果该任务指定了回调函数,那么主线程在处理该事件时,就会执行回调函数中的代码,也就是执行异步任务啦
单线程从从任务队列中读取任务是不断循环的,每次栈被清空后,都会在任务队列中读取新的任务,如果没有任务,就会等到,直到有新的任务,这就叫做任务循环,因为每个任务都是由一个事件触发的,因此也叫作事件循环
总的来说,JavaScript的异步机制包括以下几个步骤
- 所有同步任务都在主线程上执行,行成一个执行栈
- 主线程之外,还存在一个任务队列,只要异步任务有了结果,就会在任务队列中放置一个事件
- 一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,看看里面还有哪些事件,那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行
- 主线程不断的重复上面的第三步
JS同步执行、异步执行、及同步中的异步执行(promise和then)_有蝉的博客-CSDN博客_异步执行
微任务和宏任务
1.微任务
包括Promise,process.nextTick
Promise(async/await) => Promise并不是完全的同步,在promise中是同步任务,执行resolve或者reject回调的时候,此时是异步操作,会先将then/catch等放到微任务队列。当主栈完成后,才会再去调用resolve/reject方法执行
process.nextTick (node中实现的api,把当前任务放到主栈最后执行,当主栈执行完,先执行nextTick,再到等待队列中找)
MutationObserver (创建并返回一个新的 MutationObserver 它会在指定的DOM发生变化时被调用。)
2.宏任务
包括整体代码script,setTimeout,setInterval
定时器
事件绑定
ajax
回调函数
Node中fs可以进行异步的I/O操作
JS执行机制(同步、异步、微任务、宏任务)_cc° 淡忘的博客-CSDN博客_js执行机制
JS同步执行、异步执行、及同步中的异步执行(promise和then)_有蝉的博客-CSDN博客_异步执行
微观任务是不属于事件循环的,它是V8的一个实现,用来实现Promise的then/reject,以及其它一些需要同步延后的callback,本质上它和当前的V8调用栈是同步执行的,只是放到了最后面。除了Promise/MutationObserver,在JS里面发起的请求也会创建一个微观任务延后执行。
Script 代码段_weixin_30621711的博客-CSDN博客
Promise
Promise 是异步编程的一种解决方案,其实是一个构造函数,自己身上有all、reject、resolve这几个方法,原型上有then、catch等方法。 1.主要是用来解决回调地狱的问题
3.现在主流的方法是使用async 和 await 来进行解决异步问题。 4.async 和 await 本质上是一个语法糖 使函数的返回值包含在promise中返回。
三种状态:
pending(等待结果)resolved(rizaov已成功) rejected(已失败)
pending -> resolved 或 pending -> rejected
变化不可逆
状态的表现和变化:
pending状态,不会触发 then 和 catch
resolved状态,会触发后续的 then 回调函数
rejected状态,会触发后续的 catch 回调函数、
then 和 catch 对状态的影响
then 正常返回resolve状态的promise,如果有报错,返回的是reject
catch正常返回resolve状态的promise,如果有保持,返回的是reject
3 resolve会触发then的回调, reject会触发catch的回调
promise详解_土豆切成丝的博客-CSDN博客_promise详解
JavaScript 异步 实现异步的五种实现方法
1.promise 为了让异步任务顺序执行,解决地狱回调
2.window.fetch H5新增的拉取资源的 api 1.通过.then() 2.数据序列化 3.通过.then()获取数据
3.jquery.deffered 延期对象 为了让创建的deffered对象的时候和异步任务属于同步代码的结果,并且能够保证deffered(延期)和promise进行转换 def实例转化成promise实例返回
4.async和awit 实质上是对Promise()对象提供的 -语法糖-. 让代码更简洁.通过同步代码的形式来实现异步操作
5.webwork 创建分线程为了让任务在多线程去执行,防止复杂逻辑阻塞线程