Javascript 面试核心考点

本文详细探讨了JavaScript中的变量类型,包括基本类型和引用类型,以及如何区分和判断数据类型。接着,讲解了作用域和执行上下文,包括执行栈、作用域链、闭包和this的解析。此外,还阐述了异步编程的基础,如同步与异步、事件循环和回调函数。最后,介绍了原型链、继承机制以及DOM操作和BOM操作。文章通过实例深入剖析了JavaScript的核心概念,旨在帮助开发者更好地理解和应用这些技术。

一、变量类型

1. JS 的数据类型分类

根据 JavaScript 中的变量类型传递方式,分为基本数据类型和引用数据类型。

基本数据类型:

  • Undefined

  • Null

  • Boolean

  • Number

  • String

  • Symbol (ES6新增,表示独一无二的值)

引用数据类型:

  • Object 对象(主要包括对象、数组和函数)

存储差异:

  • 基本类型直接存储在栈中

  • 引用类型的对象存储在堆中,栈中存储指向堆中实体的指针

参数传递示例:

javascript

// 基本类型
var a = 10
var b = a
b = 20
console.log(a) // 10
console.log(b) // 20

// 引用类型
var a = {x: 10, y: 20}
var b = a
b.x = 100
b.y = 200
console.log(a) // {x: 100, y: 200}
console.log(b) // {x: 100, y: 200}

2. 数据类型的判断

1)typeof

javascript

typeof Symbol() // symbol 有效
typeof '' // string 有效
typeof 1 // number 有效
typeof true // boolean 有效
typeof undefined // undefined 有效
typeof new Function() // function 有效
typeof null // object 无效
typeof [] // object 无效
typeof new Date() // object 无效
typeof new RegExp() // object 无效
2)instanceof

javascript

[] instanceof Array // true
{} instanceof Object // true
new Date() instanceof Date // true
new RegExp() instanceof RegExp // true
null instanceof Null // 报错
undefined instanceof undefined // 报错
3)constructor

可以处理基本数据类型的检测,但不稳定(原型重写可能覆盖constructor)

4)Object.prototype.toString.call()

最准确最常用的方式

javascript

Object.prototype.toString.call('') // [object String]
Object.prototype.toString.call(1) // [object Number]
Object.prototype.toString.call(true) // [object Boolean]
Object.prototype.toString.call(undefined) // [object Undefined]
Object.prototype.toString.call(null) // [object Null]
Object.prototype.toString.call(new Function()) // [object Function]
Object.prototype.toString.call(new Date()) // [object Date]
Object.prototype.toString.call([]) // [object Array]
Object.prototype.toString.call(new RegExp()) // [object RegExp]
Object.prototype.toString.call(new Error()) // [object Error]

3. 浅拷贝与深拷贝

浅拷贝: 只复制指向某个对象的指针,而不复制对象本身,新旧对象共享同一块内存。

浅拷贝实现方式:

  • Object.assign()

  • 扩展运算符 ...

  • Array.prototype.slice()

  • Array.prototype.concat()

深拷贝: 在拷贝数据时,将数据的所有引用结构都拷贝一份,在内存中存在两个数据结构完全相同又相互独立的数据。

深拷贝实现方式:

  • JSON.parse(JSON.stringify())

  • 递归实现

  • 使用第三方库(如 lodash 的 _.cloneDeep)

递归实现深拷贝:

javascript

// 定义检测数据类型的功能函数
function checkedType(target) {
  return Object.prototype.toString.call(target).slice(8, -1)
}

// 实现深度克隆---对象/数组
function clone(target) {
  // 判断拷贝的数据类型
  // 初始化变量result成为最终克隆的数据
  let result, targetType = checkedType(target)
  
  if (targetType === 'Object') {
    result = {}
  } else if (targetType === 'Array') {
    result = []
  } else {
    return target
  }
  
  // 遍历目标数据
  for (let i in target) {
    // 获取遍历数据结构的每一项值
    let value = target[i]
    // 判断目标结构里的每一值是否存在对象/数组
    if (checkedType(value) === 'Object' || checkedType(value) === 'Array') {
      // 对象/数组里嵌套了对象/数组
      // 继续遍历获取到value值
      result[i] = clone(value)
    } else {
      // 获取到value值是基本的数据类型或者是函数
      result[i] = value
    }
  }
  return result
}

