javascript基础知识
变量类型和计算
知识点
- 变量类型
- 变量计算
- truely和falsely变量
变量类型
Q: 基本类型有哪些
A:
- Null
- Boolean
- Number
- String
- Undefined
- Symbol(ES6加入)
Q: 引用类型有哪些?
A:
- 对象
- 数组
- 函数
- Date
- RegExp(正则)
Q: 原始类型与引用类型的区别
A:
大部分语言都可以分为两种类型: 值类型和引用类型,引用类型也是为了提升代码的执行效率
值类型是存在栈内存中的。可以想象下图所示。值类型是不可改变的。也就是复制给一个新的变量的话,新变量改变,老的变量不会改变。

引用类型存在堆栈内存中的。可以想象下图所示,有点相当于地址簿,栈中存储的只是一个内存的地址,真正的数据存在这个内存地址中。引用类型是可以改变的。也就是复制给一个新的变量的话,新变量改变,老的变量也会改变。 
Q: typeof 能判断哪些类型?
A: 基本类型,除了null都能准确识别,null会被识别为Object。 引用类型,只能识别出function,其它都会被识别为Object。
变量计算
Q: 如何进行字符串拼接?
A:
可以使用 + 符号
const a = 10 + 10 // 20
const b = 10 + '10' // '1010'
const c = undefined + 10 //NaN
const d = undefined + '10' // 'undefined100'Q: == 与 === 的区别
A:
== 会进行自动类型转换
===不会自动类型转换
推荐平时使用用 ===
Q: 什么是 falsely 变量?
A:
falsely 变量就是双非之后为 false 的变量
!! NaN === falseif 判断和逻辑运算,都是判断 truely 和 falsely 变量而不是直接判断 true 和 false
例如:
'' && '10' // ''
100 || NaN // 100Q: 怎样区分 truely 和 falsely 变量
A:
以下为 falsely 变量:
- 0
- NaN
- ''
- null
- undefined
- false
其它都是 truely 变量
Q: 深拷贝和浅拷贝的区别
A: 浅拷贝和深拷贝的区别主要是针对引用类型。 如果是深拷贝,复制后的变量改变,不会影响老的变量。 浅拷贝,只是复制地址,复制后的变量改变,也会影响老的变量。
题目
Q: 手写深拷贝
A:
function deepClone(entry) {
let result = null
// step1: 如果入参是值类型就返回
if(typeof entry !== 'object' || entry == null) {
// 递归真正执行的语句
return entry
}
// Step2: 定义result 类型
if(entry instanceof Array) {
result = []
} else {
result = {}
}
// Step3:
for(let key in entry) {
// 对原型的处理
if(entry.hasOwnProperty(key)){
// 此处递归
result[key] = deepClone(entry[key])
}
}
return result
}作用域和闭包
知识点
- 编译阶段和执行阶段
- 作用域和自由变量
- 闭包
- this
编译阶段和执行阶段
Q: 编译阶段和执行阶段的区别 js是解释型的语言,代码执行有两个阶段,编译阶段和执行阶段。在执行阶段之前,会先预解析代码,为执行代码做准备。
编译阶段 声明前置 词法作用域 根据规范抛出特定的错误 生成运行时需要的代码。
执行阶段 解析被使用的代码 生成 AST 执行上下文 动态作用域 抛出所有的语法错误
Q: 什么是声明前置?
A: 就是在编译阶段,函数声明和变量声明都会放在最前面。但只是声明放在前面,变量不会赋值,函数不会执行。如果名字相同,函数声明会覆盖变量声明。
例如以下代码:
console.log( a )
var a = 1
function a(){}
console.log( a )可以等价于:
var a
function a(){}
console.log( a ) // function a(){}
a = 1
console.log( a ) // 1作用域
Q: 什么是作用域?
A:
就是变量作用的范围,如图所示即为作用域(这里图要换)

