javascript笔记


js执行机制

1.先执行执行栈中的同步任务

2.异步任务(回调函数)放入任务队列中

3一旦执行栈中的所有同步任务执行完毕,系统聚会按次序读取任务队列中的异步任务,于是被读取的异步任务结束等待状态,进入执行栈,开始执行

同步和异步

同步任务

同步任务都在主线程上执行,形成一个执行栈

异步任务

js的异步是通过回调函数实现的

异步任务有以下三种类型

1普通事件,如click、resize等

2资源加载,如load、error等

3定时器,包括setIntterval、setTimeout等

由于主线程不断的重复获得任务、执行任务、再获取任务、再执行,所以这种机制被称为事件循环(event loop)

变量和常量

变量实质上是一个具名的值。顾名思义,变量的值是可变的。例如,在开发一个温度监控系统,系统中可能有个变量叫做currentTempC

let currentTempC = 22;

let关键字是es6新出的。在es6之前,var是唯一用来定义变量的关键字。

let仅用来声明变量,并且只能使用一次。

声明变量的时候也可以不指定初始值,此时变量会有一个特殊的默认值;undefined;

let currentTempC;

let关键字话可以同时声明多个变量:

let currentTempC, rooml=“conferrence_room_a”, room2=“lobby”;

上面的实例代码中声明了三个变量:currentTempC 没有初始值,所以它的值是undefined, room1、room2分别指定了初始值conferrence_room_a和lobby,它们都是字符串(文本)变量。

常量也可以储存值,但它与变量不同:常量一旦初始化就不能在改变。可以用常量来表示舒适的室内温度和最高温度(关键字cost也可以声明多个常量):

const Room_TEMP_c =21.5, MAX_TEMP_C =30;

变量和常量用哪个

一般情况下,应该优先使用常量。因为往往只是在努力的给某个数据取个好名字,二七并非改变它的值。是用常量的有优点是可以防止一些不应该更改的值被意外更改。

有一种情况必须用变量而非常量:

3.1 Math概述

Math对象不是构造函数,它具有数学常数和函数的属性和方法。跟数学相关的运算(求绝对值,取整、最大值等)可以使用Math中的成员。

Math.PI

Math. floor ()

Math.ceil ()

Math.round()

Math.abs ()

Math.max () /Math.min()

基本包装类型

var str = 'andy';

console.log(str.length);

var temp = new String('andy')

str = temp;

temp = null;

自定义属性的操作

1.获取属性值

element.属性 获取属性值

element.getAttribute(‘属性’);

区别:

element.属性 获取属性值(元素本身自带的属性)

element.getAttribute(‘属性’); 主要获得自定义的属性(标准)我们程序员自定义的属性

节点操作

1父级节点

parentNode属性可返回某节点的父节点,注意数最近的一个节点

如果指定的节点没有父节点则返回null

2子节点

cheriden

firistChild 第一个子节点/lastChild 最后一个节点 不管是文本点还是元素节点

fristElementChild 返回第一个子元素节点/ lastElementChild

实际开发的写法 既没有兼容性问题又返回第一个子元素

console.log(ol.children[ol.children.length-1]);

3兄弟节点

nexSibling 下一个兄弟节点 包含元素节点或者 文本节点等等

node.nextElementSibling 返回当前元素下一个兄弟元素节点,找不到则返回null

node.previousElemetSiblig 返回当前元素上一个兄弟节点,找不到则返回null

4创建和添加节点

  1. 创建节点元素节点

    var li = document.createElement('li');

    2.添加节点 node.appendChild(child) node 父级 child 子级 后面追加元素 类似于数组中的push

var ul = document.querySelector('ul');

ul.appendChild(li);

3.添加节点 node.insertBefore(child, 指点元素);

var lili = doucument.createElement('li');

ul.insertBefore(lili, ul.children[0]);

  1. 我们想要页面添加一个新的元素 : 1.添加元素 2.创建元素

5删除节点

node.removeChild(child)

6复制节点

node.cloneNode()

如果括号位空或者位false,则是浅拷贝,即只克隆复制节点本身,不克隆里面的子节点。

如果括号参数位true,则是深度拷贝,会复制节点本身以及里面的所有子节点。

var lili = ul.children[0].cloneNode(true);

ul.appenChild(lili)

三种动态创建元素区别

doucment.write()

element.innerHTML()

document.createElement()

区别

1 document.write是直接将内容写入页面的内容流,但是文档流执行完毕,则它会导致页面全部重绘

2 innerHTML 是将内容写入某个dom节点,不会导致页面全部重绘

3 innerHTML 创建多个元素效率更高(不要拼接字符串,采取数组形式拼接)用字符速度慢,结构稍微浮躁。

4 createElement () 创建多个元素效率稍低一点点,但是结构更清晰

总结:不同游览器下,innerHTML 效率要比 creatElement高

数组

// let arr = [1,1,2]

// let arr = new Array(1,1,1)

数组增删改查

{

/*push() 在数组后添加元素,并返回新的长度

* @params:数组要新增的元素(任意数据类型,一次可添加多个,用逗号隔开)

* @return:返回数组新增元素后的长度

* 是否改变原数组:改变

* */

let arr = [1, 2, 3];

let res = arr.push(6, 7, 8);

console.log(arr);

console.log(res);

}

{

unshift() 在数组最前添加元素

* @params:数组要新增的元素(任意数据类型,一次可添加多个,用逗号隔开)

* @return:返回数组新增元素后的长度

* 是否改变原数组:改变

* */

let arr = [1, 2, 3];

let res = arr.unshift(6, 7, 8);

console.log(arr);

console.log(res);

}

{

pop() 删除数组最后一个元素并返回该元素的值

* @params:无

* @return:返回数组被删除的元素

* 是否改变原数组:改变

* */

let arr = [1, 2, 3];

let res = arr.pop();

console.log(arr);

console.log(res);

}

{

shift() 删除并返回数组中第一个元素

*@params:无

*@return:返回数组被删除的元素

*是否改变原数组:改变

* */

let arr = [1, 2, 3];

let res = arr.shift();

console.log(arr);

console.log(res);

}

