JavaScript学习——js底层原理

json

一种文件的格式,在各种语言中都可以被识别

当需要传递数据的时候可以把数据转成json字符串再进行传递,格式类似数组和对象,但是它是一个字符串。凡是字符串 使用双引号。

对象和数组里面的值,可以是数值类型,字符串,布尔,null,undefind,浮点数,数组,对象,时间对象,正则

var jsonStr1 = '["lucy","海绵宝宝","1234"]'

var jsonStr2 = '{"name":"海绵宝宝","age":12}'

前端把数据给后端,要把js里面的obj对象转换成json格式再传递。

语法:JSON.stringify(obj)

前端接受后端传递给我的数据,要把拿到的json格式转成js的对象或者别的数据类型

语法:JSON.parse()

var obj = { name: '海绵宝宝', age: 12, vip: null }

var objStr = JSON.stringify(obj)

JSON.parse(objStr)

this指向

即“上下文对象”, this在函数中有不同的含义

函数定义的时候是无法确定this指向的内容的,只有在函数调用的时候,才会指到本次调用的this应该指向的内容

function fn(a, b) {

      console.log(a)

      console.log(b)

      console.log(this)

    }

  1. 普通调用函数 里面的this是window
fn("我", "普通函数")
  1. 作为定时器的回调函数 里面的this是window
setTimeout(fn, 2000, "我", "定时器的回调函数")
  1. 作为事件处理函数(例如点击事件)里面的this是事件源

    document.addEventListener('click', fn)
    
  2. 作为对象的属性值 里面的this是调用这个方法的对象