二、作用域和闭包

1. 执行上下文和执行栈

执行上下文是当前 JavaScript 代码被解析和执行时所在环境的抽象概念。

执行上下文的生命周期:

  1. 创建阶段 → 2. 执行阶段 → 3. 回收阶段

创建阶段(函数被调用,但未执行任何内部代码之前)会做三件事:

  • 创建变量对象(Variable Object)

  • 建立作用域链(Scope Chain)

  • 确定this的指向

示例分析:

javascript

function test(arg){
  console.log(arg); // 输出:function arg() { console.log('hello world') }
  var arg = 'hello';
  function arg(){
    console.log('hello world')
  }
  console.log(arg); // 输出:hello
}
test('hi');

执行栈(调用栈) 管理执行上下文,遵循先进后出的原则。

关键点:

  • JavaScript执行在单线程上,所有代码都是排队执行

  • 同步代码在执行栈中执行

  • 异步代码会通过事件循环机制执行

2. 作用域与作用域链

作用域类型:

  • 全局作用域

  • 函数作用域

  • 块级作用域(ES6新增)

作用域链:

javascript

var a = 100
function fn() {
  var b = 200
  console.log(a) // 自由变量,向父级作用域寻找
  console.log(b)
}
fn()

作用域链的确定时机:

javascript

function F1() {
  var a = 100
  return function () {
    console.log(a)
  }
}

function F2(f1) {
  var a = 200
  console.log(f1())
}

var f1 = F1()
F2(f1) // 100 - 依据函数定义时的作用域链

3. 闭包

闭包是函数中的函数,内部函数可以访问外部函数的变量,外部变量成为内部函数的一部分。

闭包的作用:

  • 使用函数内部的变量在函数执行完后,仍然存活在内存中(延长了局部变量的生命周期)

  • 让函数外部可以操作(读写)函数内部的数据

闭包应用场景:

  • 函数作为返回值

  • 函数作为参数传递

javascript

function outer() {
  var num = 0 // 内部变量
  return function add() {
    num++ // 内部函数有引用,作为add函数的一部分了
    console.log(num)
  }
}

var func1 = outer()
func1() // 1
func1() // 2

var func2 = outer()
func2() // 1
func2() // 2

4. this 全面解析

this的值在执行时确认,定义时不能确认

this绑定规则:

  1. 默认绑定 - 全局环境中的this指向window

javascript

function foo() {
  console.log(this.a) // 1
}
var a = 1
foo()
  1. 隐式绑定 - 方法调用时,this指向调用该方法的对象

javascript

function fn(){
  console.log(this)
}
var obj = {fn: fn}
obj.fn() // this->obj
  1. new绑定 - 构造函数中的this指向新创建的实例

javascript

function CreateJsPerson(name, age){
  this.name = name // p1.name = name
  this.age = age   // p1.age = age
}
var p1 = new CreateJsPerson("尹华芝", 48)
  1. 显式绑定 - call/apply/bind 强制指定this

javascript

function add(c, d){
  return this.a + this.b + c + d
}
var o = {a: 1, b: 3}
add.call(o, 5, 7) // 16
add.apply(o, [10, 20]) // 34
  1. 箭头函数 - this指向定义时的上下文

javascript

let btn1 = document.getElementById('btn1')
let obj = {
  name: 'kobe',
  age: 39,
  getName: function () {
    btn1.onclick = () => {
      console.log(this) // obj
    }
  }
}
obj.getName()

三、异步

1. 同步 vs 异步

同步: 线性执行,必须等待上一件事完成才能执行后面的事情

javascript

// 同步
console.log(100)
alert(200)
console.log(300) // 100 200 300

异步: 并行处理,不必等待程序执行完就可以执行其他任务

javascript

