# 基础问题

# js数据类型

基本类型: Number、String、Boolean、Undefined、Null

引用类型: Object

  • 字符串使用单引号或者双引号或者\ 最好先对它进行转义, ' " \
  • 逻辑运算符 &&有假为假 ||有真为真
  • 逻辑运算符优先级:! > && > ||
  • 逻辑表达式中如果遇到一个非布尔类型的操作数,这个操作数的值就会被返回,而不是true和false。
var b = 5;
true && (b = 6) // 6
true && 'something' // something
  • parseInt() 两个参数,第一个解析的数值,第二个以什么进制解析这个数值。
  • isNaN() 用来判断某个值是否可以参与数值运算,在对数据进行计算前进行判断很有必要。

# var、let、const区别

var 变量不存在块级作用域 会变量提升

let 变量 不存在变量提升, 存在暂时性死区,只在所在的代码块内有效

const 常量 后面代码中不能修改该常量的值

# Undefined和Null的区别

Null是一个空对象引用,typeof值为object,代表是一个对象,所以最好用于未来可能是对象的值

Undefined表示一个变量没有被声明,或者被声明了但没有被赋值(未初始化)typeof为undefined

# 获取时间戳

Date.now() === (new Date()).getTime()

(new Date()).getDay() // 获取星期

(new Date()).getDate() // 获取日期

# 判断js数据类型

基本类型可以使用typeof来进行判断

typeof 'ConardLi'  // string
typeof 123  // number
typeof true  // boolean
typeof Symbol()  // symbol
typeof undefined  // undefined

你还可以用它来判断函数类型:

typeof function(){}  // function

但当是null和引用类型就不能用typeof判断了

typeof null // object
typeof [] // object
typeof {} // object
typeof new Date() // object
typeof /^\d*$/; // object

使用instanceof来判断

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。可以用来判断是什么类型。

var obj = {}
var arr = []
obj instanceof Object // true
arr instanceof Object // true
obj instanceof Array // false
arr instanceof Array // true

function(){}  instanceof Object // true

可以看出instanceof并不是一个很好的选择。

使用 toString 方法

Object对象直接使用,因为其他类型重写了toString,所以要通过call/apply来调用。返回一个类型[Object xxx] 的字符串

var obj = {}
var arr = []
obj.toString() // "[object Object]"
Object.prototype.toString.call(arr) // "[object Array]"
Object.prototype.toString.call('') // "[object String]"
Object.prototype.toString.call(null) // "[object Null]"
Object.prototype.toString.call(1) // "[object Number]"

判断是不是数组

1. Array.isArray(xx) // 判断是不是数组

2. instanceOf操作符 

var obj = {}
var arr = []
obj instanceof Object // true
arr instanceof Object // true
obj instanceof Array // false
arr instanceof Array // true

# 什么是作用域

一个包含变量、对象、函数的集合

# for循环

for循环里只有立即执行的表达式才能实时获取每一个i值,通过在外部调用里面的函数或是setTimeOut等延迟执行的都无法获取实时的值,只能获取最后一个值。

# 闭包

简单讲是可以获取其他函数内部作用域的函数

一个函数保留了访问另一个函数作用域的链接

一个函数创建就形成了一个闭包,因为能访问全局作用域。

可以通过return 或者 把函数赋值给全局变量来实现

闭包只保留作用域的访问权,不能访问具体的值。

# new一个对象发生了什么

创建空对象;

var obj = {};

设置新对象的constructor属性为构造函数的名称,设置新对象的__proto__属性指向构造函数的prototype对象;

obj.proto = ClassA.prototype;

使用新对象调用函数,函数中的this被指向新实例对象:

ClassA.call(obj);//{}.构造函数();

如果无返回值或者返回一个非对象值,则将新对象返回;如果返回值是一个新对象的话那么直接直接返回该对象。

# defer和async 异步加载

script标签添加defer或者async属性,脚本就会和后续文档并行加载(异步加载)不会阻塞文档。

deferasync的区别是:defer要等到其他渲染结束(DOM结构完成,以及其他脚本执行完成),才会执行;async是一旦加载载完成,渲染引擎就会中断其他命令来执行这个脚本。都是并行加载,一个是等其他结束才执行,一个是加载完成就执行。

都是和后面命令同步下载,不阻塞进程,但执行时机不一样。

# ready/onload的区别

ready表示文档结构(DOM结构)已经加载完成(不包含图片等非文字媒体文件),和DOMContentLoaded一样。

onload表示页面包含图片等文件在内的所有元素都加载完成。

所以ready要比onload先执行一些。

# http状态码