{

splice(index,len,[item]) 删除元素,并向数组添加一个新元素。

* @params:第一个参数,数组开始下标(必选),第二个参数删除几个(添加几个,取决于第三个是否有参数)(可选),第三个参数添加或替换什么(可选)

* @return: 返回值是一个数组,里面是删除项

* 是否改变原数组:改变

* */

// 删除

let arr = [1, 2, 3];

let res = arr.splice(0, 1)

console.log(arr); //[0,1]

console.log(res); //[2,3] || [1,2,3]

// 增加

let arr2 = [1, 2, 3];

let res2 = arr2.splice(2, 0, 1, 2)

console.log(arr2);

console.log(res2);

// 替换

let arr3 = [1, 2, 3];

let res3 = arr3.splice(1, 1, 1, 2)

console.log(arr3); //[1,1,2,3]

console.log(res3);

let arr4 = [1, 3, 2, 4]

let res4 = arr4.splice(1, 2) //3,2

let res5 = res4.splice(1, 1, 2) //2

let res6 = res5.splice(0, 1, 3, 5, 7) //2

//res6=[2]

let res7 = res6.push(6)

//res6=[2,6]

console.log(res7);

}

// 截取,拼接方法

{

Concat()连接两个或更多数组

* @params:多个任意项,可以是数组,可以是单个项

* @return:返回合并后的新数组

* 是否改变原数组:不改变

* */

let arr = [1, 2];

let res = arr.concat(1, [0, 1])

console.log(res);

console.log(arr);

}

{

Slice(start, end) 从某个已有的数组返回选定的元素

* @params 起始下标与结束下标,起始start必传,end不传默认复制到数组结束位置,可使用负值从数组的尾部选取元素。

* @return:返回复制的子数组

* 是否改变原数组:不改变

* */

let arr = [1, 2, 3, 6, 7, 8];

let res = arr.slice(1, 4);

console.log(arr); //

console.log(res); //2,3,6

}

// 查询方法

{

/* includes()用来判断一个数组是否包含一个指定的值

* @params:参数1必传,表示要查询的元素,参数2选传,表示从指定位置查起(若为负数,从后查起,负数超过数组长度,则置为0)

* @return:返回布尔值

* 是否改变原数组:不改变

* */

let arr = [1, 2, 3];

let res = arr.includes(2);

console.log(res);

}

{

/* indexOf()用来查询检索项在数组中的位置下标

* @params:参数1必传,表示要查询的元素,参数2选传,表示从指定位置查起

* @return:如若检索项存在,返回其下标,没有就返回 -1

* 是否改变原数组:不改变

* */

let arr = [1, 2, 3];

let res = arr.indexOf(2);

let res1 = arr.indexOf(8);

console.log(res);

console.log(res1);

}

{

lastIndexOf()用来查询检索项在数组中最后一次出现的位置下标

* @params:参数1必传,表示要查询的元素,参数2选传,表示从指定位置查起

* @return:如若检索项存在,返回其下标,没有就返回 -1

* 是否改变原数组:不改变

* */

let arr = [1, 2, 3, 2, 2, 3, 4];

let res = arr.lastIndexOf(3);

console.log(res);

}

排序,反转方法

{

/* sort()对数组进行排序

* @params:参数类型是函数

* @return:排序后的数组

* 是否改变原数组:改变

* */

let arr1 = [1, 3, 9, 2, 5, 3, 7, 4, 5]

let arr2 = [1, 3, 9, 1, 5, 4, 7, 8, 5]

升序

arr1.sort((c, d) => c - d)

降序

arr2.sort((a, b) => b - a)

console.log(arr1);

console.log(arr2);

}

{

reverse()对数组进行反转,把数组倒过来排列

* @params:无

* @return:倒转排序后的数组

* 是否改变原数组:改变

* */

let arr = [1, 3, 9, 1, 5, 4, 7, 8, 5]

arr.reverse()

console.log(arr)

}

转化为字符串

{

/*Join() 把数组的所有元素放入一个字符串,元素通过指定的分隔符进行分割

* @params:指定的分隔符

* @return:转换后的字符串

* 是否改变原数组:不改变

* */

let arr = [1, 2, 3, 3, 4, 5, 5, 7, 9]

let res = arr.join(‘-’)

console.log(res)

}

{

toString()将数组转换成字符串,用逗号分隔

* @params:无

* @return:转换后的字符串

* 是否改变原数组:不改变

* */

let arr = [1, 2, 3, 3, 4, 5, 5, 7, 9]

let res = arr.toString()

console.log(res)

}

{

let arr = [1,2,3,4,5,6]

let res1 = arr.slice(0,3) //123

let res2 = res1.push(2) //1232

let res3 = res1.push(3) //12323

let res4 = res1.splice(1,2) //23

let res6 = res4.reverse() //32

let res5 = res4.unshift(1) //132

console.log(res5);//3

}

函数

声明函数和调用函数

函数在使用时分为两步:声明函数和调用函数

声明函数本身并不会执行代码,只有调用函数时才会执行函数体代码

1.声明函数

function 函数名(){
函数体

}

(1)声明函数的关键字全部小写

(2)函数是做某件事情,函数名一般是动词

(3)函数不调用自己不执行

2调用函数

函数名();

形参和实参