作用域在代码预解析阶段就确定了
Q: 作用域分几种类型?
A:
- 全局作用域
- 函数作用域
- 块级作用域(ES6,let,const)
Q: 什么是暂时性死区?
A: let,const 声明的变量,存在暂时性死区。在当前块级作用域中,let,const 声明的变量,在赋值之前,都不能对该变量进行额外的访问与操作,否则就会报错。(引用)
Q: 什么是自由变量?自由变量的值是什么时候定义?怎么寻找?
A:
- 一个变量在当前作用域没有定义,但被使用了
- 自由变量时在定义函数的时候,值就确定了(一定是在定义的时候,而不是执行的时候)
- 函数在执行的过程中,先从自己内部找变量
- 如果找不到,再从
创建当前函数所在的作用域去找, 以此往上 - 如果找不到,会报错 xx is not defined
闭包
Q: 什么是闭包?
A:
如果在函数里面可以访问外面的变量,那么这个函数 + 这些 变量 = 闭包
闭包在代码解析时就能确定。

Q: 闭包的两种情况
A:
函数作为参数
jsfunction create(fn) { let a = 200 fn() } function fn() { console.log(a) } create(fn) // 200函数作为返回值被返回
jsfunction create() { let a = 100 return function(){ console.log(a) } }
this
Q: this是在什么时候被确定的? A: this 的指向,是在执行上下文的创建过程中,被确定的。
明确了这一点,我们就很容易知道,只有在函数调用时,this 的指向才会被确定。
Q: this的不同场景,怎么取值?
A:
做this题的思路就是转换为.call函数,然后看第一个参数是什么。第一个参数,就是最后执行那个函数的点之前的对象。如果没有,就是Undefied,在严格模式下是undefined,非严格模式下是window
fn() {
console.log(this)
}
相当于 fn.call(undefined) 如果是undefined就是window
fn() // 普通函数,this为window
----------------------------------------------
obj = {
fn: fn
}
obj.fn() // 作为对象方法,this的值为这个对象
----------------------------------------------
const fn2 = fn.bind({a:1})
fn2() // 使用bind/call/apply的话,this即为传入的{a:1}
----------------------------------------------
const b = ()=> {console.log(b)}
obj2 = {
b:b
}
obj2.b()// 箭头函数的this继承父级,这里会打印出window
----------------------------------------------
const c = new fn() // new之后,this会绑定在实例c上Q: call, bind, apply的区别
A: call,bind, apply 都会绑定当前作用域下的this call,apply和Bind的区别的区别是,bind方法会返回一个绑定当前this的新函数,但不会执行,call, apply会执行。
call, apply主要就是传参的格式不同,call是把参数,一个一个传入,apply是通过一个数组传入
题目
Q: bind 方法有什么作用?手写 bind 函数
A:
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被 bind 的第一个参数指定,其余的参数将作为新函数的参数供调用时使用。
// 模拟 bind
Function.prototype.bind1 = function() {
// 将参数拆解为数组
const args = Array.prototype.slice.call(arguments)
// 获取this(数组第一项)
const t = args.shift()
// fn1.bind(...)中的fn1
const self = this
// 返回一个函数
return function () {
return self.apply(t, args)
}
}Q: 实际开发中的闭包场景,举例说明 (引用)
A:
- 隐藏数据
- 做一个简单的cache工具 //TODO: 这是什么意思
Q: 创建10个 <a> 标签,点击的时候弹出对应的对象(引用)
let a
for(let i=0;i<10;i++) {
a=document.createElement('a')
a.innerHTML = i + '<br>'
a.addEventListener('click', function(e){
e.preventDefault()
alert(i)
})
document.body.appendChild(a)
}Q: 以下代码的执行结果(引用)
function create() {
let a = 200
return function(){
console.log(a)
}
}
let fn = create()
let a = 100
fn()A: 200
Q: 以下代码的执行结果(引用)
function print(fn) {
let a = 200
fn()
}
let a = 100
function fn() {
console.log(a)
}
print(fn)A: 100
Q: 以下题目中的 this 指向哪里?
const children = {
watching() {
setTimeout(function(){
console.log(this)
})
}
}A:
这里会打印出 window ,因为在 setTimeout 中,是一个普通函数的执行。
如果想指向这个对象,可以把这个函数变为箭头函数。
Q: 以下函数的执行结果
var app = {
fn1() {
setTimeout(function(){
console.log(this)
}, 10)
},
fn2() {
setTimeout(()=>{
console.log(this)
},20)
},
fn3() {
setTimeout((function(){
console.log(this)
}).bind(this), 30)
},
fn4: ()=> {
setTimeout(()=>{
console.log(this)
},40)
}
}
app.fn1()
app.fn2()
app.fn3()
app.fn4()A:
var app = {
fn1() {
function fn(){
console.log(this)
}
//过10ms 后执行
//fn.call(undefined) ,所以输出 Window
},
fn2() {
//过20ms 执行箭头函数
//箭头函数里面没资格有 自己的 this,借用 setTimeout 外面的 this,也就是 app
},
fn3() {
// 创建了一个新函数,这个新函数里面绑定了 外面的this,也就是 app
// 20 ms 后执行新函数,输出 this,也就是刚刚绑定的 app
}
fn4: ()=> {
//过40ms 执行箭头函数
//箭头函数里面没资格有 this,用 setTimeout 外面的 this
//setTimeout 所在的 fn4也是箭头函数,没资格拥有自己的 this,借用外面的 this ,也就是 Window
}
}面向对象
- 原型与原型链
- new
- 继承(原型与class)
知识点
原型
Q: new 的几个阶段
A: 创建一个空对象obj 设置原型链 即 obj . _proto _ = 构造函数.prototype ; 让构造函数中的this指向obj 返回对象obj
Q: instanceof 的作用?
A:
判断属于哪个 class 或者哪个构造函数,可以用来判断数组。
class:
class Variety {}
let variety = new Variety();
alert( variety instanceof Variety ); // true构造函数:
function Variety() {}
alert( new Variety() instanceof Variety ); // trueQ: 简单介绍原型
A:
- 每个 class 都有 显式 原型 prototype 。
- 每个实例都有 隐式 原型
_proto_。 - 实例的
_proto_指向对应 class 的 prototype 。
原型链
Q: 简述原型,原型链的执行规则
A:
- 如果实例中有⾃身属性则直接使⽤⾃身的属性,如果没有则会在⾃身的隐式原型链中去找, ⼀直找到最顶级 Object 为⽌,如果都找不到,则最后返回:
null - 每个构造函数都有⼀个显式原型,且每个显式原型都会对应有⼀个隐式原型,⽽他的隐式原型的⽗级⼜有个显式原型,他们都是⼀级级地往上寻找的,然后⼀直找到树形结构的第⼀层(Object)为终点。
- instanceof是为判断此实例或构造函数是否是⽗级继承的
class
Q: 如何构建一个 class
A:
- constructor
- 属性
- 方法
题目
Q: 如何判断数组类型
A:
instanceof
Q: 如何用 class 实现继承?(简单代码实现)
A:
class Animal() {
constructor(name) {
this.name = name
}
eat() {
console.log(`${this.name} is eating`)
}
}
class Dog extends Animal() {
constructor(name, number) {
super(name)
this.number = number
}
run() {
console.log(`${this.name} is running`)
}
}异步与同步
知识点
- 单线程和异步
- 应用场景
- 回调地狱和 Promise
- async/await
- eventHub
- 宏任务和微任务
Q: 同步与异步的区别?
A:
同步会阻塞后面程序的运行, 而异步不会阻塞后面程序的运行。
JS是单线程运行的,只能在同一时间内做一件事,所以JS需要异步
Q: 什么是回调函数?
A: 函数做为参数传入另一个函数,并且在该函数内被调动
例如:
function getImg(callback) {
var imgUrl = 'xxxx'
callback()
}Q: 什么是回调地狱? 怎样解决回调地狱的问题?
A: 主要的原因之一是因为回调地狱是代码嵌套,可以使用promise解决promise问题
例如:
$.post(url1, (data1) =>{
console.log(data1)
$.post(url2, (data2) => {
console.log(data2)
$.post(url3, (data3) => {
console.log(data3)
})
})
})Q: Promise 的基本写法
A:
function getData(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('foo');
}, 300);
})
}Q: Promise有几种状态
A: 三种:
1.pending // 正在执行 2.resolve // 成功 3.reject // 拒绝
Q: Promise then 和catch 会怎样改变Promise状态?
A:
- pending 不会触发任何 then catch 回调
- 状态变为 resolved 会触发后续的 then 回调
- 状态变为 rejected 会触发后续的 catch 回调
Q: 什么是Promise链?
A: Promise可以链式调用,并且上一个then中return的值,是下一个then的入参 例如
start()
.then(data => {
// promise start
console.log('result of start: ', data);
return Promise.resolve(1); // p1
})
.then(data => {
// promise p1
console.log('result of p1: ', data);
return Promise.reject(2); // p2
})Q: 请简单画出EventHub的流程?
A:
- 将 console.log("Hi") 推入调用栈,调用栈会执行代码
- 执行代码,控制台打印“Hi”,调用栈清空
- 执行 setTimeout,setTimeout由浏览器定义,不是ES6的内容;将定时器放到Web APIs中,到时间后将回调函数放到回调函数队列中
- 执行完了setTimeout, 清空调用栈
- console.log("Bye")进入调用栈,执行,调用栈清空
- 同步代码被执行完,,回调栈空,浏览器内核启动时间循环机制
- 五秒之后,定时器将cb1推到回调函数队列中
- 事件循环将cb1放入调用栈

