JavaScript基础知识总结(四):常见内置构造函数,正则表达式,作用域与闭包

一:常见内置对象与内置构造函数

1.常见内置对象

(1) Math
js 复制代码
Math.random(); // 0~1之间
Math.round(4.6); // 5
Math.max(1, 5, 3); // 5
(2) Date
js 复制代码
let now = new Date();
console.log(now.getFullYear(), now.getMonth() + 1, now.getDate());
(3) JSON
js 复制代码
let obj = { a: 1, b: 2 };
let str = JSON.stringify(obj);
let newObj = JSON.parse(str);

2.常见内置构造函数

js几乎所有数据类型都可以使用对象的方式来操作,在底层将简单数据类型包装成了复杂数据类型 (包装成了对象)

1.常见的构造类型

Object Array String Number Boolean Function Date Math RegExp Error

2.Object构造函数
1.构造函数
js 复制代码
//Object是内置的构造函数 用来创建对象的
const obj=new Object()
obj.name='peiqi'
obj.age=18
obj.sayHi=function(){
console.log('hi');
}
2.具体静态方法

常用静态方法: Object.keys() Object.values() Object.assign()

1.Object.keys() 返回一个数组 包含对象自身的所有属性名

js 复制代码
console.log(Object.keys(obj)) //['name', 'age', 'sayHi']数组的形式
Object.values() 返回一个数组 包含对象自身的所有属性的值
console.log(Object.values(obj)) //['peiqi', 18, ƒ]

2.Object.assign() 拷贝对象的所有属性到指定对象中 返回一个新对象 或者为对象添加属性(合并对象)

js 复制代码
const obj2=Object.assign({},obj)
console.log(obj2); //{name: 'peiqi', age: 18, sayHi: ƒ}
Object.assign(obj,{gender:'男'})
console.log(obj); //{name: 'peiqi', age: 18, sayHi: ƒ, gender: '男'}
3. Array构造函数
1.构造函数:

一般使用 字面量[ ]直接创建数组,使用构造函数会有一些麻烦

