js是单线程还是多线程(单线程)
为什么js是单线程
,js是作为浏览器的脚本语言,主要是实现用户与浏览器的交互,以及操作dom;这决定了它只能是单线程
js的内置对象:
null undefined boolean number string symbol(es6新增) object
typeof区分类型的原理
typeof原理: 不同的对象在底层都表示为二进制,在Javascript中二进制前(低)三位存储其类型信息。 000: 对象 010: 浮点数 100:字符串 110: 布尔 1: 整数 /*----------------------------------------------*/ typeof null 为"object", 原因是因为 不同的对象在底层都表示为二进制,在Javascript中二进制前(低)三位都为0的话会被判断为Object类型,null的二进制表示全为0,自然前三位也是0,所以执行typeof时会返回"object"
判断数组的方法:
通过原型链判断object.prototype.tostring.call()
isArrary()
instanceof,
Array.prototype.isPrototypeOf
数组和类数组的区别
类数组是一个拥有length属性和若干索引属性的的对象,类数组不能调用数组的方法
如何将类数组转换为数组?
slice
splice
concat
array.from
数组的原生方法?
pop:删除并返回数组最后一个元素(改变原数组);
push:返回添加完成后的数组的长度(改变原数组);
shift:移除并返回数组的第一个元素(改变原数组);
unshift:在数组头部插入一个元素
slice:slice(下标,个数)返回裁剪后的数组(不改变原数组);
splice:插入,删除或替换数组的元素
concat:合并数组返回组合数组(不改变原数组);
join:将数组用标识符链接成字符串返回拼接好的字符串(不改变原数组);
reverse:翻转数组(改变原数组);
toString:将数组转换成一个字符串;
split:把字符串分割开,以数组方式储存;
forEach:主要用于遍历数组;
every:主要用于检查数组中每个元素是否符合函数的条件,如果其中有一个不符合,则返回false;
indexOf:主要用于在数组中查找元素,并把元素的位置返回来。;
为什么函数的 arguments 参数是类数组而不是数组?如何遍历类数组?
没有数组常见的方法属性
(1)将数组的方法应用到类数组上,这时候就可以使用call和apply
function foo(){ Array.prototype.forEach.call(arguments, a => console.log(a)) }
(2)使用Array.from方法将类数组转化成数组:
function foo(){ const arrArgs = Array.from(arguments) arrArgs.forEach(a => console.log(a)) }
(3)使用展开运算符将类数组转化成数组
function foo(){ const arrArgs = [...arguments] arrArgs.forEach(a => console.log(a)) }
数组去重的方法
new set
indexof
sort
对AJAX的理解,实现一个AJAX请求
ajax是指javascript的异步通信,可以在不重新加载整个网页的情况下,与服务器交换数据,并且更新部分网页
//封装一个ajax请求 function ajax(options) { let promise = new Promise(function (resolve, reject) { // 创建核心对象 const xhr = new XMLHttpRequest(); // 处理传入参数 options = options || {}; options.type = (options.type || "GET").toUpperCase(); options.url = options.type === "GET" ? options.url + "?" + options.data : options.url; options.data = options.type === "GET" ? null : options.data; // 设置响应的数据类型 xhr.responseType = options.dataType || "json"; // 设置请求头信息 xhr.setRequestHeader("Accept", "application/json"); // 创建HTTP请求 xhr.open(options.type, options.url, true); // 发送数据 xhr.send(options.data); // 监听数据变化 xhr.onreadystatechange = function (e) { if (this.readyState !== 4) return; if (this.status == 200) { resolve(this.response); } else { reject(new Error(this.statusText)); } }; // 设置错误监听函数 xhr.onerror = function() { reject(new Error(this.statusText)); }; }); return promise; } ajax({ type: "post", dataType: "json", data: {}, url: "https://xxxx" });
JavaScript为什么要进行变量提升,它导致了什么问题?
在变量或函数声明之前访问变量或者调用函数而不会报错。
提高性能 ,容错性更好
导致问题:let const 定义变量,不存在变量提升
什么是作用域,以及对作用域链的理解
除了函数外,js是没有作用域的
在 JavaScript 中有两种作用域类型:
1. 局部作用域:只能在函数内部访问它们
2. 全局作用域:网页的所有脚本和函数都能够访问它
JavaScript 拥有函数作用域:每个函数创建一个新的作用域。
作用域决定了这些变量的可访问性(可见性)。
函数内部定义的变量从函数外部是不可访问的(不可见的)。
作用域链:内部可以访问外部的变量,但是外部不能访问内部的变量
eg:如果内部有,优先查找内部,如果内部内有就查找外部的
对执行上下文的理解
执行上下文有且只有三类,全局执行上下文,函数上下文,与eval上下文(eval一般不会使用)
- 全局执行上下文:
全局执行上下文只有一个,也就是我们熟知的window对象,我们能在全局作用域中通过this直接访问到它 - 函数执行上下文
每当一个函数被调用时都会创建一个函数上下文;
需要注意的是,同一个函数被多次调用,都会创建一个新的上下文。 - 执行上下文栈(下文简称执行栈)也叫调用栈,
执行栈用于存储代码执行期间创建的所有上下文,具有LIFO(Last In First Out后进先出,也就是先进后出)的特性。
JS代码首次运行,都会先创建一个全局执行上下文并压入到执行栈中,之后每当有函数被调用,都会创建一个新的函数执行上下文并压入栈内;由于执行栈LIFO的特性,所以可以理解为,JS代码执行完毕前在执行栈底部永远有个全局执行上下文。
let和const很相似
1、let和const的相同点:
① 只在声明所在的块级作用域内有效。
② 不提升,同时存在暂时性死区,只能在声明的位置后面使用。
③ 不可重复声明。
2、let和const的不同点:
① let声明的变量可以改变,值和类型都可以改变;const声明的常量不可以改变,这意味着,const一旦声明,就必须立即初始化,不能以后再赋值。
let、const、var的区别
(1)块级作用域: 块作用域由 { }包括,let和const具有块级作用域,var不存在块级作用域。块级作用域解决了ES5中的两个问题:
- 内层变量可能覆盖外层变量
- 用来计数的循环变量泄露为全局变量
(2)变量提升: var存在变量提升,let和const不存在变量提升,即在变量只能在声明之后使用,否在会报错。
(3)给全局添加属性: 浏览器的全局对象是window,Node的全局对象是global。var声明的变量为全局变量,并且会将该变量添加为全局对象的属性,但是let和const不会。
(4)重复声明: var声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的遍历。const和let不允许重复声明变量。
(5)暂时性死区: 在使用let、const命令声明变量之前,该变量都是不可用的。这在语法上,称为暂时性死区。使用var声明的变量不存在暂时性死区。
(6)初始值设置: 在变量声明时,var 和 let 可以不用设置初始值。而const声明变量必须设置初始值。
(7)指针指向: let和const都是ES6新增的用于创建变量的语法。 let创建的变量是可以更改指针指向(可以重新赋值)。但const声明的变量是不允许改变指针的指向。
什么是闭包?
函数嵌套函数 内部函数使用外部函数定义的局部变量
一个函数加上到创建函数的作用域的连接,闭包“关闭了”函数的自由变量
闭包的优点(解决什么问题)
内部函数可以访问到外部函数的局部变量
闭包缺点:
变量会驻留在内存中,造成内存损耗(占用内存空间,大量使用会造成栈溢出)
解决:把闭包的函数设置为null
内存泄漏(ie)
闭包的this指向问题
1.this指向window对象(因为匿名函数的执行具有全局性,所以其this对象指向window);
2.不能实现value加1(每个函数在被调用时都会自动取得两个特殊变量,this和arguments,内部函数在搜索这两个对象时,只会搜索到其活动对象为止,所以不能实现访问外部函数的this对象);
3.修改代码实现正确功能
如何确定this指向问题,改变this指向的方式有哪些?
this指向:全局上下文
无论是否是严格模式,均指向全局对象(严格模式全局对象是underfined)
函数上下文
指向函数的调用对象,且是最直接的调用对象
改变this指向:使用new关键字 call()apply()bind()
箭头函数不绑定this,会捕获其所在上下文的this值,最为自己的this值
return一个函数
函数可以作为参数
防抖和节流
原型可以解决什么问题?
对象共享属性和共享方法
谁有原型
函数拥有prototype
对象拥有:__proto__
对象查找属性或者方法的顺序
先在对象本身查找-->构造函数中查找-->对象的原型-->构造函数的原型中-->当前原型
原型链 是什么
就是把原型串联起来
原型链的最顶端是null
延迟加载js有哪些方式
async属性:async是和html解析同步,不是顺次执行脚本
defer属性:等html解析完成,才会加载js代码,顺次执行脚本
setTimeout
对事件循环的理解
javascript是一个单线程语言,同一时间只能做一件事情,为了解决单线程阻塞问题,javascrip用了计算机系统的一种运行机制(事件循环)EventLoop
在JavaScript中,所有的任务都可以分为
同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行
异步任务:异步执行的任务,比如ajax网络请求,setTimeout定时函数等
js微任务和宏任务的理解
js是单线程语言
js代码执行流程:同步执行完成=>事件循环
同步任务执行完了,才会执行事件循环(微任务,宏任务)的内容
1、js宏任务有:<script>整体代码、setTimeout、setInterval、setImmediate、Ajax、DOM事件
2、js微任务有:process.nextTick、MutationObserver、Promise.then catch finally
当前调用栈中执行的代码称为宏任务,
当前宏任务执行完,在下一个宏任务开始之前需要执行的任务(回调事件)
微任务先运行 宏任务后运行 微任务不会触发新一轮Tick 宏任务会
null与undefind的区别
- null 指空对象,但它是被定义过的
- undefined 指声明未赋值的对象或者是不存在的对象属性值
你可以定义一个值为null,证明它是空的,但如果你定义它是undefined(未被定义的),逻辑上不合理,尽管你可以这样做
null:
(1)作为函数的参数,来表示该函数的参数不是对象
(2)作为对象原型链的终点
undefined:
(1)变量被声明了,但没有赋值时,就等于undefined。
(2) 调用函数时,应该提供的参数没有提供,该参数等于undefined。
(3)对象没有赋值的属性,该属性的值为undefined。
(4)函数没有返回值时,默认返回undefined。
for in 与for of 区别
for of 遍历获取的是对象的键值,for in 获取的是键名
for in 会遍历对象的整个原型链 for of 只遍历当前的对象
for of 只返回数组的下标对应的属性值,for in 返回数组中的可枚举
箭头函数与普通函数的区别
箭头函数比普通函数更加简洁
箭头函数没有自己的this指向以及箭头函数继承的this指向永远不会改变
call()、apply()、bind()等方法不能改变箭头函数中this的指向
箭头函数不能作为构造函数使用
箭头函数没有自己的arguments
箭头函数没有prototype
箭头函数不能用作Generator函数,不能使用yeild关键字
new操作符的作用
创建了一个空对象
将空对象的原型,指向于构造函数的原型
将空对像作为构造函数的上下文(改变this指向)
对构造函数有返回值的处理判断
js的继承方式有几种?
class Parent{ constructor(){ this.age = 18; } } class Child extends Parent{ constructor(){ super(); this.name = join; } } let o1 =new Child() console.log(o1)
function Parent(){ this.age = 18; } function Child(){ this.name = join; } Child.prototype = new Parent() let o2 =new Child() console.log(o2)
function Parent(){ this.age = 18; } function Child(){ this.name = join; Parent.call(this) } let o2 =new Child() console.log(o2)
function Parent(){ this.age = 18; } function Child(){ Parent.call(this) this.name = join; } Child.prototype = new Parent() let o2 =new Child() console.log(o2)
对this对象的理解
this是执行上下文中的一个属性,它指向最后一次调用这个方法的对象
call apply bind 的区别
共同点:
1.改变this的指向 。都不会修改原先函数的this指向。
不同点:
1.call,apply会立即执行。bind不会立即执行,因为bind返回的是一个函数
2.参数不同,apply第二个参数是数组,call和bind有多个参数需要挨个写
实现call apply bind 函数的步骤
// 判断调用对象是否为函数 // 判断传入的上下文对象是否存在,如果不存在设置为window // 处理传入的参数,截取第一个参数后的所有参数 // 将函数作为上下文对象的一个属性 // 使用上下文对象调用这个 方法,并保存返回结果 // 删除刚才新增的属性 // 返回结果 Function.prototype.myCall = function(context) { // 判断调用对象 if (typeof this !== "function") { console.error("type error"); } // 获取参数 let args = [...arguments].slice(1), result = null; // 判断 context 是否传入,如果未传入则设置为 window context = context || window; // 将调用函数设为对象的方法 context.fn = this; // 调用函数 result = context.fn(...args); // 将属性删除 delete context.fn; return result; };
// 判断调用对象是否为函数 判断上下文的对象是否存在 不存在设置为window // 将函数作为上下文对象的一个属性 // 判断参数值是否存在 // 使用上下文对象调用这个方法,并保存返回结果 // 删除刚才新增属性 // 返回结果 Function.prototype.myApply = function(context) { // 判断调用对象是否为函数 if (typeof this !== "function") { throw new TypeError("Error"); } let result = null; // 判断 context 是否存在,如果未传入则为 window context = context || window; // 将函数设为对象的方法 context.fn = this; // 调用方法 if (arguments[1]) { result = context.fn(...arguments[1]); } else { result = context.fn(); } // 将属性删除 delete context.fn; return result; };
// 判断调用对象是否为函数 // 保存当前函数的引用 获取其余传入参数值 // 创建一个函数返回 // 函数内部使用apply来绑定函数调用,需要判断函数作为构造函数的情况 , // 这个时候需要传入当前函数的this给apply调用,其余情况都传入指定的上下文对象 Function.prototype.myBind = function(context) { // 判断调用对象是否为函数 if (typeof this !== "function") { throw new TypeError("Error"); } // 获取参数 var args = [...arguments].slice(1), fn = this; return function Fn() { // 根据调用方式,传入不同绑定值 return fn.apply( this instanceof Fn ? this : context, args.concat(...arguments) ); }; };
sort的原理是什么
深拷贝和浅拷贝的区别
浅拷贝:只复制引用,而未复制真正的值
如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 。
深拷贝:(复制真正的值)将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象。
localstorage,sessionstorage,cookie的区别
共同点:在客户端存放数据
区别:
1.数据存放的有效期
localstorage:持久化存储,浏览器关闭了也一直存在
sessionstirage:仅在当前浏览器窗口有效
cookie:只在设置cookie设置的过期时间之前有效,即使在窗口或者浏览器关闭也有效
2.localstorage,sessionstorage不可以设置过期时间
cookie有过期时间,可以设置过期时间(把时间调整到之前的时间,就过期了)
3.存储大小的限制
cookie存储不超过4k。localstorage,sessionstorage不超过5M
将对象进行合并
var obj1 = {name:'as',age:15} var obj2 = {myname:'zs',ages:18} // for in 遍历进行合并 for(let attr in obj2){ obj1[attr]=obj2[attr]; } console.log(obj1);//{name: "as", age: 15, myname: "zs", ages: 18} //用 Object.assign(); var o1 = { a: 1 }; var o2 = { b: 2 }; var o3 = { c: 3 }; var obj = Object.assign(o1, o2, o3); console.log(obj); // { a: 1, b: 2, c: 3 } console.log(o1); // { a: 1, b: 2, c: 3 }, 注意目标对象自身也会改变。 console.log(o2); // { b: 2 } console.log(o3); //{ c: 3 } // 三个点语法 var obj3 = {...obj1,...obj2}; console.log(obj3);//{name: "as", age: 15, myname: "zs", ages: 18}
prpmise有几种状态
有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)
promise是一个容器,里面保存这某个未来才会结束的时间(通常是一个异步操作的结果)
promise是异步编程的一种解决方案
解决的问题:地狱回调
promise的特点:
对象的状态不受外界影响
一旦状态改变就不会再变,任何时候都可以得到这个结果
Promise缺点:
首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。
其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
Promise方法:
then()catch()all() race()fiinally
对promise的理解
异步编程的一种解决方案,是一个对象,可以获取异步操作的消息,他的出现大大改善了异步编程的困境,避免了地狱回调,比传统的解决方案回调函数和事件更合理更强大
find与filter的区别
共同点:都不会改变原数组
1.返回的内容不同
filter 返回的是新数组
find返回的是具体内容(对象)
2.find 匹配到第一个即返回
fiflter 返回匹配的元素
some与every的区别
some =>如果有一个匹配就返回true
every =>全部匹配才会返回true
异步编程的实现方式有哪几种
回调函数的方式
多个回调函数的嵌套会造成回调函数地狱,代码耦合度太高,不利于代码的可维护
promise的方式
蒋嵌套的回调函数作为链式调用,会造成多个then的链式调用,造成代码的语义不够明确
generator的方式
在函数执行过程中将函数执行权转移出去,在函数外部还可以转移回来。异步函数亦是如此。
这种方式需要考虑什么时候将函数转移回来,因此需要有一个自动执行generator的机制
async函数的方式
是generator和promise是实现的一个自动执行的语法糖,内部自带执行器,当函数内部执行到一个await语句的时候,若返回一个promise对象,那么函数将会等待promise对象的状态变为resolve后继续执行。因此,可以将异步逻辑转换为同步顺序进行书写
setTimeout、Promise、Async/Await 的区别
setTimeout是异步执行函数,当js主线程运行到此函数的时候,不会等待setTimeout的回调函数,会继续执行下面的语句,当执行完当前事件循环的时候,setTimeout的回调会在下次事件循环执行
console.log('setTimeout start'); setTimeout(function(){ console.log('setTimeout execute'); }) console.log('setTimeout end '); // setTimeout start => setTimeout end => setTimeout execute
promise本身是一个同步的立即执行函数,当在执行中执行resolve()的时候,是异步操作
会先执行then()catch()等,执行完主线程之后,才会执行resolve()
console.log('script start'); var promise1 = new Promise(function (resolve) { console.log('promise1'); resolve(); console.log('promise1 end'); }).then(function () { console.log('promise2'); }) setTimeout(function () { console.log('setimeout'); }) console.log('script end'); // 因此输出的结果为 // script start => promise1 => promise1 end // =>script end =>promise2 => settimeout
async函数返回的是一个promise对象,遇到await的时候就会先返回,等到触发异步操作完成的时候,再继续执行函数体内后面的语句
async function async1(){ console.log('async1 start'); await async2(); console.log('async1 end') } async function async2(){ console.log('async2') } console.log('script start'); async1(); console.log('script end') // 输出顺序:script start->async1 start->async2->script end->async1 end
async和await 与promise两者的区别:
Promise的出现解决了传统callback函数导致的“地域回调”问题,但它的语法导致了它向纵向发展行成了一个回调链,遇到复杂的业务场景,这样的语法显然也是不美观的。而async await代码看起来会简洁些,使得异步代码看起来像同步代码,await的本质是可以提供等同于”同步效果“的等待异步返回能力的语法糖,只有这一句代码执行完,才会执行下一句。 async await与Promise一样,是非阻塞的。 async await是基于Promise实现的,可以说是改良版的Promise,它不能用于普通的回调函数。
简述 aync await 的好处
是同步编程的风格 本地浏览器支持 async关键字声明 getBooksByAuthorWithAwait()函数返回值确保是一个 promise,以便调用者可以安全调用 getBooksByAuthorWithAwait().then(...)或 await getBooksByAuthorWithAwait()
如何去掉移动端点击事件的3000ms延迟
第一次点击屏幕需要判断是否是双击操作
解决方法:FastClick.js
函数的节流与防抖
防抖:多次触发只执行最后一次(防抖类似于英雄联盟回城6秒,如果回城中被打断,再次回城需要再等6秒)
节流:规定时间内只触发一次 (节流类似于英雄联盟里的英雄平A 一定是内点击多次只进行攻击一次)
防抖和节流的使用场景
防抖(debounce)
1.search搜索时,用户在不断输入值时,用防抖来节约请求资源。
节流(throttle)
1.鼠标不断点击触发,mousedown(单位时间内只触发一次)
2.监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断
面向对象
创建方式:工厂方式 构造函数模式 原型模式 动态原型模式 寄生构造模式
对象继承方式: 原型链的方式继承 借用构造函数方式 组合式继承 原型式继承 寄生式继承 寄生组合式继承
垃圾回收与内存泄漏
当js代码运行时,需要分配内存空间来存储变量和值,当变量不参与运行时,需要系统回收被占用内存空间,这就是垃圾回收
那么哪些变量会被回收呢?
不再使用的变量也就是生命周期结束的变量(局部变量),局部变量只在函数的执行过程中存在,当函数执行完之后,所占用的空间就会被释放。而闭包中由内部函数原因,外部函数并不能算是结束
垃圾回收方式:标记清除,引用计数。
如何减少垃圾回收:
- 对数组进行优化: 在清空一个数组时,最简单的方法就是给其赋值为[ ],但是与此同时会创建一个新的空对象,可以将数组的长度设置为0,以此来达到清空数组的目的。
- 对object进行优化: 对象尽量复用,对于不再使用的对象,就将其设置为null,尽快被回收。
- 对函数进行优化: 在循环中的函数表达式,如果可以复用,尽量放在函数的外面。
哪些情况会导致内存泄漏
以下四种情况会造成内存的泄漏:
- 意外的全局变量: 由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收。
- 被遗忘的计时器或回调函数: 设置了 setInterval 定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而无法被回收。
- 脱离 DOM 的引用: 获取一个 DOM 元素的引用,而后面这个元素被删除,由于一直保留了对这个元素的引用,所以它也无法被回收。
- 闭包: 不合理的使用闭包,从而导致某些变量一直被留在内存当中。
目录
为什么函数的 arguments 参数是类数组而不是数组?如何遍历类数组?
JavaScript为什么要进行变量提升,它导致了什么问题?
localstorage,sessionstorage,cookie的区别