延迟加载js的方式
1、 通常都会将js文件的引入放到body的最后,
2、 通过async | defer可以实现延迟加载
【一般情况下js的下载和执行会暂停html的解析, 执行完毕后再继续解析html】
两种延迟加载的区别:
- async: 解析html的同时下载js, 两者同步进行, 只有执行时才暂停html解析
- defer: 解析html的同时下载js, 但是js的执行则是放到了html解析完毕之后
- defer是顺次执行, 与代码顺序有关
- async执行的顺序不一定, 先下载完先执行, js文件存在依赖关系不建议使用
js的数据类型
- 基本类型: string number booan undefined null symbol bigint
- 引用类型: object
// 隐式转换
console.log(true+1) //2
console.log(true+'name') // truename
console.log(undefined+1) // NaN(数值类型),但不是一个具体的数字
console.log(typeof null) // object
console.log(typeof undefined) // undefined
null和undefined的区别
为什么会出现undefined:
- 作者认为null是一个object类型, 认为表示'无'最好不是对象
- null会被隐式转换为0, 数据类型不匹配时, 不容易发现错误
相同点:
- 都可表示'无'
不同点:
- null是一个表示'无'的对象, 是一个空对象指针, 转换为数值为0, 需要null关键字声明
- undefined是一个表示'无'的原始值, 转换为数值为NaN, 变量定义未赋值就是undefined
js作用域
- 除了函数外, js是没有块级作用域的
- 作用域链实现内部可以访问外部的变量(内部优先), 但是外部仍然不能访问内部变量
- 声明变量是否使用了var, 如果直接使用b=1, 这是全局变量, 函数外部仍可以访问
【优先级: 声明变量 > 声明普通函数 > 传递参数 > 变量提升】
js数组去重
function noRepeat(arr) {
if (arr.length < 2) {
return arr;
}
let newArr = [arr[0]];
// 遍历原数组的每个元素和新数组每个元素比较
for (let i = 0; i < arr.length; i++) {
for (let j = 0; j < newArr.length; j++) {
if (arr[i] === newArr[j]) {
break;
}
// 循环完毕都没有重复的
if (j === newArr.length - 1) {
newArr.push(arr[i]);
}
}
}
return newArr;
}
let newArr = [...new Set(arr)]
new操作符做了什么
- 创建了一个空的对象
- 将空对象的原型指向构造函数的原型
- 改变this指向
- 对构造函数有返回值的处理判断
如果返回值是基本数据类型, 就会创建一个新的实例对象
如果返回值是一个引用数据类型, 就会返回构造函数的返回值
闭包
function fun() {
let count = 1;
return function () {
count++;
console.log(count);
};
}
let fun2 = fun(); fun执行的结果才是一个闭包
fun2(); // 2
fun2(); // 3
闭包实际上是一个函数,声明在另一个函数内部,对外部函数的局部变量存在引用作用: 实现局部变量的共享和长久保存,同时不会造成全局污染
概括: 内层函数操作外层函数的局部变量,外层函数将内层函数返回
【闭包能持久储存变量的原因是: 外层函数作用域对象没有被释放】
- 闭包的优点是延长变量的生命周期 | 创建私有环境
- 闭包的缺点是容易造成内存泄露
原型链
原型:原型可以共享属性和方法
- 函数拥有prototype,对象拥有__prototype__
查找顺序:
- 对象本身查找 —— 构造函数中查找 —— 对象的原型 —— 构造函数的原型 —— Object的原型中 ——null
原型链:
- 原型链就是将原型串联起来的结构
- 原型链的顶端是null
js继承的方式
// ES6的class继承
class Parent {
constructor() {
this.age = 18
}
}
class Child extends Parent {
constructor() {
super()
this.name = '张三'
}
}
let child = new Child()
console.log(child) // Child {age: 18, name: '张三'}
// 原型链继承
function Parent() {
this.age = 20
}
function Child() {
this.name = '张三'
}
Child.prototype = new Parent()
let child = new Child()
console.log(child.name, child.age)
// 构造函数继承
function Parent() {
this.age = 20
}
function Child() {
this.name = '张三'
Parent.call(this)
}
let child = new Child()
console.log(child)
call/apply/bind
共同点: 可以改变this指向, 改变函数体内的this指向
方法: 函数.call() 函数.apply() 函数.bind()
区别:
- call的参数是依次传递到括号中
- apply则是将所有参数放到数组中作为第二个参数
- bind不会立即执行, 返回值是一个函数, 需要手动调用
深拷贝和浅拷贝
- 浅拷贝: 只是复制引用, 而没有复制真正的值
let obj2 = Object.assign(obj1)
let arr1 = arr2
- 深拷贝: 拷贝真正的值, 两者的改变不会相互影响
let obj2 = JSON.parse(JSON.stringify(obj1)), 不能拷贝方法
解构赋值只能实现一维的深拷贝
function deepClone(source) {
// 判断是数组还是对象,
const targetObj = source.constructor === Array ? [] : {}
// 判断属性是否存在
for (let keys in source) {
if (source.hasOwnProperty(keys)) {
if (source[keys] && typeof source[keys] === 'object') {
// 引用数据类型(数组|对象)
targetObj[keys] = source.constructor === Array ? [] : {}
// 递归调用
targetObj[keys] = deepClone(source[keys])
} else {
// 基本数据类型,直接赋值
targetObj[keys] = source[keys]
}
}
}
return targetObj
}
统计字出现的次数
let str = 'aaaabbbvvvaabbvvc'
// 最终的返回值是一个对象,包含字符和数量
function count(srt) {
let obj = {}
for (let i = 0; i < str.length; i++) {
if (obj[str[i]]) {
obj[str[i]] += 1
} else {
obj[str[i]] = 1
}
}
return obj
}
console.log(count(str))
统计出现最多的次数
function getMax(obj) {
let max = 0
let name = ''
for (let key in obj) {
if (obj[key] > max) {
max = obj[key]
name = key
}
return { [name]: max }
}
}
console.log(getMax(count(str)))
解析字符串
let url = 'https://zhouyaker.cn/index.php/?key1=1&key2=2&key3=3'
// 最终返回结果为一个对象,是参数的键和值
function parseQueryString(url) {
let obj = {}
let urlArr = url.split('?')
let paramsArr = urlArr[1].split('&')
for (var i = 0; i < paramsArr.length; i++) {
let paramsItem = paramsArr[i].split('=')
obj[paramsItem[0]] = paramsItem[1]
}
return obj
}
console.log(parseQueryString(url))
文章评论