Q: async/await 的基本用法?
A:
- async/await消灭了异步回调,和Promise相辅相成。
- 执行async 函数,返回的是Promise对象
- await 相当于 Promise 的 then
- try...catch 可以捕获异常,代替了Promise 的 catch
Q: 宏任务与微任务的区别? 宏任务与微任务有哪些?
A:
微任务: 其实是es规定的,就是其实是js代码中的一些异步方法(promise) 宏任务: 是w3c规定的一些方法,也就是浏览器中的一些异步方法(settimeout,ajax)
因为微任务是在DOM渲染之前执行的,而宏任务是在DOM渲染之后执行的,所以先执行微任务再执行宏任务。
Q: 宏任务,微任务题的解题思路
A:
- 先执行同步代码
- 再执行微任务代码(await 后面为微任务)
- 再执行宏任务代码
Q: 代码的执行步骤
A:
- 同步代码
- 执行微任务
- (尝试触发Dom渲染)
- 触发EventLoop,执行宏任务
题目
Q: 手写用Promise 加载一张图片(异步)
A:
function loadImg(src) {
const p = new Promise(
(resolve, reject) =>{
const img = document.createElement('img')
img.onload = () => {
resolve(img)
}
img.onerror = () => {
const err = new Error(`图片加载失败${src}`)
reject(err)
}
img.src=src
}
)
return p
}Q: 手写promise
A:
class Promise2 {
succeed = null
fail = null
state = 'pending'
constructor(fn) {
fn(this.resolve.bind(this), this.reject.bind(this))
}
resolve(result) {
setTimeout(() => {
this.state = 'fulfilled'
this.succeed(result)
})
}
reject(reason) {
setTimeout(() => {
this.state = 'rejected'
this.fail(reason)
})
}
then(succeed, fail) {
this.succeed = succeed
this.fail = fail
}
}Q: 以下如图所示程序的执行结果: 
A: 1,3, 5, 4, 2
Q: 以下代码的执行结果
Promise.resolve().then(() => {
console.log(1)
}).catch(() => {
console.log(2)
}).then(() => {
console.log(3)
})A: 1,3
Q: 以下代码的执行结果
Promise.resolve().then(() => { // 返回 rejected 状态的 promise
console.log(1)
throw new Error('erro1')
}).catch(() => { // 返回 resolved 状态的 promise
console.log(2)
}).then(() => {
console.log(3)
})A: 1,2,3
Q: 以下代码的执行结果
Promise.resolve().then(() => { // 返回 rejected 状态的 promise
console.log(1)
throw new Error('erro1')
}).catch(() => { // 返回 resolved 状态的 promise
console.log(2)
}).catch(() => { // 这里是catch
console.log(3)
})A: 1,2
Q: promise题分析的一些注意点
A: await 后面记得也是微任务 
初始化promise是,函数会立即执行 