JS进阶笔记(原型、继承、this指向、闭包、递归、正则表达式)

文章目录

      • 1、构造函数、实例对象、原型对象三者之间的关系
      • 2、原型链
        • 2.1、JS成员查找机制
        • 2.2、原型对象中的this指向
        • 2.3、利用原型对象扩展内置对象方法
      • 3、继承
        • 3.1 call方法的应用
      • 4、类的本质
      • 5、ES5中的新增方法
        • 5.1、数组方法
        • 5.2、trim方法
        • 5.3、Object.defineProperty方法
      • 6.函数的定义和调用
        • 6.1、函数的定义方式
        • 6.2、函数的调用方式
        • 6.3 函数内this的指向
          • 6.3.1改变函数内部this指向
          • 6.3.2 call、apply、bind方法总结
      • 7、严格模式
        • 7.1 什么是严格模式
        • 7.2 怎么开启严格模式
        • 7.3 严格模式中的变化
      • 8、高阶函数
      • 9、 闭包
        • 9.1变量作用域
        • 9.2 什么是闭包
        • 9.3 闭包总结
          • 9.3.1 闭包是什么?
          • 9.3.2 闭包的作用是什么?
      • 10、 递归
        • 10.1 什么是递归?
        • 10.2 递归的应用案例
          • 10.2.1.利用递归求阶乘
          • 10.2.2.利用递归求斐波那契数列
          • 10.2.3.利用递归遍历数据
        • 10.3 浅拷贝和深拷贝
      • 11.、正则表达式
        • 11.1 什么是正则表达式
        • 11.2 正则表达式的特点
        • 11.3 正则表达式在JS中的使用
          • 11.3.1 创建正则表达式。
          • 11.3.2 正则表达式的组成
            • 1、边界符
            • 2、字符类
            • 3、量词符
          • 11.3.3 正则表达式的预定义类
          • 11.3.4 正则替换
      • 12、变量提升与函数提升
        • 12.1、变量提升
        • 12.2、函数提升
      • 13、执行上下文与执行上下文栈
        • 13.1、执行上下文
          • 13.1.1、全局执行上下文
          • 13.1.2、函数执行上下文
        • 13.2、执行上下文栈
      • 14、作用域与作用域链
        • 14.1、理解
        • 14.2、分类
        • 14.3、作用
      • 15、作用域与执行上下文
            • 区别1
            • 区别2
            • 联系

1、构造函数、实例对象、原型对象三者之间的关系

JS进阶笔记(原型、继承、this指向、闭包、递归、正则表达式)_第1张图片
实例对象可以通过__proto__.constructor指向构造函数,实际上是先借助__proto__指向原型对象prototype,然后再通过原型对象的.constructor指向构造函数。

2、原型链

JS进阶笔记(原型、继承、this指向、闭包、递归、正则表达式)_第2张图片
// 1、只要是原型对象就有__proto__原型,指向原型对象。
// 2、Star原型对象里面__proto__原型指向的是Object.prototype
// 3、Object原型对象里面__proto__原型指向的是null ,即已经到了最顶层

2.1、JS成员查找机制

1、当访问一个对象的属性或者方法时,首先会查找这个对象自身有没有该属性
2、如果没有就去该对象的原型对象中查找(也就是`__proto__`指向的`prototype`原型对象)
3、如果还没有就查找原型对象的原型(`Object`的原型对象)
4、以此类推一直找到`Object`为止(null)

2.2、原型对象中的this指向

1、在构造函数中,this指向实例对象
2、原型对象函数里面的this,指向的是实例对象

2.3、利用原型对象扩展内置对象方法

//  扩展求和方法
Array.prototype.sum = function () {
        let sum = 0;
        for (let i = 0; i < this.length; i++){
            sum += this[i];
        }
        return sum
    };

错误示范

//  会报错,会把原先的方法覆盖掉
    Array.prototype= {
        sum: function () {
            let sum = 0;
            for (let i = 0; i < this.length; i++){
                sum += this[i];
            }
            return sum
        }
    }

3、继承

3.1 call方法的应用

调用这个函数,并且修改函数运行时的this指向
fun.call(thisArg, arg1, arg2, ...)
thisArg:当前调用函数this的指向对象(当前调用的函数this指向谁)
arg1、arg2:传递的其他参数