js 复制代码
const arr = new Array() 构造数组函数,但平时不常用 因为[]更方便
2.具体的静态方法
  • forEach只遍历数组,但不返回新数组

    js 复制代码
    arr.forEach(function(item,index){
      console.log(item,index); //1 0 2 1 3 2 4 3 5 4 6 5 7 6 8 7 9 8 10 9 
    })
  • filter 筛选数组并且返回一个新数组

    js 复制代码
    const arr2=arr.filter(function(item,index){
      return item>=6 
    })
    console.log(arr2); //[6, 7, 8, 9, 10]
  • map遍历数组并且返回一个新数组

    • 注意使用map方法去遍历一个对象数组的同时,直接修改对象的属性会影响到元素组 因为这里的对象是一个引用类型的数据

      js 复制代码
      // 创建一个包含对象的数组
      let lesson = [
        {title: 'css学习', body: 'css'},
        {title: 'html学习', body: 'html'},
        {title: 'JS学习', body: 'js'}
      ];
      
      // 当我们使用 map 时,item 变量得到的是原对象的引用
      let modifiedLesson = lesson.map(item => {
        // 这里直接修改 item.title 实际上是在修改原对象
        item.title += '课程'; 
        return item;
      });
      
      console.log(lesson[0].title); // 输出: "css学习课程"
      console.log(modifiedLesson[0].title); // 输出: "css学习课程"
      // 可以看到,原数组也被修改了!
      复制代码
      内存中的情况:
      lesson 数组: [地址1, 地址2, 地址3]
                   ↓      ↓      ↓
      对象内容:   {title: "css学习", body: "css"}
                 {title: "html学习", body: "html"}
                 {title: "JS学习", body: "js"}
      
      当执行 map 时:
      item 变量: → 地址1 (指向第一个对象)
      执行 item.title += '课程' 实际上是修改了地址1指向的对象
      因此原数组中的对象也被修改了

      正确处理流程:

      js 复制代码
      let lesson = [
        {title: 'css学习', body: 'css'},
        {title: 'html学习', body: 'html'},
        {title: 'JS学习', body: 'js'}
      ];
      
      // 使用展开运算符创建新对象
      let modifiedLesson = lesson.map(item => {
        return {
          ...item,              // 复制原对象的所有属性
          title: item.title + '课程'  // 创建新的 title 属性值
        };
      });
      
      console.log(lesson[0].title); // 输出: "css学习" (未被修改)
      console.log(modifiedLesson[0].title); // 输出: "css学习课程"
    js 复制代码
    const arr3=arr.map(function(item,index){
      return item*2 
    })
    console.log(arr3); //[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
  • reduce数组累计器,一般用于数组的求和,有时候会自带初始值

    js 复制代码
    const total=arr.reduce(function(prev,cur){
      // prev 上一次的返回值 cur 当前遍历的元素
      return prev+cur
    })
    console.log(total); //55
    const total2=arr.reduce(function(prev,cur){
        return prev+cur
    },10)//这里的10就prev的初始值 
    console.log(total2); //65
  • sort数组排序,会改变原数组''

    js 复制代码
    const arr4=[1,2,3,4,5,6,7,8,9,10]
    arr4.sort((a,b)=>{
      return a-b
    })
    console.log(arr4); //[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
  • reverse 反转数组,会改变原数组

    JS 复制代码
    const arr4=[1,2,3,4,5,6,7,8,9,10]
    arr4.reverse()
    console.log(arr4); //[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  • concat拼接数组,不会改变原数组,返回一个新数组

    JS 复制代码
    const arr4=[1,2,3,4,5,6,7,8,9,10]
    const arr5=arr4.concat([11,12,13])
    console.log(arr5); //[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
  • slice截取数组,不会改变原数组

    JS 复制代码
    const arr6=arr5.slice(0,5)
    console.log(arr6); //[1, 2, 3, 4, 5]
  • slice数组替换,会改变原数组 返回一个新数组

    JS 复制代码
    const arr8=[1,2,3,4,5,6,7,8,9,10]
    const arr9=arr8.splice(0,5)
    console.log(arr9); //[1, 2, 3, 4, 5]
    console.log(arr8); //[6, 7, 8, 9, 10]
  • indexOf 返回数组中第一个满足条件的元素的索引 如果没有找到返回-1

    js 复制代码
    const arr7=[1,2,3,4,5,6,7,8,9,10]
    const index=arr7.indexOf(5)
    console.log(index); //4
  • join不会改变原数组 返回一个新数组

    js 复制代码
    const arr14=[1,2,3,4,5,6,7,8,9,10]
    const arr15=arr14.join('-')
    console.log(arr15); //1-2-3-4-5-6-7-8-9-10
  • every 会改变原数组 返回一个新数组 判断数组中是否所有元素都满足条件

    js 复制代码
    const arr16=[1,2,3,4,5,6,7,8,9,10]
    const arr17=arr16.every(item=>item>5)
    console.log(arr17); //false
  • some 会改变原数组 返回一个新数组 判断数组中是否有一个元素满足条件

    js 复制代码
    const arr18=[1,2,3,4,5,6,7,8,9,10]
    const arr19=arr18.some(item=>item>5)
    console.log(arr19); //true
  • find 会改变原数组 返回一个新数组 找到第一个满足条件的元素 返回该元素 如果没有找到返回undefined

    js 复制代码
    const arr20=[1,2,3,4,5,6,7,8,9,10]
    const arr21=arr20.find(item=>item>5)
    console.log(arr21); //6
  • from 会改变原数组 返回一个新数组 将一个伪数组对象转换成数组

4.String构造函数
1.构造函数:
js 复制代码
// 使用 String 构造函数将值转换为字符串
let num = 123;
let str = new String(num); // 创建一个 String 对象
console.log(typeof str);   // 输出: object
console.log(str);          // 输出: "123"

// 更常见的做法是直接调用 String 函数(不使用 new)
let simpleStr = String(num);
console.log(typeof simpleStr); // 输出: string
console.log(simpleStr);        // 输出: "123"
2.具体的静态方法
  • .length属性 数组的长度 数组的长度是数组中元素的个数

  • split('分隔符') 字符串的方法 把字符串分割成数组 返回一个新数组

    js 复制代码
    const str='pink,jiong,red'
    const arr=str.split(',')
    console.log(arr); //['pink', 'jiong', 'red']
  • substring(start,end) 字符串的方法 截取字符串 返回一个新字符串

    js 复制代码
     //start 开始的位置 end 结束的位置 不包括end 可以省略 省略表示到末尾
    const str2='pinkjiongred'
    const str3=str2.substring(0,4)
    console.log(str3); //pink
  • startsWith('字符串',参数)字符串的方法 判断字符串是否以指定字符串开头 返回一个布尔值

    js 复制代码
    //参数 可选 从哪个位置开始判断 默认从0开始 注意是从参数(包含参数)开始的位置
    const str4='pinkjiongred'
    const str5=str4.startsWith('pink')
    console.log(str5); //true
  • includes('字符串',参数) 字符串的方法 判断字符串是否包含指定字符串 返回一个布尔值

    js 复制代码
    //参数 可选 从哪个位置开始判断 默认从0开始 注意是从参数(包含参数)开始的位置
    const str6='pinkredgreen'
    const str7=str6.includes('jiong',0)
    console.log(str7); //false 只要包含就行,不需要考虑位置
  • toUpperCase() 字符串的方法 把字符串转换成大写 返回一个新字符串

  • toLowerCase() 字符串的方法 把字符串转换成小写 返回一个新字符串


