JavaScript高级教程(函数进阶、闭包、正则表达式、ES6新增语法、新增扩展方法)

目录

函数的定义与调用

this指向及改变this指向的方法

函数内this的指向

改变函数内部this指向

严格模式

什么是严格模式

开启严格模式

严格模式中的变化

高阶函数

闭包

递归

正则表达式

ES6新增语法

ES6的内置对象扩展

Array的扩展方法

String的扩展方法

Set数据结构


函数的定义与调用

函数有三种定义方式:

  • 自定义函数(命名函数)
function fn() {};
  • 函数表达式(匿名函数)
var fun = function() {};
  • new Function()
var fn = new Function('参数1', '参数2', '函数体')

该种方式执行效率低,也不方便书写(所有参数都要写成字符串形式),因此较少使用;所有函数都是Function的实例(对象);函数也属于对象;

<script>
  var f = new Function('a', 'b', 'console.log(a+b)');
  f(1, 2); // 3
</script>

函数的调用方式有很多,分为普通函数、对象的方法、构造函数、绑定事件函数、定时器函数、立即执行函数等

fn(); // 普通函数调用
var o = {
  sayHi: function() {
     console.log('马上放假');
  }
};
o.sayHi(); // 对象的方法调用
function Ename() {};
new Ename(); // 构造函数方式
btn.onclick = function() {}; // 绑定事件函数
setInterval(function() {}, 1000); // 定时器事件
(function() {
  console.log('立即执行');
})(); // 立即执行函数,自动调用

this指向及改变this指向的方法

函数内this的指向

        这些this的指向,是当我们调用函数的时候确定的。调用方式的不同决定了this的指向不同,一般指向调用者;

调用方式this指向
普通函数调用window
构造函数调用实例对象 原型对象里面的方法也指向实例对象
对象方法调用该方法所属对象
事件绑定方法绑定事件对象
定时器函数window
立即执行函数window

改变函数内部this指向

JavaScript为我们专门提供了一些函数方法来帮助我们更优雅的处理函数内部this的指向问题,常用的有bind()/call()/apply()方法

  • call()方法

call()方法有两个作用,一是调用函数;而是改变this指向;

fun.call(thisArg, arg1, arg2,....)

参数thisArg表示在fun函数运行时指定的this值,arg1等为向fun传递等参数;

call()方法主要用于构造函数的继承~ 

可参照之前写过的一篇文章JavaScript高级教程(面向对象编程)_迷糊的小小淘的博客-CSDN博客

  • apply()方法

apply()方法与call()方法一样,同样具有两个作用,一是调用函数,而是改变this指向;

fun.call(thisArg, [arrgsArray])

二者区别是apply()向函数fun传递参数是以数组形式传递

<script>
  var o = {
    name: 'andy'
  };
  function fun(arr) {
    console.log(this); // window
    console.log(arr); // '男'
  }
  // 正常调用函数
  fun('男')
</script>
<script>
  var o = {
    name: 'andy'
  };
  function fun(arr) {
    console.log(this); // {name: 'andy'}
    console.log(arr); // ['男']
  }
  // 调用call改变this指向为o
  fun.call(o, ['男'])
</script>
  • bind()方法

bind()方法与上述二者的区别是他不会调用函数,只可以改变this指向(传递参数方式与call方式一样);

fun.bind(thisArg, arg1, arg2, ....)

他返回由指定的this和参数改造的原函数的拷贝;

<script>
  var o = {
    name: 'andy'
  };
  function fun(a, b) {
    console.log(this); // {name: 'andy'}
    console.log(a + b); // 3
  }
  var f = fun.bind(o, 1, 2);
  f(); // 手动调用bind改造后的函数
</script>

该方法适用于:如果有函数不需要立即调用,但是又想改变这个函数内部的this指向,如与定时器的配合使用;

场景:当点击按钮后就禁用该按钮,3秒钟过后才开启此按钮~

    <button>按钮</button>
    <script>
        var btn = document.querySelector('button');
        btn.onclick = function() {
            this.disabled = true;
            setTimeout(function() {
                btn.disabled = false // 因为定时器函数的this指向window,此时不能直接用this,要用btn
            }, 3000)
        }
    </script>
    <button>按钮</button>
    <script>
        var btn = document.querySelector('button');
        btn.onclick = function() {
            this.disabled = true; // 这个this指向的是btn,将此处的this传给bind
            setTimeout(function() {
                this.disabled = false // 
            }.bind(this), 3000) // 通过对定时器中的函数绑定bind(),将this指向改为btn
        }
    </script>

