JavaScript知识点
JavaScript的数据类型
基本数据类型
- 基础数据类型有
number
、string
、boolean
、null
、undefined
。 - 基本数据类型放在栈中,存储的是值。
- 基本数据类型赋值时会开辟一个新空间赋值给新值。
复杂数据类型
- 复杂数据类型有
object
、array
、function
。 - 复杂数据类型的值放在堆中,栈中存储的是内容对应的内存地址。
- 复杂数据类型赋值时会赋值对应的地址,也就是两个变量指向堆内存中同一个对象。
JavaScript的类型转换
显示转换
-
Number()
会把括号内的变量转为数字型。只要有一个字符无法转成数值,整个字符串就会被转为
NaN
。Number(324) // 324 // 字符串:如果可以被解析为数值,则转换为相应的数值 Number('324') // 324 // 字符串:如果不可以被解析为数值,返回 NaN Number('324abc') // NaN // 空字符串转为0 Number('') // 0 // 布尔值:true 转成 1,false 转成 0 Number(true) // 1 Number(false) // 0 // undefined:转成 NaN Number(undefined) // NaN // null:转成0 Number(null) // 0 // 对象:通常转换成NaN(除了只包含单个数值的数组) Number({a: 1}) // NaN Number([1, 2, 3]) // NaN Number([5]) // 5
-
parentInt()
与Number()相比没那么严格,转换碰到第一个无法转换的字符才停止。
parseInt('32a3') //32
-
String()
转为字符串的形式。
// 数值:转为相应的字符串 String(1) // "1" //字符串:转换后还是原来的值 String("a") // "a" //布尔值:true转为字符串"true",false转为字符串"false" String(true) // "true" //undefined:转为字符串"undefined" String(undefined) // "undefined" //null:转为字符串"null" String(null) // "null" //对象 String({a: 1}) // "[object Object]" String([1, 2, 3]) // "1,2,3"
-
Boolean()
可以将任意类型的值转为布尔值,转换规则如下:
实践一下:
Boolean(undefined) // false Boolean(null) // false Boolean(0) // false Boolean(NaN) // false Boolean('') // false Boolean({}) // true Boolean([]) // true Boolean(new Boolean(false)) // true
隐式转换
-
字符串拼接
'5' + 1 // '51' '5' + true // "5true" '5' + false // "5false" '5' + {} // "5[object Object]" '5' + [] // "5" '5' + function (){} // "5function (){}" '5' + undefined // "5undefined" '5' + null // "5null"
-
数值运算
'5' - '2' // 3 '5' * '2' // 10 true - 1 // 0 false - 1 // -1 '1' - 1 // 0 '5' * [] // 0 false / '5' // 0 'abc' - 1 // NaN null + 1 // 1 undefined + 1 // NaN
-
判断 在需要布尔值的地方,就会将非布尔值的参数自动转为布尔值,系统内部会调用
Boolean
函数- undefined
- null
- false
- 0
- NaN
- ""
除了上面几种会被转化成
false
,其他都换被转化成true
深拷贝与浅拷贝
浅拷贝
将值复制一份,如果是基本类型数据就拷贝的就是基本类型的值,互不影响,但是如果有引用类型的数据则拷贝的是其地址,改变一个,另一个随之改变。常见的浅拷贝有:
- Object.assign()
- slice()、concat()
- 扩展运算符 {...obj, ...obj1}
深拷贝
就是内存中开辟一块新地址,完整拷贝一份到新地址中,如果修改一个另一个则不会受到影响。常见的深拷贝有:
-
jQuery的extend方法:
const $ = require('jquery'); const obj1 = { a: 1, b: { f: { g: 1 } }, c: [1, 2, 3] }; const obj2 = $.extend(true, {}, obj1); console.log(obj1.b.f === obj2.b.f); // false
-
JSON.stringify()
const obj2=JSON.parse(JSON.stringify(obj1));
但是这种方式存在弊端,会忽略
undefined
、symbol
和函数
const obj = { name: 'A', name1: undefined, name3: function() {}, name4: Symbol('A') } const obj2 = JSON.parse(JSON.stringify(obj)); console.log(obj2); // {name: "A"}
-
循环递归
function deepClone(obj, hash = new WeakMap()) { if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作 if (obj instanceof Date) return new Date(obj); if (obj instanceof RegExp) return new RegExp(obj); // 可能是对象或者普通的值 如果是函数的话是不需要深拷贝 if (typeof obj !== "object") return obj; // 是对象的话就要进行深拷贝 if (hash.get(obj)) return hash.get(obj); let cloneObj = new obj.constructor(); // 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身 hash.set(obj, cloneObj); for (let key in obj) { if (obj.hasOwnProperty(key)) { // 实现一个递归拷贝 cloneObj[key] = deepClone(obj[key], hash); } } return cloneObj; }
小结
前提为拷贝类型为引用类型的情况下:
- 浅拷贝是拷贝一层,属性为对象时,浅拷贝是复制,两个对象指向同一个地址
- 深拷贝是递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址
闭包
本质上是在函数内部和函数外部搭建一座桥梁,让子函数可以访问父函数中的局部变量;
- 好处就是可以保护变量不受外界污染
- 缺点就是常驻内存,消耗性能,所以开发中很少使用闭包
作用域
-
全局作用域 任何不在函数中或是大括号中声明的变量,都是在全局作用域下,全局作用域下声明的变量可以在程序的任意位置访问
// 全局变量 var greeting = 'Hello World!'; function greet() { console.log(greeting); } // 打印 'Hello World!' greet();
-
函数作用域 函数作用域也叫局部作用域,如果一个变量是在函数内部声明的它就在一个函数作用域下面。这些变量只能在函数内部访问,不能在函数以外去访问
function greet() { var greeting = 'Hello World!'; console.log(greeting); } // 打印 'Hello World!' greet(); // 报错: Uncaught ReferenceError: greeting is not defined console.log(greeting);
可见上述代码中在函数内部声明的变量或函数,在函数外部是无法访问的,这说明在函数内部定义的变量或者方法只是函数作用域
-
块级作用域 ES6引入了
let
和const
关键字,和var
关键字不同,在大括号中使用let
和const
声明的变量存在于块级作用域中。在大括号之外不能访问这些变量
{
// 块级作用域中的变量
let greeting = 'Hello World!';
var lang = 'English';
console.log(greeting); // Prints 'Hello World!'
}
// 变量 'English'
console.log(lang);
// 报错:Uncaught ReferenceError: greeting is not defined
console.log(greeting);
this
- 函数形式调用指向window
- 方法形式调用指向调用对象
- 构造函数调用指向构造函数的实例对象
- 箭头函数看外层是否有函数,如果有外层函数的this就是箭头函数的,如果没有就是window
事件模型
on+事件名
-
不支持事件铺货,只支持事件冒泡
-
同一个事件类型只能绑定一次
-
绑定速度快
DOM0
级事件具有很好的跨浏览器优势,会以最快的速度绑定,但由于绑定速度太快,可能页面还未完全加载出来,以至于事件可能无法正常运行
addEventListener
-
可以在一个
DOM
元素上绑定多个事件处理器,各自并不会冲突 -
执行时机
当第三个参数(
useCapture
)设置为true
就在捕获过程中执行,反之在冒泡过程中执行处理函数
typeof和instanceof的区别
typeof
typeof
会返回一个变量的基本类型- 虽然可以判断基础数据类型(
null
除外),但是引用数据类型中,除了function
类型以外,其他的也无法判断
typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
typeof null // 'object'
typeof [] // 'object'
typeof {} // 'object'
typeof console // 'object'
typeof console.log // 'function'
instanceof
instanceof
返回的是一个布尔值instanceof
可以准确地判断复杂引用数据类型,但是不能正确判断基础数据类型
构造函数通过new
可以实例对象,instanceof
能判断这个对象是否是之前那个构造函数生成的对象
// 定义构建函数
let Car = function() {}
let benz = new Car()
benz instanceof Car // true
let car = new String('xxx')
car instanceof String // true
let str = 'xxx'
str instanceof String // false
事件代理
事件委托,会把一个或者一组元素的事件委托到它的父层或者更外层元素上,真正绑定事件的是外层元素,而不是目标元素
当事件响应到目标元素上时,会通过事件冒泡机制从而触发它的外层元素的绑定事件上,然后在外层元素上去执行函数
适合事件委托的事件有:click
,mousedown
,mouseup
,keydown
,keyup
,keypress
使用事件委托存在两大优点:
- 减少整个页面所需的内存,提升整体性能
- 动态绑定,减少重复工作
但是使用事件委托也是存在局限性:
focus
、blur
这些事件没有事件冒泡机制,所以无法进行委托绑定事件mousemove
、mouseout
这样的事件,虽然有事件冒泡,但是只能不断通过位置去计算定位,对性能消耗高,因此也是不适合于事件委托的
如果把所有事件都用事件代理,可能会出现事件误判,即本不该被触发的事件被绑定上了事件
new
- 创建一个新的obj
- 通过构造函数的原型链把对象绑定过去
- 修改this的指向
ajax原理
是什么
是JavaScript和xml创建异步交互的网页开发技术,可以在不重复刷新的情况下局部刷新页面。通过XmlHttpRequest对象实现
过程
- 创建一个XmlHttpRequest对象
- 调用open方法,括号内填入请求方法和路径
- 调用send方法发送数据
- 通过
onreadystatechange
方法监听通信状态 - 接受结果,渲染到html页面中
apply、bind、call
- 都是修改this的指向问题
- 第一个参数都是要修改的对象,如果不传或者是undefined或null,默认为指向全局window
- apply第二个参数传递一个数组,bind和call传递值队列
- apply和call立即执行一次,bind返回的是绑定后的函数
- bind可以多次传数据,app和call只能传一次
事件循环
同步任务与异步任务
同步任务与异步任务的概念大家都不陌生,但是蛋蛋这么划分是不够的,先看一段代码。
console.log(1)
setTimeout(()=>{
console.log(2)
}, 0)
new Promise((resolve, reject)=>{
console.log('new Promise')
resolve()
}).then(()=>{
console.log('then')
})
console.log(3)
如果划分同步任务与异步任务,则最终结构是:1,new Promise,3,2,then。但结果是1,new Promise,3,then,2。因为异步任务又分为微任务和宏任务。
微任务
一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前
常见的微任务有:
- Promise.then
- MutaionObserver
- Object.observe(已废弃;Proxy 对象替代)
- process.nextTick(Node.js)
宏任务
宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合
常见的宏任务有:
- script (可以理解为外层同步代码)
- setTimeout/setInterval
- UI rendering/UI事件
- postMessage、MessageChannel
- setImmediate、I/O(Node.js)
async与await
async
是异步的意思,await
则可以理解为 async wait
。所以可以理解async
就是用来声明一个异步方法,而 await
是用来等待异步方法执行。
正常情况下,await
命令后面是一个 Promise
对象,返回该对象的结果。如果不是 Promise
对象,就直接返回对应的值
不管await
后面跟着的是什么,await
都会阻塞后面的代码
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(function () {
console.log('settimeout')
})
async1()
new Promise(function (resolve) {
console.log('promise1')
resolve()
}).then(function () {
console.log('promise2')
})
console.log('script end')
最后的结果是:script start
、async1 start
、async2
、promise1
、script end
、async1 end
、promise2
、settimeout
DOM的操作
创建节点
- createElement创建一个新元素
- createAttribute创建一个自定义属性节点
获取节点
- querySelector,获取第一个符合条件的元素
- querySelectorAll,返回一个包含节点子树内所有与之相匹配的
Element
节点列表,如果没有相匹配的,则返回一个空节点列表
更新节点
- innerHTML、innerText:更新文本内容
- style:更新css样式
添加节点
- appendChild:在父元素最后追加新的子节点
- insetBefore:在父元素最开始添加新的子节点
- setAttribute:在指定元素中添加一个属性节点,如果元素中已有该属性改变属性值
删除节点
删除一个节点,首先要获得该节点本身以及它的父节点,然后,调用父节点的removeChild
把自己删掉
本地存储
- cookie大小4kb,localStrong和session大小5mb
- cokie在时间内有效,local永久有效,session在浏览器关闭前有效
- cookie会默认携带给服务器,另外两个保存在本地