一、变量类型
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 代码被解析和执行时所在环境的抽象概念。
执行上下文的生命周期:
-
创建阶段 → 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绑定规则:
-
默认绑定 - 全局环境中的this指向window
javascript
function foo() {
console.log(this.a) // 1
}
var a = 1
foo()
-
隐式绑定 - 方法调用时,this指向调用该方法的对象
javascript
function fn(){
console.log(this)
}
var obj = {fn: fn}
obj.fn() // this->obj
-
new绑定 - 构造函数中的this指向新创建的实例
javascript
function CreateJsPerson(name, age){
this.name = name // p1.name = name
this.age = age // p1.age = age
}
var p1 = new CreateJsPerson("尹华芝", 48)
-
显式绑定 - 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
-
箭头函数 - 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 阶段:
-
执行同步代码(宏任务)
-
执行栈为空,查询是否有微任务需要执行
-
执行所有微任务
-
必要的话渲染UI
-
开始下一轮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事件模型和事件流
事件传播三阶段:
-
捕获阶段:从window对象自上而下向目标节点传播
-
目标阶段:真正的目标节点正在处理事件
-
冒泡阶段:从目标节点自下而上向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 的区别:
| 特性 | cookie | localStorage | sessionStorage |
|---|---|---|---|
| 生命周期 | 可设置过期时间 | 永久保存(除非手动删除) | 会话级别(页面关闭即释放) |
| 存储大小 | 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 | 静态 | 浏览器/服务器 | 编译时加载,模块输出值的引用 |
本文详细探讨了JavaScript中的变量类型,包括基本类型和引用类型,以及如何区分和判断数据类型。接着,讲解了作用域和执行上下文,包括执行栈、作用域链、闭包和this的解析。此外,还阐述了异步编程的基础,如同步与异步、事件循环和回调函数。最后,介绍了原型链、继承机制以及DOM操作和BOM操作。文章通过实例深入剖析了JavaScript的核心概念,旨在帮助开发者更好地理解和应用这些技术。
1272

被折叠的 条评论
为什么被折叠?