// 异步
console.log(100)
setTimeout(function(){
  console.log(200)
})
console.log(300) // 100 300 200

2. 异步和单线程

JavaScript 是单线程语言,异步是为了避免阻塞。

HTML5 Web Worker 允许创建多线程,但子线程受主线程控制且不能操作DOM。

3. 前端异步的场景

  • 定时任务:setTimeout、setInterval

  • 网络请求:ajax请求、动态图片加载

  • 事件绑定

4. Event Loop

Event Loop 阶段:

  1. 执行同步代码(宏任务)

  2. 执行栈为空,查询是否有微任务需要执行

  3. 执行所有微任务

  4. 必要的话渲染UI

  5. 开始下一轮Event Loop,执行宏任务中的异步代码

示例分析:

javascript

Promise.resolve().then(() => {
  console.log('Promise1')
  setTimeout(() => {
    console.log('setTimeout2')
  }, 0)
})

setTimeout(() => {
  console.log('setTimeout1')
  Promise.resolve().then(() => {
    console.log('Promise2')
  })
}, 0)

// 输出:Promise1, setTimeout1, Promise2, setTimeout2

四、原型链与继承

1. 原型和原型链

原型: prototype对象,表示类型之间的关系

原型链: 对象之间的继承关系通过prototype指向父类对象,直到Object对象为止

javascript

var Person = function() {
  this.age = 18
  this.name = '匿名'
}

var Student = function() {}
// 创建继承关系,父类实例作为子类原型
Student.prototype = new Person()

var s1 = new Student()
console.log(s1)

原型链查找机制: 当访问对象属性时,如果对象本身没有,会去它的__proto__(构造函数的prototype)中寻找,直到找到或返回undefined。

2. 继承

1)组合继承

javascript

function Parent(value) {
  this.val = value
}

Parent.prototype.getValue = function() {
  console.log(this.val)
}

function Child(value) {
  Parent.call(this, value) // 继承属性
}

Child.prototype = new Parent() // 继承方法

const child = new Child(1)
child.getValue() // 1
child instanceof Parent // true

优点: 构造函数可以传参,不会与父类引用属性共享,可以复用父类的函数
缺点: 调用了两次父类构造函数,生成两份实例

2)寄生组合继承(最优)

javascript

function Parent(value) {
  this.val = value
}

Parent.prototype.getValue = function() {
  console.log(this.val)
}

function Child(value) {
  Parent.call(this, value)
}

Child.prototype = Object.create(Parent.prototype, {
  constructor: {
    value: Child,
    enumerable: false,
    writable: true,
    configurable: true
  }
})

const child = new Child(1)
child.getValue() // 1
child instanceof Parent // true
3)Class 继承

javascript

class Parent {
  constructor(value) {
    this.val = value
  }
  
  getValue() {
    console.log(this.val)
  }
}

class Child extends Parent {
  constructor(value) {
    super(value)
    this.val = value
  }
}

let child = new Child(1)
child.getValue() // 1
child instanceof Parent // true

五、DOM操作与BOM操作

1. DOM操作

获取DOM节点:

javascript

var div1 = document.getElementById('div1')

添加新节点:

javascript

var p1 = document.createElement('p')
p1.innerHTML = 'this is p1'
div1.appendChild(p1)

移动已有节点:

javascript

var p2 = document.getElementById('p2')
div1.appendChild(p2)

获取父节点:

javascript

var parent = div1.parentElement

获取子节点:

javascript

var child = div1.childNodes

删除节点:

javascript

div1.removeChild(child[0])

2. DOM事件模型和事件流

事件传播三阶段:

  1. 捕获阶段:从window对象自上而下向目标节点传播

  2. 目标阶段:真正的目标节点正在处理事件

  3. 冒泡阶段:从目标节点自下而上向window对象传播

事件冒泡示例:

html

<div id="outer">
  <div id="inner"></div>
</div>

<script>
window.onclick = function() { console.log('window') }
document.onclick = function() { console.log('document') }
document.documentElement.onclick = function() { console.log('html') }
document.body.onclick = function() { console.log('body') }
outer.onclick = function(ev) { console.log('outer') }
inner.onclick = function(ev) { console.log('inner') }