二:类型转换

(1) 显式转换

js 复制代码
Number('123'); // 123
String(123);   // "123"
Boolean(0);    // false

(2) 隐式转换

js 复制代码
console.log('5' - 1); // 4(字符串转数字)
console.log('5' + 1); // "51"(数字转字符串)

三:正则表达式

正则表达式 适用于匹配字符串组合的模式 js正则表达式也是对象,通常用来查找 替换 哪些符合正则表达式的文本,许多语言都支持正则表达式

1.定义方法:

js 复制代码
const  变量名 = /表达式/

2.查找是否匹配:(使用reg.text(字符串) 的方法)

js 复制代码
const str=" 你好,世界"
//定义规则
const reg= /你好/
//text()方法 用来查看表达式与字符串是否匹配 返回true false
console.log(reg.test(str));
//esec()方法 在一个指定字符串中执行一个搜索匹配 如果匹配成功返回一个数组,返回失败是NUll
console.log(reg.exec(str));

3.元字符

元字符是一种特殊字符提高了灵活性和匹配功能.

js 复制代码
const regex1 = /\d+/;  // 匹配一个或多个数字
const regex2 = /^[A-Z]/; // 匹配以大写字母开头的字符串
const regex3 = /\w{4,}/; // 匹配至少4个字母/数字/下划线

具体方法:

  1. 边界符号 ^ : 用来界定字符所处的位置 \^ 表示匹配行首的文本(以谁开始) 表示匹配行尾的文本(以谁结束)

    js 复制代码
    // 示例1:匹配以"Hello"开头的字符串
    console.log(/^Hello/.test("Hello World")); // true
    console.log(/^Hello/.test("Say Hello"));   // false
    
    // 示例2:匹配以"World"结尾的字符串
    console.log(/World$/.test("Hello World"));  // true
    console.log(/World$/.test("World Peace"));  // false
    
    // 示例3:精确匹配整个字符串
    console.log(/^Hello World$/.test("Hello World")); // true
    console.log(/^Hello World$/.test(" Hello World ")); // false
  2. 量词: 表示重复次数 设定某个模式(规则)出现的次数

    js 复制代码
    // * 匹配前一个字符0次或多次 如果加入精确匹配,只允许匹配的字符出现 如果出现其他字符就是false
    console.log(/ab*c/.test("ac"));     // true (b出现0次)
    console.log(/ab*c/.test("abc"));    // true (b出现1次)
    console.log(/ab*c/.test("abbbc"));  // true (b出现多次)
    
    // + 匹配前一个字符1次或多次 >=1
    console.log(/ab+c/.test("ac"));     // false (b至少出现1次)
    console.log(/ab+c/.test("abc"));    // true
    console.log(/ab+c/.test("abbbc"));  // true
    
    // ? 匹配前一个字符0次或1次 0||1
    console.log(/ab?c/.test("ac"));     // true (b出现0次)
    console.log(/ab?c/.test("abc"));    // true (b出现1次)
    console.log(/ab?c/.test("abbbc"));  // false (b出现多次)
    
    // {n} 匹配前一个字符恰好n次   ==n
    console.log(/a{3}/.test("aaa"));    // true
    console.log(/a{3}/.test("aa"));     // false
    
    // {n,} 匹配前一个字符至少n次   >=n
    console.log(/a{2,}/.test("aa"));    // true
    console.log(/a{2,}/.test("aaa"));   // true
    console.log(/a{2,}/.test("a"));     // false
    
    // {n,m} 匹配前一个字符n到m次  >=n&& <=m
    console.log(/a{2,4}/.test("aa"));   // true
    console.log(/a{2,4}/.test("aaa"));  // true
    console.log(/a{2,4}/.test("a"));    // false
    console.log(/a{2,4}/.test("aaaaa")); // false
  3. 字符类型: [a-z]26个英文字母 [a-zA-Z]大小写都行 [ ] 匹配方括号中的任意一个字符 可以添加量词允许重复

    js 复制代码
    // 示例1:匹配a或b或c
      console.log(/^[abc]$/.test("a"));      // true (精确匹配单个字符)
      console.log(/^[abc]{2}$/.test('ab'));  // true (精确匹配两个字符)
      console.log(/^[abc]{2}$/.test('a'));   // false (不足两个字符)
      console.log(/^[abc]{2}$/.test('abc')); // false (超过两个字符)
  4. 元字符的预定义

    js 复制代码
    //预定义是某些常见模式的简写方式
    // \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]
    // ^\d{4}-\d{1,2}-\d{1,2}$
  5. 修饰符

    js 复制代码
    //修饰符约束正则执行的某些细节行为,如是否区分大小写,是否支持多行匹配等
    // /表达式/修饰符
    // i 匹配时不区分大小写
    // g 匹配时,匹配所有满足正则表达式的结果
    console.log(/a/i.test('A')); //true
    
    //replace替换 找到所需要的文本替换
    const str='java是我要学的语言,但是我不想学JAVA'
    const re=str.replace(/java/ig,'js');
    console.log(re);

