夜来了 我点上白蜡烛 看它的眼泪淌成什么形象
JavaScript篇
typeof类型判断
typeof是否能正确判断类型,instanceof能正确判断对象的原理是什么
typeof对于原始数据类型来说,除了null都能显示正确的类型
如👆所示,在console执行typeof null之后,对于对象来说,除了函数都会显示object,所以typeof并不能准确的判断变量到底是什么类型
重点来了,如果我们想判断一个对象的正确类型,这时候可以使用instanceof,内部机制是通过原型链来判断的,如果判断的类型是对象,则会返回true,否则返回false
instanceof的原理
instanceof可以正确的判断对象的类型,内部机制是通过判断对象的原型链是不是能找到类型的prototype
instanceof的实现:
- 首先获取类型的原型
- 获取对象的原型
- 循环判断对象的原型是否等于类型的原型,直到对象原型为null,因为原型链的最终为null
类型转换
首先我们要知道,在JS中类型转换只有三种情况,分别是:
- 转换为boolean
- 转换为number
- 转换为string
转boolean
在进行条件判断时,除了undefined、null、false、NaN,'',0,-0,其他所有的值都转为true,包括对象
对象转原始类型
对象在转换类型的时候,会调用内置的[[ToPrimitive]]函数,对于该函数来说,算法逻辑一般如下
- 调用x.valueOf(),如果转换为基础类型,就返回转换后的值
- 调用x.toString()
- 如果以上都没有返回原始类型,就会报错
四则运算(隐式转换)
特点:
- 运算中其中一方为字符串,那么就会把另一方也转换为字符串
- 如果一方不是字符串或者数字,那么会将它转换为数字或者字符串
需要注意一点:'a' + + 'b'会返回aNaN,因为第一个加号会将之后的部分隐式转换为number,即NaN,
- 对于除了加法的运算符来说,只要其中一方是数字,那么另一方就会被转为数字
this
先来看以下👇几个函数的调用场景
可以试着做一下,以上场景分别会打印出什么结果:
- 对于直接调用foo来说,不管函数被放在哪个位置,this一定是指向window的
- 对于obj.foo()来说,只需要记住,谁调用了函数,谁就是this,所以这个场景下,foo函数中的this就是obj对象
- 对于new的方式来说,this是被永远绑定在c上面的,不会被任何方式改变this
箭头函数的this
function a() {
return () => {
return () => {
console.log(this)
}
}
}
console.log(a()()())
- 首先箭头函数是没有this的,箭头函数中的this只取决于包裹他的第一个普通函数的this,这里的为a,所以此时的this是window
- 如果对一个函数进行多次bind
不管我们给函数bind几次,fn中的this永远是由第一次bind决定的,所以结果永远都是window
闭包
定义
闭包的定义其实很简单:函数A内部有一个函数B,在函数B可以访问到函数A中的变量,那么函数B就是闭包。 如下:
闭包存在的意义就是让我们可以间接访问函数内部的变量
经典面试题,循环中使用闭包解决var定义函数的问题
因为setTimeout是异步函数,所以会先把循环全部执行完毕,
(即他需要等主进程全部运行完毕之后才会运行,当setTimeout()函数内部回调运行的时候,主进程已经全部运行完毕了)这时候i就是6了,程序会输出6个6。
注意:需要注意的一点是,很多小伙伴会认为产生这种现象的原因是闭包,其实不是的哈,需要特别注意😄
解决方法
- 可以使用闭包
首先,我们先使用一个立即执行函数(function(){}()将i传入到函数内部,这个时候值就会固定在参数j上面且不会发生改变,当下次执行timer这个闭包的时候,就可以使用外部函数的变量j,从而实现如下👇效果
2. 使用setTimeout的第三个参数,这个参数会被当成timer函数的惨呼传入
3. 使用let定义i(推荐)
let 声明的变量存在暂时性死区,即只要块级作用域中存在let,那么它所声明的变量就绑定了这个区域,不再受外部的影响。
拓展:var、let以及const的区别
- var 和 let 用以声明变量,const 用于声明只读的常量;
- var 声明的变量,不存在块级作用域,在全局范围内都有效,let 和 const 声明的,只在它所在的代码块内有效;
- let 和 const 不存在像 var 那样的 “变量提升” 现象,所以 var 定义变量可以先使用,后声明,而 let 和 const 只可先声明,后使用;
- let 声明的变量存在暂时性死区,即只要块级作用域中存在 let,那么它所声明的变量就绑定了这个区域,不再受外部的影响。
- let 不允许在相同作用域内,重复声明同一个变量;
- const 在声明时必须初始化赋值,一旦声明,其声明的值就不允许改变,更不允许重复声明;
参考文章
模块化
为什么要使用模块化?都有哪几种方式可以实现模块化,各有什么特点
优点:
- 解决命名冲突
- 提供复用性
- 提高代码可维护性
立即执行函数
在早期,使用立即执行函数实现模块化是常见的手段。
CommonJS
有四个重要的环境变量为模块化的实现提供支持:module、exports、require、global。实际使用时,用module.exports定义当前模块对外输出的接口(不推荐直接用exports),用require加载模块。
进程与线程
进程与线程的区别是什么?JS单线程带来的好处?
进程描述了CPU在运行指令及加载和保存上下文所需的时间,放在应用上来说就代表了一个程序。
线程是进程中更小的单位,描述了执行一段指令所需要的时间
为了更好理解,以浏览器作为实例,当打开一个tab页面时,其实就是创建了一个进程,一个进程可以有多个线程,比如渲染线程、JS引擎线程、HTTP请求线程等。当你发起一个请求时,其实就是创建了一个线程,当请求结束之后,线程可能会被销毁
JS单线程优点:节省内存,节约上下文切换时间
手写call、apply、bind函数
实现call
- 首先context为可选参数,如果不传入的话默认上下文为window
- 接下来给context创建一个fn属性,并将值设置为需要调用的函数
- 因为call可以传入多个参数作为调用函数的参数,所以需要将参数剥离出来
- 然后调用函数并将对象上的函数删除
Function.prototype.myCall = function(context) {
if(typeof this != 'function') {
// throw语句用来抛出一个用户自定义的异常
throw new TypeError('error')
}
context = context || window
context.fn = this
const args = [...arguments].slice(1)
const result = context.fn(...args)
delete context.fn
return result
}
apply实现
Function.prototype.myApply = function(context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
context = context || window context.fn = this
let result // 处理参数和 call 有区别
if (arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}
bind实现
bind需要返回一个函数,可以直接调用函数,或者new的方式
Function.prototype.myBind = function(context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
const _this = this
const args = [...arguments].slice(1) // 返回一个函数
return function F() {
// 因为返回了一个函数,我们可以 new F(),所以需要判断
if (this instanceof F) {
return new _this(...args, ...arguments)
}
return _this.apply(context, args.concat(...arguments))
}
}
new
new的原理是什么?通过new创建对象和通过字面量创建有什么区别
在调用new的过程中会发生四件事情
- 新生成一个对象
- 链接到原型
- 绑定this
- 返回新对象
实现new的过程
- 创建一个空对象
- 获取构造函数
- 设置空对象的原型
- 绑定this并执行构造函数
- 确保返回值为对象
为什么0.1 + 0.2 != 0.3
原因
因为js采用的是jeee 754双精度版本(64位),将64位份为三段:
- 第一位表示符号
- 接下去的11位用来表示指数
- 其他的位数表示有效位
0.1 在二进制中是无限循环的一些数字,js采用的浮点数标准会裁掉我们的数字,裁掉之后就会出现精度丢失的问题,也就造成了0.1不是0.1,而是:0.100000000000000002,0.2、0.同理
接下来我们做一个判断:
所以我们可以得到0.1 + 0.2:
解决方案
typeof(parseFloat((0.1 + 0.2).toFixed(10)))
浏览器的内核分别是什么
ie,内核:trident
firefox,内核:gecko
safari,内核:webkit
chrome,内核:blink
opera,内核:blink
每个HTML文件里开头都有个Doctype,知道这是什么吗
声明位于文档中最前面的位置,处于< html >标签之前,可告知浏览器文档使用哪种HTML或者XHTML规范(重点:告知浏览器按照哪种规范解析页面)为什么利用多个域名来储存网站资源会更有效
CDN缓存更方便、突破浏览器并发限制、节约c锕ookie带宽、节约主域名的链接数、优化页面的相应速度
请描述一下cookies,seeionStorage和localStorage的区别
sessionStorage(会话存储),session中的数据,这些数据只有在同一个会话的页面才能访问,当前会话结束后数据也会随之销毁,因此sessionStorage不是一个持久化的本地存储,仅仅是会话级的存储。而 localStorage用于持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的。
web storage和cookie的区别
两者的概念相似,区别是cookie的大小是受限的,并且每次请求一个新的页面的时候,cookie的值都会被发送出去,且需要指定作用域,不可以跨域调用。cookie的作用是与服务器进行交互,作为HTTP规范的一部分而存在,而Web storage仅仅是在本地“存储”数据。
简述src与href的区别
scr用于替换当前元素,href用于在当前文档和引用资源之间确立联系。
src指向外部资源的位置,指向的内容将会嵌入到文档中当前标签所在的位置;当浏览器解析到该元素的时候,会暂停其他资源的下载和处理,直到将该资源加载、编译、执行完毕,这也是为什么js脚本放在底部而不是头部
href指向网络资源所在的位置,建立和当前元素(瞄点/文档/链接)之间的链接,若我们在文档中引入添加一个css文件,那么浏览器会识别该文档,并行下载资源并且不会停止对当前文档的处理。
网页制作的图片格式有哪些
png-8、png-24、jpeg、gif、svg(不做过多解释)
这里要说一个相对比较新的技术:Webp,这种图片格式是google开发的,旨在加快图片加载速度,图片压缩体积大约只有jpeg的2/3,并能节省大量的服务器带宽资源和数据空间,现在facebook等知名网站以及在测试并使用这种格式了,如果面试可以回答出来,是一个亮点
一个页面上有大量的图片,加载很慢,优化图片加载有哪些方法
- 图片懒加载,这里可以参考👉实现图片懒加载
- 如果图片为css图片,可以使用精灵图、iconfont、base64等技术
- 如果图片过大,可以使用特殊编码的图片,加载时会先加载一张压缩后的缩略图,以提高用户体验
什么是塌陷,怎么解决的
原因:在标准文档流中,竖直方向的margin会出现叠加现象(水平方向不会塌陷),两个margin紧挨着,中间没有border或者padding,当两个margin直接接触,就产生了合并,即外边距合并,表现为较大的margin会覆盖掉较小的margin,竖直方向的两个盒子中间只有一个较大的margin,这就是margin塌陷现象。
这个问题在之前的文章:CSS定位基础知识有提到过,可以看一下,以下为几种解决方案:
rgba()和opciaty的透明效果有什么不同
rgba()只作用于元素的颜色或者背景的颜色,而opciaty作用于元素以及元素内所有内容的透明度
CSS中可以让文字在垂直和水平方向上重叠的两个属性是什么
垂直方向:line-height
水平方向:letter-spacing