ES6之前并没有提供extends继承,我们可以通过构造函数+原型对象模拟实现继承,被称为组合继承

核心原理
通过call()把父类的this指向子类的this,这样就可以实现子类继承父类的属性

// 借用父构造函数继承属性
    // 1、父构造函数
    function Father(uname, age) {
        // this指向父构造函数的实例对象
        this.name = uname;
        this.age = age
    }
    Father.prototype.money = function (){
        console.log('父亲要挣钱')
    };
    // Son.prototype = Father.prototype;   直接赋值会有问题,如果
    // 子原型对象发生变化,那么父原型对象也会变化
    
    // 2、子构造函数
    function Son(uname, age, score) {
        // this指向子构造函数的实例对象
        Father.call(this, uname, age,);
        this.score = score
    }
    // 这个是子构造函数专门的方法
    // 将Father实例对象赋值给Son原型对象,此时,Father实例对象和Father原型对象
    处于不同的存储地址中,所以不会造成数据混乱
    // 又因为Father实例对象可以通过__proto__访问Father原型对象,所以Son原型对象
    可以使用Father原型对象中的money方法

    Son.prototype = new Father();
    // 如果利用对象的形式修改了原型对象,别忘了利用constructor指回原来的构造函数
    Son.prototype.constructor = Son;
    Son.prototype.exam = function (){
        console.log('孩子要考试');
    }
    let son = new Son('ckw',18, 100);
    // console.log(son);
    console.log(son);
    console.log(Father.prototype);
    console.log(Son.prototype.constructor);

4、类的本质

类的本质还是一个函数,我们也可以简单的认为 类 就是构造函数的另外一种写法

//  ES6之前通过 构造函数+原型对象 实现面向对象变成
    // 1、构造函数有原型对象prototype
    // 2、构造函数原型对象prototype里面有constructor指向构造函数本身
    // 3、构造函数可以通过原型对象添加方法
    // 4、构造函数创建的实例对象里面有__proto__原型指向构造函数的原型对象
    class Star{

    }

    console.log(typeof Star);
    //  ES6之后通过 类 实现面向对象编程
    //  1、类也有原型对象prototype
    console.log(Star.prototype);
    // 2、类的原型对象prototype里面也有constructor指向构造函数本身
    console.log(Star.prototype.constructor);
    // 3、类也可以通过原型对象添加方法
    Star.prototype.sing = function (){
        console.log('我会唱歌')
    }
    let ldh = new Star();
    console.dir(ldh);
    // 5、类创建的实例对象也有__proto__原型指向类的原型对象
    console.dir(ldh.__proto__ === Star.prototype);
    // 所以ES6的类就是语法糖

5、ES5中的新增方法

5.1、数组方法

迭代(遍历)方法:forEach()、map()、filet()、some()、every()

1、array.forEach(function(currentValue, index, arr))

  • currentValue:数组当前项的值
  • index:数组当前项的索引
  • arr:数组对象本身
  • forEach没有返回值

2、 array.filter(function(currentValue, index, arr))
filter()方法会创建一个新的数组,新数组的元素是经过筛选以后的数组

  • currentValue:数组当前项的值
  • index:数组当前项的索引
  • arr:数组对象本身
// filter筛选方法
    let arr = [11,22,33,44,20];
    let newArr = arr.filter(function (value, index){
        return value >= 20;
    })
    console.log(newArr);

3、array.some(function(currentValue, index, arr){})

  • some()方法用于检测数组中的元素是否满足指定条件,
  • 注意它的返回值是boolean值,如果查到就返回true,没查到就返回false
  • 如果找到第一个满足条件的元素,就终止循环,不再继续查找
  • currentValue:数组当前项的值
  • index:数组当前项的索引
  • arr:数组对象本身
let arr = [11,22,33,4];
    let flag = arr.some(function (value, index){
        return value < 3;
    })
    console.log(flag);
    let arr1 = ['red', 'pink', 'yellow']
    let flag1 = arr1.some(function (value){
        return value === 'red';
    })
    console.log(flag1);

    // filter也是查找满足条件的元素,但返回值是一个数组,而且会返回所有符合条件的元素
    // some的返回值是true/false,如果查找到第一个满足条件的元素,就会终止循环

forEach和some的区别
1、在forEach里面,return不会终止迭代
2、