http状态码主要分为5种类型:

  • 1** 信息反馈,服务器收到请求,需要请求者继续执行操作
  • 2** 请求成功,操作被成功接收并处理
  • 3** 重定向,需要进一步的操作以完成请求
  • 4** 客户端错误,请求包含语法错误或无法完成请求
  • 5** 服务器错误,服务器在处理请求的过程中发生了错误

常见的几个状态码:

  • 200 请求成功

  • 301 永久重定向

  • 302 临时重定向

  • 400 客户端请求的语法错误,服务器无法理解

  • 401 未授权,需要身份验证

  • 403 禁止访问 服务器理解请求客户端的请求,但是拒绝执行此请求

  • 404 服务器找不到请求的资源(网页),请求路径不对。

  • 405 请求方法被禁止。

  • 409 服务器请求时发生冲突

  • 500 服务器内部错误,无法完成请求。

  • 501 服务器不支持请求的功能,无法完成请求。

  • 502 (错误网关) 服务器作为网关或代理,从上游服务器收到无效响应。

  • 503 (服务不可用) 服务器目前无法使用(由于超载或停机维护)。

# 打印&&、||

// 根据判断条件来打印其中某个值,而不是返回两个共同的结果

// 先判断第一个是否为真,为真取第二个,为假取第一个
console.log(true && true) // true
console.log(true && false) // false
console.log(true && 1) // 1
console.log(true && 0) // 0
console.log(false && false) // false
console.log(false && true) // false
console.log(false && 1) // false
console.log(false && 0) // false
console.log(0 && 1) // 0
console.log(1 && 0) // 0

// 有真取真 没有取最后一个
console.log(true || true) // true
console.log(true || false) // true
console.log(true || 1) // true
console.log(true || 0) // true
console.log(false || true) // true
console.log(false || false) // false
console.log(false || 0) // 0
console.log(false || 1) // 1
console.log(0 || 1) // 1
console.log(1 || 0) // 1
console.log(false || 0 || '') // ''

// 按顺序计算
// 1 && 0 取 0, 0 && 1 取 0
console.log(1 && 0 && 1) // 0
// 0 && 1 取 0,0 && 1 取 0
console.log(0 && 1 && 1) // 0

console.log(1 && false && 1) // false
console.log(1 || false && false) // false
console.log(false || 0 && null) // 0

console.log(typeof false && 1) // 1
console.log(typeof '' && 1) // 1
console.log(typeof null && 1) // 1
console.log(1 && typeof true) // boolean
console.log(1 && typeof null) // object

# MVC和MVVM模式

MVC模式:

  • M 模型(Model):数据(js变量)
  • V 视图(View):用户界面(HTML,CSS)
  • C 控制器(Controller):事件交互、业务逻辑(DOM绑定事件,操作变量)

用户通过View传送指令到Controller,Controller完成业务逻辑,改变Model数据的状态,Model将新的状态反应到View视图上,用户得到反馈。

MVVM模式:

  • M 模型(Model):数据、变量
  • V 视图(View):用户界面
  • VM (ViewModel):实现数据的双向绑定,就是VUE,省去了DOM操作。

# 构造函数不使用new时

对于 sarah,我们没有使用 new 关键字。当使用 new 时,this 引用我们创建的空对象。当未使用 new 时,this 引用的是全局对象(global object)。

this属性指了向全局window,firstName被挂在了window上,而sarah本身依然是undefined。

function Person(firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
}
const sarah = Person('Sarah', 'Smith')

console.log(sarah) // undefined
console.log(window.firstName) // Sarah

# 标签模板功能

ES6标签模板:当模板字符串紧跟在一个函数名后面的时候,该函数将被调用来处理这个模板字符串。这被称为“标签模板”功能(tagged template)。

当函数使用字符串模板时,带有变量的模板字符串会被先处理成参数形式,再传入函数中。规则就是: 字符串中的非变量被拆成数组形式为arr,然后变量作为值紧跟数组后面作为参数...values传入,没有变量就只传数组。

arr:模板字符串中所有那些没有变量替换的部分,没有变量就是一个原字符元素数组,如果有变量,则原字符被变量分割成多个字符数组,如果变量在开始,则数组第一个元素为一个空字符。arr数组的最后一项raw保存的是转义后的原字符串。 …values:各个变量替换后的变量值

let a = 'Oh!';
let b = 'the';
let c = 'very much!'

function tag(arr, ...values) {
  console.log(arr);      
	for(let i in values){
    console.log(values[i]);     
  }
  
}

tag `${a} I love ${b} JavaScript ${c}`;
//["", " I love ", " javascript ", "", raw: Array[4]]
//oh! 
//the 
//very much!

tag`jjkskfs ${b}ddfdf`
// ["jjkskfs ", "ddfdf", raw: Array(2)]

tag`jjkskfs dfdf`
// ["jjkskfs dfdf", raw: Array(1)]

# 对象的键值底层都是字符串

