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() 方法
定义和用法
split() 方法用于把一个字符串分割成字符串数组。
语法
stringObject.split(separator,howmany)
参数 | 描述 |
---|---|
separator | 必需。字符串或正则表达式,从该参数指定的地方分割 stringObject。 |
howmany | 可选。该参数可指定返回的数组的最大长度。如果设置了该参数,返回的子串不会多于这个参数指定的数组。如果没有设置该参数,整个字符串都会被分割,不考虑它的长度。 |
返回值
一个字符串数组。该数组是通过在 separator 指定的边界处将字符串 stringObject 分割成子串创建的。返回的数组中的字串不包括 separator 自身。
但是,如果 separator 是包含子表达式的正则表达式,那么返回的数组中包括与这些子表达式匹配的字串(但不包括与整个正则表达式匹配的文本)。
提示和注释
注释:如果把空字符串 ("") 用作 separator,那么 stringObject 中的每个字符之间都会被分割。
注释:String.split() 执行的操作与 Array.join 执行的操作是相反的。
JavaScript join() 方法
实例
把数组中的所有元素转换为一个字符串:
var fruits = ["Banana", "Orange", "Apple", "Mango"];
var energy = fruits.join();
energy输出结果:
Banana,Orange,Apple,Mango
定义和用法
join() 方法用于把数组中的所有元素转换一个字符串。
元素是通过指定的分隔符进行分隔的。
浏览器支持
所有主要浏览器都支持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 原生的数据类型(非自定义数据类型)。
而且存在问题:
- 会忽略 undefined
- 会忽略 symbol
- 不能序列化函数
- 不能解决循环引用的对象
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.review
和 obj.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);