let arr = [1,2,4,'pink','green',7,11,22,33];
    arr.some(function (value){
        if (value === 'green'){
            console.log('找到了该元素');
            return true;     //some中return true会终止迭代,效率更高
        }
        console.log(value)
    });
    arr.forEach(function (value){
        if (value === 'green'){
            console.log('找到了该元素');
            return true;        // forEach中return不会终止迭代
        }
        console.log(value);
    })

forEach输出结果在这里插入图片描述
some输出结果在这里插入图片描述

5.2、trim方法

trim方法:去除字符串两端的空格,但是不能去除字符串中的空格

5.3、Object.defineProperty方法

object.defineProperty:定义新属性或修改原有的属性
object.defineProperty(obj, prop, descriptor)接收三个参数
obj:必传。目标对象
prop必传。需要定义或修改的属性的名字
descriptor必传。目标属性所拥有的特性
descriptor说明
该参数需要以对象形式书写

  • value:设置属性的值,默认为undefined
  • wirtable:值是否可以重写,默认为false
  • enumerable:目标属性是否可以被枚举,true/false,默认为false
  • configurable:目标属性是否可以被删除或再次修改特性,true/false,默认为false

6.函数的定义和调用

6.1、函数的定义方式

1、函数声明方式function关键字(命名函数)

function fn() {}

2、函数表达式(匿名函数)

let fn = function () {}

3、new Function(‘参数1’, ‘参数2’, ‘函数体’)
Function里面参数都必须是字符串格式
这种方法执行效率低,不方便书写,较少使用

let f = new Function ('console.log('123')') {}		//不传参情况

4、所有函数都是 Function的实例(对象)
函数也属于对象,因为有原型__proto__
JS进阶笔记(原型、继承、this指向、闭包、递归、正则表达式)_第3张图片

6.2、函数的调用方式

// 1、普通函数
    function fn() {
        console.log('nb666');
    }
    fn();
    fn.call();
    // 2、对象的方法
    let o = {
        name:'ckw',
        say: function (){
            console.log('我是o对象的say方法');
        }
    }
    o.say();
    // 3、构造函数
    function Star() {}
    new Star();
    // 4、绑定事件函数
    btn.onclick = function (){
        console.log('123123')
    }
    // 5、定时器函数
    setInterval(function (){},1000);     //每1秒执行一次
    // 6、立即执行函数
        (function (){
            console.log(123);
        })()
    // 立即执行函数会自动调用

6.3 函数内this的指向

JS进阶笔记(原型、继承、this指向、闭包、递归、正则表达式)_第4张图片

6.3.1改变函数内部this指向

1、call()方法
fun.call(thisArg, arg1, arg2, ...)
call()方法调用一个对象,简单理解为调用函数的方式,但是它可以改变函数的this指向

    let o = {
        name: 'ckw'
    };
    function fn(a, b) {
        console.log(this);
        console.log(a+b)
    }
    fn.call(o,1,2);

    function Father(uname, age, sex){
        this.uname = uname;
        this.age = age;
        this.sex = sex;
    }
    function Son(uname, age, sex){
        Father.call(this,uname, age, sex);
    }
    let son = new Son('ckw',18,'男');
    console.log(son);

输出结果:在这里插入图片描述
在这里插入图片描述

call方法:第一个可以调用函数,第二个可以改变函数内的this指向。也可以实现继承

2、apply()方法

// 2、apply()方法
    let o = {
        name: 'andy'
    };
    function fn(arr){
        console.log(this);
        console.log(arr);		// 此处只输出第一个pink
    }
    fn.apply(o, ['pink','blue','aqua']);
    // 1、apply()方法既可以调用函数,也可以修改函数内部的this指向
    // 2、但是它的第二个参数必须是数组/伪数组
    // 3、apply()的主要应用,比如说可以利用 apply 借助Math内置对象求最大值
    let arr = [1,0,3,8,9,77];
    let arr1 = ['red', 'aqua']
    let res = Math.max.apply(null,arr);
    let res1 = Math.max.apply(null,arr1);			// 如果传入的是字符串数组,则会输出NaN
    console.log(res);
    console.log(res1);

值得注意的是,当apply方法的第二个参数传入的是一个有多个元素的数组时,它只会输出一个元素。

3、bind()方法
bind()方法不会调用函数,但是能改变函数内部this指向
fun.bind(thisArg, arg1, arg2, ...)

  • thisArg:在fun函数运行时指定的this值
  • arg1, arg2:传递的其他参数
  • 返回由指定的this值和初始化参数改造的原函数拷贝