严格模式

什么是严格模式

严格模式(strict mode)消除了JavaScript语法的一些不合理、不严谨之处,减少了一些怪异行为;

消除了代码运行的一些不安全之处,保证代码运行的安全;

提高了编译器效率,增加运行速度;

禁用了在ECMAScript的未来版本中可能会定义的一些语法,为未来新版本的JS做好铺垫,如保留字class,enum,export,extends、import,super不能做变量名

开启严格模式

严格模式可应用到整个脚本或个别函数中,因此在使用时,可将严格模式分为为脚本开启严格模式和为函数开启严格模式两种情况;

  • 为脚本开启严格模式

为整个脚本文件开启严格模式,需要在所有语句之前放一句'use strict';(或"use strict";)

<script>
    "use strict";
    // 接下来写正常js代码,遵循严格模式
</script>
  • 为函数开启严格模式
<script>
        function fn() {
            "use strict";
            // 接下来写正常js代码,遵循严格模式
        }
</script>

严格模式中的变化

  • 变量规定

在正常模式中,若一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,变量都必须先声明,然后再使用;

严禁删除已经声明的变量,如delete x;是错误的

  • 严格模式下this指向问题

以前在全局作用域函数中的this指向window对象,在严格模式下全局作用域函数中的this是undefined(其余的this指向保持不变);

以前构造函数不加new也可以调用,当普通函数调用,this指向全局对象,在严格模式下,若构造函数不加new调用,this会报错(还是因为全局作用域中的this指向了undefined的原因);

<script>
    function Star() {
       this.sex = '男'
    };
    Star();
    console.log(window.sex); // 男
</script>
<script>
    'use strict'
    function Star() {
       this.sex = '男'
    };
    Star();
    console.log(window.sex); // // Cannot set properties of undefined (setting 'sex')
</script>
  • 函数变化

函数不能有重名的参数;

函数声明必须在顶部,ES6中引入块级作用域,不允许在非函数的代码块内声明函数;

                         图来源于严格模式 - JavaScript | MDN

高阶函数

高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出;最典型的就是作为回调函数;

<script>
        function fn(callback) {
            callback && callback();
        }
        fn(function() {
            alert('hi')
        })
         // 结果会弹出hi 
</script>

闭包

一般情况下,变量根据作用域的不同分为两种:全局变量和局部变量;

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

闭包则实现了从一个作用域访问另一个函数内部的局部变量;

官方说法:闭包(closure)指有权访问另一个函数作用域中变量的函数,闭包延伸了变量的作用范围;

关于闭包的详细讲解见后续篇章,敬请期待~

递归

如果一个函数在内部调用其本身,那么该函数就是递归函数;但是要记得加退出递归条件

// 求阶乘 n!
     <script>
        function fn(n) {
            if (n === 1) {
                return 1
            }
            return n * fn(n - 1);
        }
        console.log(fn(4));
     </script>

递归可以用来实现深拷贝

补充: 

浅拷贝只是拷贝一层,对于更深层次级别(如数组/对象)只是拷贝了地址索引;object.assign实现的是浅拷贝;

深拷贝指拷贝每一级别的数据;

     <script>
        function deepClone(newObj, oldObj) {
            for (var k in oldObj) {
                // 首先判断属性值属于何种数据类型
                var item = oldObj[k];
                // 先判断是不是数组类型(因为万物皆对象)
                if (item instanceof Array) {
                    newObj[k] = [];
                    deepClone(newObj[k], item);
                } else if (item instanceof Object) {
                    newObj[k] = {};
                    deepClone(newObj[k], item);
                } else {
                    newObj[k] = item;
                }
            }
        }
        var obj = {
            name: '小明',
            sex: '男',
            subject: {
                name: '语文',
                score: 90
            },
            hobbys: ['play', 'playing PingPang']
        }
        var newObj = {}
        deepClone(newObj, obj);
        console.log(newObj); // 结果与obj一样
        newObj.subject.name = '英语';
        console.log(obj, newObj); // obj的subject对象下的name属性没变,newObj的subject对象下的name属性改变了
    </script>

<script>
// 也可以使用该种方法实现深拷贝
export function deepCopy (obj) {
  var result = Array.isArray(obj) ? [] : {}
  for (var key in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      if (typeof obj[key] === 'object' && obj[key] !== null) {
        result[key] = deepCopy(obj[key])
      } else {
        result[key] = obj[key]
      }
    }
  }
  return result
}
</script>

正则表达式

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