function 函数名 (形参1,形参2){//在声明函数的小括号里面的是形参(形式上的参数)} 形参是接受实参的

函数名(实参1,实参2) //在函数调用的小括号里面是实参(实际的参数)

形参实参匹配问题

1.如果实参的个数和形参的个数一致,则正常输出结果

2.如果实参的个数多于形参的个数,则会取形参的个数

3.如果实参的个数小于形参的个数,形参可以看做是不用声明的变量,num2是一个变量但是没有接受值,结果就是undefined

建议:让形参和实参的个数相匹配

函数的返回值

函数返回值的格式

function函数名() {

return 需要返回的结果

}

1我们函数只是实现某种功能,最终的结果需要返回给函数的调用者函数名()通过return实现的

2只要函数遇到return酒吧后面的结果,返回给函数的调用者,函数名()=return后面的结果

3return后面的代码不会被执行

4return只能返回一个值(返回的结果是最后一个值)

5函数如果有return则返回return后面的值

如果没有则返回undefined

break,continue,return的区别

break:结束当前的循环体(如for、while)
continue:跳出本次循环,继续执行下次循环(如for、while)
return:不仅可以退出循环,还能够返回return语句中的值,同时还可以结束当前的函数体内的代码

arguments的使用

不确定有多少个参数传递的时候,可以用arguments来获取。arguments对象中存储了传递的所有实参。

只有函数才有arguments对象,而且是每个函数都内置好了这个arguments。

arguments展示形式是一个伪数组,因此可以进行遍历。伪数组具有以下特点:

1.具有length属性

2.按索引方式储存数据

3.不具有数组的push,pop等方法

流程控制

分支架构

if语句

1.if的语法结构 如果的意思

if(条件表达式){
`}

2.执行思路

如果if里面的调价表达式结果为真true 则执行大括号里面的 执行语句

如果if 条件表达式结果为假 则不执行大括号里面的语句 则执行if 语句后面的代码

3 if else

if(){
}else {
}

执行思路 如果表达式结果为真 那么执行语句1 否则 执行语句2

for in 语句

for/in语句用于循环对象属性

循环中的代码每执行一次,就会对数组的元素或者对象的属性进行一次操作

不要使用 for/in 语句来循环数组的索引,你可以使用 for 语句替代。

语法

for (var in object) {
 执行的代码块
}
<!DOCTYPE html>
<html>
<body>

<p>点击按钮循环对象属性。</p>

<button onclick="myFunction()">点我</button>

<p id="demo"></p>

<script>
function myFunction() {
    var person = {fname:"John", lname:"Doe", age:25}; 
    
    var text = "";
    var x;
    for (x in person) {
        text += person[x] + " ";
    }
    document.getElementById("demo").innerHTML = text;
}
</script>

</body>
</html>

for of语句

for 循环除了使用 in 方式来循环数组,还提供了一个方式: of , 遍历数组时更加方便。

for…of 是 ES6 新引入的特性。它既比传统的for循环简洁,同时弥补了forEach和for-in循环的短板。

for-of的语法:

for (var value of myArray) {
  console.log(value);
}

for-of 的语法看起来跟 for-in 很相似,但它的功能却丰富的多,它能循环很多东西。

for-of 循环使用例子:

循环一个数组(Array):

let iterable = [10, 20, 30];

for (let value of iterable) {
  console.log(value);
}
// 10
// 20
// 30

我们可以使用const来替代let,这样它就变成了在循环里的不可修改的静态变量。

循环一个字符串:

let iterable = "boo";

for (let value of iterable) {
  console.log(value);
}
// "b"
// "o"
// "o"

DOM 重点核心

文档对象模型,是w3c组织推荐的处理可扩展标记语言(html或者xml)的标准编程接口

w3c已经定义了一系列的dom接口,通过这些dom接口可以改版网页的内容、结构和样式。

1对于JavaScript,为了能够使JavaScrip操作HTML , JavaScript就有了一套自己dom编程接口。

2.对于HTML, dom使得html形成一棵dom树.包含文档、元素、节点

关于dom操作,我们主要针对与元素的操作。主要有创建、增、删、改、查、属性操作、事件操作

事件高级导读

1注册事件

给元素添加事件,称为注册事件或者绑定事件。

注册事件有两种方式:传统方式和方法监听注册方式


传统注册方式

利用on开头的事件onclick

特点:注册事件的唯一性

同一个元素同一个事件只能设置一个处理函数,最后注册的处理函数将会覆盖前面注册的处理函数—


方法监听注册方式

w3c标准 推荐方法

addEventListener()它是一个方法

ie9之前的ie 不支持此方法, 可使用attachEvent()代替

特点:同一个元素同一个事件可以注册多个监听器

按注册顺序依次执行

eventTarget.addEventLIstener('type',listener[, useCapture])

`eventTarget.addEventLIstener()发给发将指定的监听器注册到eventarget(目标对象)上,当该对象出发指定的事件时,就会执行事件处理函数

该方法接收三个参数:

type:事件类型字符串,比如click、mouseover,注意不要带on,属于字符串要加引号

listener:事件处理函数,事件发生时,会调用该监听函数

useCapture:可选参数,是一个布尔值,默认是false

2删除事件

1传统注册方式

evenTarget.onclick = null;

2方法监听注册方式

1 eventTarget.remoeEvenListener(type, listener[, useCapture]);

2 eventTarget.detachEvent(eventNaeWithOn, callback);

3事件对象

evenTarget.onclick = function(event){}

evenTarget.addEventListener(‘click’ , function(event{}))

这个event就是事件对象,还可以写成e或者evt

event对象代表事件的状态,比如键盘按键的状态、鼠标的位置、鼠标按钮的状态。

简单理解:事件发生后,跟事件相关的一系列信息数据的集合都放到这个对象里面,这个对象就是事件对象event,他有很多属性和方法

event是个形参,系统帮我们设定为事件对象,不需要传递实参过去。

但我们注册事件时,event对象就会被系统自动创建,并依次传递给事件监听器(事件处理函数)。

事件对象的常见属性和方法

e.target 返回触发事件的对象 标准

e.srcEl^nent 返回触发事件的对象 非标准ie6・8使用

e.type 返回事件的类型比如click mouseover不带on

e.cancelBubble 该属性阻止冒泡非标准ie6・8使用

e.returnValue 该属性阻止默认事件(默认行为)非标准ie6-8使用比如不让链接跳转

e.preventDefault() 该方法阻止默认事件(默认行为)标准比如不让链接跳转

e.stopPropagation( ) 阻止圈泡标准

事件委托

事件委托的原理

不是每个子节点单多设置事件监听器,而是事件监听器设置在其父节点上,然后利用冒泡原理设置每个子节点。

4键盘事件对象

onkeyup 某个键盘被松开时触发

onkeydown 某个键盘按键按下时触发 能识别功能键

onkeypress 某个键盘按键被按下时触发 但是它不识别功能键 比如 ctrl shift 箭头等

三个事件的执行顺序 keydown - - keypress – keyup


keyCode 返回该键的ASCII值

注意:onkeydown 和 onkeyup 不区分字母大小写,onkeypress区分字母大小写。在实际开发中,我们更多是哦那个keydown和keyup,他能识别所有的键(包括功能键)Keypress不识别功能键,但是keyCode属性能区分大小写,返回不同的ASCII值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mQVtzUKt-1653612972527)(G:\typora图片\js\键盘事件对象.PNG)]