6.3.2 call、apply、bind方法总结

相同点:都可以改变函数内部的this指向
不同点:

  1. call和apply会调用函数,并改变函数内部this指向。
  2. call和apply传递的参数不一样,call是用arg1,arg2, ...的方式传递,apply是用[arg1,arg2, ...]的方式传递
  3. bind不会调用函数,就可以改变函数内部的this指向

主要应用场景

  1. call经常用作继承
  2. apply经常跟数组有关,例如借助数学对象求最大和最小值
  3. bind不调用函数,但是需要改变this指向,例如改变定时器中的this指向

7、严格模式

7.1 什么是严格模式

JS除了提供普通模式以外,还提供了严格模式(strict mode),ES5的严格模式是采用具有限制性JavaScript变体的一种方式,即在严格模式下运行JS代码。
严格模式对正常的JS语义做了一些更改

  1. 除了JavaScript语法的一些不合理,不严谨之处,减少了一些怪异行为。

  2. 消除代码运行的一些不安全之处,保证代码运行的安全。

  3. 提高编译器效率,增加运行效率

  4. 禁用了ECMAScript的未来版本中可能会定义的一些语法,为未来的JS版本做好铺垫。比如一些保留字:

  5. enum,class,export,extends,import等不能做变量名

7.2 怎么开启严格模式

  1. 为脚本开启严格模式
    JS进阶笔记(原型、继承、this指向、闭包、递归、正则表达式)_第5张图片

  2. 为函数开启严格模式
    JS进阶笔记(原型、继承、this指向、闭包、递归、正则表达式)_第6张图片

7.3 严格模式中的变化

  1. 严格模式下的this指向问题
    普通模式中全局作用域的函数中的this指向window对象
    严格模式下全局作用域的函数中的this为undefined
  2. 严格模式下如果构造函数不加new调用,this会报错
  3. 定时器中的this指向的还是window对象
  4. 事件、对象还是指向调用者

严格模式中的函数不能有重名参数

8、高阶函数

高阶函数是对其他函数进行操作的函数,它接收函数作为参数或者将函数作为返回值输出

function fn(a, b, callback) {
        console.log(a + b);
        callback && callback();
    }
    fn(1,2,function (){alert('我是最后调用的')});
function fn1() {
        return function (){};
    }
fn1();

上面两种函数都属于高阶函数

9、 闭包

9.1变量作用域

变量根据作用域的不同可以分为:全局变量局部变量

  1. 函数内部可以使用全局变量
  2. 函数外部不可以使用局部变量
  3. 当函数执行完毕,本作用域内的局部变量会销毁

9.2 什么是闭包

闭包指有权访问另一个函数作用域中变量的函数
即一个作用域可以访问另一个函数内部的局部变量

// 闭包:fun这个函数访问到了fn函数作用域中的局部变量num,此时就产生了闭包
// fn外面的作用域也可以访问fn内部的局部变量num
// 闭包的主要作用:延伸了变量的作用范围。
    function fn() {
        let num = 10;
        // function fun() {
        //     console.log(num);
        // }
        return function () {
            // 此时这个函数内并没有Num变量,但是依旧可以正常打印,这就产生了闭包
            console.log(num);
        };
    }
    let f = fn();
    f();
// 类似于
// f = function fun() {
// console.log(num);
// }

9.3 闭包总结

9.3.1 闭包是什么?

闭包是一个函数(一个作用域可以访问另一个作用域的局部变量)

9.3.2 闭包的作用是什么?

延伸了变量的作用范围

10、 递归

10.1 什么是递归?

如果一个函数可以在内部调用其本身,那么这个函数就是递归函数
递归函数的作用和循环效果一样
由于递归很容易发生“栈溢出”错误(stack overflow ),所以必须要加退出条件return

10.2 递归的应用案例

10.2.1.利用递归求阶乘
function fn(n){
        if (n === 1){
            return 1;
        }
        return n * fn(n - 1);
    }

    console.log(fn(3));
    console.log(fn(4));

    // 详细思路
    // 第一次执行,return 3*fn(2)
    // 第二次执行,return 3*( 2 * fn(2 - 1) )
    // 第三次执行,return 3*( 2 * 1 ),最后输出结果为6
