js知识点总结二

85 阅读9分钟

原型链继承

原型链继承是把字类的原型指向父类的实例 缺点: 不能直接向父类的构造函数传递参数,子类的实例,会被相互影响。

  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的属性和方法都在原型上

image.png

防抖/节流

  • 防抖:时间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'

image.png

let reg = /ab/g
let arr = 'abcbabc'.match(reg)
console.log(arr) 

可以看到加g之后就可以匹配全局的了

image.png

正则的使用

使用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);
    }