正则表达式特点:

  1. 灵活性、逻辑性和功能性非常的强;
  2. 可迅速用极简单的方式达到字符串的复杂控制;

正则表达式的使用:

  • 创建正则表达式

在JS中,可通过两种方式创建一个正则表达式,

1、通过调用RegExp对象的构造函数创建

var 变量名 = new RegExp(/表达式/);

2、通过字面量创建

var 变量名 = /表达式/
  • 测试正则表达式test

test()正则对象方法,用于检测字符串是否符合该规则,该对象会返回true或false,其参数是测试字符串

regexObj.test(str); // regexObj是写的正则表达式,str是要测试文本
  • 正则表达式的组成

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

1、边界符

边界符用来提示字符所处的位置,主要有两个字符:

^ 表示匹配行首的文本(以谁开始)

$ 表示匹配行尾的文本(以谁结束)

若二者在一起,则表示精确匹配;

2、字符类

字符类表示有一系列字符可供选择,只要匹配其中一个就可以了,所有可供选择的字符都放在方括号内;

[-]:-表示范围,如[a-z],表示a-z之间任意一个字符;

[^]:方括号内部取反符^ ,表示不是方括号内部的字符即可;

3、量词符

量词符用来设定某个模式出现的次数。

* : 表示重复零次或更多次[0-n次];

+:表示重复一次或更多次[1-n次];

?:表示重复零次或一次[0-1次];

{n}:表示重复n次;

{n,}:表示至少重复n次;

{n,m}:表示重复n到m次(左闭右必);

4、括号类

大括号为量词符,表示重复次数;

中括号为字符集合,表示匹配方括号中的任意字符;

小括号表示优先级;

5、预定义类

预定义类指的是某些常见模式的简写方式:

预定义说明
\d匹配0-9之间的任一数字,相当于[0-9]
\D匹配所有0-9以外的字符,相当于[^0-9]
\w匹配任意的字母、数字和下划线,相当于[A-Za-z0-9_]
\W匹配除字母、数字和下划线以外的字符,相当于[^A-Za-z0-9_]
\s匹配空格(包括换行符、制表符、空格符等),相当于[\t\r\n\v\f]
\S匹配非空格的字符,相当于[^ \t\r\n\v\f]
  • 正则表达式中的替换

1、replace替换

replace()方法可实现替换字符串操作,用来替换的参数可以是一个字符串或是一个正则表达式

stringObject.replace(regexp/substr,replacement)

第一个参数为被替换的字符串或是正则表达式;第二个参数是替换为的字符串;返回值是一个替换完毕的新字符串;

但是replace方法只能替换第一个满足条件的字符串,用下面的修饰/g可实现全局替换;

2、正则表达式修饰符

/表达式/[修饰符]

修饰符表示按照什么样的模式进行匹配,写在正则表达式的外部,有三种值:

  1. g表示全局匹配
  2. i表示忽略大小写
  3. gi表示全局匹配+忽略大小写

详细的正则表达式讲解见之前的一篇文章:https://blog.csdn.net/ks795820/article/details/117279381?spm=1001.2014.3001.5501

ES6新增语法

  • let

let声明的变量只在所处于的块级有效(使用let关键字声明的变量才具有块级作用域,使用var声明的变量不具备块级作用域特性);不存在变量提升

  • const

const用于声明常量,常量即值(内存地址)不能变化的量;

具有块级作用域;

声明变量时必须赋值;

变量赋值后,值不能修改;

  • 解构赋值

ES6中允许从数组中提取值,按照对应位置,对变量赋值。对象也可以实现解构;

1、数组解构

<div id="root">
        <div>你好啊</div>
    </div>
    <script>
        let [a, b, c] = [1, 2, 3];
        console.log(a); // 1
        console.log(b); // 2
        console.log(c); // 3
    </script>

若解构不成功,即左右两边并未一一对应的话,则右侧变量值为undefined

 <div id="root">
        <div>你好啊</div>
    </div>
    <script>
        let [a, b, c] = [1, 2];
        console.log(a); // 1
        console.log(b); // 2
        console.log(c); // undefined
    </script>

2、对象解构

与数组解构类似,

<div id="root">
        <div>你好啊</div>
    </div>
    <script>
        let person = {
            name: 'xiaoming',
            age: 20
        };
        let {
            name,
            age
        } = person;
        console.log(name); // xiaoming
        console.log(age); // 20
    </script>
  • 箭头函数
() => { } // 小括号中放置形参,大括号中为函数体
或
const fn = () => {}  // 通常能将箭头函数赋值给一个变量,便于后面调用