BOM

bom是什么?

bom即游览器对象模型,它提供了独立于内容二与游览器窗口进行交互的对象,其核心对象是window。

bom由一系列相关的对象构成,并且每个对象都提供了很多方法与属性


windows对象常见事件

页面加载事件

ie9以上才支持

load 要等内容全部加载完毕,包含页面dom元素 图片 flash css 等

DOMContentLoaded 是 DOM 加载完毕,不包含图片falsh css 等就可以执行 加载速度笔 load更快一些

调整窗口大小

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WofmZikk-1653612972528)(G:\typora图片\js\bom调整窗口大小.PNG)]

定时器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TTq7mBvm-1653612972529)(G:\typora图片\js\bom定时器.PNG)]

clearTimeout () 方法取消了先前通过调用setTimeout()建立的定时器

setTimeout 延时时间到了,就去调用这个回调函数,只调用一次 就结束了这个定时器

setInterval 每隔这个延时时间,就去调用这个回调函数,会调用很多次,重复调用这个函数

location对象

统一资源定位符(uniform resource locator;URL)时互联网上标准资源地址。互联网上的每个文件都有一个唯一的url,它包含的信息指出文件的位置以及游览器应该怎么处理他

navigator对象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DNKBGt50-1653612972529)(G:\typora图片\js\navigator对象.PNG)]

history对象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-omAhYQKq-1653612972530)(G:\typora图片\js\history对象.PNG)]

正则表达式

常用函数

​ match,test,replace

//match
//返回匹配的值的数组或null。
var regex = /hello/;
var array="hello";
array.match(regex);//true

//test
//返回true或false
var regex = /hello
regex.test("hello");//true

//replace
//正则替换
var str = 'cqyzsC012QzAabcd';
// 全局匹配 忽略大小写 
var str1 = str.replace(/a|c|q/gi, '你好')
console.log(str1);//["你好你好yzs你好012你好z你好你好b你好d"]

字符匹配

​ 两种模糊匹配(横向,纵向)

例:

var regex = /hello/;
console.log( regex.test("hello")) =>true //只允许匹配hello

横向模糊匹配
:
一个正则可匹配的字符串的长度不是固定的,可以是多种情况的。
var regex = /ab{2,5}c/g;
var string = "abc abbc abbbc abbbbc abbbbbc abbbbbbc";
console.log( string.match(regex) ); // => ["abbc", "abbbc","abbbbc","abbbbbc"]
案例中用的正则是/ab{2,5}c/g,后面多了g,它是正则的一个修饰符。表示全局匹配,即在目标字符串中按顺序找到满足匹配模式的所有子串,强调的是“所有”,而不只是“第一个”。g是单词global的首字母。


纵向模糊匹配
一个正则匹配的字符串,具体到某一位字符时,它可以不是某个确定的字符,可以有多种可能。
var regex = /a[123]b/g;
var string = "a0b a1b a2b a3b a4b";
console.log( string.match(regex) ); // => ["a1b", "a2b", "a3b"]
[abc],表示该字符是可以字符“a”、“b”、“c”中的任何一个

括号总结

​ {},[],()

例:

//大括号  量词符 里面表示重复次数
var reg = /^abc{3}$/; 
//只是让c重复三次  abccc
//中括号 字符集合,匹配方括号中的任意字符
var reg = /^[abc]$/;
//a也可以 b也可以 c也可以 a||b||c
//小括号 表示优先级
var reg = /^(abc){3}$/; 
//让abc重复三次

字符组

​ 范围表示,排除字符组,常用的简写

例:

//范围表示
[123456abcdefGHIJKLM] => [1-6a-fG-M]

匹配“a”、“-”、“z”这三者中任意一个字符
[-az][az-][a\-z]

//排除字符组
[^abc]
表示是一个除"a""b""c"之外的任意一个字符。字符组的第一位放^(脱字符),表示求反的概念

//常用的简写
\d就是[0-9]。表示是一位数字。记忆方式:其英文是digit(数字)。

\D就是[^0-9]。表示除数字外的任意字符。

\w就是[0-9a-zA-Z_]。表示数字、大小写字母和下划线。记忆方式:w是word的简写,也称单词字符。

\W[^0-9a-zA-Z_]。非单词字符。

\s是[ \t\v\n\r\f]。表示空白符,包括空格、水平制表符、垂直制表符、换行符、回车符、换页符。记忆方式:s是space character的首字母。 

\S[^ \t\v\n\r\f]。 非空白符。

量词

​ 简写形式,贪婪匹配和惰性匹配

例:

//量词符:用来设定某个模式出现的次数
//简单理解:就是人下面的a这个字符重复多少次
{m,} 表示至少出现m次。

{m} 等价于{m,m},表示出现m次。

? 等价于{0,1},表示出现或者不出现。记忆方式:问号的意思表示,有吗?

+ 等价于{1,},表示出现至少一次。记忆方式:加号是追加的意思,得先有一个,然后才考虑追加。

* 等价于{0,},表示出现任意次,有可能不出现。记忆方式:看看天上的星星,可能一颗没有,可能零散有几颗,可能数也数不过来。