四: 作用域与闭包

(1)作用域

1.环境与作用域
1)执行环境

环境的存在价值就是被需要,全局环境不会被回收,比如创建一个函数,每次调用函数都会创建一个函数环境就,函数执行完之后这个环境就会被销毁。函数执行多次,里面的内存地址也会被声明多个内存地址,执行完一次后,里面的内存地址被清理掉

  • 全局环境:代码开始执行时创建,程序退出前不会被回收
  • 函数环境:每次函数调用都会创建新的执行环境,函数执行完毕后该环境被销毁
  • 环境就是系统执行代码遵循的运行规则->决定代码的生命周期与实际值
2)作用域
  • 全局作用域:在代码任何地方都能访问的变量和函数
  • 局部作用域:在特定函数内部声明的变量和函数,只能在该函数内部访问
  • 作用域是编写代码的词法规则->决定变量在哪可以被访问

简单总结:环境就是系统执行代码遵循的运行规则->决定代码的生命周期与实际值,作用域是编写代码的词法规则->决定变量在哪可以被访问

3)延长环境的生命周期

假如函数中是一个自增的n,那么该环境的生命周期就是 函数被调用-->环境被创建-->n自增-->执行完毕内存中环境销毁;当多次调用该函数n是不会一直自增的

js 复制代码
function hd(){
    let n=0;
    function sum(){
        console.log(++n);
    }
    sum();
}
hd();
hd();

如果想延长这个生命周期,就需要将sum函数返回出去使得外部存在sum的引用 这样就可以延长环境的生命周期

js 复制代码
function hd1(){
    let n=0;
    function sum(){
        console.log(++n);
    }
    return sum;
}
let fn=hd1();
fn();
fn();

延长构造函数中的生命周期

js 复制代码
function hd(){
    let n=0;
    this.sum=function(){
        console.log(++n);
    }
}
let a=new hd();
a.sum();
a.sum();
5) let var在for循环中的不同效果

使用var声明for中的i

js 复制代码
for (var i = 1; i <= 3; i++) {
    // 因为var是全局作用域,每次循环都是把上次的结果改变,倒计时1秒后已经循环完毕,循环完毕的结果就是4
    // 因为循环了三次,所以打印出3个4
    // 结果为4的原因是第四循环时已经把i变成了4,但是没有通过 i <= 3的判断条件
    // 但此时i的值已经变成了4,所以打印结果就是4
    setTimeout(() => {
      console.log(i); // 三个4
    }, 1000);
  }
js 复制代码
 for (let a = 1; a <= 3; a++) {
    // 因为let是块级元素,每次循环都会产生一个新的块级作用域,每个作用域里面存着每次循环的结果
    // 倒计时1秒后吧每个块里面的元素打印出来,所以就是 1 2 3
    setTimeout(() => {
      console.log(a); // 1 2 3
    }, 1000);
 }

利用自执行函数改造var

