# 延迟加载 JS 有哪些方式?

  • 比如下面有两个文件夹
  • HTML
<script type="text/javascript" src="./index.js"></script>
<script type="text/javascript" src="./a.js"></script>
<script type="text/javascript" src="./b.js"></script>
<div id="app">app</div>
  • index.js & a.js & b.js
// index.js
console.log(document.querySelector('#app'))  // 1.null
// a.js
console.log('aaa') // 2.aaa
// b.js
console.log('bbb') // 3.bbb
  • 因为浏览器是从上个到下执行, script 是在 app 上面的,那么 app 元素还没解析,上面 JS 代码就已经执行了,是获取不到 app 的

# <script async>

  • async 是和 HTML 解析同步的 (一起的),不是顺次执行 JS 脚本 (谁先加载完谁先执行)
<script async type="text/javascript" src="./index.js"></script>
<script async type="text/javascript" src="./a.js"></script>
<script async type="text/javascript" src="./b.js"></script>
<div id="app">app</div>
  • JS , 谁先加载完谁先执行
// index.js
console.log(document.querySelector('#app')) 
// a.js
console.log('aaa')
// b.js
console.log('bbb')

# <script defer>

  • 等 HTML 全部解析完成,才会执行 JS 代码,顺次执行

  • 所以 defer 也是可以延迟加载可以获取到的

<script defer type="text/javascript" src="./index.js"></script>
<script defer type="text/javascript" src="./a.js"></script>
<script defer type="text/javascript" src="./b.js"></script>
<div id="app">app</div>
  • index.js & a.js & b.js
// index.js
console.log(document.querySelector('#app')) 
// a.js
console.log('aaa')
// b.js
console.log('bbb')
// 永远都是顺次执行 -> index a b
// 1.<div id="app">app</div>
// 2.aaa
// 3.bbb

# JS 数据类型

  • JS 数据类型:JS 的数据类型有几种?

    • 8 种。Number、String、Boolean、Null、undefined、object、symbol、bigInt。
  • JS 数据类型:Object 中包含了哪几种类型?

    • 其中包含了 Data、function、Array 等。这三种是常规用的。
  • JS 数据类型:JS 的基本类型和引用类型有哪些呢?

    • 基本类型(单类型):除 Object。 String、Number、boolean、null、undefined

    • 引用类型:object,里面包含了 function、Array、Date。

  • NaN 是一个数值类型,但是不是一个具体的数字

# null & undefined 区别

  • 作者在设计 JS 的都是先设计的 null (为什么设计 null:最初设计 JS 的时候借鉴了 java 的语言)
  • null 会被隐式转换成 0,很不容易发现错误
  • 先有 null 后有 undefined,出来 undefined 是为了弥补之前的坑
  • 具体区别,JavaScript 的最初版本是这样区分的:
    • null 是一个表示 ' 无 ' 的对象 (空对象指针),转为数值时为 0
    • undefined 是一个表示 ' 无' 的原始值,转为数值时为 NaN

# == & === 有什么不同?

  • ==:比较的是值
1 == '1' // true
true == 1 // true
[1,2,3] == '1,2,3' // true
null == undefined // true
  • string == number || boolean || number ... 都会隐式转换
  • 通过 valueOf 转换 (valueOf () 方法通常由 JavaScript 在后台自动调用,并不显示)
let arr = [1, 2, 3]
let str = '1,2,3'
Object.prototype.valueOf = function () {
  console.log('Object valueOf')
}
Array.prototype.valueOf = function () {
  console.log('Array valueOf')
}
console.log(str == arr) // false
  • 上面例子会优先找自己原型的 valueOf 方法,所以会输出 Array valueOf ,但如果没有重写 Array.prototype.valueOf ,那么此时打印的就是 Object valueOf !
  • ===:除了比较值,还比较类型
1 === '1' // false
true === 1 // false
[1,2,3] === '1,2,3' // false
null === undefined // false

# JS 微任务 & 宏任务

  • JS 是单线程语言
  • JS 代码执行流程:同步执行完 ==> 事件循环
    • 同步的任务执行完了,才会执行事件循环的内容
    • 进入事件循环:Ajax、定时器、事件 ......
  • 事件循环中包含:微任务、宏任务
  • 微任务:promise.then 、queueMicrotask、async/await ......
  • 宏任务:setTimeout、setInterval ......
  • 要执行宏任务的前提是清空所有微任务
  • 事件循环的整体流程
    • 先清空 call stack 中的同步代码
    • 执行微任务队列中的微任务
    • 尝试 DOM 渲染
    • 触发 Event Loop 反复询问 callbackQueue 中是否有要执行的语句,有则放入 call back 继续执行