若函数体只有一句代码,并且代码的执行结果就是函数的返回值,则函数体的大括号可以省略;

<script>
        const sum = (n1, n2) => n1 + n2;
        const result = sum(10, 20);
        console.log(result); // 30
</script>

同理,若形参只有一个,也可以省略小括号;

<script>
        const sum = n1 => n1;
        const result = sum(10);
        console.log(result); // 10
</script>

注意:箭头函数不绑定this关键字,箭头函数中的this,指向的是函数定义位置的上下文this(即箭头函数被定义在哪,则this就会指向哪里)。

  • 剩余参数

剩余参数允许将一个不定数量的参数表示为一个数组;

<script>
        function sum(first, ...args) {
            console.log(first); // 1
            console.log(args); // [2,3,4]
        }
        sum(1, 2, 3, 4);
</script>
<script>
        const sum = (...args) => {
            let total = 0;
            args.forEach(item => {
                total += item;
            })
            return total;
        };
        const sum1 = sum(1, 2, 3, 4);
        const sum2 = sum(1, 2);
        console.log(sum1); // 10
        console.log(sum2); // 3
</script>

剩余参数和解构配合使用:

<script>
        let students = ['小明', 18, '男'];
        let [s1, ...s2] = students;
        console.log(s1); // '小明'
        console.log(s2); // [18, '男']
</script>

ES6的内置对象扩展

Array的扩展方法

  • 扩展运算符(展开语法)

扩展运算符可将数组或对象转为用逗号分隔的参数序列;

<script>
        let students = ['小明', 18, '男'];
        console.log(...students); // 小明 18 男
</script>

此处使用...students后变为小明, 18, 男, 然后经由console.log后将逗号去掉了;

扩展运算符可以用于合并数组;

<script>
        let students = ['小明', 18, '男'];
        let studentso = ['大学生'];
        let studentsa = [...students, ...studentso]
        console.log(studentsa); // ['小明', 18, '男', '大学生']
</script>

扩展运算符还可将可遍历的伪数组转换为真正的数组,从而使用数组的方法;

<script>
        let lis = document.getElementsByTagName('li');
        lis = [...lis];
</script>
  • Array.from()

该方法将类数组或可遍历对象转换为真正的数组;

<script>
        let arrayLike = {
            "0": 'a',
            "1": 'b',
            "2": 'c',
            length: 3
        };
        let lis = Array.from(arrayLike);
        console.log(lis); // ['a', 'b', 'c']
</script>

该方法还可以接受第二个参数,类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组;

 <script>
        let arrayLike = {
            "0": '1',
            "1": '1',
            "2": '1',
            length: 3
        };
        let lis = Array.from(arrayLike, item => item * 2);
        console.log(lis); // [2, 2, 2]
</script>

String的扩展方法

  • 模板字符串

是创建字符串的方式,使用反引号定义:

 let name = `xiaoming`;

模板字符串中可以解析变量;使用${}

<script>
        let name = `xiaoming`;
        console.log(name); //xiaoming
        let sayHello = `hello,my name is ${name}`;
        console.log(sayHello); // hello,my name is xiaoming
</script>

在模板字符串中可以调用函数;

<script>
        const sayHell = function() {
            return '新年到了~';
        };
        let greet = `${sayHell()}`
        console.log(greet); // 新年到了~
</script>

Set数据结构

ES6提供了新的数据结构Set,它类似于数组,但是成员的值都是唯一的,没有重复的值;

Set本身是一个构造函数,用来生成Set数据结构;

const s = new Set();

Set函数可以接受一个数组作为参数,用来初始化;

const set = new Set([1,2,3]);
 <script>
        const data = new Set(['1', 1, '1', 'a', 'a'])
        console.log(data); // {'1', 1, 'a'} 去掉了重复的数据
 </script>
  • set数据结构的实例方法

add(value)用于添加某个值,返回Set结构本身;

delete(value)用于删除某个值,返回一个布尔值,表示删除是否成功;

has(value)用于测试该值是否为set成员,返回一个布尔值;

clear()用于清除所有set成员,没有返回值;

foreach()与数组一样,用于对每个成员执行某种操作,没有返回值;

  <script>
        const data = new Set(['1', 1, '1', 'a', 'a'])
        data.forEach(value => console.log(value)) // '1' 1 'a'
</script>

关于ES6的新增特性后续会开一篇文章详细讲解,敬请期待~

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

迷糊的小小淘

整理不易,赏点动力~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值