js 复制代码
for (var a = 0; a <= 5; a++) {
    // 自执行函数,每次循环都产生一个新的函数作用域,每个函数作用域存着这次循环的结果
    // 倒计时1秒后打印出每个函数作用域里面的值
    (function(a) {
      setTimeout(() => {
        console.log(a);
      }, 1000);
    })(a);
  }

(2)闭包

1.概念:

闭包是指一个函数能够访问并"记住"其外部作用域中的变量,即使在外层函数已经执行完毕后,内层函数仍然可以访问这些变量。

简单理解就是里层函数用到了外层的变量 就会被捆绑在一起作为闭包

2.形成条件:
  • 在函数中存在一个内部函数
  • 内部函数引用了外部函数里面的变量
  • 外部函数返回这个内部函数
3.核心机制:

当一个函数被创建时,它会记住自己被定义时的环境(即词法作用域),这样即便在不同的执行上下文中调用该函数,它依然能访问到原始作用域中的变量。

4.常规用途:
  • 创建私有变量(模拟封装)
  • 实现数据缓存或记忆功能
  • 在异步操作、回调函数中保持状态
5.注意事项:
  • 如果你不小心创建了很多闭包,或者长时间持有不必要的引用,可能会导致内存泄漏
  • 因此,在不需要的时候应该手动解除引用(例如将函数设为 null)以帮助垃圾回收。
  • this在闭包中也存在问题 因为闭包会造成对象中的方法的this没有指向 所以在对象中如果使用闭包 返回的函数为箭头函数会好一些
js 复制代码
let hd={
    name:'后盾人',
    getName:function(){
        return function(){
            return this.name
        }
    }
}
console.log(hd.getName()());//undefined
//因为我么调用getname后返回一个函数 这个函数中的this是没有指向的 也就是直接指向了window 

let hd2={
    name:'后盾人',
    getName:function(){
        return ()=>{
            return this.name
        }
    }
} 
//箭头函数没有自己的this 他的this是继承的上层作用域的this
//这就是解决方法
console.log(hd2.getName()());//undefined
6.实例代码:
js 复制代码
/**
 * 闭包示例:
 * - outerFunction 执行后返回 innerFunction
 * - innerFunction 保留对 outerVariable 的引用
 * - 即使 outerFunction 执行完毕,outerVariable 也不会被回收
 * - 这就是闭包实现私有变量的原理
 */

//一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域
//闭包=内层函数+外层函数的变量
function outerFunction(x) {
    // 外层函数的局部变量
    let outerVariable = x;

    // 返回一个内层函数
    return function innerFunction(y) {
        // 内层函数使用了外层函数的变量
        console.log(outerVariable + y);
    };
}

const closureExample = outerFunction(10);
closureExample(5); // 输出 15

问题总结与回答:

1.为什么可以对外层函数的局部变量进行私有化?

比如在示例代码中,outerFunction() 是一个外层函数,其中定义了一个局部变量 outerVariable,该变量被内层函数 innerFunction() 所引用。这是一种典型的闭包引用关系。

尽管 outerFunction() 已经执行完毕,按理说其内部变量应当被垃圾回收机制清理掉,但由于 outerFunction()innerFunction() 返回并赋值给了外部环境中的变量 closureExample,这就使得 innerFunction() 依然存活在内存中。

由于 innerFunction() 依赖于 outerVariable,而 innerFunction() 又依赖于全局变量 closureExample 的存在,而全局变量不会被垃圾回收器回收,因此 outerVariable 也被保留在内存中,不会被释放。

这一过程实际上实现了变量的"私有化"------外部无法直接访问 outerVariable,只能通过闭包暴露的接口(即 closureExample)间接操作它。

相关推荐
3秒一个大1 小时前
Ajax 数据请求详解:从概念到实战
javascript
清凉夏日2 小时前
Flutter 国际化完整指南
前端·flutter
Jony_2 小时前
动态代理机制
前端
掘金一周2 小时前
重新思考 weapp-tailwindcss 的未来 | 掘金一周 11.13
前端·人工智能·后端
Pu_Nine_92 小时前
Vue 3 项目 ESLint 配置详解:初始模板的正确配置
前端·javascript·vue.js
Jolyne_2 小时前
【浏览器插件】一键下载页面图片和文本
前端
Jony_2 小时前
Android 类加载机制
前端·客户端
im_AMBER2 小时前
HTTP概述 01
javascript·网络·笔记·网络协议·学习·http
1024小神2 小时前
在 Swift 中,参数标签(argument label),用于在调用函数时提高代码的可读性。
前端