# JS 作用域考题

  • 除了函数外,JS 是没有块级作用域
  • 作用域链:内部可以访问外部的变量,但是外部不能访问内部的变量。如果内部有,优先查找到内部,如果内部没有就查找外部的
  • 注意声明变量是用关键字 (let/const/var) 定义,没有写则是 (window.)
  • 注意:JS 有变量提升的机制 (变量悬挂声明)
  • 优先级: 声明变量 > 声明普通函数 > 变量提升
  • 普通声明函数是不看写函数的时候顺序
  • 下面栗几道简单的题

# 例题一

function a() {
  var b = 1
  function c() {
    console.log(b)
    var b = 2
    console.log(b)
  }
  c()
  console.log(b);
}
a()
  • 输出结果的顺序是 undefined 2 1

# 例题二

  • 这道题考察三点,作用域、按位取反运算符与立即执行函数
var age = 16
const res = ~ function () {
  if (typeof age == 'undefined') {
    var age = 17
    console.log('1' + age)
  } else {
    console.log('2' + age);
  }
  return age
}()
console.log(res)
  • 输出结果的顺序是 '117' -18

# 例题三

  • 该题考察声明变量与声明函数的优先级
function foo() {
  var bar = 10
  function bar() {}
  console.log(bar)
}
foo()
  • 输出结果 10

# 例题四

  • 该题考察普通声明函数不看写函数的时候顺序
function foo() {
  console.log(bar)
  var bar = 10
  function bar() {}
}
foo()
  • 输出结果 [Function: bar]

# 例题五

function foo(bar) {
  var bar = 10
  function bar() {}
  console.log(bar)
}
foo(233)
  • 输出结果 10

# 例题六

  • 该题考查变量的再次赋值
function foo() {
  bar = 10
  console.log(bar)
  var bar = 20
  console.log(bar)
}
foo()
  • 输出结果的顺序是 10 20

# JS 对象考题

  • 对象是通过 new 操作符构建出来的,所有对象之间不相等 (除了引用外)

  • 对象注意:引用类型 (相同地址)

  • 对象的 key 都是字符串类型

  • 对象如何找属性 | 方法:

    • 查找规则:先在对象本身找 -> 构造函数中找 -> 对象原型中找 -> 构造函数原型中找 -> 对象上一层原型查找
function Foo() {
  this.a = '2.在Foo函数中添加的' 
}
Foo.prototype.a = '4.这是Foo原型添加到'
let foo = new Foo()
foo.a = '1.对象本身'
foo.__proto__.a = '3.这是对象原型添加到'
Object.prototype.a = '5.这是Object添加的'
// 会按照 12345 的顺序来找,如果都没有找到则是 undefined
console.log(foo.a)

# 考题一

[1, 2, 3] == [1, 2, 3] // false

# 考题二

  • 该题考察对象的 key 都是字符串类型,所以会被覆盖掉
const lain = {
  name: 'lain'
}
const saber = lain
saber.name = 'saber'
console.log(lain)   
~function () { 
  console.log(a) // undefined
  var a = 1
}()
  • 输出结果的顺序 {name: 'saber'} undefined

# JS 作用域 + this 指向 + 原型考题

# 考题一

function Foo() {
  getName = function () {
    console.log(1)
  }
  return this
}
Foo.getName = function () {
  console.log(2)
}
Foo.prototype.getName = function () {
  console.log(3)
}
var getName = function () {
  console.log(4)
}
function getName() {
  console.log(5)
}
Foo.getName() 
getName() 
Foo().getName() 
getName() 
new Foo().getName()
  • 输出结果的顺序 2 4 1 1 3

# 考题二

var o = {
  a: 10,
  b: {
    fn: function () {
      console.log(this.a)
      console.log(this)
    }
  }
}
o.b.fn()
  • 输出结果的顺序是 undefined { fn: [Function: fn] }

# 考题三

window.name = 'Hello World'
function Foo() {
  this.name = 232
}
Foo.prototype.getFoo = function () {
  console.log(this)
  return this.name + 1
}
let foo = new Foo()
let funFoo = foo.getFoo
console.log(funFoo())
  • 输出结果的顺序是 Window 'Hello World1'

# 考题四

var length = 10
function fn() {
  return this.length + 1
}
var obj = {
  length: 5,
  test1: function () {
    return fn()
  }
}
obj.test2 = fn
console.log(obj.test1()) 
console.log(obj.test2())
console.log(fn() === obj.test2())
console.log(obj.test1() == obj.test2());

输出结果的顺序是 Window 'Hello World1'

# JS 判断变量是不是数组,你能写出哪些方法?

# Array.isArray

const arr = [1, 2, 3]
const str = 'Hello World'

# instanceof

  • 不严谨,但也可以做到
const arr = [1, 2, 3]
const str = 'Hello World'
Array.isArray(arr) // true
Array.isArray(str) // false

# Object.prototype.toString.call

const arr = [1, 2, 3]
const str = 'Hello World'
Object.prototype.toString.call(arr) === '[object Array]' // true
Object.prototype.toString.call(str) === '[object Array]' // false
// 或者使用 indexOf
Object.prototype.toString.call(arr).indexOf('Array') > -1 // true
Object.prototype.toString.call(str).indexOf('Array') > -1 // false