const obj = { 1: 'a', 2: 'b', 3: 'c' }
const set = new Set([1, 2, 3, 4, 5])

obj.hasOwnProperty('1') // true
obj.hasOwnProperty(1) // true
set.has('1') // false
set.has(1) // true

# 扩展运算符(...args)会返回实参组成的数组

function getAge(...args) {
  console.log(typeof args)
}

getAge(21) // "object"

# break、return和continue之间的区别

  • break 会跳出循环并终止循环。
  • continue只跳出本次迭代,循环会继续运行、
  • return 在函数中返回值,其后面代码不会执行。只在函数中出现,如果不是在函数中会报语法错误 SyntaxError

# try catch捕获异常

在 try 里无论是代码错误,还是使用 throw 抛出错误,都会被 catch 捕获到,并执行 catch 代码块中的语句。try...catch 后还可以跟随一个 finally 代码块,无论是否抛出异常,finally 都会执行。

try {
  throw 'error'  // 抛出错误
} catch (e) {
  console.log(e); // 'error' e就是代码错误或抛出的错误
} finally {
  console.log('finally') // 'finally'
}

# delete 关键字

使用delete删除对象的属性,对原型和全局变量都适用,因为全局变量挂载在全局对象上,但是如果通过var let const 声明过就不行了,不在是一个对象了。删除操作返回一个布尔值。

const name = "Lydia";
age = 21;

console.log(delete name); // false
console.log(delete age); // true

# JSON.stringify(value[, replacer])

JSON.stringify的第二个参数是 替代者(replacer). 替代者(replacer)可以是个函数或数组,用以控制哪些值如何被转换为字符串。

如果替代者(replacer)是个 数组 ,那么就只有包含在数组中的属性将会被转化为字符串。

const settings = {
  username: "lydiahallie",
  level: 19,
  health: 90
};

const data = JSON.stringify(settings, ["level", "health"]);
console.log(data);
// "{"level":19, "health":90}"

# import导入模块会先执行

// index.js
console.log('running index.js');
import { sum } from './sum.js';
console.log(sum(1, 2));

// sum.js
console.log('running sum.js');
export const sum = (a, b) => a + b;

执行 running sum.js, running index.js, 3 会先打印sum.js中的,如果使用require则会按顺序打印。

# 箭头函数

1、箭头函数的this指向定义时所在的对象,而不是使用时所在的对象。所以在一个对象里不要使用箭头函数,里面的this不是指向对象,而是对象所在的环境。

2、箭头函数不可当做构造函数,没有原型prototype属性,不能new

# 尾调用优化

在最后一步通过return 执行一个函数,没有其他步骤。如果在函数A中调用函数B,A就会保留B的调用帧,等B结束A才会消失。尾调用是在A最后调用B,A执行自己的调用帧后就结束了,不需要保持自己的内部状态和B的调用帧,所以如果每个函数都是尾调用,这样就能大大节省内存。

只在严格模式下开启。。。

function A() {
  return B();
}

# 尾递归

尾递归就是递归函数尾调用自己。递归会保持很多调用帧,而尾递归只需要保存自己一个调用帧就可以,大大节省内存。

正常递归:

function factorial(n) {
  if (n === 1) return 1;
  return n * factorial(n - 1); // 不是直接返回函数
}

factorial(5) // 120

改写成尾递归:确保最后一步只调用自己

function factorial(n, total) {
  if (n === 1) return total;
  return factorial(n - 1, n * total); // 直接返回函数
}

factorial(5, 1) // 120

# 函数柯里化

# url从输入到页面生成发生了什么

解析 DNS解析获取IP地址

TCP三次握手连接服务器

客户端发生HTTP请求

服务端发送响应

浏览器解析内容渲染页面

渲染

解析HTML生成DOM树

解析CSS生成CSSOM规则树

将DOM树与CSSOM规则树合并在一起生成渲染树

遍历渲染树开始布局,计算每个节点的位置大小信息

将渲染树每个节点绘制到屏幕

# 重排与重绘

重排:DOM元素的添加或删除、改变位置或大小时,会导致浏览器重新生成渲染树,这个过程叫重排。

重绘:当重新生成渲染树后,就要将渲染树每个节点绘制到屏幕,这个过程叫重绘。

不是所有的动作都会导致重排,例如改变字体颜色,只会导致重绘。记住,重排会导致重绘,重绘不会导致重排 。

减少重排或重绘:

用 JavaScript 修改样式时,最好不要直接写样式,而是替换 class 来改变样式。

# 性能优化

减少http请求(雪碧图和字体图标)

使用CDN

缓存

压缩代码和压缩文件 (JS、CSS、图片)

懒加载和预加载

减少重排和重绘,减少操作DOM

CSS放头部,JS放底部