10.2.2.利用递归求斐波那契数列
function fb(n) {
        if (n === 1 || n === 2) {
            return 1;
        }
        return fb(n - 1) + fb(n - 2);
    }

    console.log(fb(6));
    // 输入n = 6
    // 第一次 return fb(5)+fb(4)
    // 第二次 return fb(4)+fb(3) + return fb(3)+fb(2)
    // 第三次 return fb(3)+fb(2) + return fb(2)+fb(1)  +  return fb(2)+fb(1) + return 1
    // 第四次 return fb(2)+fb(1) + return 1   +   return 1+1 + return 1+1 + return 1
    // return 1+1 + return 1 + return 1+1 + return 1+1 + return 1;
    // 输出结果为8
10.2.3.利用递归遍历数据
let data = [
    {
      id: 1,
      name: '家电',
      goods: [
        {
          id: 11,
          gname: '洗衣机'
        },
        {
          id: 22,
          gname: '冰箱',
          goods: [
            {
              id: 111,
              name: '海尔'
            },
            {
              id: 112,
              name: '西门子'
            }
          ]
        }
      ]
    },
    {
      id: 2,
      name: '服饰'
    }
  ]
  // 需要输入id就返回name
  // 利用forEach()遍历数组中的每一个对象
  function getId(json, id) {
    let obj = {}
    json.forEach(item => {
      // console.log(item);
      if (item.id === id) {
        // console.log(item);
        obj = item;
        //    想要里层的id:11/id:12的数据,可以利用递归函数
        //    里面应该有goods这个数组,且数组不为空
      } else if (item.goods && item.goods.length !== 0) {
        // 再调用一次getId
        obj = getId(item.goods, id)
      }
    });
    return obj;
  }

  console.log(getId(data, 1));
  console.log(getId(data, 22));
  console.log(getId(data, 112));

10.3 浅拷贝和深拷贝

1.浅拷贝只是拷贝一层,更深层次对象级别的只会拷贝引用(地址)

  let obj = {
    id: 1,
    name: 'zs',
    msg: {
      age: 18
    }
  };
  let o = {};
  // for (let k in obj){
  //     k是属性名,obj[k]是属性值
  //     o[k] = obj[k];
  // }
  // console.log(o);
  // o.msg.age = 20;
  // console.log(obj);

  // 用ES6语法糖:Object.assign()来实现浅拷贝
  Object.assign(o, obj);
  console.log(o);
  o.msg.age = 20;
  console.log(obj);

JS进阶笔记(原型、继承、this指向、闭包、递归、正则表达式)_第7张图片
也可以用ES6语法糖Object.assign()来实现浅拷贝,但是修改age的结果与上面这种方法相同
2.深拷贝会拷贝多层,每一层级别的数据都会拷贝
递归方式实现深拷贝

  let obj = {
    id: 1,
    name: 'zs',
    msg: {
      age: 18
    },
    color: ['red', 'blue']
  };
  let o = {};

  // 封装函数
  // newobj[k]是key,item是value
  function deepCopy(newobj, oldobj) {
    for (let k in oldobj) {
      // 判断属性值是哪种数据类型
      // 1.获取属性值   oldobj[k]
      let item = oldobj[k];
      // 2.判断这个值是否是数组
      if (item instanceof Array) {
        newobj[k] = [];
        deepCopy(newobj[k], item)
      } else if (item instanceof Object) {
        // 3.判断这个值是否是对象
        newobj[k] = {};
        deepCopy(newobj[k], item)
      } else {
        // 4.属于简单数据类型
        newobj[k] = item
      }
    }
  }

  deepCopy(o, obj);
  console.log(o);

11.、正则表达式

11.1 什么是正则表达式

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

11.2 正则表达式的特点

1.灵活性、逻辑性和功能性非常强
2.可以迅速的用极简单的方式达到字符串的复杂控制
3.对初学者来说比较晦涩难懂
4.实际开发中,一般是直接复制写好的正则表达式,但是需要会使用并且根据实际情况修改。

11.3 正则表达式在JS中的使用

11.3.1 创建正则表达式。

1.通过RegExp对象的构造函数创建

// 利用RegExp对象来创建正则表达式
    let regexp = new RegExp('/123/');
    console.log(regexp);
  1. 通过字面量创建正则表达式
    // 利用字面量创建正则表达式
    let reg = /123/;

    // test方法用来检测字符串是否符合正则表达式要求的规范
    console.log(reg.test(123));
    console.log(reg.test('abc'));
