JS入门学习笔记(基础,ES6,正则)

JS入门学习

JS基础

indexOf 查找某个元素

  • 定义和用法

    indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置。

  • 语法

    stringObject.indexOf(searchvalue,fromindex)
    
参数 描述
searchvalue 必需。规定需检索的字符串值。
fromindex 可选的整数参数。规定在字符串中开始检索的位置。它的合法取值是 0 到 stringObject.length - 1。如省略该参数,则将从字符串的首字符开始检索。
  • 提示和注释

注释:indexOf() 方法对大小写敏感!

注释:如果要检索的字符串值没有出现,则该方法返回 -1。

concat() 连接数组

  • concat()可以将两个数组连接起来

    const a = [1,2,3];
    const b = [4,5,6];
    console.log(a.concat(b));  //输出[1,2,3,4,5,6]
    

JavaScript split() 方法

JavaScript String

定义和用法

split() 方法用于把一个字符串分割成字符串数组。

语法

stringObject.split(separator,howmany)
参数 描述
separator 必需。字符串或正则表达式,从该参数指定的地方分割 stringObject。
howmany 可选。该参数可指定返回的数组的最大长度。如果设置了该参数,返回的子串不会多于这个参数指定的数组。如果没有设置该参数,整个字符串都会被分割,不考虑它的长度。

返回值

一个字符串数组。该数组是通过在 separator 指定的边界处将字符串 stringObject 分割成子串创建的。返回的数组中的字串不包括 separator 自身。

但是,如果 separator 是包含子表达式的正则表达式,那么返回的数组中包括与这些子表达式匹配的字串(但不包括与整个正则表达式匹配的文本)。

提示和注释

注释:如果把空字符串 ("") 用作 separator,那么 stringObject 中的每个字符之间都会被分割。

注释:String.split() 执行的操作与 Array.join 执行的操作是相反的。

JavaScript join() 方法

Array 对象参考手册 JavaScript Array 对象

实例

把数组中的所有元素转换为一个字符串:

var fruits = ["Banana", "Orange", "Apple", "Mango"];
var energy = fruits.join();

energy输出结果:

Banana,Orange,Apple,Mango

尝试一下 »


定义和用法

join() 方法用于把数组中的所有元素转换一个字符串。

元素是通过指定的分隔符进行分隔的。


浏览器支持

Internet ExplorerFirefoxOperaGoogle ChromeSafari

所有主要浏览器都支持join() 属性。


语法

array.join(separator)

参数值

参数 描述
separator 可选。指定要使用的分隔符。如果省略该参数,则使用逗号作为分隔符。

返回值

类型 描述
String 返回一个字符串。该字符串是通过把 arrayObject 的每个元素转换为字符串,然后把这些字符串连接起来,在两个元素之间插入 separator 字符串而生成的。

reduce() 累加器

  • reduce()可以接受一个函数作为累加器,将数组中的每个数从左到右利用该方法缩减为一个数,如计算一个数组的总和:

    const sum = (...args) => {
        return args.reduce((a, b) => a + b, 0);
    }
    
  • 参数介绍:

    array.args((preValue, currentValue, currentIndex, arr) => {
        //todo 实现方法
    }, initialValue)
    
    • preValue:前一次调用函数后在数组中的一个值
    • currentValue:定位到的数组中的值
    • currentIndex:定位到的数组中的索引
    • arr:调用的数组
  • 其他知识点:(...args)是rest操作符,可以表示多个参数,rest参数本质是一个数组(如这里的args)

slice() 切割数组

  • slice可以将数组切割为一个更小的子数组
const arr = [a0,a1,...an];
arr.slice(begin,end); //切割为数组中从begin到end位置的子数组,但不包括end

splice()删除元素

  • splice(index, howmany, item1, item2, ……itemn)

    表示从index位置删除howmany个数,并在此添加item1,item2,……itemn

let arr = [1,2,3,4,5,6];
arr.splice(3,2,7,8,9);
//改变后arr=[1,2,3,7,8,9,6]

Object.keys()返回属性名

// array like object
var obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.keys(obj)); // console: ['0', '1', '2']

关于函数

函数声明: function fnName() {...} ;
使用 function 关键字声明一个函数,再执行一个函数名,叫函数声明

函数表达式: var fnName = function() { ... } ;
使用 function 关键字声明一个函数,但未给函数命名,最后将匿名函数赋予一个变量,叫函数表达式,这是最常见的函数表达式语法形式。