// 点击inner输出:inner → outer → body → html → document → window
</script>

阻止事件冒泡:

javascript

inner.onclick = function(ev) {
  console.log('inner')
  ev.stopPropagation() // 阻止事件冒泡
}

3. 事件代理(事件委托)

利用事件冒泡机制,将子元素的事件监听函数定义在父元素上。

示例:

html

<div id="div1">
  <a href="#">a1</a>
  <a href="#">a2</a>
  <a href="#">a3</a>
  <a href="#">a4</a>
</div>
<button>点击增加一个 a 标签</button>

<script>
var div1 = document.getElementById('div1')
div1.addEventListener('click', function (e) {
  var target = e.target
  if (target.nodeName === 'A') {
    alert(target.innerHTML)
  }
})
</script>

事件代理的优点:

  • 减少内存消耗

  • 动态绑定事件

  • 代码简洁

4. BOM 操作

获取屏幕信息:

javascript

console.log(screen.width)
console.log(screen.height)

获取URL信息:

javascript

// 当前网址:https://juejin.im/timeline/frontend?a=10&b=10#some
console.log(location.href) // https://juejin.im/timeline/frontend?a=10&b=10#some
console.log(location.protocol) // https:
console.log(location.pathname) // /timeline/frontend
console.log(location.search) // ?a=10&b=10
console.log(location.hash) // #some

浏览器历史:

javascript

history.back()    // 后退
history.forward() // 前进

获取浏览器信息:

javascript

var ua = navigator.userAgent
var isChrome = ua.indexOf('Chrome')
console.log(isChrome)

5. Ajax与跨域

手写XMLHttpRequest:

javascript

var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
  if (xhr.readyState == 4) {
    if (xhr.status == 200) {
      alert(xhr.responseText)
    }
  }
}
xhr.open("GET", "/api", false)
xhr.send(null)

跨域解决方案:

  • JSONP

  • CORS(跨域资源共享)

  • 服务器代理

  • postMessage

  • WebSocket

  • document.domain + iframe

6. 存储

sessionStorage、localStorage 和 cookie 的区别:

特性cookielocalStoragesessionStorage
生命周期可设置过期时间永久保存(除非手动删除)会话级别(页面关闭即释放)
存储大小4KB左右5MB或更多5MB或更多
与服务器通信每次都会携带在HTTP头中仅在客户端保存仅在客户端保存
易用性需要自己封装原生接口易用原生接口易用

作用域:

  • localStorage:相同协议、主机名、端口下可访问

  • sessionStorage:同一窗口(浏览器标签页)下可访问

六、模块化

常见模块化规范

1. CommonJS
  • 主要用于服务器端(Node.js)

  • 同步加载模块

  • 使用 module.exports 和 require

javascript

// 导出
module.exports = { ... }

// 导入
const module = require('./module')
2. AMD(Asynchronous Module Definition)
  • 异步模块定义

  • 主要用于浏览器端

  • 使用 define 定义模块,require 加载模块

javascript

// 定义模块
define(['dependency'], function() {
  return { ... }
})

// 加载模块
require(['module'], function(module) {
  // ...
})
3. CMD(Common Module Definition)
  • 通用模块定义

  • 按需加载

  • 代表实现:Sea.js

javascript

// 定义模块
define(function(require, exports, module) {
  var dependency = require('dependency')
  exports.value = ...
  module.exports = { ... }
})
4. ES6 Module
  • ES6 官方模块系统

  • 静态化,编译时就能确定模块的依赖关系

  • 使用 export 和 import

javascript

// 导出
export const value = ...
export default { ... }

// 导入
import module from './module'
import { value } from './module'

各模块化规范对比:

规范加载方式适用环境特点
CommonJS同步服务器端简单易用,模块输出值的拷贝
AMD异步浏览器端提前执行,依赖前置
CMD异步浏览器端按需执行,依赖就近
ES6 Module静态浏览器/服务器编译时加载,模块输出值的引用
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值