11.3.2 正则表达式的组成

一个正则表达式可以由简单的字符构成,例如/abc/,也可以是简单和特殊字符的组合,例如/ab*c/。其中,特殊字符也被称为元字符,在正则表达式中是具有特殊意义的专用符号,如^、$、+

1、边界符

JS进阶笔记(原型、继承、this指向、闭包、递归、正则表达式)_第8张图片

let rg = /abc/; //正则表达式里面不需要加引号,无论数字型或字符串型
    // /abc/ 只要包含有abc这个字符串返回的都是true
    console.log(rg.test('abcc'));   // true
    console.log('---------');
    let reg = /^abc/;
    // /^abc/ 表示以必须以abc开头
    console.log(reg.test('abcd'));  // true
    console.log(reg.test('aabcd')); //false
    console.log('---------');
    let reg1 = /^abc$/;
    // /^abc$/ 表示精确匹配,要求必须是abc字符串才符合规范
    console.log(reg1.test('abc'));  // true
    console.log(reg1.test('abcd'));  // false
    console.log(reg1.test('aabcd')); // false
    console.log(reg1.test('abcabc')); // false
2、字符类

[]字符类:表示有一系列字符可供选择,只要匹配其中一项就可以(多选一)

let rg = /[abc]/    //只要包含a、b、c中的任何一项,则返回true
    console.log(rg.test('aa1q123'));    //true
    console.log(rg.test('zsdf3123'));    //false
    console.log('----------');
    let rg1 = /^[abc]$/     //三选一,只有是a/b/c才返回true
    console.log(rg1.test('ab'));    //false
    console.log(rg1.test('a'));    //true
    console.log(rg1.test('b'));    //true
    console.log(rg1.test('c'));    //true
    console.log('----------');
    let reg = /^[a-z]$/     //26个英文字母中的任意一个都返回true
    console.log(reg.test('a'));     //true
    console.log(reg.test('b'));     //true
    console.log(reg.test('c'));     //true
    console.log(reg.test('A'));     //false
    console.log(reg.test(1));     //false
    console.log('------------');


    // 字符组合
    let reg1 = /^[a-zA-Z0-9_·-]$/     //大小写的英文字母和数字都可以
    console.log(reg1.test('a'));     //true
    console.log(reg1.test('B'));     //true
    console.log(reg1.test('1'));     //true
    console.log(reg1.test('_'));     //true
    console.log(reg1.test('-'));     //true
    console.log(reg1.test('·'));     //true
    console.log(reg1.test('-_'));    //false
    console.log('--------------------');

    // 如果中括号中有'^'则代表取反,需要与边界符分割开理解
    let reg2 = /^[^a-zA-Z0-9_·-]$/
    console.log(reg2.test('!'));    // true
3、量词符
// 量词符:用来设定某个模式出现的次数
    // 简单理解:就是让下面的/^a$/字符重复多少次
    // let reg = /^a$/

    // * 代表 >= 0 代表可以出现0次和很多次
    let reg = /^a*$/
    console.log(reg.test(''));      // true
    console.log(reg.test('a'));     // true
    console.log(reg.test('aa'));    // true
    console.log('----------------');

    // + 代表 >=1 代表可以出现1次和很多次
    let reg1 = /^a+$/
    console.log(reg1.test(''));      // false
    console.log(reg1.test('a'));     // true
    console.log(reg1.test('aa'));    // true
    console.log('-----------------');

    // ? 相当于 1 || 0
    let reg2 = /^a?$/
    console.log(reg2.test(''));      // true
    console.log(reg2.test('a'));     // true
    console.log(reg2.test('aaaa'));    // false
    console.log('-------------');

    // {n}重复n次
    let reg3 = /^a{3}$/
    console.log(reg3.test(''));      // false
    console.log(reg3.test('a'));     // false
    console.log(reg3.test('aaaa'));    // false
    console.log(reg3.test('aaa'));    // true
    console.log('----------------');

    // {n,}大于等于n次
    let reg4 = /^a{3,}$/
    console.log(reg4.test(''));      // false
    console.log(reg4.test('a'));     // false
    console.log(reg4.test('aaaa'));    // true
    console.log(reg4.test('aaa'));    // true
    console.log('--------');

    // {n,m}大于等于 n 次,小于等于 m 次
    let reg5 = /^a{3,5}$/
    console.log(reg5.test(''));      // false
    console.log(reg5.test('a'));     // false
    console.log(reg5.test('aaaa'));    // true
    console.log(reg5.test('aaa'));    // true
    console.log(reg5.test('aaaaa'));    // true
    console.log(reg5.test('aaaaaaa'));    // false