匿名函数: function() { ... } ;
使用 function 关键字声明一个函数,但未给函数命名,所以叫匿名函数,匿名函数属于函数表达式,匿名函数有很多作用,赋予一个变量则创建函数,赋予一个事件则成为事件处理程序或创建闭包等等。

立即执行函数

函数声明函数表达式不同之处在于
1、JavaScript 引擎在解析 JavaScript 代码时会“函数声明提升”当前执行环境(作用域)上的函数声明,而函数表达式必须等到 JavaScript 引擎执行到它所在行时,才会从上而下一行一行地解析函数表达式。

2、函数表达式后面可以加括号立即调用该函数,函数声明不可以,只能以 fnName() 形式调用。

(function(a) {
  console.log(a);  //使用()运算符,打印出123
})(123);

JavaScript Array filter() 方法

语法

array.filter(function(currentValue,index,arr), thisValue)
参数 描述
function(currentValue, index,arr) 必须。函数,数组中的每个元素都会执行这个函数 函数参数: 参数描述currentValue必须。当前元素的值index可选。当前元素的索引值arr可选。当前元素属于的数组对象
thisValue 可选。对象作为该执行回调时使用,传递给函数,用作 "this" 的值。 如果省略了 thisValue ,"this" 的值为 "undefined"

例子

let arr = [1, 3, 5, 8]
let arrFilter = arr.filter(ele => ele > 4)
console.log(arrFilter) // [5, 8]

在这里ele=>ele >4是一个箭头函数,ele充当参数,对应的就是filter里function的第一个参数,所以这个函数的意思是筛选出array中值currentvalue > 4 对应的数组中的值。

JS深拷贝与浅拷贝

参考:https://juejin.cn/post/6844903830665035789

浅拷贝---浅拷贝是指复制对象的时候,只对第一层键值对进行独立的复制,如果对象内还有对象,则只能复制嵌套对象的地址

深拷贝---深拷贝是指复制对象的时候完全的拷贝一份对象,即使嵌套了对象,两者也相互分离,修改一个对象的属性,也不会影响另一个。其实只要递归下去,把那些属性的值仍然是对象的再次进入对象内部一 一进行复制即可。

引发深浅拷贝的一些问题

变量包含两种不同数据类型的值,基本类型和引用类型两种,基本类型就包括 String,Number,Boolean,Null,Undefined和Symbol,引用类型值指那些可能由多个值构成的对象,具体如下:

Object(Object、Array、Function)

当基本类型传递时,就是按值传递,不存在深浅拷贝的问题例如:

let a = 10; // 定义一个变量a并赋值为10
let b = a;  // 将a的值10赋值给b (a、b都是基本类型,值传递)
b++;  // b自加
console.log(a, b) // 10, 11

但是引用类型则是地址传递,将存放在栈内存中的地址赋值给接收的变量。

let a = ['a', 'b', 'c']; // 定义一个数组a并赋值 
let b = a;   // 数组是引用类型,采用地址传递,将a的地址赋值给b
b.push('d'); // 给b数组增加一个'd'元素
console.log(a) // ['a', 'b', 'c', 'd']
console.log(b) // ['a', 'b', 'c', 'd']

为了解决这个引用的问题,就出现了深浅拷贝。

浅拷贝常用方法

利用assign
const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };

const returnedTarget = Object.assign(target, source);
// 将source拼接到target中,重合部分则保留target部分

console.log(target);
// expected output: Object { a: 1, b: 4, c: 5 }

console.log(returnedTarget === target);
// expected output: true
利用spread运算符
const a = {
    en: { c: 'Bye' },
    de: 'Tschüss'
}
let b = { ...a }
b.de = 'Ciao';
b.en.c = 'aaa';
console.log(b.de) // Ciao
console.log(a.de) // Tschüss

console.log(b.en.c);  // aaa
console.log(a.en.c);  // aaa

但是要注意,浅拷贝只能拷贝第一层!上述代码中a对象中en的c在第二层,就被按地址传值,所以改ba也会变。

深拷贝常用方法

JSON.parse(JSON.stringify(target))

const a = {
  foods: {
    dinner: 'Pasta'
  }
}
let b = JSON.parse(JSON.stringify(a))
b.foods.dinner = 'Soup'
console.log(b.foods.dinner) // Soup
console.log(a.foods.dinner) // Pasta