//贪婪匹配
var regex = /\d{2,5}/g;
var string = "123 1234 12345 123456";
console.log( string.match(regex)); // => ["123", "1234", "12345", "12345"]
//其中正则/\d{2,5}/,表示数字连续出现2到5次。会匹配2位、3位、4位、5位连续数字。只要在能力范围内,越多越好
      
//惰性匹配
var regex = /\d{2,5}?/g;
var string = "123 1234 12345 123456";
console.log( string.match(regex) ); // => ["12", "12", "34", "12", "34", "12", "34", "56"]
//其中/\d{2,5}?/表示,虽然2到5次都行,当2个就够的时候,就不在往下尝试了

多选分支

例:

//用| =>表示其中任何之一
var regex = /good|nice/g;
var string = "good idea, nice try.";
console.log( string.match(regex) ); // => ["good", "nice"]
//要匹配"good"和"nice"可以使用/good|nice/

var regex = /good|goodbye/g;
var string = "goodbye";
console.log( string.match(regex) ); // => ["good"]
//用/good|goodbye/,去匹配"goodbye"字符串时,结果是"good":

var regex = /goodbye|good/g;
var string = "goodbye";
console.log( string.match(regex) ); // => ["goodbye"]
//修改过后会优先匹配|前面的字符,也就是说,分支结构也是惰性的,即当前面的匹配上了,后面的就不再尝试了

JavaScript高级程序设计

集合引用类型

object

创建object有两种方式,

//第一种new操作符和object构造函数
let person = new Object();
person.name = "Nicholas";
person.age = 29; 

//第二种对象字面量方法
let person = {
 name: "Nicholas",
 age: 29
};

//在对象字面量表示法中,属性名可以是字符串或数值,比如:
let person = {
 "name": "Nicholas",
 "age": 29,
 5: true
}; 


let person = {}; // 与 new Object()相同
person.name = "Nicholas";
person.age = 29;

array

创建数组

//有几种基本的方式可以创建数组。一种是使用 Array 构造函数,比如:
let colors = new Array();

//如果知道数组中元素的数量,那么可以给构造函数传入一个数值,然后 length 属性就会被自动创
//建并设置为这个值。比如,下面的代码会创建一个初始 length 为 20 的数组:
let colors = new Array(20);

//也可以给 Array 构造函数传入要保存的元素。比如,下面的代码会创建一个包含 3 个字符串值的
//数组:
let colors = new Array("red", "blue", "green");

//创建数组时可以给构造函数传一个值。这时候就有点问题了,因为如果这个值是数值,则会创建一
//个长度为指定数值的数组;而如果这个值是其他类型的,则会创建一个只包含该特定值的数组。下面看
//一个例子:
let colors = new Array(3); // 创建一个包含 3 个元素的数组
let names = new Array("Greg"); // 创建一个只包含一个元素,即字符串"Greg"的数组

//在使用 Array 构造函数时,也可以省略 new 操作符。结果是一样的,比如:
let colors = Array(3); // 创建一个包含 3 个元素的数组
let names = Array("Greg"); // 创建一个只包含一个元素,即字符串"Greg"的数组

//另一种创建数组的方式是使用数组字面量(array literal)表示法。数组字面量是在中括号中包含以
//逗号分隔的元素列表,如下面的例子所示:

let colors = ["red", "blue", "green"]; // 创建一个包含 3 个元素的数组
let names = []; // 创建一个空数组
let values = [1,2,]; // 创建一个包含 2 个元素的数组

ES6

类和对象

注意事项

1、再es6中类没有变量提升,所以必须先定义类,才能通过类实例化对象

2、类里面的共有的属性和方法一定要加this使用

3、constructor里面this指向实例对象,方法里面的this指向这个方法的调用者

构造函数和原型

构造函数是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,总与new一起使用,我们可以把对象中一些公共的熟悉感和方法抽取出来,然后封装到这个函数里面。


es5中的新方法

数组方法

fillter()

用于创建一个新的数组,新数组中的元素是通过检查指定数组中复合条件的所有元素,主要用于筛选数组并且返回一个新数组


some()方法

用于检测数组中的元素是否满足指定条件,查找数组中是否有满座条件的数组

注意返回的是布尔值,查到返回true,没查到返回false。如果找到第一个满足条件的元素,则终止循环,不查找

函数进阶

this指向问题

 //1普通函数
    function fn() {
        console.log('普通函数的this'+this)
    }
	//指向的是window
    fn();

	//2对象的方法
     var o = {
        sayhi: function () {
            console.log(this)
        }
    }
     //指向o对象
    o.sayhi();

    //3构造函数
    //this指向ldh这个实例对象 原型对象里面的this 指向的也是ldh 这个实例对象
    function Star() {
        console.log(this)
    }
    Star.prototype.sing = function (){
    
    }
    var ldh = new Star();


	//4绑定事件函数
    btn.onclick = function (){
       console.log(this)} //this指向调用者

 	 //5定时器函数
   nsetInterval(function (){console.log(this)},1000);//this指向window

	//6立即执行函数
  	(function (){
        console.log(this)
   })() //this指向window

改变函数内部this指向的三种方法

call()方法

call方法会调用函数,并且改变函数内部this指向

bind()方法

bind方法不会调用函数,但是能改变函数内部this指向

返回有指定的this值和初始化参数改造的原函数拷贝

如果有的函数我们不需要立即调用,又想改变这个函数内部的this指向此时使用bind

总结

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

区别点:

1、call和apply回调用函数,并且改变函数内部this指向

2、call和apply传递的参数不一样,call传递参数aru1,aru2…形式 apply必须数组形式

3、bing不会调用函数,剋改变函数内部this指向

主要应用场景

1、call经常做继承

2、apply经常跟数组有关系,比如借助与数学对象实现数组最大值最小值

3、bing不调用函数,但是还想改变this指向,比如改变定时器内部的this指向。

高阶函数

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

function fn(a, b, callback) {
        console.log(a + b)
        callback && callback();
    }

    fn(1, 2, function () {
            console.log('我是最后调用的')
        }
    );
//此时fn就是一个高阶函数
//函数也是一种数据类型,同样可以作为参数,传递给另外一个参数使用,最典型的就是作为回调函数