var obj = {
      name: '海绵宝宝',
      age: 12,
      sayHello: function () {        						  console.log('hello')
      console.log(this)
      }
}
obj.sayHello()
  1. 自调用/自执行函数 里面的this指向window

    (function () {
    
          console.log("自执行函数")
    
          console.log(this)
    
        })()
    
  2. 构造函数 里面的this 是本次new赋值的那个实例对象

    function Panda() {

    function Panda() {
    
     this.age = 1
    
     this.color = '黑白'
    
     this.name = '小美'
    
    }
    
    let pp1 = new Panda()
    
  3. 箭头函数:箭头函数没有自己的this,看其外层的是否有函数,如果有,外层函数的this就是内部箭头函数的this,如果没有,则this是window。(函数定义时的this,而不是调用时this)

call,apply,bind改变this的指向

this在函数调用的时候确定

  1. ​ call方法

    我们可以在函数调用的时候,来指定本次调用中this的指向

    语法 : 函数名.call(指定的this的指向,实参1,实参2) 参数列表

    单次 , 暂时改变一次 我指定的this指向

  2. ​ apply方法

    我们可以在函数调用的时候,来指定本次调用中this的指向

    语法 : 函数名.apply(指定的this的指向,[实参1,实参2]) 数组形式

    单次 , 暂时改变一次 我指定的this指向

  3. ​ bind方法

​ 使用bind生成一个this固定了的新函数

​ 生成一个新函数 NewFn ,里面的this不管怎么调用都是调用中this的指向

​ 语法:var 新函数 = 旧函数.bind(新函数的this指向)

​ 永久改变this 但是旧函数我还用没改变之前的 所以bind给我生成一个新函数

作用域和作用域链

一段程序代码中所用到的名字不总是有效和可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。

JS作用域可以分为两大类:全局作用域 、局部作用域(函数作用域)

  1. 全局作用域:当对文件顶部代码完成编译后,会产生全局执行上下文,同时也形成了全局作用域。全局作用定义好的变量可以在任何地方使用,这个作用域会一直存在,直到浏览器关闭
  2. 局部作用域:当执行一个函数的时候,会先对函数的代码进行编译,编译完成之后就会生成函数的执行上下文,同时就形成了函数的作用域,在js中只用函数能生成一个局部作用域在局部作用域中定义的变量,只能在这个局部作用域中起作用,每一个函数都是一个单独的作用域
  • ES6 新增块级作用域

    块级作用域:对于块级作用域,会在代码执行的代码块中执行{},ES6引入了letconst关键字,和var关键字不同,在大括号中使用letconst声明的变量存在于块级作用域中。在大括号之外不能访问这些变量

作用域链

函数被创建的作用域中可使用的作用域集合,各个作用的嵌套关系组成一条作用域链。

当在Javascript中使用一个变量的时候,首先Javascript引擎会尝试在当前作用域下去寻找该变量,如果自身作用域中声明该变量,则无需使用作用域链,如果自身作用域中未声明该变量,则到它的上层作用域寻找,以此类推沿着作用域链直到找到该变量,或是已经到了全局作用域,如果在全局作用域里仍然找不到该变量,它就会在全局范围内隐式声明该变量(非严格模式下)或是直接报错。

作用域主要是进行标识符(变量和函数)的查询,标识符解析沿着作用域链一级一级的搜索标识符的过程,而作用域链就是为了保证变量和函数进行有序的访问

预解析

js引擎运行js代码的时候分为两步

  1. 代码预解析:js引擎会把js里面所有的var function 提升到当前全局作用域的最前面,即进行变量提升和函数提升
  2. 代码执行:按照代码书写顺序从上往下执行

预解析分为变量预解析(变量提升) 和 函数预解析 (函数提升)

  1. 变量提升:就是把所有的变量声明提升到当前的作用域最前面 不提升赋值操作
  2. 函数提升:就是把所有的函数声明提升到当前作用域的最前面 不调用函数

预解析的规则:

  1. 函数声明和变量声明的是同一个变量名称时,函数的预编译优先级高于变量

    function fx() {
         console.log('海绵宝宝')
    }
    var fx = '派大星'
    
  2. 多个同名函数声明,由最后的函数声明来替换前面的

fx()
function fx() {
    console.log('fx111111')
}
function fx() {
	console.log('fx222222')
}

eg:

var a=100
function fn() {
      console.log(a);
      var a=200
      console.log(a);
    }
fn()
console.log(a);
var a
console.log(a);
var a=300
console.log(a);

上面的代码预解析成下面的代码:

var a
function fn() {
      console.log(a);
      var a=200
      console.log(a);
 }
a=100
fn()
console.log(a);
console.log(a);
a=300
console.log(a);

面向对象

​ 面向过程:每一步都要自己亲力亲为,每一个步骤都很清楚,注重过程

​ 面向对象:找到合适的对象,来让对象完成需求,注重结果

eg:做一到菜 西红柿炒番茄

​ 面向过程:

自己买番茄和西红柿 - 自己洗番茄 - 自己切菜 -下锅 - 放调料 - 装盘- 自己吃

​ 面向对象:

​ - 自己拿出手机 - 打开外卖软件 - 下单 一个西红柿炒番茄 - 等待外卖 - 吃

对象

​ 现实中的对象:是可是描述出来的具体事物 eg:我手上的杯子

​ 描述对象:通过对象的特征和行为来进行描述

​ 代码中的对象:是现实中的对象的抽象

​ 抽象的过程:将特征抽象为 属性,讲行为抽象为方法

对象

在JavaScript中,一切皆为对象。有两种,分别为普通对象和函数对象。是

  • 凡是通过 new Function() 创建的对象都叫函数对象,其他的都叫普通对象。
  1. 普通对象

    var o1 = {}; 
    var o2 =new Object();
    var o3 = new f1();
    
  2. 函数对象

    function f1() {}; 
    var f2 = function() {};
    var f3 = new Function('str', 'console.log(str)')
    
    • 所有的构造函数都是函数对象,函数对象都是Function构造器产生的
    function Person(){
        
    }
    var person1 = new Person();
    person1.name = 'caomei';
    console.log(person1.name) // caomei
    

    ​ Person 是一个构造函数,person1 是 Person 的实例对象。

创建对象

创建对象的方式

  1. 字面量方式创建

    let o = {
        name: '海绵宝宝',
        study: function () {
           console.log('哈哈哈')
        },
        hobby() {
            console.log('羽毛球')
        }
    }
    
  2. 内置构造函数方式创建

let o = new Object()

o.age = 12

console.log(o.age)

​ 对象成员的操作:增删改查

  • 点语法 .

批量创建对象 (工厂函数)

  1. 字面量创建 – 不能批量创建对象

  2. 内置构造函数创建对象 才可以批量创建

  3. 封装一个函数来创建对象 (这个函数就是工厂函数)

    function createObj(name, age) {
    
         // 创建一个空对象
         let o = new Object()
    
         // 添加数据
         o.name = name
    
         o.study = () => { console.log('hhh') }
    
         // 返回对象
         return o
    
        }
    
    //批量创建对象
    let o1 = createObj('张三', 18)
    
    let o2 = createObj('海绵宝宝', 12)
    
    console.log(o1, o2)
    

数据类型

按照存储方式,JavaScript的数据类型可以分为两种,原始数据类型(原始值)和引用数据类型(引用值)。

  1. 原始数据类型(基本类型)

    Number、String、Boolean、Null、Undefined、Symbol(ES6),这些类型是可以直接操作的保存在变量中的实际值。原始数据类型存放在栈中,数据大小确定,它们是直接按值存放的,所以可以直接按值访问。若将a的值赋给b,即使两个变量的值相等,这两个变量也保存了两个不同的内存地址。

  2. 引用数据类型(复杂类型)

    在JavaScript中除了原始数据类型以外的都是Object(对象)类型,包括数组、函数、正则表达式、Date、RegExp、Map、Set等都是对象。

    引用类型是存放在堆内存中的对象,变量是保存在栈内存中的一个指向堆内存中对象的引用地址。当定义了一个变量并初始化为引用值,若将它赋给另一个变量,则这两个变量保存的是同一个地址,指向堆内存中的同一个内存空间。如果通过其中一个变量去修改引用数据类型的值,另一个变量也会跟着改变。

检测数据类型

  1. typeof()

    对于原始数据类型,除了null比较特殊(null会被认为是一个空的对象引用),其它的我们可以用typeof进行准确判断:

    console.log(typeof (100)) // number
    
    console.log(typeof ('haha')) // string
    
    console.log(typeof (true)) // boolean
    
    console.log(typeof (undefined)) // undefined
    
    console.log(typeof function() {}) // function
    
  2. instanceof()

    instanceof 会检测一个对象A是不是另一个对象B的实例,它在底层会查看对象B是否在对象A的原型链上存在着。如果存在,则返回true,如果不在则返回false,instanceof只对引用类型值(复杂数据类型)进行判断:

    var obj = {}
    
    console.log(obj instanceof Object) //true
    
    console.log([1,2,3] instanceof Array) //true
    
    console.log(function foo(){ } instanceof Function) //true
    
    console.log(/[0-9,a-z]/ instanceof RegExp) //true
    
  3. Object.prototype.toString.call()(万能检测)

console.log(Object.prototype.toString.call(100)) // number
console.log(Object.prototype.toString.call('haha')) // string
console.log(Object.prototype.toString.call(true)) // boolean
console.log(Object.prototype.toString.call(undefined)) // undefined
console.log(Object.prototype.toString.call(null)) // object   
console.log(Object.prototype.toString.call({})) // object 
console.log(Object.prototype.toString.call(new Date())) // object 

构造函数

在面向对象中要么能直接得到一个对象,要么弄出一个能创造对象的东西,自己创造对象。JavaScript中的构造函数可以用来创建特定类型的对象。为了区别于其它函数,构造函数一般使用大写字母开头(程序员编码规范)。

在JavaScript中,构造函数与其它函数的唯一区别,就在于调用它们的方式不同。任何函数,只要通过new操作符来调用,就可以作为构造函数。构造函数里面的this 是本次new赋值的那个实例对象。

当我们使用new操作符实例化构造函数时,构造函数内部会执行以下步骤:

  1. 隐式创建一个this空对象

  2. 执行构造函数中的代码(为当前this对象添加属性)

  3. 隐式返回当前this对象

    • 如果构造函数显式返回一个对象,那么实例为这个返回的对象,否则则为隐式返回的this对象。自定义构造函数的时候不需要写return,若写return ,如果返回的是一个普通数据类型,自动忽视并且继续返还this。如果返回的是一个复杂数据类型,那么会强制返回该复杂类型 但是构造函数变无意义。
    function Panda() {
    
         this.age = 1
    
         this.color = '黑白'
    
    	 this.name = '小美'
    
        }
    
    let pp1 = new Panda()
    
  • 注意:当我们调用构造函数创建实例后,实例便具备构造函数所有的实例属性和方法。对于通过构造函数创建的不同实例,它们之间的实例属性和方法都是各自独立的。哪怕是同名的引用类型值,不同实例之间也不会相互影响。

  • 在构造函数创建出来的时候,系统会默认创建一个对象与这个构造函数相关联,这个对象就是这个构造函数的原型对象。

原型

JavaScript的所有对象中都有一个原型,称为隐式原型(proto)。隐式原型指向构建出这个实例的类,即构造函数的显示原型(prototype)。

原型prototype(显式原型)是函数特有的属性,任何时候,只要创建了一个函数,这个函数就会自动创建一个prototype属性(即原型),并指向该函数的原型对象。

  1. 所有引用类型都有一个__proto__(隐式原型)属性,属性值是一个普通的对象
  2. 所有函数都有一个prototype(显式原型)属性,属性值是一个普通的对象
  3. 所有引用类型的__proto__属性指向它构造函数prototype
  4. prototype原型指向一个对象,也称为原型对象
  5. [[prototype]]是__proto__的别名
  6. 实例对象的__proto__和该实例对象的构造函数的prototype相等

constructor

当前对象的构造函数,告诉我当前对象是哪个构造函数new出来的。

function Person(){
    
}
var person1 = new Person();
person1.name = 'caomei';
console.log(person1.name) // caomei

每个函数都有一个原型对象,每一个原型对象都含有一个 constructor (构造函数)属性,这个属性指向的又是它对应的那个构造函数本身。即:

Person.prototype.constructor == Person

实例对象可以有一个 constructor(构造函数)属性,这个属性是一个指针,指向的是 Person。

console.log(person1.constructor == Person); // true

原型链

当我们在对象中尝试查找一个属性或方法时,如果在自身对象上找不到,就会往它的__proto__上查找,如果找不到,还会往该对象的原型的原型,依次层层向上搜索,直至找到匹配的属性或者方法,或者到达原型链的末尾。

从实例对象开始沿着__proto__形成的链式结构就是这个实例对象的原型链,__proto__是原型链中的连接点。

Object是js中的顶级构造函数,因此原型链的最顶端(终点)是 Object.prototype,Object.prototype.__proto__是null

函数

函数的定义阶段:

  1. 在内存中开辟一个存储空间
  2. 把函数体内的代码当做“字符串”一模一样的放在这个空间中碰到的所有变量都不进行解析
  3. 把这个空间地址赋值给函数名

函数的调用阶段:

  1. 每一个函数调用的时候都会开辟一个执行空间
  2. 你调用一次,开辟一个空间
  3. 执行完毕,执行空间销毁
  4. 再次调用的时候,再次开辟一个新空间
  5. 执行完毕,执行空间再销毁
  • 在一个特殊的情况下,函数的执行空间不会销毁:

​ 当函数内部返回一个‘复杂数据类型’,并且在外部有变量接收这个‘复杂数据类型’的时候,这个时候函数的执行空间不能被销毁(不会被销毁)

  • 什么时候会被销毁:

​ 当外部接收的那个变量不在引用函数内部的返回值的时候,这个函数执行空间就销毁了

函数继承

出现在两个构造函数之间的关系,当A构造函数的属性和方法被B构造函数的实例使用了,那么我们就说B继承自A构造函数
A是B构造函数的父类,B是A构造函数的子类

原型链继承

原型继承就是通过改变原型链的方式来达到继承的效果

子类.protype = 父类的实例

优点:写法方便简洁,容易理解。

缺点:

  1. 多个实例对象共享一个原型对象,也就是说实例对象获取的原型对象上的属性和方法的内存空间是共享的,其中一个实例对象改变了它的原型对象,另外的实例对象获取原型对象身上的属性和方法也会发生改变。
  2. 创建子类实例时,不能传父类的参数给子类实例,要去__proto__里面找,这样不利于代码阅读和维护

构造函数继承

在子类的构造函数体内,借用构造函数执行一下

强制让父类的构造函数的this指向子类的实例 可使用call apply改变this指向

Parent.call(this,属性1,属性2)

优点:解决原型链继承的弊端,使用构造函数来继承可以传父类的参数,可以解决子类共享父类构造函数中属性的问题

缺点:

  1. 只能继承父类的属性
  2. 不能继承父类原型上的属性和方法

组合继承(原型链继承+构造函数继承)

优点:融合原型链和借用构造函数的优点,是js中最常用的继承方式

缺点:无论什么情况下,父类构造函数都会被调用两次,一是创建子类原型对象时,二是子类构造函数内部,会存在多一份的父类实例属性

ES6的继承

ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。

ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this

super(属性1,属性2) ==> Parent.call(this,属性1,属性2)

  • 注意:

    super就代表父类的构造函数,且他会自动改变this指向

    super要写在子类构造函数的第一行

函数的柯里化

柯里化又称部分求值,接受了这些参数后,函数不会立即求值,而是继续返回另一个函数,刚才传入的参数在函数形成闭包中被保存起来,待到函数被真正需要求值的时候,之前传入的所有参数都被一次性用于求值。

生产出一个固定学科和班级的函数:

// 写一个闭包
function printInfo2(xueke, banji) {
	return function (name, age) {
    	console.log(`我是${xueke} ${banji}班级 我叫${name} ${age}岁了`)
    }
}
let print2020 = printInfo2('计算机', '前端')
// 开辟了一个printInfo空间 xxff11
// 在这个空间里面进行形参的赋值
// let xueke = 计算机
// let banji = 前端
// 定义函数 function (name, age) {}
// 把执行空间里面定义的这个函数地址xxff1122 赋值给 print2020
print2020('派大星', 18)
print2020('海绵宝宝', 19)
柯里化优点
  1. 参数复用
  2. 提前确认
  3. 延迟运行

闭包

如果在函数A的内部,声明了(直接或间接返回)另外一个函数B,并且函数B可以访问A中定义的变量或是数据,此时函数A和函数B就形成了一个不会销毁的函数空间即闭包。

闭包可简单理解成 " 定义在一个函数内部的函数"

function a() {
	let num = 100
    return function b() {
		console.log(num)
    }
}
let res = a()
// 从现在开始 res随时可以被调用
// 在我们的执行空间里 函数a里面的一切都不会被销毁
// 当res调用的时候,打印的是私有变量num的值

闭包的优点

  • 可读取函数内部的变量,延长了变量的生命周期
  • 局部变量可以保存在内存中,实现数据共享
  • 执行过程中所有变量都匿名在函数内部

闭包的缺点

  • 使函数内部变量存在于内存中,内存消耗大

  • 滥用闭包可能导致内存泄露

  • 闭包可以在父函数外部改变父函数内部的值,慎操作

    • 内存泄漏:

    ​ 例子:

    ​ 我有一个盆,能装2L的水

    ​ 我正常倒入2L的水,没有任何问题

    ​ 如果这个盆里有一个闭包,闭包永久的占用了我0.5L空间

    ​ 我再往里到2L的水,一定有一部分漏出来了

    ​ 所以闭包要慎用

    ​ 记得用完闭包后 把承接闭包的变量的值改变成null

闭包的使用场景

​ 当需要延长变量的生命周期或者需要访问某一个函数内部的私有变量的时候,就可以使用闭包来解决问题

​ 前提是如果有别的办法,优先使用别的办法

防抖和节流

优化高频率执行代码的一种手段

防抖debounce

n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时

每次操作会先清除掉定时器,并开启定时器,如果连续操作不停清除,使定时器没办法运行完成。只有停止操作,定时器才会运行完成并执行动作。

防抖函数的实现步骤:

  1. 先接收一个回调函数并返回回调函数
  2. 定义定时器并开启,每次执行先清除
  3. 传入对应的参数并改变this指向

function debounce(fun,delay){
    let timer
    return function(){
        if(timer) cleanTimeout(timer)
        let context = this; // 保存this指向
        let args = arguments; // 拿到event对象
        timer = setTimeout(()=>{
            fun.apply(context,args)
        },delay)
        
    }
}

防抖应用场景:

  • 搜索框搜索输入。只需用户最后一次输入完,再发送请求
  • 手机号、邮箱验证输入检测
  • 窗口大小resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。

节流throttle

n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效

当我们持续触发事件,需要记录初始执行的时间,每次执行的时候要记录当前时间,当前时间 - 初始执行时间 < 设定值时,不执行;只有 > 设定值时才执行,并将现在的时间赋值给初始时间。

节流函数的实现步骤:

  1. 先接收一个回调函数并返回回调函数
  2. 定义初始时间,每次执行时获取当前的时间,用当前时间 - 初始时间 > 设定值时执行相应的方法,并将现在的时间赋值给初始时间,下次执行时初始时间就变成了更新后的值
  3. 传入对应的参数并改变this指向
function throttle(fun,time){
	let start = 0
	return function(){
        let now = new Date()
        let context = this; // 保存this指向
        let args = arguments; // 拿到event对象
        if(now - start >time){
            fun.apply(this,arg)
            start = now
        }
    }
}

节流应用场景:

  • 滚动加载,加载更多或滚到底部监听
  • 搜索框,搜索联想功能

正则表达式

正则表达(Regular Expression)是用于匹配字符串组合的模式。在JavaScript中,正则表达式也是对象。

正则表达式也叫规则表达式,通常被用来检索、替换那些符合某个模式(规则)的文本,例如验证表单。此外,还常用于过滤掉页面内容中的一些敏感词(替换),或从字符串中获取我们想要的特定部分(提取)等。

创建正则表达式

  1. 字面量创建

    var reg = /abc/
    // var 变量名=/表达式/
    
  2. 通过调用RegExp对象的构造函数创建

var reg2 = new RegExp('abc')

正则表达式的方法

  1. test

    检测字符串中的内容

    语法:正则.test(字符串)

    用来检测字符串 是否符合我们的正则标准 ,返回boolean

  2. exec

    把字符串中符合条件的内容捕获出来

    语法:正则.exec(字符串)

    返回值:字符串中符合正则的第一项以及一些其他信息,返回array

字符串里面有一些方法可以和正则一起使用:

  1. search

    是查找字符串中是否有满足正则的内容

    语法:字符串.search(正则)

    返回值:有的话返还第一个匹配元素的索引,没有的话返还 -1

    用不用g 无所谓

  2. match

    方法可在字符串内检索指定的值,找到一个或多个正则表达式的匹配

    语法:字符串.match(正则)

    返回值:不开g 和exec一模一样 字符串中符合正则的第一项以及一些其他信息,返回数组类型

    开g 返回一个数组,里面是匹配到的每一项

  3. replace

    将字符串中符合正则表达式的字符 替换掉

    语法:字符串.replace(正则,‘替换成的字符’)

    返回值:替换好的字符串

    不开g 值替换一个

    开g 所有符合的都替换掉

// search  数字出现3次

let reg = /\d{3}/

let str = 'hello 123'

console.log(str.search(reg))


// match 数字至少出现一次

let reg = /\d+/

let reg2 = /\d+/g

let str = 'helloaho13242sjohohs2423'

console.log(str.match(reg))

console.log(str.match(reg2))


// replace 数字至少出现一次
let reg2 = /\d+/g

let str = 'hellosfho1242sjohs2343'

console.log(str.replace(reg2, '*'))

正则表达式的符号

元字符

​ . :匹配非换行的任意字符 什么都对除了换行符 (\n)

\d : 匹配数字

\D : 匹配非数字

\w : 匹配字母 或数字 或下划线

\W :匹配非字母 或非数字 或非下划线

​ \s :匹配空白字符串 (空格 tab制表符)

​ \S :匹配非空白字符串 (空格 tab制表符)

​ \ :转义字符,把有意义的符号转换成没有意义的符号,把没有意义的转换成有意义的

// 1 规则:要求有空格
var reg = /\s/
var str = 'a 123123  b'
console.log(reg.test(str))  // true

// 2 规则:要求有数字
// \d 表示 字母d
// \\d 表示 \d
var reg = /\d/
var reg = new RegExp('\\d')
console.log(reg.test(str))
限定符

+: 前一个内容重复至少1次,也就是可以出现1-正无穷次

*: 前一个内容重复至少0次,也就是可以出现0-正无穷次

? : 前一个内容重复0到1次,也就是可以出现0-1次

{n} : 前一个内容必须重复n次,也就是必须出现n次

{n,}: 前一个内容至少重复n次,也就是说必须出现n-正无穷次

{n,m}: 前一个内容重复知识n次,至多m次,也就是说可以出现n-m次

  • 注意:限定符经常和元字符一起使用
边界符

是限定正则字符串开始和结束的

^ :表示他后面的内容必须在开头

$ :表示他前面的内容必须在结尾

^$ :表示有且仅有他们中间的内容 ,也就是精确匹配

特殊符号

() :限定一组元素

[] :字符集合,表示写在[]里面的任意一个都行

[^] :反字符集合,表示写在[] 里面之外的任意一个都行

  • :范围,比如a-z 从小写字母a到z ,A-Z(大写A-Z)

| :或,正则里面的 a|b 就表示 a或者b 都可以通过

//写出中国人民 姓名的正则 2-4中文
// Unicode中国汉字常用 4E00-9FA5  unicode 编码在使用的时候前面要加上\u
let reg = /^[\u4E00-\u9FA5]{2,4}$/
let str = 'lucy'
console.log(reg.test(str))
标识符

i : 表示忽略大小写

g : 表示全局匹配 就是在找到第一个匹配成功的元素之后仍然会继续查找

m : 表示多行匹配

let reg = /apple/i
let str = 'i have an ApPle'
console.log(reg.test(str))

let reg = /^\d/
let reg2 = /^\d/m
let str = 'haha\n12haha'
console.log(reg.test(str)) //f
console.log(reg2.test(str)) //t

浏览器的垃圾回收机制

​ 就是js执行环境(浏览器,window) 会负责代码执行过程中使用的内存

​ 原理:垃圾收集器会定期的(周期性)找出不再使用的变量,释放内存

​ 何时触发垃圾回收机制:js引擎垃圾回收方案是编辑清除,遍历所有可访问的对象,回收已经不可访问的对象

  • 要定期不是实时监测的原因:

因为垃圾回收时需要停止响应其他操作,垃圾回收开销比较大,所以这个。过程在设计之初就不是实时的,而是按照固定的时间周期性的执行

两种实现方式:

  1. 标记清除 - 较为常用,大部分浏览器使用的时候选择标记清除的垃圾回收策略或者类似策略

    1. 不能释放‘进入环境’的变量所占的内存(js的预解析),只要执行流进入相应的环境就可能用到他们,而当变量离开环境的时候,则将其标记为‘离开环境’
    2. 进行标记的方式有很多种:翻转某个特殊位来记录一个变量何时进入环境、使用一个 ‘进入环境’ 变量列表和一个 ’离开环境‘ 变量列表
    3. 垃圾回收器在运行的时候会给所有变量都加上标记,然后环境中的变量以及环境中的变量的引用变量,他们身上的标记会被去掉
    4. 而在此之后再被加上标记的变量,将被视为准备删除的变量
    5. 最后,垃圾收集器完成内存清楚工作,销毁那些标记的值并收回他们所占用的内存空间
  2. 引用计数 - 不太常用 跟踪记录每个值被引用的次数

    1. 当声明了一个变量,并将一个引用类型赋值给该变量时,则这个值的引用次数是1(以下的值都是引用类型,垃圾回收的都是存放在堆中的引用类型数据)
    2. 如果同一个值又被赋予另外一个变量,则改值的引用次数+1
    3. 相反如果这个值的引用又取得了另外一个值,则引用次数-1
    4. 当这个引用计数为0的时候,则说明没有变量引用这个值了
    5. 当垃圾收集器下次再运行的时候,他就会销毁那些标记的值并收回他们所占用的内存空间

设计模式

理解:设计模式就是针对“特定问题”,然后给出的简洁而优美的处理方案。 一个设计模式A只能解决A类型的问题,而B类型的问题,A设计模式就解决不了了。同一个问题,在不同的位置,是不一定能用同一个设计模式方案解决的。设计模式,只在特定的情况,特定的时期,针对特定的问题使用。

我认为市面上的设计模式总共分为下面这几类:

创建型设计模式:工厂模式,单例模式

结构型设计模式:组合模式,适配器模式

行为型设计模式:发布订阅模式(观察者),中介者模式(代理模式)

ES6(ECMAScript6)

声明变量方式 let const

let , const 的区别:

  1. 重复赋值
    • let可以重复赋值,const定义后不能赋值
  2. 声明时赋值
    • let声明的时候可以不赋值,const声明的时候必须赋值

var 与let , const 的区别:

  1. 不存在变量提升(变量预解析)
    • var 命令会发生变量提升现象,即变量可以在声明之前使用,值为undefined
    • let 和 const 没有变量声明提升的功能,必须要先声明才能使用。否则使用let和const会显示 ‘ReferenceError: Cannot access ‘a’ before initialization’
  2. 不允许重复声明
    • var命令能重复声明,后者覆盖前者
    • let 和 const不允许在相同作用域内,重复声明同一个变量
  3. 作用域
    • var 的作用域是以函数为界限
    • let 和 const 的作用域是块作用域,块级作用域指 { } 内的范围
    • var 可以定义全局变量和局部变量,let 和 const 只能定义局部变量
    • const 的声明的常量不能被修改,但对于引用类型来说,堆内存中的值是可以被改变的。
  4. 变量作为全局属性
    • 定义的变量会作为window对象的属性,let不会
  5. 预解析
    • var有预解析,let和const没有预解析
    • 变量没有声明赋值时,用var声明会显示 ‘undefined’,let和const会显示 ‘ReferenceError: Cannot access ‘a’ before initialization’

暂时性死区(临时性死区)

let、const没有预解析,在没有赋值的时候就开始使用,就会报错,在定义好但没有使用的这个代码区间就是临时性死区。

流程在进入作用域创建变量,到变量开始可被访问访问之前的一段时间,称为临时性死区(TDZ)

箭头函数

箭头函数是es6语法中针对函数表单式的一种简写

ES5函数的定义有两种常用方法:

  • 声明式 function show(){ } -无法用箭头函数简写

  • 赋值式 var fn = function(){} -可以用箭头函数简写

    语法:(形参)=>{要执行的代码}

箭头函数的使用方法

  1. 当函数没有参数时,()不能省略

  2. 当函数只有一个参数,且函数体是一句代码,且是返回语句时,参数的

    ()可省略、函数体 {} 可省略、return 可省略、中间使用 => 连接

  3. 若函数体只有一句,且不是return 语句, 不能省略 {}

  4. 若函数体有多条语句,不能省略 {}

  5. 若函数有多个参数,不能省略()

  6. 若函数的返回值为对象,此时不能省略return

使用箭头函数注意

  1. 箭头函数不适用于声明函数
  2. 箭头函数不适用于DOM事件
  3. 箭头函数不能作为构造函数(迭代器)
  4. 箭头函数内不能使用arguments,普通函数里有
  5. 不能使用yield命令
  6. 原来的函数怎么调用 现在就怎么调用 函数名()
  7. 箭头函数没有自己this,他的this就是所处环境的this,定义时候绑定,就是this是继承自父执行上下文中的this

函数参数默认值

​ 语法:

​ function fn(形参1 = 默认值,形参2= 默认值){}

​ (形参1 = 默认值,形参2= 默认值)=>{}

  function fn(a, b) {

      console.log(a, b, '普通函数')

    }
  fn(1, 2)

​ 参数没有传递实参时用默认值,参数传递实参时就去用传递的值

function fn(a = 10, b) {

      console.log(a, b, '普通函数用了es6')
   }

fn()

​ 如果箭头函数只有一个形参,但是设置了默认值,就不能省略小括号

var func = (a = 'hello') => { console.log(a) }

   func(123)

扩展运算符…

… 作用于数组、对象

​ 作用是作为展开运算符和合并运算符使用

  1. ​ 作为展开运算符

    1. 展开数组

      var arr = [10, 20, 30]
      console.log(...arr) // 10 20 30
      
      // 展开后可以作为方法的实参使用
      
      var res = Math.max(...arr)
      
      console.log(res) // 30
      
      // 展开后作为新数组的元素
      
      var newArr = [1, 2, 3, ...arr]
      
      console.log(newArr) // (6)[1,2,3,10,20,30]
      
    2. 展开对象

      var obj = { name: '海绵宝宝', age: 123 }
      
      var o = {
      
          a: 1,
      
          b: 2,
      
      	c: 3,
      
          ...obj
      
        }
        console.log(o) 
        // {"a": 1,"b": 2,"c": 3,"name": "海绵宝宝","age": 123}
      
  2. 作为合并运算符

    1. 合并数组

      var arr1 = [9, 10, 11]
      
      var newArr2 = [...arr, ...arr1]
      
      console.log(newArr2) // (6)[10,20,30,9,10,11]
      
    2. 函数合并参数

      function fn(...arg) {
      
            console.log(arg) //(5)[1,2,3,4,100]
      
          }
      
      fn(1, 2, 3, 4, 100)
      
    3. 箭头函数

      • 注意:扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错
      var ff = (n, ...arg) => {
      
           console.log(n) // 1
      
           console.log(arg) // (2)[2,4]
      
      }
      
      ff(1, 2, 4)
      

解构赋值

允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构

作用:快速的从数据集合中获取数据

  1. ​ 解构数组

    语法: var [变量1,变量2] = 数组

    var arr = ['海绵宝宝', '天线宝宝', '花园宝宝', '小猪佩奇', 123]
    
    var [e] = arr
    
    console.log(e) // 海绵宝宝
    
    var arr = [1, 2, [3, 4, [5, [16]]]]
    
    var [a1, b1, c1] = arr
    
    console.log(a1, b1, c1) // 1,2(3)[3,4,[5,[16]]]
    
    var [a, b, [c, d, [e]]] = arr
    
    console.log(e + 100) // 105
    
  2. ​ 解构对象

    语法:var {键} = 对象

    var o = { a: 1, b: 2 }
    
    // 给解构出来的变量给另起一个别名
    var { a:haha, b } = o
    
    console.log(a, b) //1 2
    
    console.log(haha) // 1
    
    // 解构多级对象
    var info = {
        
    	username: '张三',
        
    	love: { boyfriend: '李四', boyfriend2: '王小妹' }
            
    }
    
    var { username, love: { boyfriend2 } } = info
            
    console.log(boyfriend2) // 王小妹
            
    var boyfriend = info.love.boyfriend
            
    console.log(info.love.boyfriend) // 李四
    

对象简写

对象中单属性名和属性值变量名同名的时候,我们就可以只写一个属性名

set和map

set和map都是es6新增的数据结构

Set结构

Set类型类似数组类型(类数组结构) ,是值的集合

集合里面的值是不会重复的,set类型里面的每一个值都是唯一的

var arr0 = ['a', 'b', 123, true, true, 123]

var mySet = new Set(arr0)

console.log(mySet) // {0:'a', 1:'b', 2:123, 3:true}

方法:返回值是set可以连缀
add(val) 加元素
delete(val) 删元素
has(val) 判断是否包含元素 boolean
clear() 删除所有数据

属性:
size 元素个数

使用new Set()实例化

var arr = [12, 11, 10, 10, 9, 9, 8, 4, 5]

// 定义一个set类型

var newSet = new Set(arr)

newSet.add('海绵宝宝')

var result = [...newSet]

console.log(result) // (7)[12,11,10,9,8,4,5]

Map结构

类似对象类型,里面是键值对的集合

这些键值对是有序的,且键名可以是任意类型,键的类型范围不局限于字符串,可以是各类的,相比于Object类型的“键值对” map结构提供了一个“值值对”

方法:返回值是Map实例可以连缀
set(key,val) 加元素
get(key) 取元素
has(key) 判断是否包含元素 boolean

​ delete(key) 删元素

clear() 删除所有数据

属性:
size 键值对个数

使用new Map()实例化

var arr = [1, 2, 3]

// 定义一个Map

var myMap = new Map([

      ['name', '海绵宝宝'], // “键名”是name  值是海绵宝宝

      ['age', 12],// “键名”是age  值是12

      [1, 999],// “键名”是111  值是999

      [undefined, 23],// “键名”是undefined  值是23

      [true, 888],// “键名”是true  值是888

      [arr, function () { }]// “键名”是arr  值是function

   ])

myMap.set(null, 'qwe')

set和map的区别

  1. map是键值对,set是值的集合,当然键和值可以是任何的值
  2. map可以通过get方法获取值,而set不行,因为set只有值
  3. 都可以通过迭代器进行 for…of遍历
  4. set的值是唯一的,可以用来做数组去重,map由于没有格式限制,可以做数据存储

for in和for of

for in 可以遍历对象和数组, for of 可以遍历数组、set、map

  • ​ 相同点: for in 可以遍历数组 for of 也可以遍历数组
  • ​ 不同点: for in 可以遍历对象 for of 不行
var arr = ['海绵宝宝', '天线宝宝', '巴士宝宝']

var obj = { name: '海绵宝宝', age: 12 }

for (var value in arr) {

      console.log(value) // 数组索引0 1 2

      console.log(arr[value]) // 数组的值 海绵宝宝

    }

for (var value of arr) {

      console.log(value) // 数组的值 海绵宝宝

    }

for (var value in obj) {

      console.log(value)    // for in 拿对象里面的键

      console.log(obj[value])  // for in 拿对象里面的值

    }

模块化语法

以前使用src引入js文件,在es6可以使用模块化语法导入js模块

每个js文件都是一个独立的模块,即一个文件就是一个作用域,每个模块里面的数据只能自己模块使用

  1. 设置script的 type=‘module’

  2. 引入 要以html文件的http格式打开

  3. 导入方式

    1. 导入重命名

      import { rand as nnn } from ‘./public.js’

    2. 导入整个模块

      import * as nnn from ‘./public.js’

    3. 导入默认模块

      import Tool from ‘./public.js’

模板字符串

传统字符串字面量使用单引号''或者双引号""

模板字符串使用反单引号(backquote) ``

  • 模板字符串可以插入表达式 ${expression}
var str = '宝宝巴士'

var age = 120

console.log(str + '今年已经' + age + '岁了')

console.log(`${str}明年就${age + 1}岁了`)

箭头函数

箭头函数是es6语法中针对函数表单式的一种简写

​ == 函数的定义有两种常用方法

​ == 声明式 -无法用箭头函数简写

​ == 赋值式 -可以用箭头函数简写

​ == 语法:(形参)=>{要执行的代码}

​ == 原来的函数怎么调用 现在就怎么调用 函数名()

​ == 箭头函数没有自己this,他的this就是所处环境的this

​ == 箭头函数里面没有arguments对象,普通函数里有

类语法书写构造函数

​ 语法:

​ class 类名(也叫构造函数名) {

​ constructor (形参1,形参2){

​ 这里的代码类似es5语法中的构造函数的代码

​ }

​ 函数名1(){

​ 这里的代码类似es5语法中的 构造函数.prototype.函数名 = function(){}

​ }

​ }

class Person {

      constructor(name, age) {

        this.name = name

        this.age = age

      }

      // 原型链上的函数

      sayHi(a) {
      
        console.log('hello', a)

      }

    }

let p2 = new Person("宝宝巴士", 18)

p2.sayHi(a)

console.log(p2)
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值