但是这里要注意的是,你只能使用这种方法拷贝 JavaScript 原生的数据类型(非自定义数据类型)。

而且存在问题

  1. 会忽略 undefined
  2. 会忽略 symbol
  3. 不能序列化函数
  4. 不能解决循环引用的对象

ES6

const

  • const只能防止变量标识符的重新分配,如:

    const s = [1,2,3];
    s = [4,5,6];  //错误
    s[2] = 100; //成功
    console.log(s);  //打印[1,2,100]
    

Object.freeze(防止对象改变)

let obj = {
  name:"FreeCodeCamp",
  review:"Awesome"
};
Object.freeze(obj);
obj.review = "bad";
obj.newProp = "Test";
console.log(obj);

obj.reviewobj.newProp 赋值将导致错误,因为我们的编辑器默认在严格模式下运行,控制台将显示值 { name: "FreeCodeCamp", review: "Awesome" }

箭头函数

  • 箭头函数中没有this 的指向,在箭头函数中this 的指向会指向离他最近的那个作用域(不是很懂)
  • 箭头函数不能当做构造函数
  • 箭头函数中没有 arguments 这个参数

spread运算符

  • 通俗理解,将数组拆分为多个逗号隔开的迭代对象

    //*spread运算符
    const arr1 = [1, 2, 3, 4, 5];
    const arr2 = [6, 7, 8, 9, 10];
    let arr3;
    arr3 = [...arr2, 11, 12, 13, 14, 15, ...arr1];
    
    console.log(arr3);
    //输出[
       6,  7,  8,  9, 10, 11,
      12, 13, 14, 15,  1,  2,
       3,  4,  5
    ]
    

闭包

闭包 就是能够读取其他函数内部变量的函数。由于 js 作用域链,只有函数内部的子函数才能读取函数内部的局部变量,所以可以把闭包简单理解成"定义在一个函数内部的函数",本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁

//*闭包
for (var i = 0; i < 5; i++) {
    setTimeout(function () { console.log(i); }, 0);
}
//?本意是想打印0~4,但因为js为单线程,先执行了同步任务for循环,异步任务setTimeout被塞进排队队列,等i变成5,
//?for循环执行完毕,才执行setTimeout;

//*解决方法1:采用立即执行函数
for (var i = 0; i < 5; i++) {
    ((j) => {
        setTimeout(() => console.log(j), 0);
    })(i);
}

//*解决方法2:利用let形成闭包
for (let i = 0; i < 5; i++) {
    setTimeout(function () { console.log(i); }, 0);
}


//*匿名函数的作用,构建闭包,让外部可以访问内部变量
function box() {
    var age = 100;
    return function () {
        age++;
        return age;
    }
}

//*利用b
var b = box();//将返回值赋值给b
console.log(b());//实现累加
console.log(b());//实现累加
console.log(b());//实现累加
console.log(box()());//*无法实现累加,因为函数被调用完后被摧毁了,局部变量不在内存中
console.log(box()());//*无法实现累加,因为函数被调用完后被摧毁了,局部变量不在内存中
b = null;//使用闭包在调用结束时不会立即销毁内存,导致性能下降,所以需要解除占用

错题:

function Foo() {
    var i = 0;
    return function() {
        console.log(i++);
    }
}

var f1 = Foo(),
    f2 = Foo();
f1();
f1();
f2();

打印的值应为0 1 0,在函数内部定义一个匿名函数,这是最常见的创建闭包的方式,闭包会常驻内存,所以f1第一次 调用后,i会保留在内存中,而f2和f1的声明互相独立,故两个i不相关

Promise

//*Promise
const makeServerRequest = new Promise((resolve, reject) => {
    // responseFromServer 表示从服务器获得一个响应
    console.log("makeServerRequest的Promise实例被创建");
    let responseFromServer = false;

    if (responseFromServer) {
        resolve("We got the data");    //*resolve被执行,参数被传给then
    } else {
        reject("Data not received");   //*reject被执行,参数被传给catch
    }
});

makeServerRequest.then(result => {
    console.log(result);
}).catch(error => console.log(error)).finally(() => console.log("over"));
//?catch在所有Promise类的then被执行后才会被执行,finally无论是否成功都会执行

//*Promise的执行顺序以及与计时器的混用
let myPromise = new Promise((resolve, reject) => {
    console.log("myPromise的Promise实例被创建");
    console.log(1);
    resolve();
})