# Array.prototype.isPrototypeOf

const arr = [1, 2, 3]
const str = 'Hello World'
Array.prototype.isPrototypeOf(arr) // true
Array.prototype.isPrototypeOf(str) // false

# constructor

const arr = [1, 2, 3]
const str = 'Hello World'
arr.constructor.toString() === 'function Array() { [native code] }' // true
str.constructor.toString() === 'function Array() { [native code] }' // false
// 或者使用 indexOf
const arr = [1, 2, 3]
const str = 'Hello World'
arr.constructor.toString().indexOf('Array') > -1 // true
str.constructor.toString().indexOf('Array') > -1 // false

# slice、splice 是否改变原数组

  • slice 用来截取,不改变原数组,返回新数组
const arr1 = [1, 2, 3, 4, 5]
const arr2 = arr1.slice(0, 2)
console.log(arr1) // [1, 2, 3, 4, 5]
console.log(arr2) // [1, 2]
  • splice 可以插入、删除、替换
  • 返回删除的元素,会改变数组
const arr1 = [1, 2, 3, 4, 5]
const arr2 = arr1.splice(0, 2, '插入')
console.log(arr1) // [' 插入 ', 3, 4, 5]
console.log(arr2) // [1, 2]

# JS 数组去重

# Set

const arr = [1, 2, 1, 3, 2, 4, 4, 5, 6, 6]
Array.from(new Set(arr)) // [1, 2, 3, 4, 5, 6]
[...new Set(arr)]        // [1, 2, 3, 4, 5, 6]

# unique 函数

const arr = [1, 2, 1, 3, 2, 4, 4, 5, 6, 6]
function unique(arr) {
  const newArr = []
  for (let i = 0; i < arr.length; i++){
    if (newArr.indexOf(arr[i]) === -1) {
      newArr.push(arr[i])
    }
  }
  return newArr
}
unique(arr) // [1, 2, 3, 4, 5, 6]

# 排序方式

const arr = [1, 2, 1, 3, 2, 4, 4, 5, 6, 6]
function unique(arr) {
  const newArr = []
  arr = arr.sort()
  for (let i = 0; i < arr.length; i++){
    if (arr[i] !== arr[i - 1]) {
      newArr.push(arr[i])
    }
  }
  return newArr
}
unique(arr) // [1, 2, 3, 4, 5, 6]

# 找出多维数组最大值

const arr =[[1,2,3,4], [5,6,7,8], [9,10,11,12]]
function maxArr(arr) {
  const newArr = []
  arr.forEach(item => {
    newArr.push(Math.max(...item))
  })
  return newArr
}
maxArr(arr) // [4, 8, 12]

# 找出字符串出现最多次数的字符以及次数

const str = 'abbcccddddeeeee'
function repeatMaxStr(str) {
  // 1. 以对象形式统计字符串与出现次数
  const hash = {}
  for (let i = 0; i < str.length; i++){
    if (!hash[str[i]]) {
      hash[str[i]] = 1
    } else {
      hash[str[i]]++
    }
  }
  
  // 2. 求出次数最大的值
  let max = 0
  for (const key in hash) {
    if (hash[key] > max) max = hash[key]
  }
  
  // 3. 根据最大的值求出对于的字符串
  let res = {}
  for (const key in hash) {
    if (hash[key] == max) {
      res[key] = max
    } 
  }
  // 4. 以对象形式返回出现最多的字符串以及次数
  return res
}
repeatMaxStr(str) // {e: 5}

# 闭包

  • 闭包是什么?

    • 闭包 是一个函数加上到创建函数的作用域的连接,闭包关 '' 闭 '' 了函数的自由变量
  • 闭包可以解决什么问题 [闭包的优点]

    • 内部函数可以访问到外部函数的局部变量
    • 闭包可以解决的问题
    <ul>
      <li>1</li>
      <li>2</li>
      <li>3</li>
    </ul>
    <script>
      const lis = document.querySelectorAll('ul li')
      for (var i = 0; i < lis.length; i++) {
        (function (i) {
          lis[i].onclick = function () {
            console.log(i)
          }
          lis[i] = null
        }(i))
      }
    </script>
  • 闭包的缺点

    • 变量会驻留在内存中,造成内存损耗问题
    • 解决:把闭包的函数设置为 null lis[i] = null
    • 内存泄露:可以提到 IE

# 原型链

  • 原型可以解决什么问题?

    • 对象共享属性和共享方法
  • 谁有原型

    • 函数:prototype
    • 对象:proto
  • 对象查找属性或者方法的顺序

    • 对象本身 -> 构造函数 -> 对象原型 -> 构造函数原型 -> 当前原型的原型
  • 原型链

    • 就是把原型串联起来
    • 原型链的最顶端是 null