用量词符实现简单的表单验证

	<input type="text">
    <button>提交button>
    <div id="warn">div>
	let inputVal = document.getElementsByTagName("input");
    let btn = document.getElementsByTagName("button");
    let warn = document.getElementById('warn');
    let reg = /^[a-zA-z0-9-_]{6,16}$/;
    console.log(inputVal);
    inputVal[0].onblur = function (){
        // console.log(reg.test(this.value));
        warn.innerHTML = '';
        if (reg.test(this.value)){
            inputVal[0].style.borderColor = 'aquamarine';
        } else {
            warn.innerHTML = '请输入正确的用户名';
        }
    }
11.3.3 正则表达式的预定义类

JS进阶笔记(原型、继承、this指向、闭包、递归、正则表达式)_第9张图片

11.3.4 正则替换

1.replace正则替换

	let str = 'andy';
    let newstr = str.replace(/n/, '*');
    console.log(newstr);	// a*dy

2.正则表达式参数:

/表达式/[switch]
switch参数:
g:全局匹配
i:忽略大小写
gi:全局匹配+忽略大小写

12、变量提升与函数提升

12.1、变量提升

​ 通过var声明的变量在定义语句之间就可以访问到,值为undefined

12.2、函数提升

  • 通过function声明的函数,在声明之前就可以调用,值:函数定义(对象)
  • 块级作用域内的函数,会在声明语句执行过后,被映射到全局作用域。
  • 在块级作用域中定义的函数,会被提升到块级作用域的头部。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rsnuNGTp-1663658730051)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20220920094733229.png)]

13、执行上下文与执行上下文栈

13.1、执行上下文

13.1.1、全局执行上下文
  1. 在执行全局代码前将window确定为全局执行上下文
  2. 对全局数据进行预处理
    • var定义的全局变量===>undefined,添加为window的属性
    • function声明的全局函数===>赋值,添加为window的方法
    • this ===> 赋值 window
  3. 开始执行全局代码
13.1.2、函数执行上下文
  1. 在调用函数、准备执行函数体之前,创建对应的函数执行上下文对象
  2. 对局部数据进行预处理
    • 形参变量 ===> 赋值(实参),添加为执行上下文的属性
    • arguments ===> 赋值(实参列表),添加为执行上下文的属性
    • var 定义的局部变量 ===> undefined,添加为执行上下文的属性
    • function声明的函数 ===> 赋值(fun),添加为执行上下文的属性
    • this ===> 赋值(调用函数的对象),
  3. 开始执行函数体代码

函数执行上下文只在调用的时候产生,不调用不产生

13.2、执行上下文栈

  1. 在全局代码执行前,JS引擎就会创建一个栈来存储所有的执行上下文对象
  2. 在全局执行上下文(window)确定后,将其放入栈中(压栈)
  3. 在函数执行上下文创建后,压入栈中
  4. 在当前函数执行完毕后,出栈
  5. 当所有的代码执行完后,栈中只剩下window

14、作用域与作用域链

14.1、理解

  • 作用域相当于一块地盘
  • 相对于上下文对象是静态的,在编写代码时就确定了

14.2、分类

  • 全局作用域
  • 局部作用域
  • 块作用域(ES6新增)

14.3、作用

隔离变量:不同作用域的同名变量不会冲突

15、作用域与执行上下文

区别1
  • 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义的时候就确定了,而不是在函数调用时
  • 全局执行上下文环境是在全局作用域确定后,js代码马上执行之前创建
  • 函数执行上下文是在调用函数时,函数体执行代码之前创建
区别2
  • 作用域是静态的,只要函数定义好了就一直存在,且不会再变化
  • 上下文环境是动态的,调用函数时创建,函数调用完毕上下文环境就会被释放
联系
  • 上下文环境从属于所在的作用域
  • 全局上下文对象=>全局作用域
  • 函数上下文环境=>对应的函数作用域

你可能感兴趣的