filter

filter(function or None, iterable) 筛选可迭代对象iterable 中的数据,返回一个可迭代对象,此可迭代对象将对iterable生成的数据进行筛选

filter中的回到函数必须返回一个boolean值

function 数将对iterable中每个元素进行求值,返回Flase则将此数据丢充,
返回True则保留此数据

const nums = [10,20,30,404,90,100,234,99];
let newNums = nums.filter(function (n){
    return n < 100;
})
console.log(newNums);

严格模式

好处:

1、消除js语法的不合理、不严谨、减少怪异行为

2、消除掉吗运行的一些不安全指出,保证代码运行的安全

3、提高编译器效率,增加运行速度

4、警用ecmascript的未来版本中可能会定义的一些语法,为未来版本的js做好铺垫。

变化

1.变量先声明在使用

2.不能删除已经定义的变量

3.严格模式下全局作用域中的函数this是undefined

4.严格模式下,如果构造函数不加new调用,this会报错

5.函数不能有重名的参数

6.函数必须声明在顶层

闭包

闭包指有权访问另一个函数作用域中的变量的函数。

作用:延申了变量的作用范围

变量作用域

1.函数内部可以使用全局变量

2.函数外部不可以使用局部变量

3.当函数执行完毕,本作用域内的局部遍历会销毁

递归

如果一个函数在内部调用其本身,那么这个函数就是递归函数

递归函数的作用和循环效果一样

由于递归容易发生‘栈溢出’, 所有必须要加退出条件return

浅拷贝

1、浅拷贝只是拷贝一层,更深层对象级别的只拷贝引用

2、深拷贝拷贝多层,每一集别的数据都会拷贝

3、Object.assign(target,…sources) es6新增方法可以浅拷贝 浅拷贝的语法糖

正则表达式

用test方法来检测字符串是否符合要求

量词符

*相当于可以出现零次或者很多次

+相当于要出现 >=1 次或者很多次

?相当于只能出现1次或者不出现

{ 3 } 只能重复3次

{3,}相当于出现 大于等于3次

{3,16} 大于3次 并且 小于等于16

变量和常量

let

let声明的变量具有块级作用域

1.在一个大括号中let关键字才具有块级作用域,var关键字是不具备这个特点的

2.let要先声明在使用,因为let不存在变量提升

3.暂时性死区

var num = 10;
if(true){
console.log(num);
let num = 20; //num id not defind 此时let里的num和log绑定一个块级形成了暂时性死区
}

const

作用:声明常量,常量就是值(内存地址)不能变化的量

1.具有块级作用域

2.声明时必须赋值

3.赋值后不能更改,除了复杂数据类型

let、const、var的区别

1、使用var声明的变量,其作用域为该语句所在的函数内,且存在变量提升现象

2、使用let声明的变量,起作用域为噶语句所在的代码块内,不存在变量提升问题

3、使用const声明的时常量,在后面出现的代码不能在修改常量的值

箭头函数

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

function fn(num2,num3){
       return num2+num3;
 }
console.log(fn(1, 5));;

const  sum = (num1,num2) => num1 + num2;
console.log(sum(1, 4));
   

//如果形参只有一个,可以省略小括号
function fn(v){
    return v;
}
const fn = v => v;

箭头函数中的this指向

箭头函数不绑定this关键字,箭头函数中的this,指向的时函数定义位置的上下文this

promise

1、为什么需要promise

promise解决了回调地狱

2、promise的基本使用

promise是一个构造函数,通过new关键字实例化对象

语法

new Promise((resolve,reject)=>{})
  • Promise接受一个函数作为参数

  • 在函数中接受两个参数

    • resolve 成功函数

    • reject 失败函数

promise实例

promise实例有两个属性

  • ​ state:状态

  • ​ result:结果

1)promise的状态

第一种状态:pending(准备,待解决,进行中)

第二种状态:fulfilled(已完成,成功)

第三种状态:rejected(已拒绝,失败)

2)promise状态的改变

通过resolve和reject改变当前状态的改变

实例

  const p = new Promise((resolve, reject) => {
        //resolve():调用函数,可以使promise对象的状态改成fulfilled
        //reject():调用函数,使当前promise对象的状态改成rejected
        resolve();
    })
    console.dir(p) //fulfilled
  • ​ resolve():调用函数,可以使promise对象的状态改成fulfilled
  • ​ reject():调用函数,使当前promise对象的状态改成rejected

promise状态的改变是一次性的

3)promise的结果

实例

 const p = new Promise((resolve, reject) => {
        //通过调用resolve,传递参数,改变当前promise对象的结果
        // resolve('成功的结果');
        reject('失败的结果');
    })
    console.dir(p)

3、promise的方法

1)then方法

    const p = new Promise((resolve, reject) => {
        //通过调用resolve,传递参数,改变当前promise对象的结果
        // resolve('成功的结果');
        reject('失败的结果');
    })
    /*
    then方法函数
    参数
    1、是一个函数
    2、还是一个函数
    返回值:是一个promise对象
     */
    p.then((value)=>{
        //当promise的状态是fulfilled时,执行
        console.log(value)
    },(reason)=>{
        //当promise的状态是rejected时,执行
        console.log(reason)
    })
    console.log(p)
  • 在then方法的参数函数中,通过形参使用promise对象的结果

then方法返回一个新的promise实例,状态是pending

promise的状态不改变,不会执行then里的方法

在then方法中,可以通过return将返回的promise实例改为fulfilled状态

     //如果promise的状态不改变,then里的方法不会执行
    const p = new Promise((resolve, reject) => {
        resolve(1231)
    })
    const t = p.then((value) => {
        console.log('成功',value)
        return 132131;
        //使用return可以将t实例的状态改为fulfilled
    }, (reason) => {
        console.log('失败');
    })
    t.then((value) => {
        console.log('成功2',value); 
    }, (reason) => {
        console.log('失败');
    })

如果在then方法中,出现代码错误,会将返回的promise实例改为rejected状态