setTimeout(() => console.log(2));
myPromise.then(() => console.log(3));
console.log(4);
//?解释:当Promise实例被创建的时候,1就被打印,然后同步执行setTimeout,then以及打印4,前两者会产生两个新的异步任务打印2和打印3,
//?同步任务会被先完成,故先打印4,至于先打印2还是3,因为setTimeout是浏览器的api,Promise是JS引擎内部的任务,引擎内部任务优先级高于浏览器API
//?故先打印3再打印2

打印结果:

makeServerRequest的Promise实例被创建
myPromise的Promise实例被创建
1
4
3
Data not received
over
2

Symbol

symbol 是一种基本数据类型 (primitive data type)。Symbol()函数会返回symbol类型的值,该类型具有静态属性和静态方法。它的静态属性会暴露几个内建的成员对象;它的静态方法会暴露全局的symbol注册,且类似于内建对象类,但作为构造函数来说它并不完整,因为它不支持语法:"new Symbol()"。

Symbol用法

let apple = Symbol()
let banana = Symbol()

typeof apple // 'symbol'
apple == banana // false
apple === banana // false
  • 任意两个symbol都不相等

Symbol.for(key)

使用给定的key搜索现有的symbol,如果找到则返回该symbol。否则将使用给定的key在全局symbol注册表中创建一个新的symbol

let uid1 = Symbol.for('uid')
let uid2 = Symbol.for('uid')

uid1 // 'Symbol(uid)'
uid2 // 'Symbol(uid)'
uid1 === uid2 // true

Symbol.keyFor(sym)

从全局symbol注册表中,为给定的symbol检索一个共享的symbol key

let uid = Symbol.for('uid')
Symbol.keyFor(uid) // 'uid'

Symbol的使用场景

  • 解决属性名被覆盖的问题

    由于一个symbol值能作为对象属性的标识符,而且每个从Symbol()返回的symbol值都是唯一的,这样可以避免一些复杂对象中一些属性被覆盖的问题

    const obj = {
        'index': 'test0',
    }
    
    obj['index'] = 'test1';
    
    console.log(obj['index']);
    

    假设obj是一个比较复杂的对象,在后续想添加一些属性的时候又不小心用了'index‘这个字符串最为对象属性的标识符,这样就会把原来对象中的’index‘给覆盖掉

    但是如果用Symbol就能解决这个问题

    const index = Symbol('index');
    const obj = {
        [index]: 'test0',
    }
    
    obj['index'] = 'test1';
    
    console.log(obj[index]);
    

    这样就不用担心被字符串属性名覆盖,而且也不会出现被同一个Symbol覆盖的问题,因为一旦有了用Symbol作为对象属性标识符的意识,当你想用Symbol添加一个新的属性名的时候,一定会新建一个Symbol,假如重复声明就会出现报错

    const index = Symbol('index');
    const obj = {
        [index]: 'test0',
    }
    
    const index = Symbol('index'); //重复声明,出错
    obj['index'] = 'test1';
    
    console.log(obj[index]);
    

正则表达式

忽略大小写

let myString = "Hello, World!";
let myRegex = /Halo|world/i;  //*i表示忽略大小写
let result = myRegex.test(myString); // 修改这一行
console.log(result);

全局匹配

let twinkleStar = "Twinkle, twinkle, little star";
let starRegex = /twinkle/ig; //*g表示全局匹配,会返回所有的匹配而不只是第一个
let b = twinkleStar.match(starRegex); // 修改这一行
console.log(b);

用[]匹配多种可能性

let quoteSample = "Beware of bugs in the above code; I have only proved it correct, not tried it.";
let vowelRegex = /[aeiou]/ig; // 修改这一行
let result = quoteSample.match(vowelRegex); // 修改这一行
console.log(result);

用 ‘-’匹配多种连续字符

let quoteSample = "Blueberry 3.141592653s are delicious.";
let myRegex = /[h-s2-6]/ig; // 修改这一行
let result = quoteSample.match(myRegex); // 修改这一行
console.log(result);

用‘^'匹配单个未指定字符

let quoteSample = "3 blind mice.";
let myRegex = /[^aeiou0-9]/ig; // 修改这一行
let result = quoteSample.match(myRegex); // 修改这一行
console.log(result);

用‘+’匹配一次或多次出现的字符(零次或多次用*)

let difficultSpelling = "Mississippi";
let myRegex = /s+/ig; // 修改这一行
let result = difficultSpelling.match(myRegex);
console.log(result);

