原型链继承
原型链继承是把字类的原型指向父类的实例 缺点: 不能直接向父类的构造函数传递参数,子类的实例,会被相互影响。
function Person() {
this.name = '无名';
}
Person.prototype.setName = function(name) {
this.name = name
}
Person.prototype.getName = function(name) {
return this.name
}
function Child() {
}
Child.prototype = new Person()
const child1 = new Child()
child1.setName('第一')
console.log(child1.getName()) // 第一
const child2 = new Child()
console.log(child1.getName()) // 第一
构造函数继承
- 在子类的构造函数里面,调用父类的构造函数,并且把父类的this指向子类
- 优点:子类的实例对象,不会相互影响。
- 缺点:子类不能继承父类原型上的方法。
function Person(name) {
this.name = name
}
Person.prototype.getName = function (name) {
this.name = name
}
function Child() {
Person.call(this, '孩纸')
}
const child1 = new Child()
const child2 = new Child()
child1.name = '孩子1'
child1.getName() // 报错
console.log(child1) // 孩子1
console.log(child2) // 孩纸
组合继承
组合继承就是合并原型链继承和构造函数继承
- 优点:能够继承父类的属性和方法,并且能够在继承的时候,传递参数
- 缺点:在子类声明实例的时候,会重复调用父类的构造函数,一次是在子类构造函数中,一次是在子类原型赋值中。浪费性能
function Parent(name) {
this.name = name
}
Parent.prototype.getName = function() {
return this.name
}
function Child() {
// 构造函数
Parent.call(this, '小丽')
}
// 原型链
Child.prototype = new Parent()
Child.prototype.constructor = Child // 确保Child原型指向正确的构造函数,否则会指向Parent的构造函数
let child1 = new Child()
child1.name = '小一'
let child2 = new Child()
child2.name = '小二'
console.log(child1.getName())
console.log(child2.getName())
寄生组合继承
具备组合继承的优点,但是又不会重复调用父类的构造函数
function Parent(name) {
this.name = name
}
Parent.prototype.getName = function() {
return this.name
}
function Child() {
// 构造函数
Parent.call(this, '小丽')
}
// 原型链
Child.prototype = Object.create(Parent.prototype) // 减少了一个父类构造函数的调用
Child.prototype.constructor = Child // 确保Child原型指向正确的构造函数,否则会指向Parent的构造函数
let child1 = new Child()
child1.name = '小一'
let child2 = new Child()
child2.name = '小二'
console.log(child1.getName())
console.log(child2.getName())
创建对象的方式
字面量
var o1 = {
name: 'o1',
method: () => {
console.log(this.name)
}
}
new Object()
var o2 = new Object()
o2.name = 'o2'
o2.method = function () {
console.log(this.name)
}
构造函数
function O3() {
this.name = '03'
}
O3.prototype.method = function() {
console.log(this.name)
}
var o3 = new O3()
Object.create()
var o4 = Object.create(o1)
打印结果
console.log(o1)
console.log(o2)
console.log(o3)
console.log(o4)
o1,o2 的属性和方法都在对象自己上面
o3的属性在自己身上,方法在原型上
o4的属性和方法都在原型上
防抖/节流
- 防抖:时间n内,重复触发只执行最后一次
function debounce (func, wait) { let timer = null return function() { if (timer) { clearTimeout(timer) } timer = setTimeout(func, wait) } } - 节流:事件连续触发时,每隔n秒执行一次
防抖/节流都形成了闭包,闭包中的function throttle (func, wait) { // 主要是每n秒执行一下 let timer = null return function() { let context = this let args = arguments if (!timer) { timer = setTimeout(() => { func.apply(context, args) // 执行完毕之后,需要清除,这样在下个n秒后又可以执行了 timer = null }, wait) } } }timer是为了保存,上一次延迟函数,使用闭包而不使用全局变量,是为了,当时全局污染。
web性能优化
web的性能指的是,页面从加载到显示,到响应用户行为所使用的时间,包括按钮点击,页面滚动等。这个时间包含代码执行的实际时间和用户的感知时间
web性能优化的方向
- 确定性能指标
- 使用可以量化分析的工具(lighthouse)
- 分析网站的页面的响应周期,查找原因
- 对项目进行技术改造,优化项目配置等
性能报告
lighthouse可以产生性能报告
性能优化的具体方式
前端的优化主要集中在“资源和耗时”
耗时优化
浏览器发起资源请求,获取资源,开始页面渲染,页面渲染完成之后,用户才能操作。这里的每一步都需要时间。
前端页面在加载的过程中,浏览器的渲染和js的加载是相互排斥的。在页面渲染的时候,遇到js的加载执行的时候,就会阻塞页面的渲染, 所以js文件一般放在body的后面,说这设置defer和async属性
耗时优化的方向包含:
- 网络请求优化
- 减少不必要的网络请求
- 设置缓存,强缓存和协商缓存,缓存静态资源,减少请求,servers Worker在没有网络的时候,保持页面能够打开
- 域名拆分,提升并发请求数
- 资源压缩,拆分
- 首屏加载优化
- 拆分页面内容,只放关键内容
- 先加载首要内容,剩余内容可以懒加载或者异步加载
- 骨架屏
- 按需记载模块内容
- ssr服务器返回页面内容
- 渲染过程优化
主要目的是缩短用户在操作过程中的等待时间
- 合并/减少dom操作
- 预加载资源
- 优先使用类似transform这样可以使GPU加速的方式
- 提升代码运算速度
- 拆分javascript的大快代码,实行高内聚,低耦合,并合理利用ajax这样的异步任务,方式任务阻塞
- 使用web worker 开启多线程,加快计算速度
- 缓存计算结果
资源优化
- 合理使用缓存,注意及时清除缓存
- 避免内存泄漏,避免使用全局变量,及时清除闭包里面保存的变量
- 避免循环递归,导致爆栈
正则规则
字符类
| 字符 | 说明 |
|---|---|
| \D | 所有的非数字 |
| \w | 所有的字母(不区分大小写)、数字、_ 相当于[A-Za-z0-9_] |
| \W | 除了\w所代表的所有的字符 |
| \s | 匹配单个空白字符包括空格 制表符 回车符 换行符,[\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff] |
| \b | 匹配词语的边界 |
边界符
边界符主要用来表示字符存在的位置
| 字符 | 说明 |
|---|---|
| ^ | 表示谁是开始 |
| $ | 表示谁是结束 |
| ^和$一起使用表示精准匹配 |
let reg = /^a.*b$/;// 表示以a开头,以b结束,中间是任意字符
let reg1 = /^(ab)(.*)(ab)?$/; // 表示以ab开头,或者以ab结束
字符集合
没有边界符的集合
// 匹配0-9中的任意一个数字,只要字符串中含有就可以
let reg1 = /[0-9]/;
reg1.test('0') // true
reg1.test('012') // true
reg1.test('0123abc') // true
reg1.test('abc') // false
有边界符的集合
let reg = /^[0-9]$/ // 表示以数字开头,以数字结尾,且只能包含一个数字
reg.test(0) // true
reg.test(1) // true
reg.test(3) // true
reg.test(123) // false
let reg = /^[0-9]$/ // 匹配以数字开头的字符
reg.test('012') // true
reg.test('0qwe') // true
reg.test('a0123') // false
let reg1 = /[^a-z]/ // 表示[a-z]取反
量词符
用来模式出现的次数
| 量词 | 说明 |
|---|---|
| * | >=0 |
| + | >=1 |
| ? | 0或者1 |
| {n} | 重复n次 |
| {n, m} | 重复n-m次 |
分组
正则表达式用()分组
正则表达式的参数
/表达式/[switch]
| 表示 | 说明 |
|---|---|
| g | 全局搜索 |
| i | 不区分大小写搜索 |
| m | 多行搜索 |
| s | 允许.匹配换行符 |
| u | 使用unicode码的模式进行匹配 |
| y | 执行“粘性(sticky)”搜索,匹配从目标字符串的当前位置开始 |
正则里面的match可以获取匹配的次数
let reg = /ab/
let arr = 'abcbabc'.match(reg)
console.log(arr)
这里可以看到,只匹配到第一'ab'
let reg = /ab/g
let arr = 'abcbabc'.match(reg)
console.log(arr)
可以看到加g之后就可以匹配全局的了
正则的使用
使用replace替换字符
str.replace(egexp|substr, newSubStr|function)
- 要求1:将手机号的中间四位替换成*,例如13877283312变成138****3312
let reg = /(\d{3})\d{4}(\d{4})/g
let res = '13877283312'.replace(reg, '$1****$2')
console.log(res)
- 要求2:单词首字母转为大写,例如my name is allen, i like code.
let reg = /\b(\w)/g
let str = 'my name is allen, i like code.'
let res = str.replace(reg, function(m) {
return m.toUpperCase()
})
console.log(res)
var/let/const的区别
let/const都可以产生块级作用域
let
let用来声明可变的变量,但是不能重复声明相同的变量,并且不能在声明之前使用用变量
const
- const声明的变量不能重新赋值
- 引用数据可以添加属性,修改属性,但是不能改变地址
- 只声明不赋值会报错
const a = 1
a = b // 报错
const obj = {}
obj = {} // 报错
obj.a = 1 // 正确写法
var
var声明的变量有变量提升,for循环里的var会被提升到全局,并且var还可以重复声明相同的变量
- 变量提升
console.log(a) // undefined
var a = 1
- 变量作用到全局
var arr = []
for(var i = 0; i<10; i++) {
arr[i] = function() {
console.log(i)
}
}
console.log(arr[3]()) // 10
console.log(arr[4]()) // 10
上面的代码其实等同于
var arr = []
var i = 0;
for(;i<10;) {
arr[i] = function() {
console.log(i)
}
i++
}
console.log(arr[3]())
console.log(arr[4]())
arr[i]里面保存了一个函数,函数内部打印i,但是没有立即调用,等到循环之后调用,i实际上是一个全局变量,此时全局的i已经变成10了,所以在全局里面寻找就会最终只能得到10
解构赋值
数组解构
数组解构常用的是模式匹配的方式,只要左右的结构相同,就可以给左边的变量赋值右边的值
let [a,b] = [1,2]
console.log(a, b) // 1,2
let [a, b] = [1, [2, 3]]
console.log(a, b) // 1, [2, 3]
let [a, [b, c]] = [1, [2, 3]]
console.log(a, b, c) // 1,2,3
let [,,c] = [1,2,3]
console.log(c) // 3
let [a, ...b] = [1,2,3,4]
console.log(a) // 1
console.log(b) // [2,3,4]
let arr = [['name', '章三'], ['title', '修仙']]
arr.forEach(([key, val]) => {
console.log(key, val) // name 章三 // title 修仙
})
对象解构赋值
对象解构赋值是为了能方便的获取对象的属性
let obj = {
name: 'zhangsan',
age: 20
}
let {name, age} = obj
console.log(name, age) // zhangsan // 20
对象的解构赋值的规则是,找到同属性的名字然后赋值。赋值的时候是可以起别名的,这样别名才是真正的结果变量。
let obj = {
name: 'zhangsan',
age: 20
}
let {name: name1, age, sex} = obj
console.log(name === '') // true
console.log(name1) // zhangsan
console.log(sex) // undefined
结构可以设置默认值,但是只对属性的值是undefined的生效
let obj = {
name: 'zhangsan',
age: 20,
sex: undefined,
like: null
}
let {name, age = '10', sex = 'man', like='paly'} = obj
console.log(name) // zhangsan
console.log(age) // 20
console.log(sex) // man
console.log(like) // null
继承的属性也是可以结构的
let obj = {
name: 'zhangsan',
age: 20,
sex: undefined,
like: null
}
let obj2 = {}
obj2.__proto__ = obj;
let {name, age = '10', sex = 'man', like='paly'} = obj2
console.log(name) // zhangsan
console.log(age) // 20
console.log(sex) // man
console.log(like) // null
let obj = {
a: 1,
foo: {
b: 2,
c: 3
},
bar: [4,5,6]
}
let {a, foo: {b, c}, bar: bar1} = obj
console.log(a) // 1
console.log(foo) // foo is not defined
console.log(b) // 2
console.log(c) // 3
console.log(bar) // bar is not defined
console.log(bar1) // [4,5,6]
symbal
- 用来声明独一无二的变量
let a = Symbol(1)
let b = Symbol(1)
console.log(a === b) // true
- Symbol在接收对象的时候,对象会被自动转化为字符串
let obj = {
c: 1
}
let a = Symbol(obj)
let b = Symbol(obj)
console.log(a === b) // false
console.log(a) // Symbol([object Object])
- Symbol可以被转化成字符串和布尔类型,但是不能转化成数字
- Symbol 不能参与运算
let a = Symbol(a)
let b = 1;
let c = a+b; // Cannot access 'a' before initialization
console.log(c)
- Symbal的属性是不可枚举的
let c = Symbol()
let obj = {
a: 1,
b: 2,
}
obj[c] = 3
for(let key in obj) {
console.log(key) // a, b
}
- getOwnPrototypeSymbols可以获取所有的Symbol属性
let c = Symbol(3)
let d = Symbol(4)
let obj = {
a: 1,
b: 2,
}
obj[c] = 3
obj[d] = 4
const arr = Object.getOwnPropertySymbols(obj)
console.log(arr)
- 因为Symbol是不可枚举的属性,所以可以被当作私有属性使用
Symbol.iterator
Symbol.iterator起到迭代的作用
- 数组上已经自带Symbol.iterator所以可以用for..of遍历所有的值
- 对象上没有自带Symbol.iterator所以不能使用for..of遍历
// 给对象自定义一个迭代器 let obj = { data: [1,2,3,4,5], [Symbol.iterator]() { let index = 0 let data = this.data return { next() { if (index < data.length) { return { value: data[index++], done: false, } } else { return { done: true } } } } } } for (const item of obj) { console.log(item); }