//如果promise的状态不改变,then里的方法不会执行
    const p = new Promise((resolve, reject) => {
        resolve(1231)
    })
    const t = p.then((value) => {
        console.log('成功',value)
        //使用return可以将t实例的状态改为fulfilled
        // return 132131;

        //如果这里的代码出错,会将t实例的状态改成rejected
        console.log(a)
    }, (reason) => {
        console.log('失败');
    })
    t.then((value) => {
        console.log('成功2',value);
    }, (reason) => {
        console.log('失败',reason);
    })

2)catch方法

实例

   const p = new Promise((resolve, reject) => {
        // reject();
        // console.log(a)
        throw new Error('出错了')
    })
    //思考:catch中的参数哈数在什么时候被执行?
    //1.当promise的状态改为rejected时,被执行
    //2.当promise执行体中出现代码错误时,被执行
    p.catch((res) => {
        console.log('失败',res);
    })
    console.log(p)

4、用promise解决回调地狱

实例

      function getData(url, data = {}) {
            return new Promise((resolve, reject) => {
                $.ajax({
                    type: 'GET',
                    url: url,
                    data: data,
                    success: function (res) {
                        //promise的状态为成功,修改promise的结果为res
                        resolve(res);
                    },
                    error: function (res) {
                        //promise的状态为失败,修改promise的结果为res
                        reject(res)
                    }
                })
            })
        }

        getData('resource/content.json')
            .then((data) => {
                const {id} = data //对象解构
                return getData('resource/content2.json', {id})
            })
            .then((data) => {
                const {username} = data
                return getData('resource/content3.json', {username})
            })
            .then((data) => {
                console.log(data.title)
            })

扩展运算符

1、指数运算符(**)

ES2016 新增了一个指数运算符(**)。

2 ** 2 // 4
2 ** 3 // 8

这个运算符的一个特点是右结合,而不是常见的左结合。多个指数运算符连用时,是从最右边开始计算的。

// 相当于 2 ** (3 ** 2)
2 ** 3 ** 2
// 512

上面代码中,首先计算的是第二个指数运算符,而不是第一个。

指数运算符可以与等号结合,形成一个新的赋值运算符(**=)。

let a = 1.5;
a **= 2;
// 等同于 a = a * a;

let b = 4;
b **= 3;
// 等同于 b = b * b * b;

2、链判断运算符(?.)

编程实务中,如果读取对象内部的某个属性,往往需要判断一下,属性的上层对象是否存在。比如,读取message.body.user.firstName这个属性,安全的写法是写成下面这样。

// 错误的写法
const  firstName = message.body.user.firstName || 'default';

// 正确的写法
const firstName = (message
  && message.body
  && message.body.user
  && message.body.user.firstName) || 'default';

上面例子中,firstName属性在对象的第四层,所以需要判断四次,每一层是否有值。

三元运算符?:也常用于判断对象是否存在。

const fooInput = myForm.querySelector('input[name=foo]')
const fooValue = fooInput ? fooInput.value : undefined

上面例子中,必须先判断fooInput是否存在,才能读取fooInput.value

这样的层层判断非常麻烦,因此 ES2020 引入了“链判断运算符”(optional chaining operator)?.,简化上面的写法。

const firstName = message?.body?.user?.firstName || 'default';
const fooValue = myForm.querySelector('input[name=foo]')?.value

上面代码使用了?.运算符,直接在链式调用的时候判断,左侧的对象是否为nullundefined。如果是的,就不再往下运算,而是返回undefined

下面是判断对象方法是否存在,如果存在就立即执行的例子。

iterator.return?.()

上面代码中,iterator.return如果有定义,就会调用该方法,否则iterator.return直接返回undefined,不再执行?.后面的部分。

对于那些可能没有实现的方法,这个运算符尤其有用。

if (myForm.checkValidity?.() === false) {
  // 表单校验失败
  return;
}

上面代码中,老式浏览器的表单对象可能没有checkValidity()这个方法,这时?.运算符就会返回undefined,判断语句就变成了undefined === false,所以就会跳过下面的代码。

链判断运算符?.有三种写法。

  • obj?.prop // 对象属性是否存在
  • obj?.[expr] // 同上
  • func?.(...args) // 函数或对象方法是否存在

下面是obj?.[expr]用法的一个例子。