惰性匹配最小字符

let text = "<h1>Winter is coming</h1>";
let myRegex = /<.*?>/; // 修改这一行
let result = text.match(myRegex);
console.log(result);

用'^'匹配字符串开头,用'$匹配字符串结尾

let rickyAndCal = "Cal and Ricky both like racing.";
let calRegex = /^Cal/; // 修改这一行
let result = calRegex.test(rickyAndCal);
console.log(result);
let racingRegex = /racing.$/;
result = racingRegex.test(rickyAndCal);
console.log(result);

用'\w'表示所有大小写字母及数字(包括下划线),用'\W'表示所有非大小写字母及数字(包括下划线)

let quoteSample = "The five boxing wizards jump quickly.";
let alphabetRegexV2 = /\w/g; // 修改这一行
let result = quoteSample.match(alphabetRegexV2).length;
console.log(result);

用'\d'匹配所有数字,用'\D'匹配所有非数字

let movieName = "2001: A Space Odyssey";
let numRegex = /\d/g; // 修改这一行
let result = movieName.match(numRegex).length;
console.log(result);

例题:用户名限制

//?规则:用户名只能是数字字母字符。

//?用户名中的数字必须在最后。 数字可以有零个或多个。 用户名不能以数字开头。

//?用户名字母可以是小写字母和大写字母。

//?用户名长度必须至少为两个字符。 两位用户名只能使用字母。
let username = "J";
let userCheck = /^[a-z][a-z]+\d*$|^[a-z]\d\d+$/i;
// 修改这一行
let result = userCheck.test(username);
console.log(result);

用'\s'匹配空白字符,'\S'匹配非空白字符

let sample = "Whitespace is important in separating words";
let countWhiteSpace = /\s/g; // 修改这一行
let result = sample.match(countWhiteSpace);
console.log(result);

用{}指定匹配的上限和下限

let ohStr = "Ohhh no";
let ohRegex = /Oh{3,6}/g; // 修改这一行
let result = ohRegex.test(ohStr);
console.log(result);

用 ?可以检查前面零个或一个元素

let favWord = "favorite";
let favRegex = /favou?rite/; // 修改这一行
let result = favRegex.test(favWord);
console.log(result);

先行断言

//* (?=...)表示正向先行断言,会查看并确保搜索匹配模式中的元素存在,但实际上并不匹配,其中 ... 就是需要存在但不会被匹配的部分。
//* (?!...)表示负向先行断言,会查看并确保搜索匹配模式中的元素不存在,其中 ... 是希望不存在的匹配模式。 如果负向先行断言部分不存在,将返回匹配模式的其余部分。
//* 以下是使用先行断言以匹配大于 5 个字符且有两个连续数字的密码的例子
let sampleWord = "astronaut";
let pwRegex = /(?!\w{6,})(?!\w*\d\d\w*)/; // 修改这一行
let result = pwRegex.test(sampleWord);
console.log(result);

用()来检查混合字符组

let testStr = "Pumpkin";
let testRegex = /P(engu|umpk)in/;
let result = testRegex.test(testStr);
console.log(result);

用()来捕获组,来确定重复组

//?下面这个案例是只匹配三个空格间隔的相同数字
let repeatNum = "42 42 42";
let reRegex = /^(\d+)\s\1\s\1$/; //*解释:前面的(\d+)其实已经代表了一个重复组,要表示三个的话,再补两个\1即可
//*\1表示的是捕获整个字符串里第一个重复的字符串,这里要用^和$匹配结尾的原因是避免“42 42 42 42“也被匹配
let result = (repeatNum).match(reRegex);
let test = reRegex.test(repeatNum);
console.log(test);
console.log(result);

使用捕获组进行重复和替换,用$访问捕获组

let str = "one two three";
let fixRegex = /(\w+)\s(\w+)\s(\w+)/; // 修改这一行
let replaceText = "$3 $2 $1"; // 修改这一行
let result = str.replace(fixRegex, replaceText);
console.log(result);

使用捕获组 删除字符串开头和末尾的空格

let hello = "   Hello, World!  ";
let wsRegex = /^(\s+)|(\s+)$/g; // 修改这一行
let result = hello.replace(wsRegex, ''); // 修改这一行
console.log(result);
posted @ 2023-01-17 20:50  Davy-Chen  阅读(52)  评论(0编辑  收藏  举报