let hex = "#C0FFEE".match(/#([A-Z]+)/i)?.[1];

上面例子中,字符串的match()方法,如果没有发现匹配会返回null,如果发现匹配会返回一个数组,?.运算符起到了判断作用。

下面是?.运算符常见形式,以及不使用该运算符时的等价形式。

a?.b
// 等同于
a == null ? undefined : a.b

a?.[x]
// 等同于
a == null ? undefined : a[x]

a?.b()
// 等同于
a == null ? undefined : a.b()

a?.()
// 等同于
a == null ? undefined : a()

上面代码中,特别注意后两种形式,如果a?.b()a?.()。如果a?.b()里面的a.b有值,但不是函数,不可调用,那么a?.b()是会报错的。a?.()也是如此,如果a不是nullundefined,但也不是函数,那么a?.()会报错。

使用这个运算符,有几个注意点。

(1)短路机制

本质上,?.运算符相当于一种短路机制,只要不满足条件,就不再往下执行。

a?.[++x]
// 等同于
a == null ? undefined : a[++x]

上面代码中,如果aundefinednull,那么x不会进行递增运算。也就是说,链判断运算符一旦为真,右侧的表达式就不再求值。

(2)括号的影响

如果属性链有圆括号,链判断运算符对圆括号外部没有影响,只对圆括号内部有影响。

(a?.b).c
// 等价于
(a == null ? undefined : a.b).c

上面代码中,?.对圆括号外部没有影响,不管a对象是否存在,圆括号后面的.c总是会执行。

一般来说,使用?.运算符的场合,不应该使用圆括号。

(3)报错场合

以下写法是禁止的,会报错。

// 构造函数
new a?.()
new a?.b()

// 链判断运算符的右侧有模板字符串
a?.`{b}`
a?.b`{c}`

// 链判断运算符的左侧是 super
super?.()
super?.foo

// 链运算符用于赋值运算符左侧
a?.b = c

(4)右侧不得为十进制数值

为了保证兼容以前的代码,允许foo?.3:0被解析成foo ? .3 : 0,因此规定如果?.后面紧跟一个十进制数字,那么?.不再被看成是一个完整的运算符,而会按照三元运算符进行处理,也就是说,那个小数点会归属于后面的十进制数字,形成一个小数。


3、Null 判断运算符

读取对象属性的时候,如果某个属性的值是nullundefined,有时候需要为它们指定默认值。常见做法是通过||运算符指定默认值。

const headerText = response.settings.headerText || 'Hello, world!';
const animationDuration = response.settings.animationDuration || 300;
const showSplashScreen = response.settings.showSplashScreen || true;

上面的三行代码都通过||运算符指定默认值,但是这样写是错的。开发者的原意是,只要属性的值为nullundefined,默认值就会生效,但是属性的值如果为空字符串或false0,默认值也会生效。

为了避免这种情况,ES2020 引入了一个新的 Null 判断运算符??。它的行为类似||,但是只有运算符左侧的值为nullundefined时,才会返回右侧的值。

const headerText = response.settings.headerText ?? 'Hello, world!';
const animationDuration = response.settings.animationDuration ?? 300;
const showSplashScreen = response.settings.showSplashScreen ?? true;

上面代码中,默认值只有在左侧属性值为nullundefined时,才会生效。

这个运算符的一个目的,就是跟链判断运算符?.配合使用,为nullundefined的值设置默认值。

const animationDuration = response.settings?.animationDuration ?? 300;

上面代码中,如果response.settingsnullundefined,或者response.settings.animationDurationnullundefined,就会返回默认值300。也就是说,这一行代码包括了两级属性的判断。

这个运算符很适合判断函数参数是否赋值。

function Component(props) {
  const enable = props.enabled ?? true;
  // …
}

上面代码判断props参数的enabled属性是否赋值,基本等同于下面的写法。

function Component(props) {
  const {
    enabled: enable = true,
  } = props;
  // …
}

??本质上是逻辑运算,它与其他两个逻辑运算符&&||有一个优先级问题,它们之间的优先级到底孰高孰低。优先级的不同,往往会导致逻辑运算的结果不同。

现在的规则是,如果多个逻辑运算符一起使用,必须用括号表明优先级,否则会报错。

// 报错
lhs && middle ?? rhs
lhs ?? middle && rhs
lhs || middle ?? rhs
lhs ?? middle || rhs

上面四个表达式都会报错,必须加入表明优先级的括号。

(lhs && middle) ?? rhs;
lhs && (middle ?? rhs);

(lhs ?? middle) && rhs;
lhs ?? (middle && rhs);

(lhs || middle) ?? rhs;
lhs || (middle ?? rhs);

(lhs ?? middle) || rhs;
lhs ?? (middle || rhs);

4、逻辑赋值运算符

ES2021 引入了三个新的逻辑赋值运算符(logical assignment operators),将逻辑运算符与赋值运算符进行结合。

// 或赋值运算符
x ||= y
// 等同于
x || (x = y)

// 与赋值运算符
x &&= y
// 等同于
x && (x = y)

// Null 赋值运算符
x ??= y
// 等同于
x ?? (x = y)

这三个运算符||=&&=??=相当于先进行逻辑运算,然后根据运算结果,再视情况进行赋值运算。

它们的一个用途是,为变量或属性设置默认值。

// 老的写法
user.id = user.id || 1;

// 新的写法
user.id ||= 1;

上面示例中,user.id属性如果不存在,则设为1,新的写法比老的写法更紧凑一些。

下面是另一个例子。

function example(opts) {
  opts.foo = opts.foo ?? 'bar';
  opts.baz ?? (opts.baz = 'qux');
}

上面示例中,参数对象opts如果不存在属性foo和属性baz,则为这两个属性设置默认值。有了“Null 赋值运算符”以后,就可以统一写成下面这样。

function example(opts) {
  opts.foo ??= 'bar';
  opts.baz ??= 'qux';
}
``undefined`时,才会返回右侧的值。

```javascript
const headerText = response.settings.headerText ?? 'Hello, world!';
const animationDuration = response.settings.animationDuration ?? 300;
const showSplashScreen = response.settings.showSplashScreen ?? true;

上面代码中,默认值只有在左侧属性值为nullundefined时,才会生效。

这个运算符的一个目的,就是跟链判断运算符?.配合使用,为nullundefined的值设置默认值。

const animationDuration = response.settings?.animationDuration ?? 300;

上面代码中,如果response.settingsnullundefined,或者response.settings.animationDurationnullundefined,就会返回默认值300。也就是说,这一行代码包括了两级属性的判断。

这个运算符很适合判断函数参数是否赋值。

function Component(props) {
  const enable = props.enabled ?? true;
  // …
}

上面代码判断props参数的enabled属性是否赋值,基本等同于下面的写法。

function Component(props) {
  const {
    enabled: enable = true,
  } = props;
  // …
}

??本质上是逻辑运算,它与其他两个逻辑运算符&&||有一个优先级问题,它们之间的优先级到底孰高孰低。优先级的不同,往往会导致逻辑运算的结果不同。

现在的规则是,如果多个逻辑运算符一起使用,必须用括号表明优先级,否则会报错。

// 报错
lhs && middle ?? rhs
lhs ?? middle && rhs
lhs || middle ?? rhs
lhs ?? middle || rhs

上面四个表达式都会报错,必须加入表明优先级的括号。

(lhs && middle) ?? rhs;
lhs && (middle ?? rhs);

(lhs ?? middle) && rhs;
lhs ?? (middle && rhs);

(lhs || middle) ?? rhs;
lhs || (middle ?? rhs);

(lhs ?? middle) || rhs;
lhs ?? (middle || rhs);

你可能感兴趣的:(javascript,前端,开发语言)