javascript笔记知识(可能有错误、欢迎指正)

常用的浏览器

  • webkit内核(v8)引擎

    • 谷歌chrome
    • safari
    • opera(v14)
    • 国产浏览器
    • 手机浏览器
  • Gecko

    • 火狐Firefox
  • presto

    • opera <v14
  • Trident

    • IE
    • IE EDGE(开始采用双内核)

JS做客户端语言

  • 按照相关语法来操作页面中的元素,有时还要操作浏览器
  • ECMAScript3/5/6…:JS的语法规范
  • DOM(文档对象模型):提供一些JS的属性和方法,用来操作页面中的DOM元素
  • BOM(浏览器对象模型):提供一些JS的属性和方法,用来操作浏览器的BOM元素

JS中的变量 variable

变量:可变的量,在编程语言中,就是一个名字,用来存储和代表不同值的东西

//ES3
var a = 12;
a = 13;
console.log(a);//输出的是a,值为13

//ES6
let b = 100;
b = 200;

const c = 1000;
c = 2000;//报错,因为const创建的值不能被修改(可以理解为常量)
=========================================
``**但是**``如果const定义的对象是引用数据类型,则const只能保证指针不变而修改引用类型的值还是可以变化
=========================================

// const person = {
     name : '老王',
     sex : '男'
 }
 
 person.name = '老李'
 
 console.log(person.name)//输出老李

person = {
   name : 'test',
   sex : '男'
}//则会报错

//创建函数也相当于创建变量
function fn(){}
//创建类也相当于创建变量
class A{}
//ES6的模块导入也相当于创建变量
import B from './B.js'
//Symbol 创建唯一值
let n = Symbol(100);
let m = Symbol(100);
n==m;//会返回false

JS中的命名规范

  • 严格区分大小写
  • 使用数字、字母、下划线、$,数字不能开头
let $box//一般用jquery获取以$开头
let _box//一般公共变量都是_开头
  • 驼峰命名法:首字母小写,其余每个单词首字母大写(命名尽可能语义化)
  • 不能使用关键字和保留字

JS中常用的数据类型

  • 基本数据类型
    • 数字number
      常规数字和NAN
    • 字符串string
      所有用单引号、双引号、反引号包起来的都是字符串
    • 布尔型Boolean
      true和false
    • 空对象指针null
    • 未定义类型undefined
  • 引用数据类型
    • 对象数据类型object
      • {}普通对象
      • []数组对象
      • /^$/正则对象
      • Math数学函数对象
      • 日期对象
    • 函数数据类型function

number数字类型

包含:常规数字、NaN

NaN

not a number:不是一个数,但是隶属于数字类型
NaN和任何值(包括自己)相比较,都不相等,NaN!=NaN,所以我们不能用相等的方式判断是否为有效数字

isNaN

检测一个值是否为有效数字,如果不是有效数字则返回true,否则返回false

在使用isNaN来检测时,会首先验证检测的值是否为数字类型,如果不是,先基于Number()这个方法,把值转化为数字类型,然后再检测
ex:console.log(isNAN('10'))//返回false,先Number('10')变成10,isNaN(10)返回false
ex1:console.log(isNaN('AA'))//先进行Number('AA')变成NaN,然后isNaN(NaN),结果返回true

使用Number()来转换

  • 字符串转数字

console.log(Number(‘12.5’))//结果为12.5
console.log(Number(‘12.5px’))//结果为NaN
console.log(Number(‘12.5.0’))//结果为NaN
console.log(Number(’’))//空串结果返回0,注意空串不等同于空格

  • 布尔转数字

console.log(Number(true))//结果为1
console.log(Number(false))//结果为0
console.log(Number(isNaN(true)))//结果也为0,过程则是先判断isNaN(true),将true用Number(true)变为1,然后isNaN(1),返回false,结果就是Number(false)返回0
console.log(isNan(true))//结果返回false

  • 空指针类型转数字

console.log(Number(null))//结果为0

  • 未定义类型转数字

console.log(Number(undefined))//结果为NaN

  • 引用数据类型转数字//把引用数据类型转化为数字采用toString方法先转化为字符串,然后再转化为数字

console.log(Number({}))//结果为NaN,过程先是({}).toString()转化为"[object Object]",然后再Number("[object Object]"),返回NaN
console.log(Number([]))//结果为0,过程也是先将数组使用toString()转化为"",然后Number(""),结果返回0
console.log(Number([10]))//结果为10
console.log(Number([1,2]))//结果为NaN

Number()底层机制总结

Number()是按照浏览器从底层机制,把其他数据类型转换为数字
字符串:看是否包含非有效数字字符,包含其结果就是NaN,其中""结果为0
布尔:true->1 false->0
null->0
undefined->NaN
引用类型值都要先转换为字符串,再转换为数字

  • {} /正则/ 函数等->NaN
  • []->""->0
  • [“12”]->“12”->12
  • [12,13]->“12,13”->NaN

使用parseInt()和parseFloat()

  • 他们都是转化数字类型,但是专门针对字符串进行查找,规则是从左到右依次查找有效数字,直到遇到非有效数字字符,则停止查找(不管后面是否还有有效数字,都不再查找)

parseInt/Float()转换机制

它们都是遵循按照字符串从左到右查找的机制找到有效数字字符(所以传递的值一定是字符串,如果不是字符串也一定要转换为字符串然后再查找)
parseInt(undefined)->parseInt(“undefined”)->NaN
parseInt("")->NaN(因为没有找到有效数字,如果是Number()方法则会返回0)

使用==进行比较

  • ‘10’==10//结果返回true,将字符串’10’转换为数字10

String字符串数字类型

所有用单引号、双引号、反引号包起来的都是字符串

把其它类型值转换为字符串

  • [val].toString()//注意null和undefined不能直接toString,普通对象{}.toString不是直接加引号,而是变成"[object Object]"
  • 字符串拼接
    console.log(‘10’+10)//输出结果为1010
    console.log(‘10’-10)//输出结果为0
    console.log(‘10px’-10)//输出结果为NaN,'10px’会被浏览器底层使用Number()方法将’10px’转换为NaN,再进行运算
    ex:
    let a = 10 + null + true + [] + undefined+ ‘你好’ + null + [] + 10 + false
    console.log(a)
    //10 + null ->10
    //10 + true ->11
    //11 + [] ->11 + ‘’ ->‘11’
    //‘11’ + undefined -> ‘11undefined’
    //‘11undefined’ + ‘你好’ -> ‘11undefined你好’
    //‘11undefined你好’ + null -> ‘11undefined你好null’
    //‘11undefined你好null’ + [] -> ‘11undefined你好null’
    //‘11undefined你好null’+ 10 -> ‘11undefined你好null10’
    //‘11undefined你好null10’ + false -> ‘‘11undefined你好null10false’’

boolean布尔数据类型

只有两个值,true和false

其他数据类型转换为boolean

除了0、NaN、’’、null、undefined五个值转为false,其余都为true(没有任何特殊情况)

转换方法

  • Boolean(val)
  • !/!!
  • 条件判断
if(1){
   console.log("你好");
 }//1转换boolean类型为true,则输出

 if('3px' + 3){
   console.log("你好");
 }//'3px' + 3 进行字符串拼接,变为'3px3',然后再转换为Boolean值为true,最后输出

 if('3px - 3'){
   console.log("你好");
 }//'3px' - 3 进行四则运算,结果为NaN,再转换为Boolean为false,结果则是不输出
**`ex:`**
console.log(Boolean(0))//false
console.log(Boolean(''))//false
console.log(Boolean(' '))//true
console.log(Boolean(null))//false
console.log(Boolean(undefined))//false
console.log(Boolean([]))//true
console.log(Boolean([10]))//true
console.log(Boolean(-1))//true
console.log(!1)//结果为false,先转为Boolean,再取反
console.log(!!1)//结果为true,!!取反再取反,等于没取反,只是转为Boolean

null和undefined

都表示没有

  • null一般都是意料之中(开始不知道数值,手动设置为null,后期再给予赋值)
let num = null;//定义了一个变量,赋值null
  • undefined一般都是意料之外的
let num;//定义了一个变量没有赋值,则默认undefined

object对象数据类型-普通对象

{[key]:[value],…}任何一个对象都是由零到多组键值对(属性名:属性值)组成的,并且属性名不能重复
获取对象的方法:

  • 对象.属性名/对象[属性名]
let person = {
  name:'小王',
  age:'20',
  sex:'boy',
  height:'185cm',
  1:'100分'
} //person.name/person['name']都可以获取到name这个属性

  • 如果获取的属性名不存在,则默认为undefined
console.log(person.weight)//结果为undefined
  • 如果一个对象内的属性名为数字,则不能使用对象.属性名来获取
console.log(person['1'])//输出100分
console.log(person.1)//SyntaxError语法错误
  • 添加属性值
person.girlFriend = '小玉'
console.log(person.girlFriend)//输出小玉
  • 删除属性值
    • 假删除(属性还在,值为空):
    person.name = 'null'//给person内的name属性置为空
    
    • 真删除(属性都没了)
    delete person['1']//给person内这个1属性删除
    

object对象数据类型-数组对象

数组是特殊的对象数据类型

let ary = [10,20,'你好',true]//相当于下面这样
let ary = {
  0:10,
  1:20,
  2:'你好',
  3:true,
  length:4
}//所以我们在数组中写的内容都为属性值,而数组的属性名从0开始依次递增

索引数组

console.log(ary.length-1)//索引数组最后一个元素内容
console.log(ary[0])//索引数组第一个元素内容

添加数组内容

ary[ary.length] = 5;//在数组末尾添加一个内容为5的属性值,数组长度自增
console.log(ary);

基本类型和引用类型区别

基本类型按值操作
引用类型按引用地址操作
ex:

	let a =12;
    let b = a;
    b = 13;
    console.log(a);//输出12

    let n = {
        name:"boy"
    }

    let m = n;
    m.name = "girl";
    console.log(n.name);//输出girl

JS中的数据类型检测

  • typeof [val]:用来检测数据类型的运算符,它不是函数
    • 基于typeof检测出来的结果

    1.首先是一个字符串
    2.字符串中包含对应类型

    • typeof局限性

    1.typeof null => “object”,但是null并不是对象
    2.基于typeof无法细分出当前值是普通对象还是数组对象等,因为只要是对象数据类型,返回的结果都是"object"
    ex:

    console.log(typeof typeof typeof [])//输出结果为"string",最里面的typeof []返回结果为"object",外层的typeof检测为"string"
    
  • instanceof:用来检测当前实例是否属于某个类的实例
  • constructor:基于构造函数检测数据类型(也是基于类的方法)
  • Object.prototype.toString.call():检测数据类型最好的方法

JS中的操作语句:判断、循环

判断

条件成立做什么?条件不成立做什么?

  • if/elseif/else
  • 三元运算符

条件?条件成立处理的事情:条件不成立处理的事情
如果处理的事情比较多,用括号包裹,每一件事用逗号分隔
如果不需要处理事情,则用null或者undefined占位

let a = 10;
a >=0 && a<20 ? (a++,console.log(a)):null
  • switch case

一个变量在不同值情况下的不同操作
在每种case结束时,最好加上break,不加break后面条件不管是否成立都会执行,直到遇到break为止
每种case情况的比较都是用的===“绝对相等”,而ifelse的情况比较使用的是==“相等”
=========================================================

  • =比较

==:相等(如果两边的数据类型不相同,则会转化为相同的数据类型,再进行比较)
=:绝对相等(如果两边类型不相同,肯定不相等,不会默认转换数据类型),项目中为了保证业务严谨性,推荐使用=

双等号==:

(1)如果两个值类型相同,再进行三个等号(===)的比较

(2)如果两个值类型不同,也有可能相等,需根据以下规则进行类型转换在比较:

1)如果一个是null,一个是undefined,那么相等

2)如果一个是字符串,一个是数值,把字符串转换成数值之后再进行比较

三等号===:

(1)如果类型不同,就一定不相等

(2)如果两个都是数值,并且是同一个值,那么相等;如果其中至少一个是NaN,那么不相等。(判断一个值是否是NaN,只能使用isNaN( ) 来判断)

(3)如果两个都是字符串,每个位置的字符都一样,那么相等,否则不相等。

(4)如果两个值都是true,或是false,那么相等

(5)如果两个值都引用同一个对象或是函数,那么相等,否则不相等

(6)如果两个值都是null,或是undefined,那么相等

=======================================================

let b= 1;
switch(b){
  case 1:
    console.log('执行条件为1的事件');
    break;
  case 2:
    console.log('执行条件为2的事件');
    break;
  case 3:
    console.log('执行条件为3的事件');
    break;
  default:
    console.log('上述条件都不满足时执行的事件');
}//输出结果为'执行条件为1的事件'

//当不同的条件需要做相同的事情时就可以利用不加break来实现
switch(num){
  case 1:
  case 2:
    num++;
    break;
  default:
  console.log(num);
}//这段代码就能够让当num=1和num=2时,都能执行num++这个条件

i++与++i的区别(i++与i+=1以及i=i+1之间的区别)

i++是先拿值进行操作,最后再自增
++i是先将该值自增,最后再拿自增后的值进行计算
i++是纯粹的进行数学计算,而i+=1以及i=i+1如果i为字符串则会进行拼接

**ex:**

let i="10";
i=i+1;// => i="101"
i+=1// => i="101"
i++// => i="11"

i–与--i的区别

区别与i++与++i一样,但是i-=1与i=i-1没有区别,因为减法操作只有数值运算

简单的DOM元素操作

传统操作DOM的方式实现业务需求
1.想操作谁就先获取谁
2.给某元素绑定某事件
3.事件触发时候做些什么事情

  • document.getElementById([ID]):在整个文档中,通过元素的ID获取到当前这个元素对象
  • 元素对象.onxxx=function(){}:代表事件绑定,xxx事件类型(click/mouseover/mousedown/keydown…)
  • 元素对象.style.xxx=xxx:修改元素某个样式值
    ex:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>鼠标点击</title>
</head>
<style>
    *{
        margin: 0;
        padding: 0;
    }
    .box{
        width: 200px;
        height: 40px;
        box-sizing: border-box;
        line-height: 40px;
        text-align: center;
        border: solid 1px red;
        margin: 20px auto;
        position: relative;
        cursor: pointer;
    }
    .detail{
        width: 400px;
        height: 200px;
        box-sizing: border-box;
        border: solid 1px red;
        position: absolute;
        top: 38px;
        right: -1px;
        display: none;
        z-index: -1;
    }
    
</style>
<body>
    <div class="box" id="shopbtn">
        <span>购物车</span>
        <div class="detail" id="content" style="display: none;">//由于绑定事件修改样式是添加内敛样式,所以我们必须先主动添加none,便于后来获取时进行判断当前为none还是block
            隐藏的内容
        </div>
    </div>
    <=script>
        let box = document.getElementById("shopbtn");//获取外部盒子点击的目标
        let detail =document.getElementById("content");//将显示目标赋给变量detail
        box.onclick = function(){
            let n = detail.style.display;//将detail的样式值获取赋给变量n
            if(n==="none"){     //判断n当前的值为none还是block
                detail.style.display="block";
                box.style.borderBottomColor = "white";
            }else{
                detail.style.display="none";
                box.style.borderBottomColor = "red";
            }
            
        }
    </=script>
</body>
</html>

循环

重复做某些事情

  • for循环
    • 1.创建初始循环值
    • 2.设置(验证)循环执行的条件
    • 3.条件成立执行循环体中的操作
    • 4.当前循环结束执行步长累计操作
    • 5.循环嵌套循环时外循环执行一次,内循环执行全部
      ex1:
for (int i = 0; i < 4; i++ )
  {
    System.out.println("i==>"+i);
    for (int j = 0; j < 3; j++ )
    {
      if( j==1){
        continue;
      }
      System.out.println(j);
    }
  }

//输出结果为:
i==>0
0
2
i==>1
0
2
i==>2
0
2
i==>3
0
2

ex2:

for (var i=10;i>4;i-=2){
  if(i<7){
    i++;
  }else{
    i--;
  }
}
console.log(i);
//第一次:i=10判断i>4?,结果成立执行循环体操作,i=10判断i<7?,不成立执行i--,此时i=9,最后执行i-=2,i=7;
//第二次:i=7判断i>4?,结果成立执行循环体操作,i=7判断i<7?,不成立执行i--,此时i=6,最后执行i-=2,i=4;
//第三次:i=4判断i>4?,结果不成立退出循环输出结果最终i=4

循环中的两个关键词:

  • continue:结束当前这轮循环(continue后面的代码不再执行),继续执行下一轮循环
    -break:强制结束整个循环(break后面的代码也不再执行),整个循环啥也不干直接结束
    ex3:
//question:一共输出几个i,分别是多少?
for(var i=0;i<10;i++){
  if(i>=2){
    i+=2;
    continue;
  }
  if(i>=6){
    i--;
    break;
  }
  i++;
  console.log(i);
}
console.log(i);
//i=0,判断i<10?,成立判断i>=2?,不成立,判断i>=6?,不成立,执行i++,输出i为1,最后循环自增变为2.
//i=2,判断i<10?,成立判断i>=2?,成立,执行i+=2,i=4,遇见continue后面代码不执行,循环自增1,i=5.
//i=5,判断i<10?,成立判断i>=2?,成立,执行i+=2,i=7,遇见continue后面代码不执行,循环自增1,i=8.
//i=8,判断i<10?,成立判断i>=2?,成立,执行i+=2,i=10,遇见continue后面代码不执行,循环自增1,i=11.
//i=11,判断i<10?,不成立,循环结束,执行最后一个输出语句,输出i=11;
//一共输出两个i,第一个i=1,第二个i=11.
  • for in循环
  • for of循环
  • while循环
  • do while循环

堆栈内存指向问题

box.style.color="red";
//下面的几种方式是否都可以修改元素样式
//第一种方式
let a = box.style;
a.color="red";//可以
//第二种方式
let b = box.style.color;
b = "red";//不可以

堆栈内存

  • docment.getElementByID方式获取的元素是对象数据类型的值
    //我们可以通过console.log(typeof box)检测出是object类型
  • 基于.dir可以看到一个对象的详细信息,本质上也是一个对象,包含对象的属性名和属性值
    • id:操作元素的ID值
    • className:操作元素的class类样式值
    • innerHTML:操作元素的内容(可以识别标签)
    • innerText:和innerHTML的区别是无法识别标签
    • style:操作元素的行内样式
    • tagName:获取元素的标签名(一般大写)

      //我们可以通过console.dir(box)来查看一个元素的所有属性

奇偶行变色

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>奇偶行变色</title>
    <style>
        *{
            margin: 0;
            padding: 0;
        }
        ul{
            list-style: none;
        }
        #liBox{
            box-sizing: border-box;
            width: 400px;
            height: 400px;
            border: solid 1px gold;
            margin: 0 auto;
        }
        #liBox>li{
            line-height: 80px;
            text-align: center;
        }
        /* #liBox>li:nth-of-type(even){
            background-color:red;
        } */
    </style>
</head>
<body>
    <ul id="liBox">
        <li>我是第1个li</li>
        <li>我是第2个li</li>
        <li>我是第3个li</li>
        <li>我是第4个li</li>
        <li>我是第5个li</li>
    </ul>
    <=script>
        // let liBox=document.getElementById("liBox");
        let newsList=liBox.getElementsByTagName("li");
        //1.实现奇偶行变色
        //循环获取每一个li,判断每个li奇偶行,让其拥有不同的颜色
        for(let i=0;i<newsList.length;i++){
            //第一轮i=0,操作第一个Li,newsList[0]
            //第二轮i=0,操作第二个Li,newsList[1]
            //...
            //获取到当前的列表
            let curLi= newsList[i]
            if(i%2==0){
                //i取模为0说明i是偶数,但是数组是从0开始,则为奇数行
                curLi.style.background="red";//奇数行为红色
            }else{
                curLi.style.background="green";//偶数行为绿色
            }
        //2.循环给每个LI加mouseover/mouseout事件
            curLi.onmouseover=function(){
                //移入时,使背景颜色变为白色
                curLi.style.background="white";//移入变为白色
            }
            curLi.onmouseout=function(){
                curLi.style.background="gold";//移出变为金色
                //如何判断移出时原来的背景颜色?
            }
        }

    </=script>
</body>
</html>

函数 function

函数就是一个方法或者一个功能体,函数实际上就是把实现某个功能的代码放到一起进行封装,以后想要操作实现该功能,只需要把函数执行即可=>“封装”,减少代码的冗余,提高代码的重复使用率,也就是高内聚,低耦合

洗衣机就是一个函数,生产洗衣机就是封装一个函数,生产的时候不知道用户在洗衣服的时候会放什么水、衣服、洗衣液等,我们需要提供出入口(提供的入口在函数中叫形参,执行中具体放入的东西叫做实参),洗完衣服需要拿出来,洗衣机提供一个出口(函数中叫做返回值:把函数处理的结果能够返回给外面使用)

  • 创建函数
    • 形参
    • 返回值
  • 执行函数
    • 实参
  • arguments
  • 函数底层运行机制

  • 函数底层运行机制

创建函数

//es5老方式
function [函数名]([形参变量1],[形参变量2]...){
  //函数体:基于JS实现的功能;
  return [处理后的结果];
}

//执行函数
[函数名]([实参1]...);

ex:

//定义一个函数,求两个数的和,然后将该值乘以10再除以2,将结果返回输出
function sum(n,m){
  //初始化形参
  if(n === undefined){
    n = 0;
  }
  if(typeof m === "undefined"){
    m = 0;
  }//跟上面初始化方式一样,一般采用这种
  let res = n+m;
  res *= 10;
  res /= 2;
  console.log(res)
}
sum(1,2);//调用函数,传递两个实参1和2
函数返回值问题

函数如果不设置返回值,则外部调用函数则会默认undefined
返回值return返回的一定是值
函数体中遇见return不一定有返回值,也可能是后面代码不再执行

<=script>
        function sum(m,n){
            //初始化变量
            if(typeof m==="undefined"){
                m=0;
            }
            if(typeof n === "undefined"){
                n=0;
            }
            let res = m+n;
            return res;//return返回的一定是值,如果没有返回值,函数外面调用结果则为undefined
        }
        //console.log(res);=>Uncaught ReferenceError: res is not defined
        let a=sum(10,20);//外面想要调用该函数返回值则需要定义一个变量来接受返回值
        console.log(a);
    </=script>

匿名函数

匿名函数之表达式:把一个匿名函数本身赋给其它东西,这种函数一般不是手动触发执行,而是靠其它程序触发执行
ex:

document.body.onclick=function(){}
setTimeout(function(){},1000)//设置一个定时器,1000ms后执行匿名函数

匿名函数之自执行函数:创建完一个匿名函数后,紧接着就在后面加个小括号立刻执行该函数
ex:

(function(n){})(100)//函数内的n值为100

选项卡练习

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>选项卡</title>
</head>
<style>
    *{
        margin: 0;
        padding: 0;
    }
    #selBox{
        box-sizing: border-box;
        width: 400px;
        margin: 20px auto;
        /* border: solid 1px red; */
        
    }
    #selNav{
        display: flex;
        list-style: none;
    }
    #selNav>li{
        height: 35px;
        line-height: 35px;
        box-sizing: border-box;
        border: solid 1px green;
        padding: 0 10px;
        margin-right: 10px;
        cursor: pointer;
    }
    #selNav>li.active{
        border-bottom-color: white;
    }
    #selBox>div{
        height: 150px;
        box-sizing: border-box;
        border: solid 1px green;
        position: relative;
        top: -1px;
        display: none;
        z-index: -1;
    }
    #selBox>div.active{
        display: block;
    }
</style>
<body>
    <div id="selBox">
        <ul id="selNav">
            <li class="active">读书</li>
            <li>编程</li>
            <li>运动</li>
        </ul>
        <div class="active">读书使我快乐</div>
        <div>编程使我难受</div>
        <div>运动使我健康</div>
    </div>
    <=script>
        //获取ID为selBox的DIV
        var selBox=document.getElementById("selBox");
        //获取ID为selBox的DIV下的3个DIV,将他们赋给selBoxList
        var selBoxList=selBox.getElementsByTagName("div");
        //获取ID为selNav的UL
        var selNav=document.getElementById("selNav");
        //获取ID为selNav的UL下的3个LI,将他们赋给selNavList
        var selNavList=selNav.getElementsByTagName("li");


        //循环三个LI,给每个LI绑定点击事件
        //selNavList[i]是当前循环下我们需要操作的那个li,例如selNavList[0]则是第一个li,内容为读书
        //============以下方式无法实现===============================
        // for(var i=0;i<selNavList.length;i++){
        //     selNavList[i].onclick=function(){
        //         console.log(i);//输出的所有的i都为3,因为网页显示给用户是将整个代码跑完,由于我们给selNavList[i]加了一个点击事件,所以只有网页加载完成,才能给用户去点击,那么这个点击事件在加载的时候将不会触发.i=0,i<3,selNavList[0],selNavList[0].onclick=function()这段代码将不会执行,i++后边为1,重复循环,当i=3时退出循环.然后跑完整个代码,显示给用户,然后用户点击触发函数,但是此时的i=3,所以changeTab(3)将找不到。
        //         changeTab(i);
        //     }
        // }


        //===============解决办法1.设置自定义属性==============================
        for(var i=0;i<selNavList.length;i++){
            selNavList[i].myindex=i;//给每个LI设置一个自定义属性myindex,属性值存储的是当前LI的索引
            selNavList[i].onclick=function(){
                changeTab(this.myindex);//this.myindex获取的就是之前绑定在自定义属性上的索引值
            }
        }

        //循环过程
        //i=0  selNavList[0].myindex=0
        //selNavList[0].onclick=function(){...}
        //i=1  selNavList[1].myindex=1
        //selNavList[1].onclick=function(){...}
        //...



        //===============其他解决办法1==============================
        //闭包方法
        for(var i=0;i<selNavList.length;i++){
            selNavList[i].onclick=(function (i){
                return function(){
                    changeTab(i);
                }
            })(i);
        }



        //===============其他解决办法2==============================
        //es6中let解决方案
        for(let i=0;i<selNavList.length;i++){
            selNavList[i].onclick=function(){
                changeTab(i);
            }
        }



        
        //封装函数实现选项卡的切换
        //创建函数的时候还不知道点击的是谁,所以定义一个入口clickindex(存储点击这一项的索引),执行方法的时候把点击这一项的索引传递过来即可
        function changeTab(clickindex){
            //1.先让所有LI和DIV的样式都去除
            for(var i=0;i<selNavList.length;i++){
                selNavList[i].className="";
                selBoxList[i].className="";//由于DIV和LI的数量是一样的,都是3个所以可以写一起
            }
            //2.点击谁给谁加active样式
            selNavList[clickindex].className="active";
            selBoxList[clickindex].className="active";
        }
    </=script>
</body>
</html>

常用的输出方式

  • console.log
  • console.dir

dir输出一个对象的详细键值对信息

  • console.table

把一个多维json数组在控制台按照表格方式输出

  • 多维数组
    • [10,12]一维数组
    • [10,{name:“xxx”}]二维数组
    • [12,[{xxx:“xxx”}]]三维数组
浏览器窗口弹窗
  • alert(弹出框)
  • confirm(确认选择型弹框)
  • prompt(在confirm基础上多了一个输入框)

三种结果输出方式必先经过toString转换为字符串
三种方式都会阻断当前JS代码的执行

js放在head和body尾部区别

放在头部如果JS中代码需要获取元素来操作元素,则会获取不到,如果非要放在头部可以使用window.οnlοad=function(){},但是window方法只能用一次,JQ中也有一个类似的方法$(document).ready(function(){})

<head>
<=script>
window.onload=function(){
  JS代码
}
</=script>
</head>

对象的进一步理解

var name=10;
var obj ={
name:“小王”
};
console.log(obj.name);//输出小王
console.log(obj[“name”]);//输出小王
console.log(obj[name]);//相当于obj[10] => 输出undefined

var name =“小王”
var obj={
name:name,
name,//es6中的简化,相当于name:name
}

for in 来遍历对象中的属性名

var obj={
name:“小王”,
age:“18”,
1:10,
2:20,
3:30
}
for(var key in obj){
console.log(“属性名:”+key+“属性值:”+obj[key])
}key是用来输出当前obj中的所有属性名,并且从属性名为最小的数字开始,而obj[key]是输出所有的属性值

自定义属性的深层理解

前期把一些值存储到自定义的属性上,后期需要用到的时候,直接从属性上获取这些值即可

隔行变色,滑过时变色,而且还能保持原本颜色
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>隔行变色,并且离开还原本色</title>
</head>
<style>
    *{
        margin: 0;
        padding: 0;
    }
    #box{
        list-style: none;
        width: 400px;
        margin: 20px auto;
    }
    #box li{
        height: 40px;
        line-height: 40px;
        box-sizing: border-box;
        border-bottom: dashed 2px #999;
    }
</style>
<body>
    <ul id="box">
        <li>这是第1个li</li>
        <li>这是第2个li</li>
        <li>这是第3个li</li>
        <li>这是第4个li</li>
        <li>这是第5个li</li>
    </ul>

    <=script>
        let box = document.getElementById("box");
        let liList=box.getElementsByTagName("li");
        //循环遍历每个li
        for(var i=0;i<liList.length;i++){
            //将i的值取余进行判断,奇数则是偶数行,为绿色,偶数则为奇数行,为白色
            let bg=i%2===0?"white":"green";
            liList[i].style.backgroundColor=bg;
            //自定义一个属性,用来存储原先获取到的颜色值
            liList[i].myOriginBg=bg;
            //循环给每个li加一个鼠标滑过事件
            liList[i].onmouseover=function(){
                this.style.backgroundColor="blue";
            }
            //循环给每个li加一个鼠标滑出事件
            liList[i].onmouseout=function(){
                this.style.backgroundColor=this.myOriginBg;
            }
        }
    </=script>
</body>
</html>

arguments

1.类数组集合,集合中存储着所有函数执行时,传递的实参信息
2.不论是否设置形参,arguments都存在
3.不论是否设置实参,arguments都存在

任意数求和

1.传递的实参个数不确定
2.传递的值是否为有效数字不确定
3.需要把传递的有效数字求和并输出结果

**ex:**

<script>
        function anyNumSum(){
            let total = null;//定义一个变量来接收求和值,默认为null
            //循环类数组arguments,有多少个实参就会有多少个键值对
            for(var i=0;i<arguments.length;i++){
                var list=Number(arguments[i]);//将循环取得的值转化为数字型
                if(isN判断是否有非有效数字
                    continue;
                }
                total+=list;//累加获取的值
            }
            return total;
        }
        let a=anyNumSum(10,20,'1');
        console.log(a);//结果为31
    </script>

初识箭头函数(存在Es6语法规范中)

<script>
  //老语法规范中的函数创建
  function sum(n,m){
    return n+m;
  }
  //改写的箭头函数
  let sum = (n,m) => {
    return n+m;
  }

  //如果箭头函数中的内容只有一行return,则可以省略return和花括号
  let sum =(n,m) => n+m;


  //老语法中的fn嵌套fn写法
  function fn(n){
    return function(m){
      return n+m;
    }
  }

  //改写成箭头函数
  let fn = (n) => (m) => n+m;

  //老语法中的给未赋值的形参设置为0
  function sum(m,n){
    if(typeof m==="undefined"){
      m=0;
    }
    if(typeof n==="undefined"){
      n=0;
    }

    return m+n;
  }

  //改为箭头函数
  let sum=(m=0,n=0)=>m+n;//传统function中也可以这样直接写在m和n中,但是一般不这样写
</script>

``**注意:**``箭头函数中没有arguments,但是可以用...变量名(剩余运算符来获取传递的实参集合),它与arguments不同在于它是数组,而arguments是类数组,那么以后就能用数组的处理方式来进行后续操作

<script>
  let sum=(...arg)=>{
    return eval(arg.join('+'));//arg.join('+')是将arg数组拼接成字符串,eval是计算出结果
  }
  sum(1,2,3,4)//结果返回10
</script>

思考:
let sum=(…arg)=>{
return eval(arg.join(’+’));
}
sum(1,2,3,4)//10
Math.max(sum(1,2,3))//6
Math.max(sum([1,2,3]))//3
sum([1,2,3])//3
sum(1,2,3)//6
sum([1])//1
sum([1,2])//2

JS中的数学函数Math

Math数学函数:它本身不是一个函数,而是一个对象,对象中存储了很多操作数学的属性方法,因此被称为数学函数

Math中常用的属性和方法

Math.abs([number value]) 取一个数的绝对值,如果里面不是一个数则会基于Number()这个方法将它转化为数字
Math.ceil/floor([number value]) 向上(取整一定比原数大)/向下(取整一定必原数小)取整,如果里面不是一个数则会基于Number()这个方法将它转化为数字
Math.round([number value]) 四舍五入法(如果是负数.5则会舍去),如果里面不是一个数则会基于Number()这个方法将它转化为数字
Math.max/min([val1],[val2]…) 获取一堆数中的最大值/最小值

**思考题:**求Math.max/min([10,20,30,40,50])

Math.sqrt([number value]) 给一个数开方(负数不能开方)
Math.pow([val],[val]) 给一个数计算幂次
Math.random() 获取0-1之间随机小数

  • Math.round(Math.random())//随机获取0或1
  • Math.round(Math.random()*(m-n)+n)//随机输出n-m的整数

数组

    let ary=[12,23,34,45];
    console.log(typeof ary);=>object
    console.dir(ary);
      ary={
        0:12,
        1:23,
        2:34,
        3:45,
        length:4
      }
      数字作为索引
      length代表数组长度
      ary[0] 代表根据索引获取指定项内容
      ary.length 获取数组长度
      ary.length-1 获取最后一项的索引

数组中的常用方法

  • 按以下内容学习数组操作
    • 方法的作用和含义
    • 方法的实参(类型和含义)
    • 方法的返回值
    • 原来的数组是否会发生改变
1.实现数组增删改的方法(这一部分方法都会修改原有数组)
  • push: 向数组末尾添加内容
    • @params
      多个任意类型
    • return
      新增后数组的长度
    let ary =[10,20];
    let res=ary.push(30,'AA',{name:'小王'}); => 给数组末尾添加内容
    ary[ary.length]=40; => 基于原生JS向数组最后一项添加内容
    console.log(ary,res); => 数组长度从2变为5,res=5 ary=[10,20,30,'AA',{name:'小王'}]
  • unshift: 向数组开头添加内容
    • @params
      多个任意类型
    • return
      新增后数组的长度
    let ary =[10,20];
    let res=ary.unshift(30,'AA',{name:'小王'}); => 给数组开头添加内容
    ary=[50...ary]; => 基于原生ES6展开运算符,把原有数组克隆一份,在新数组创建第一项,其余内容使用原有数组内容,也算实现了向开头添加
    console.log(ary,res); => 数组长度从2变为5,res=5 ary=[30,'AA',{name:'小王'},10,20,]
  • shift: 删除数组中的第一项
    • @params
      没有
    • return
      删除的那一项
    let ary =[10,20,30,40];
    let res=ary.shift(); => 删除数组开头内容
    delete ary[0]; => 基于原生JS中的delete,把数组当作普通对象,确实可以删除某一项,但是不会影响数组本身结构特点(length长度不会发生变化),真实项目中不能使用这种方法删除
    console.log(ary,res); => res=10 ary=[20,30,40]
  • pop: 删除数组中的最后一项
    • @params
      没有
    • return
      删除的那一项
    let ary =[10,20,30,40];
    let res=ary.pop(); => 删除数组最后一项
    ary.length--; => 基于原生JS,让数组删除一位(默认删除最后一位)
    console.log(ary,res); => res=40 ary=[10,20,30]
  • splice: 实现数组的增加、删除、修改
    • 删除部分
    • @params
      n,m 都是数字,从索引n开始删除m个元素(m不写则一直删除到最后一个)
    • return
      将删除部分内容用新数组存储返回
    let ary =[10,20,30,40,50,60,70,80,90];
    let res=ary.splice(2,4); => 删除数组索引从2开始,删除四个内容(如果只有一个数字,则从它开始一直删到末尾)
    console.log(ary,res); => 数组长度从9变为5,res=[30,40,50,60] ary=[10,20,70,80,90]
    ary.splice(ary.length-1) => 删除最后一项
    ary.splice(0,1) => 删除第一项
  • splice: 实现数组的增加、删除、修改
    • 增加、修改部分
    • @params
      n,m,x 都是数字,从索引n开始删除m个元素,用x占据原先删除的位置(修改)
      n,0,x 都是数字,从索引n开始一个都不删,把x放到索引n前面(添加)
    • return
      将删除部分内容用新数组存储返回
    let ary =[10,20,30,40,50,60,70,80,90];
    let res=ary.splice(2,4); => 删除数组索引从2开始,删除四个内容(如果只有一个数字,则从它开始一直删到末尾)
    console.log(ary,res); => 数组长度从9变为5,res=[30,40,50,60] ary=[10,20,70,80,90]

    //实现修改
    ary.splice(2,4,'老王','老李'); => res=[70,80,90] ary=[10,20,'老王','老李']

    //实现添加
    ary.splice(2,0,'老陈'); => res=[] ary=[10,20,'老陈','老王','老李']

    //向数组末尾添加
    ary.splice(ary.length,0,'老雷'); => res=[] ary=[10,20,'老陈','老王','老李','老雷']

    //向数组开头添加
    ary.splice(0,0,'老牛'); => res=[] ary=['老牛',10,20,'老陈','老王','老李','老雷']

数组的查询和拼接(此组学习方法原数组不会改变)

  • slice: 实现数组的查询
    • @params
      n,m 都是数字,从索引n开始找到索引为m的这一项(不包含m这一项)
    • return
      将找到的部分内容用新数组存储返回
    let ary=[10,20,30,40,50];
    let res=ary.slice(1,3);
    console.log(res,ary) => res=[20,30] ary=[10,20,30,40,50]

    => m不写(找到末尾)
    ary.slice(1) => ary=[20,30,40,50]

    => 数组克隆(浅克隆,参数0不写也可以)
    res=ary.slice(0) => res=[10,20,30,40,50]

思考:
1.如果n/m为负数会咋样?
2.如果n>m会咋样?
3.如果是小数会咋样?
4.如果是非有效数字会咋样?
5.如果m或者n的值比最大索引还要大会咋样?

解答:1.暂时没发现规律
2.n>m 则找不到
3.小数则向下取整
4.如果n为非有效数字,m为有效数字,则n默认为0.若m为非有效数字,则找不到
5.如果n的索引比数组大,则找不到,如果m的索引比数组大,则从n索引开始找完整个数组

  • concat: 实现数组的拼接
    • @params
      多个任意类型值
    • return
      拼接后的新数组,原数组不变
    let ary1=[10,20,30,40];
    let ar2=[40,50,60,70];
    let res=ary1.concat('小陈',ary2);
    console.log(res); => res=[10,20,30,40,'小陈',40,50,60,70];

把数组转换为字符串(原数组不变)

  • toString(): 把数组转换为字符串
    • @params
    • return
      转换后的字符串,每一项用逗号分隔
    let ary=[10,20,30];
    let res=ary.toString();
    console.log(res); => res='10,20,30'
  • join(): 把数组转换为字符串
    • @params
      指定分隔符(分隔符为字符串类型,不指定默认逗号)
    • return
      转换后的字符串(原数组不变)
    let ary=[10,20,30];
    let res=ary.join(); => res="10,20,30"
    res=ary.join(""); => res="102030"
    res=ary.join("|"); => res="10|20|30"
    res=ary.join("+"); => res="10+20+30"
    console.log(eval(res)); => 60 eval把字符串变成JS表达式来执行

检测数组中是否包含某一项

  • indexOf(x,y)/lastIndexOf(x,y): 检测当前项x在数组中第一次/最后一次出现位置的索引值
    • @params
      x为要检测的这一项(x不是索引值),y为开始查找位置的索引
    • return
      这一项位置出现的索引值(一定是一个数字),如果没有这一项,则返回结果-1,原来数组不变;
    let ary=[1,2,3,1,2,3];
    console.log(ary.indexOf(2)); => 1
    console.log(ary.lastIndexOf(3)); => 5

    检测数组中是否包含"小陈"
    if(ary.indexOf("小陈")===-1){
        不包含
    }else{
        包含
    }
  • includes(): 检测当前项在数组中是否出现过
    • @params
      要检测的这一项
    • return
      出现返回true,没有出现返回false
let ary=[1,2,3];
let res=ary.includes(4); => res=false

数组的排序或者排列(原数组发生改变)

  • reverse(): 把数组倒过来排列
    • @params
    • return
      排列后的新数组,原数组发生改变
let ary=[1,2,3];
let res=ary.reverse(); => res=[3,2,1] ary=[3,2,1]
  • sort(): 实现数组的排序(从小打大)
    • @params
      可有可无,也可以是个函数(**注意:**如果不传递参数,只能排序10以下的(不包括10),默认按照每一项第一个字符来排)
    • return
      排列后的新数组,原数组发生改变
    let ary=[7,8,5,2,4,6]
    let res=ary.sort(); => res=[2,4,5,6,7,8]
    let ary1=[12,15,9,28,10,22];
    res=ary1.sort(); => res=[10,12,15,22,28,9]

    => 如果要实现降序或者升序则需要在sort内写入函数
    let res=ary1.sort(function(a,b){
      return a-b; => 实现升序res=[9,10,12,15,22,28]
      return b-a; => 实现降序 res=[28,22,15,12,10,9]
    });

    => 改写为箭头函数
    let res=ary1.sort((a,b)=>a-b); res=[9,10,12,15,22,28];
    let res=ary1.sort((a,b)=>b-a); res=[28,22,15,12,10,9];

遍历数组的方法(不会改变原数组)

  • forEach()

  • map()

  • filter()

  • find()

  • reduce()

  • some()

  • every()

  • forEach(): 遍历数组中的每一项

    • @params
      回调函数
    • return
      没有返回值,原数组不发生改变
  => 基于for循环解决数组遍历问题
    let ary=['我','是','老','王','啊'];
    for(let i=0;i<ary.length;i++){
    console.log('索引:'+i+' '+'内容是:'+ary[i]);
  }

  =>使用forEach()
  let ary=['我','是','老','王','啊'];
  ary.forEach((item,index)=>{
    console.log('索引:'+index+'内容是:'+item);  =>箭头函数中的参数第一个为索引内容(不管取什么名字),第二个为索引(不管取什么名字)
  });

数组去重

=> 方法一:
1.建立一个新数组,遍历原数组的每一项
2.如果新数组中没有这一项就将它添加进新数组

    let ary=[1,3,2,4,2,3,3,4,1,2,2,1,3,4,4];
    let newAry=[];
    for(let i=0;i<ary.length;i++){
      let item=ary[i]; => 循环原数组中每一项给item
      if(newAry.includes(item)){
        continue; => 如果新数组中包含这一项就跳过当前循环;
      }
      newAry.push(item);
    }
    console.log(newAry);


优化方法一(IE6、7、8不兼容):
    let newAry=[];
    ary.forEach((item)=>{
      if(newAry.includes(item)) return;
      newAry.push(item);
    });
    newAry.sort((a,b)=>{
      return a-b;
    });
    console.log(newAry);

=> 方案二(在原数组中操作,如何解决数组塌陷):
1.先分别拿出数组的每一项A
2.用这一项A和后面的每一项依次进行比较,如果遇到和当前项A相同的,则在原来的数组中把这一项删除

        let ary=[1,3,2,4,2,3,3,4,1,2,2,1,3,4,4];
        for(let i=0;i<ary.length;i++){
            let item=ary[i];
            =>i=0 item=1 j=1 compare=3 1!==3
            =>i=0 item=1 j=2 compare=2 1!==2
            =>i=0 item=1 j=3 compare=4 1!==4
            =>i=0 item=1 j=4 compare=2 1!==2
            =>i=0 item=1 j=5 compare=2 1!==3
            =>i=0 item=1 j=6 compare=2 1!==3
            =>i=0 item=1 j=7 compare=2 1!==4
            =>i=0 item=1 j=8 compare=2 1===1 ary.splice(8,1) ary=[1,3,2,4,2,3,3,4,(1),2,2,1,3,4,4] j-- =>j=7 j++ =>j=8
            =>i=0 item=1 j=8 compare=2 1!==2
            =>...
            for(let j=i+1;i<ary.length;j++){
            let compare=ary[j];
                if(item === compare){
                ary.splice(j,1);
                j--; => 因为每当删除时,后面元素的索引会向前移动一位,所以为了保证后续操作不变,则将索引先减少再增加
                
            }
        }
    }
        ary.sort((a,b)=>{
        return a-b;
        });
        console.log(ary);

=> 方案三(创建一个对象,如果数组中含有对象,则不能使用这种方法):

    let ary=[1,3,2,4,2,3,3,4,1,2,2,1,3,4,4];
    let obj={};
    => 循环数组中的每一项,把每一项向数组中进行存储(item:item)(数组中的内容作为对象obj的属性名和属性值)
    for(let i=0;i<ary.length;i++){
      => i=0 ary[0]=1 item=1 obj[1]==undefined obj[1]=1;
      => i=1 ary[1]=3 item=3 obj[3]==undefined obj[3]=3;
      => i=2 ary[2]=2 item=2 obj[2]==undefined obj[2]=2;
      => i=3 ary[3]=4 item=4 obj[4]==undefined obj[4]=4;
      => i=4 ary[4]=2 item=2 obj[2]!==undefined splice(4,1) ary=[1,3,2,4,(2),3,3,4,1,2,2,1,3,4,4] i-- =>i=3 i++=>i=4;
      => i=4 ary[4]=3 item=3 obj[3]!==undefined splice(4,1) ary=[1,3,2,4,(3),3,4,1,2,2,1,3,4,4] i-- =>i=3 i++=>i=4;
      => i=4 ...
      let item=ary[i];
      if(obj[item]!==undefined){
        =>每一次存储前进行判断,如果obj中存在这项,则需要删除
        ary.splice(i,1);
        i--;=>解决数组塌陷问题
        continue;
      }
      obj[item]=item;
}

=> 方案三(优化性能)

    let ary=[1,3,2,4,2,3,3,4,1,2,2,1,3,4,4];
    let obj={};
    => 循环数组中的每一项,把每一项向数组中进行存储(item:item)
    for(let i=0;i<ary.length;i++){
      let item=ary[i];
      //每一次存储前进行判断,如果obj中存在这项,则需要删除
      if(obj[item]!==undefined){
        ary[i]=ary[ary.length-1]; => 把要删除的那项跟最后一项互换位置,然后把最后一项删除
        ary.length--;
        i--;
        continue;
      }
      obj[item]=item;

=> 写成函数

/*
 *  unique: 实现数组去重方法
 *  @params
 *    ary [Array] 要去重的数组
 *  @return
 *    去重后的数组
 *  by cly on 2021.5.3
 */

    function unique(ary){
      let obj={};
      for(let i=0;i<ary.length;i++){
        let item=ary[i];
        if(obj[item]!==undefined){
          ary[i]=ary[ary.length-1];
          ary.length--;
          i--;
          continue;
        }
        obj[item]=item;
      }
      return ary;
    }

=> 方法四(Set去重,基于ES6,该方法可以实现快速去重)

    let let ary=[1,3,2,4,2,3,3,4,1,2,2,1,3,4,4];
    ary=[...new Set(ary)];
    console.log(ary);

字符串中常用方法

所有用单引号、双引号、反引号包裹的都是字符串

    let str='xiaochen'
    str.length => 字符串长度
    str[0] => 获取索引为0的(第一个)字符
    str[str.length-1] => 获取最后一个字符str.length-1最后一项索引

    => 循环输出字符串中的每一个字符
    let str="xiaowang"
    for(let i=0;i<str.length;i++){
      let char =str[i];
      console.log(char);
}
  • charAt(): 根据索引获取指定位置的字符

    • @params
      n [number] 获取字符指定的索引
    • return
      返回查找的字符
      找不到返回的是空字符串而不是undefined
  • charCodeAt(): 获取指定字符的ASCII码值(Unicode编码值)

    • @params
      n [number] 获取字符指定的索引
    • return
      返回编码值
    let str='xiaoxiao'
    console.log(str.charAt(0)); => 'x'
    console.log(str[0]); => 'x'
    console.log(str.charAt(10000)); => ''
    console.log(str[10000]); => undefined
    console.log(str.charCodeAt(0)); => 120;
    console.log(String.fromCharCode(121)); => 'y'
  • substr(n,m): 字符串的截取(在原来的字符串中查找到自己想要的)

    • @params
      n为字符串的索引值,从字符串索引为n开始截取m个字符串,如果不写m则截取到末尾(类似splice)
    • return
  • substring(n,m): 字符串的截取

    • @params
      n,m都为字符串的索引值,从字符串索引为n开始截取到索引为m(不含m,类似数组中的slice)
    • return
  • slice(n,m): 字符串的截取

    • @params
      n,m都为字符串的索引值,从字符串索引为n开始截取到索引为m(不含m),但是slice可以支持负数作为索引,前两种方法都不行(负数索引我们可以按照str.length+负索引的方式来查找)
    • return
    let str='xiaoxiao'
    console.log(str.substr(2,3)); => 'aox'
    console.log(str.substring(2,3)); => 'a'
    console.log(str.slice(-4,-1)); => 'xia' str.length=8 str.slice(8-4,8-1);
  • indexOf(x,y)/lastIndexOf(x,y): 获取x字符在字符串中首次/最后一次出现位置的索引
    • @params
      x为需要查找的字符,y为开始查找位置的索引
    • return
      返回找到位置的索引,如果没有找到则返回-1
    let str='xiaoxiaonihaowoshixiaochen';
    console.log(str.indexOf('a')); => 2
    console.log(str.lastIndexOf('o'); => 21
    console.log(str.includes('@')); => false
    console.log(str.indexOf('@')); => -1
    console.log(str.indexOf('ni')); =>8
    console.log(str.indexOf('niho')); => -1
    console.log(str.indexOf('x',5)); => 18 查找从索引为5后面首次出现字符为'x'的索引
  • toUpperCase()/toLowerCase(): 字母转大写/小写
    • @params
    • return
      转换后大写/小写的结果
    let str='xiaoxiao'
    console.log(str.toUpperCase()); => XIAOXIAO
    console.log(str.toLowerCase()); => xiaoxiao

    => 实现第一个字母大写,其余字母小写
    str=str.substr(0,1).toUpperCase()+str.substr(1); => Xiaoxiao
  • split([分隔符]): 把字符串按照指定的分隔符拆分成数组(与数组中的join相对应)
    • @params
    • return
      转换后的结果
    => 把竖线分隔符变为逗号分隔符
    let str='music|movie|eat|sport';
    let ary=str.split("|"); => ["music", "movie", "eat", "sport"]
    str=ary.join(","); => "music,movie,eat,sport"
    console.log(str);
  • replace(老字符,新字符): 实现字符串的替换(经常伴随正则一起使用)
    • @params
    • return
      转换后的结果
let str='小陈@小王@小雷';
str=str.replace('@','-'); => "小陈-小王@小雷" 在不使用正则表达式的情况下,执行一次replace只能替换一次字符
str=str.replace(/@/g,'-'); => "小陈-小王-小雷" /@/g,为正则表达式,g代表全局

实现一些常用的需求

时间字符串的处理

let time='2021-5-14 11:43:20'
=> 变为自己需要的格式,例如:
'2021年5月25日 11时43分20秒'

方案一,一直replace
time = time.replace('-','年').replace('-','月').replace(' ','日 ').replace(':','时').replace(':','分')+'秒';
console.log(time);

方案二,获取值的方法
let n=time.indexOf('-'); =>获取到字符串'-'首次出现的索引,将它赋给变量n
let m=time.lastIndexOf('-'); =>获取到字符串'-'最后一次出现的索引,将它赋给变量m
let x=time.indexOf(' '); =>获取到字符串' '首次出现的索引,将它赋给变量x
let y=time.indexOf(':'); =>获取到字符串':'首次出现的索引,将它赋给变量y
let z=time.lastIndexOf(':'); =>获取到字符串':'最后一次出现的索引,将它赋给变量z
let year=time.substring(0,n); =>从索引0开始,截取到索引为n的项(不包含n),获取到年
let month=time.substring(n+1,m); =>从索引n的后一项开始,截取到索引为m的项(不包含m),获取到月
let day=time.substring(m+1,x); =>从索引m的后一项开始,截取到索引为x的项(不包含x),获取到日


通过split一项项的拆分
let n=time.split(' '); =>通过字符串' ',把整个时间拆分成两个数组["2021-5-14", "11:43:20"]
let m=n[0].split('-'); =>通过字符串'-',把数组中第一个内容又拆成数组["2021", "5", "14"]
let x=n[1].split(':'); =>通过字符串':',把数组中第二个内容又拆成数组["11", "43", "20"]

通过正则表达式直接全拆分
let ary=time.split(/(?: |-|:)/g); =>ary=["2021", "5", "14", "11", "43", "20"]
time=addZero(ary[0])+'年'+addZero(ary[1])+'月'+addZero(ary[2])+'日'+' '+addZero(ary[3])+'时'+addZero(ary[4])+'分'+addZero(ary[5])+'秒'

=>如果时间不足两位数,则需要在前面补个0
function addZero(val){
  if(val.length<2){
    val='0'+val;
  }
  return val;
}

实现一个方法jueryURLparameter(获取一个URL地址问号后面传递的参数信息)

得到一个对象,里面包含{
lx:‘1’,
name:‘xiaoyu’,
age:‘18’,
HASH:‘box1’
}

let url1='https://sports.qq.com/kbsweb/game.htm?lx=1&name=xiaoyu&age=18#box1';
let url2='https://sports.qq.com/kbsweb/game.htm?mid=100000:55077655#box2';
let result={};

let askIndex=url1.indexOf('?'); =>获取到?首次出现的索引
let wellIndex=url.indexOf('#'); =>获取到#首次出现的索引
let askText=url1.subString(askIndex+1,wellIndex); =>获取到?到#之间的内容(不包含两端),lx=1&name=xiaoyu&age=18
let wellText=url1.subString(wellIndex+1) =>获取到#后面的内容(不包含两端),box1

let askAry=askText.split('&'); =>["lx=1", "name=xiaoyu", "age=18"]
askAry.forEach((item)=>{
  let n=item.split('=');
  //console.log(n) ["lx", "1"]["name", "xiaoyu"]["age", "18"]
  let key=n[0];
  let value=n[1];
  //console.log(key,value); lx 1 name xiaoyu age 18
  result[key]=value;
});
result['HASH']=wellText;

将queryURLparameter封装成一个方法

function queryURLParams(url){
  let askIndex=url.indexOf('?'),
      wellIndex=url.indexOf('#'),
      askText='',
      wellText='',
      result={};
  if(wellIndex===-1){
    wellIndex=url.length; =>没有#则让索引为整个url的长度
  }else{
    wellText=url.substring(wellIndex+1); =>将#索引后面内容截取给wellText
    result['HASH']=wellText;
  }
  if(askIndex!==-1){
    askText=url.substring(askIndex+1,wellIndex); =>截取从索引为?后一项开始,到索引为#(不包含这项)
    let askAry=askText.split('&'); =>将从?到#截取的内容按&分成数组内容
    askAry.forEach((item)=>{
    let n=item.split('='); =>将每个item分别以=划分
    result[n[0]]=n[1]; =>n[0]为上面划分的属性名 n[1]为上面划分的属性值
    });
  }
  return result;
}

let paramsObj=queryURLParams(url1); =>{HASH: "box1", lx: "1", name: "xiaoyu", age: "18"}

实现比较简单的四位随机验证码(数字+字母)

<body>
    <input type="text" id="inpCode">
    <br>
    <span id="ranCode"></span>
    <button style="margin-top: 5px;" id="btn">看不清,点我切换</button>

    <script>
        let inpCode=document.getElementById('inpCode'),
            ranCode=document.getElementById('ranCode'),
            btn=document.getElementById('btn');
    
            function codeChange(){
            let area='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
            let result='';
                for(let i=0;i<4;i++){
                    //每次循环随机一个索引
                    let ran=Math.round(Math.random()*61);
                    result += area.charAt(ran);
                }
                ranCode.innerHTML=result;
            }
            codeChange();
            btn.onclick=codeChange;
            inpCode.onblur=function(){
                let val=inpCode.value;
                let code=ranCode.innerHTML;
                if(val.toLowerCase()===code.toLowerCase()){
                    alert("输入成功!");
                }else{
                    alert("验证码输入有误,请重新输入!");
                    inpCode.value='';
                    //重新生成
                    codeChange();
                }
            }
    </script>
</body>

日期对象的基本操作

let time = new Date(); => 获取当前客户端(本地电脑)时间,用户可以自行修改,不能作为参考依据

标准日期对象中提供了一些属性和方法,供我们操作日期信息

getFullYear() 获取年
getMonth() 获取月
getDate() 获取日
getDay() 获取星期(0-6 星期日-星期六)
getHours() 获取小时
getMinutes() 获取分
getSeconds() 获取秒
getMilliseconds() 获取毫秒
getTime() 获取当前日期距离1970/1/1 00:00:00 这个日期之间的毫秒差
toLocaleDateString() 获取年月日
toLocaleString() 获取完整的年月日,时分秒

简单的小时钟案例
<head>
    <meta charset="UTF-8">
    <title>小时钟案例</title>
    <style>
        *{
            margin: 0;
            padding: 0;
        }
        #clockBox{
            position: absolute;
            right: 0px;
            top: 0px;
            padding: 0px 15px;
            line-height: 40px;
            font-size: 24px;
            background-color: gold;
            background: -webkit-linear-gradient(top left,red,yellow,blue);
            color: white;
        }
    </style>
</head>
<body>
    <div id="clockBox">
    </div>
 /引入script
        let clockBox=document.getElementById('clockBox');

        //封装一个日期位数不为两位补0的方法
        function addZero(val){
            val=Number(val);
            return val<10?'0'+val:val;
        }

        //封装一个自定义时间格式的方法
        function queryTimeDate(){
            let time= new Date(),
            year=time.getFullYear(),
            month=time.getMonth()+1,
            day=time.getDate(),
            week=time.getDay(),
            hour=time.getHours(),
            minute=time.getMinutes(),
            second=time.getSeconds();
            let weekAry=['天','一','二','三','四','五','六'];
            let result=year+'年'+addZero(month)+'月'+addZero(day)+'日'+' '+'星期'+weekAry[week]+' '+addZero(hour)+'时'+addZero(minute)+'分'+addZero(second)+'秒';
            clockBox.innerHTML=result;
        }
        queryTimeDate();//执行上述函数

        setInterval(queryTimeDate, 1000);//定时器,1秒执行一次方法
</body>

续基于日期对象处理时间

        let time='2021-5-16 12:0:0'
        //位数不够补0
        function addZero(val){
            val=Number(val);
            return val<10?'0'+val:val;
        }
        //把时间字符串变为标准格式
        function handleDate(time){
            time=time.replace(/-/g,'/');
            time=new Date(time);
            let year=time.getFullYear(),
            month=addZero(time.getMonth()+1),
            day=addZero(time.getDate()),
            hour=addZero(time.getHours()),
            minute=addZero(time.getMinutes()),
            second=addZero(time.getSeconds());
            return year+'年'+month+'月'+day+'日'+' '+hour+'时'+minute+'分'+second+'秒'
        }
        time=handleDate(time);
        console.log(time);

DOM基础操作

document.getElementById() 指定在文档中,基于元素的ID或者这个元素对象
[context].getElementsByTagName() 在指定上下文中,通过标签名获取一组元素集合
[context].getElementsByClassName() 在指定上下文中,通过样式类名获取一组元素集合(不兼容IE6-8)
document.getElementsByName() 在整个文档中,通过标签的Name属性值获取一组节点集合(在IE中只有表单元素的Name才能识别到,所以我们一般只用于表单元素的处理)
document.head/document.body/document.documentElement 获取页面的head/body/整个页面
[context].querySelector([selector]) 在指定上下文中,通过选择器获取到指定的元素对象(不兼容IE6-8)
[context].querySelectorAll([selector]) 在指定上下文中,通过选择器获取到指定的元素集合(不兼容IE6-8)

元素选择器:

let box=document.querySelector('#box'); => 获取到ID为box的对象
let links=box.querySelectorAll('a'); => 获取到box下的所有a标签

JS中的节点和描述节点之间关系的属性

节点node (页面所有内容都是由节点组成)
节点集合nodeList (getElementsByName/querySelectorAll获取的都是节点集合)

元素节点(元素标签)

  • nodeType:1
  • nodeName:大写的标签
  • nodeValue:null

文本节点
nodeType:3
nodeName:’#text’
nodeValue:文本内容
注释节点
nodeType:8
nodeName:’#commen’
nodeValue:注释内容
文档节点(document)
nodeType:9
nodeName:’#document’
nodeValue:null

描述这些节点之间的关系的属性(对DOM操作的一种补充)

childNodes:获取所有的子节点 =>所有儿子
children:获取所有元素的子节点(子元素标签集合) => 所有元素儿子
parent:获取父亲节点
firstChild:获取第一个子节点(啥节点类型都有可能) => 大儿子
lastChild:获取最后一个子节点(啥节点类型都有可能) => 小儿子
firstElementChild/lastElementChild:获取第一个/最后一个元素子节点(不兼容IE6-8) 大元素哥哥/弟弟
previousSibling:获取上一个哥哥节点(啥节点类型都有可能) => 上一个哥哥
nextSibling:获取下一个弟弟节点(啥节点类型都有可能) => 下一个弟弟
previousElementSibling/nextElementSibling:获取哥哥/弟弟元素节点(不兼容IE6-8) => 上一个元素哥哥/下一个元素弟弟

<body>
        <ul id="box">
            <li>git和npm操作</li>
            <li>面向对象和原型:研究插件源码,自己写插件</li>
            <li>闭包作用域:堆栈内存处理</li>
            <li>ES6:从入门到放弃</li>
            <li>同步异步编程及核心:微任务、宏任务、时间循环</li>
            <li>DOM及其事件模型</li>
            <li>jQuery及源码分析</li>
            <li>设计模式:发布订阅、单例、Promise、PromiseA+</li>
            <li>AJAX及跨域解决方案:封装一款超牛X的AJAX库</li>
            <li>一入HTTP深似海</li>
            <li>性能安全优化</li>
            <li id="vueBox">VUE全家桶:vue-cli\vue\vue-router\vuex\vue element...</li>
            <li>vue和vuex的核心源码</li>
            <li>Recat全家桶:create-react-app、antd、antdpro、react、react-dom、react-native、react-router-dom、redux、react-redux、dva、redux-saga、mobx...</li>
            <li>react核心原理</li>
            <li>redux源码和中间件编写</li>
            <li>webpack</li>
            <li>node和express</li>
            <li>type script</li>
            <li>...</li>
        </ul>

        script>
            let box=document.getElementById('box');
            //标准浏览器(非IE6-8)中会把空格和换行当作文本节点处理
            console.log(box.childNodes);
            //但是IE6-8下使用children会把注释也当作元素节点
            console.log(box.children);
            //===================================================
            //找所有为元素儿子的节点方法
            function queryChildren(context){
                //获取到所有的孩子节点
                let res=[],
                nodeList=context.childNodes;
                //遍历整个孩子节点,如果是元素节点就放进数组中
                for(i=0;i<nodeList.length;i++){
                    var item=nodeList[i];//获取到当前循环的这项给item
                    item.nodeType===1?res.push(item):null;
                }
                return res;
            }
            console.log(queryChildren(box).length);
            console.log(box.firstChild);
            console.log(box.firstElementChild);
            let vueBox=document.getElementById('vueBox');
            console.log(vueBox.previousSibling); //=> #text 因为它的上一个哥哥是空格
            console.log(prev(vueBox.previousSibling)); //=> <li>性能安全优化</li>成功找到

            //找上一个为元素哥哥的方法
            function prev(context){
                //找到第一个哥哥,然后判断它是否是元素哥哥,不是则找哥哥的哥哥
                var pre=context.previousSibling;
                while(pre.nodeType!==1){
                    pre=pre.previousSibling;
                }
                return pre;
            }
        </script>
</body>

在JS中动态增删改元素

createElement 创建元素对象
createTextNode 创建文本对象
appendChild 把元素添加到指定容器的末尾 语法:容器.appendChild(元素)
insertBefore 把元素添加到指定容器中指定元素的前面 语法:容器.insertBefore([新增元素],[指定元素])
cloneNode(true/false) 克隆元素或者节点(深克隆:连子元素都克隆/浅克隆:不克隆子元素)
removeChild 移除容器中的某个元素

自定义属性的另一种方式(这种方式是把自定义属性放到元素结构上面)

setAttribute 设置元素的自定义属性信息
getAttribute 获取元素的自定义属性信息
removeAttribute 移除元素的自定义属性信息

============两种自定义属性方式======================
<body>
    <button>这是第1个按钮</button>
    <br>
    <button>这是第2个按钮</button>    
    <br>
    <button>这是第3个按钮</button>
    <br>
    <button>这是第4个按钮</button>
    <br>
    <button>这是第5个按钮</button>


    <script>
        //var btnList=document.getElementsByTagName("button");
        var btnList=document.querySelectorAll('button');
        for(var i=0;i<btnList.length;i++){
            btnList[i].setAttribute('myindex',i);//设置一个自定义属性
            //btnList[i].myindex=i
            btnList[i].onclick=function(){
                //alert(this.myindex);
                alert(this.getAttribute('myindex'));//获取自定义属性
            }
        }
    </script>
</body>

GIT版本控制系统

  • 版本控制器
    • 1.记录历史版本信息(记录每一次修改的记录)

分布式和svn集中式的区别

分布式和svn集中式的区别1
分布式和svn集中式的区别2

git的工作原理

  • 工作区 :我们能看到的,并用来写代码的区域
  • 暂存区 :临时存储用的区域
  • 历史区 :生成历史版本
  • 中央仓库 :协同开发存放整个代码的区域
  • 流程 :工作区->暂存区->历史区->中央仓库( git add-A、git commit -m’备注信息’ 、git pull origin master(先从仓库内拉取) git push origin master(再提交到仓库))

git的全局配置

  • 第一次安装完成git后,我们在全局环境下配置基本信息(告诉git我是谁)
  • $ git config -l :查看配置信息
  • $ git config --global -l :查看全局配置信息
  • 第一次添加全局 : git config --global user.name ‘xxx’ 、git config --global user.email ‘xxx@xx.xx’

创建仓库完成版本控制

  • $ git init =>会生成一个隐藏的.git文件(不能删除,因为这里面含有暂存区和历史区的一些其他信息,如果删除了就不是一个完整的git仓库)

在本地编写完代码后(在工作区中),把一些文件提交到暂存区中

  • $ git add xxx :把某一个文件或者文件夹提交到暂存区
  • $ git add . :把当前仓库内所有最新修改的文件都提交到暂存区
  • $ git add -A :同上
  • $ git status :查看当前文件夹内的状态(红色代表有文件在工作区,绿色代表在暂存区,看不见内容说明所有修改的信息文件都已经提交到了历史区)

把暂存区的内容提交到历史区中

  • $ git commit -m’描述信息,本次提交内容的一个描述’

查看历史版本信息

  • $ git log : 查看历史信息
  • $ git reflog :查看包含历史回滚信息

git的整个操作流程

git的整个操作流程

本地仓库连接到远程仓库(建立本地仓库和远程仓库的链接)

  • $ git remote -v :查看本地仓库和哪些远程仓库保持链接
  • $ git remote add origin [git远程仓库地址] :让本地仓库和远程仓库建立一个连接,origin是随便取的一个链接名字,可以改成任意你想要的名字,只不过一般使用该名字
  • $ git remote rm origin :删除关联信息
  • $ git pull origin master :提交前最好先拉取,看看有没有新内容
  • $ git push origin master :把本地代码提交到远程仓库(需要输入github账户和密码)
  • $ git clone [git远程仓库地址] [别名,可以不设置,默认是仓库名]
    // => 真实项目开发流程为:1.组长或者负责人先建立中央仓库 2.小组成员基于git clone把远程仓库以及默认的内容克隆到本地(解决了三个事情: 1.初始化一个本地仓库git init 2.和对应的远程仓库保持关联git remote add 3.把远程仓库默认内容拉取到本地git pull) 3.每个小组成员完成自己的程序后,基于git add .和get commit -m把本地内容提交到历史区,最后通过git push上传到中央仓库(上传前最好先git pull看看有没有别人添加的新内容)

NPM(node package manger)

NODE模块管理工具,根据npm我们可以快速安装,卸载我们所需要的资源(例如:jQuery、Vue、Vue-router…)
node -v :查看node版本
npm -v :查看npm版本

基于npm进行模块管理

https://www.npmjs.com/ 基于npm是从npmjs.com平台上下载安装
npm install xxx :把模块安装在当前项目中(node_modules)
npm install xxx -g :把模块安装在全局环境中
npm i xxx@1.0.0 :安装指定版本号的模块
npm view xxx versions > xxx.version.json :查看某个模块的版本信息(输出到指定JSON文件中)
npm init -y :初始化当前项目的配置清单(项目文件夹的名字不能包含大写字母、中文、特殊符号,创建成功后会在当前项目中生成一个package.json的文件清单,dependencies:生产依赖模块:项目开发和部署都需要,devDependencies:开发依赖模块:只有在开发的时候需要,scripts:配置本地可执行的命令)
npm i xxx --save :把模块保存到清单生产依赖中
npm i xxx --save-dev :把模块保存到清单开发依赖中
npm install :跑环境,按照清单安装所需模块
npm root -g :查看全局安装模块目录
npm uninstall xxx :
npm uninstall xxx -g :卸载安装过的模块

整个项目流程(新项目的开始)

1.创建一个项目文件夹
2.把它作为一个新仓库进行代码管理(可以基于$ git clone把远程仓库克隆下来即可)
3.初始化模块配置清单package.json($ npm init -y)
4.安装所需的模块($ npm i jquery bootstrap@3 less)[@3代表安装bootstrap3代中最后一个版本]
5.开始编写代码
6.开发中:可能需要在本地配置命令去完成一些功能(例如LESS文件编译,此时需要配置npm可执行命令,在配置清单package.json中找到scripts,添加你需要的编译代码)

"scripts": {
    "lesscly": "lessc css/index.less css/index.min.css -x" //这段作用就是编译index.less这个文件,将它转换为可执行的css,编译的时候执行npm run lesscly即可执行该命令
  }

7.开发过程中我们需要基于git把文件进行管理:生产对应的历史版本,提交到暂存区、历史区、远程仓库的时候,项目中很多文件是无需上传的,例如node_modules、ideas…,我们生产一个gitignore忽略文件
8.由于每次git提交的时候,我们都不去提交node_modules,所以团队协作开发中,每当我们拉下来程序后,都需要’跑环境’,执行:$ npm install,按照项目中的package.json中的依赖信息,把缺失的模块都安装一遍

变量提升

当浏览器开辟出供代码执行的栈内存后,然后代码并没有自上而下执行,而是做了很多事情,其中一件事情就是把当前作用域中所有带var和function关键字进行提前声明和定义(这就是变量提升机制)

  • 带var的只是提前声明(declare) var a;//如果只声明,没有赋值,默认为undefined
  • 带function的不仅声明,还定义了(defined) //定义实际上就是赋值,准确来说就是让变量和某个值关联起来
console.log(a);//=> a=undefined
var a=12;
var b=a;
b=13;
console.log(a);//=> a=12


console.log(sum(10,20));//=>30
function sum(n,m){
  return n+m;
}

//函数表达式方式,由于使用var创建sum,变量提升阶段只会声明变量,不会赋值,因此函数没有值是无法执行(真实项目中,这种方式更常用,因为这种操作更严谨)
console.log(sum(10,20));//=>报错 Uncaught TypeError: sum is not a function
var sum=function(n,m){
  return n+m;
}

变量提升

带var和不带var的区别

/* => 在全局作用域下的区别
 *
 * 不带var的,相当于给全局对象window设置了一个属性a
 * window.a=13;
 */
a=13;
console.log(a); //=> 13

/*
 * 栈内存变量存储空间
 *      b  
 * 带var的:在全局作用域下声明了一个变量b(全局变量),但是在全局下声明的变量也同样相当于给window添加了一个对应的属性(只有全局作用域具备这个特点)
 */

 var b=14;
 console.log(b);//=> 14
 console.log(window.b);//=> 14
let/const和var的区别

1.let和const不存在变量提升机制

创建变量的六种方式中,只有var和function有变量提升,let、const、import、class都不存在变量提升机制
2.var允许被重复声明,而let不能重复声明
在相同的作用域/执行上下文中,如果使用var和function关键词声明变量并进行重复声明,是不会有影响的(声明第一次后,之后再遇到就不会再重复声明了)
但是使用let或者const就不行,浏览器会校验当前作用域是否已经存在这个变量,如果存在,再次声明就会报错

=> 在浏览器开辟栈内存供代码自上而下执行前,不仅有变量提升操作,还有很多其他操作,其中还有个操作叫'词法解析'或叫'词法检测',作用就是检测当前即将执行的代码是否会存在'语法'错误,如果出现错误,代码将不会执行,直接报告错误
  console.log(1); =>由于下面语法错误,所以不输出
  let a=12;
  console.log(a); =>由于下面语法错误,所以不输出
  let a=13;  =>Uncaught SyntaxError: Identifier 'a' has already been declared
  console.log(a); =>由于上面面语法错误,所以不输出


  console.log(1); =>这里的1能够输出
  console.log(b); =>Uncaught ReferenceError: Cannot access 'a' before initialization
  let b=12;

  =>所谓的重复是指,不管之前是通过什么办法,只要当前栈内存中存在这个变量,我们使用let/const再次声明这个变量的时候就是语法错误(在词法解析阶段,而不是在代码执行阶段)
  console.log(a);
  var a=12;
  let a=13; =>Uncaught SyntaxError: Identifier 'a' has already been declared
  console.log(a);

fn();
function fn(){console.log(1);};
fn();
function fn(){console.log(2);};
fn();
var fn=function(){console.log(3);};
fn();
function fn(){console.log(4);};
fn();
function fn(){console.log(5);};
fn();

let和var的区别

/* 全局作用域
 * 1.变量提升阶段.不管if中条件是否成立,都要先执行变量提升这个阶段
 *    var a =>此时的a为undefined,同时创建了个全局变量a,同时也给window一个属性值window.a
 * 2.执行代码阶段
 *   [property] in [object]:检测当前属性是否属于这个对象,返回boolean型结果
 */
console.log(a); => undefined
if(!('a' in window)){   => 'a' in window===true  !true===false
  var a = 14;
}
console.log(a); => undefined
/* 全局作用域
 * 1.变量提升阶段
 *    但是'函数'如果在'条件判断'中,不管条件成立与否,提升变量阶段只会提升变量而'不会赋值',在老版本浏览器中会提升也会赋值,但是在新版本浏览器中,为了兼容ES6严谨的语法规范,条件中的函数在变量提升阶段只会提前声明,而不会赋值
 * 2.执行代码阶段
 *   
 */
//fn(); => Uncaught TypeError: fn is not a function,因为fn在if判断中,提升变量阶段没赋值,因此此时的fn()相当于只是声明的一个变量,而不是一个函数,然后我们把它注释掉,执行下面代码
if('fn' in window){  
  fn();   => 'fn' in window===true 条件成立,进来的第一件事就是给fn赋值,然后再执行代码,因此这个fn()能够输出,内容为你好!
  function fn(){
    console.log('你好!');
  }
}
fn(); => 你好!

自执行函数

形如(fn([形参]){})([实参]),也可以+fn([形参]){}([实参])、fn([形参]){}([实参])、!fn([形参]){}([实参])、-fn([形参]){}([实参]),前面加()、、+、-只是为了语法不报错,没有别的特殊意思
自执行函数本身由于没有函数名,因此不进行变量提升

f=function(){return ture;} => 全局变量,window.f=...
g=function(){return false;} => 全局变量,window.g=...
~function(){  => 自执行函数
  /*
   * 函数执行会形成一个私有作用域
   * 1.变量提升 function g(由于在if条件中,因此只是提升变量,而没有赋值)
   * 2.代码执行
  /
  if(g()&&[]==![]){   => Uncaught TypeError: g is not a function 由于此时g()还不是一个函数,因此条件不成立,所以会报错,后面代码将不再执行
    f=function(){return false;}
    function g(){return true;}
  }
}();
console.log(f());
console.log(g());

let能解决typeof检测时出现暂时性死区的问题)(let(const、class、import)比var(function)更严谨)

console.log(a); =>Uncaught ReferenceError: a is not defined

console.log(typeof a); =>'undefined'这是浏览器的一个BUG,因为a没有定义,应该报错

console.log(typeof a); =>Uncaught ReferenceError: Cannot access 'a' before initialization
let a;

私有作用域下的变量提升

console.log(a,b);
var a=12;
var b=12;
function fn(){
  console.log(a,b);
  var a=b=13;
  console.log(a,b);
}
fn();
console.log(a,b);

私有作用域下的变量提升

console.log(a,b,c);
var a=12,
    b=13,
    c=14;
function fn(a){
  console.log(a,b,c);
  a=100;
  c=200;
  console.log(a,b,c);
}
b=fn(10);
console.log(a,b,c);

私有作用域下的变量提升2

全局变量和私有变量

如果全局变量和私有变量指向同一个地址,那么它们就有关系(全局变量和私有变量毫不相干的说法是错误的)

var ary=[12,23];
function fn(ary){
  console.log(ary);
  ary[0]=100;
  ary=[100];
  ary[0]=0;
  console.log(ary);
}
fn(ary);
console.log(ary);

全局变量和私有变量

作用域和作用域链

从函数创建开始,作用域就已经制定好了
当前函数是在哪个作用域(N)下创建的,那么函数执行形成的作用域(M)的上级作用域就是N,和函数在哪执行没有关系,只和创建的地方有关

var n=1;
function fn(){
  var n=2;
  function f(){
    n--;
    console.log(n);
  }
  f();
  return f;
}
var x=fn();
x();
console.log(n);

在这里插入图片描述

闭包作用域

  • 创建函数

    • 开辟一个堆内存,把函数里面的代码当作字符串存储进去
    • 把堆内存的地址赋给函数名或者变量名
    • 函数在哪创建,那么执行的时候所查找的上级作用域就是谁
  • 函数执行

    • 形成一个全新的私有栈内存(执行一次形成一个,多个之间也不会产生影响)
    • 形参赋值&变量提升
    • 代码执行(把所属堆内存中的代码字符串拿出来一行行执行)
    • 作用域链机制:遇到一个变量,首先看它是否为私有变量(形参和在私有栈内存中声明的变量叫私有变量),是私有的就操作自己的变量即可,不是私有的就按上级作用域查找,一直找到全局作用域为止
    • 闭包的保护机制:私有变量和外界变量没有必然关系,可以理解为被私有栈内存保护起来了
  • 堆栈内存释放问题

    • 函数执行就会形成一个栈内存(从内存中分配一块空间),如果内存都不销毁释放,很容易就会导致栈内存溢出(内存爆满,电脑卡死),堆内存的释放是学习JS的核心知识之一
  • 堆内存释放

    • 创建一个引用类型数据值,就会产生一个堆内存,如果当前创建的堆内存不被其它东西占用了(浏览器会在空闲的时候,查找每一个内存的引用情况,不被占用的都会回收释放掉),则会释放
    let obj ={
      name:'小王';
    }
    let oop=obj;
    => 此时obj和oop都占用着对象的堆内存,想要释放堆内存,需要手动解除变量和值的关联(null空对象指针)
    obj=null;
    oop=null;
    
  • 栈内存

    • 打开浏览器形成的全局作用域就是栈内存
    • 手动执行函数形成的私有作用域就是栈内存
    • 基于ES6中的let/const形成的块作用域也是栈内存
  • 栈内存释放

    • 全局栈内存:在浏览器页面关闭或者刷新时会销毁
    • 私有栈内存:一般情况下,函数只要执行完成,私有栈内存就会被销毁释放掉,但是一旦栈内存中某个东西(一般都是堆地址)被私有作用域以外的事物给占用了,则当前栈内存不能立即被释放销毁,市面上认为的闭包:函数执行形成的不能被销毁的私有栈内存,这样的才是闭包.也有的认为只要形成私有栈内存,来保护里面的变量,这个机制就是闭包机制
    function x(){
      return function(){...}
    }
    let f=x();
    
  • 闭包的两大作用

    • 保护(私有变量和外界没有必然联系)
    • 保存(形成不能销毁的栈内存,里面的私有变量等信息也被保存下来)
var i=5;
function fn(i){
  return function(n){
    console.log(n+(++i));
  }
}
var f=fn(1);
f(2);
fn(3)(4);
fn(5)(6);
f(7);
console.log(i);

闭包1

var i=20;
function fn(){
    i-=2;
    return function (n){
        console.log((++i)-n);
    }
}
var f=fn();
f(1);
f(2);
fn()(3);
fn()(4);
f(5);
console.log(i);

闭包2

var a=9;
function fn(){
	a=0;
	return function(b){
		return b+a++;
	}
}
var f=fn();
console.log(f(5));
console.log(fn()(5));
console.log(f(5));
console.log(a);

闭包3

详细解释闭包的两个作用(保护和保存)

从性能的角度上讲,我们真实项目中应该减少对闭包的使用(因为闭包会产生不释放的栈内存,过多使用就会导致内存溢出或者降低性能)

  • 保护

    • 1.JQuery(JQ)前端非常经典的类库:提供了大量的方法供开发人员使用=>同时为了防止全局变量的污染(解释:导入JQ之后,它里面有大量的方法,如果这些方法不保护起来,用户编写的方法很容易和JQ中的方法名字产生冲突,产生冲突可以理解为全局变量污染),JQ中的方法和变量需要用闭包保护起来
  • 保存

this问题

函数执行的主体(不是上下文):意思是谁把函数执行,那么执行主体就是谁,this非常的不好理解,因此以后遇见this,想一句话:“你以为你以为的就是你以为的”
给元素的某个事件绑定方法,当事件触发方法执行的时候,方法中的this是当前操作的元素本身
如何确定主题:当方法前如果有点,则this为方法前的内容,没有点,this则为window或者undefined(严格和非严格的区别)

var name='老王';
function fn(){
  console.log(this.name);
};
var obj={
  name:'老李',
  fn:fn
};
obj.fn();  => obj执行的,则this为obj,输出老李
fn();  => 非严格模式下,fn()为window.fn(),则this为window,输出老王
var fullName='language';
var obj={
  fullName:'javascript',
  prop:{
    getFullName:function(){
        return this.fullName;
    }
  }
};
console.log(obj.prop.getFullName()); =>this:obj.prop  return obj.prop.fullName  undefined
var test=obj.prop.getFullName;  
console.log(test());  =>this:window  return window.fullName  language
var num=10;
var obj={num:20};
obj.fn=(function(num){
  this.num=num*3;
  num++;
  return function(n){
    this.num+=n;
    num++;
    console.log(num);
  }
})(obj.num);
var fn=obj.fn;
fn(5);
obj.fn(10);
console.log(num,obj.num);

this问题

在赋值情况下逻辑或与非的意义

A || B :先验证A的值真假情况,若A为真,则将A的值返回,否则返回B的值
A && B :先验证A的值真假情况,若A为真,则将B的值返回,否则返回A的值
&&的优先级高于||

var foo='hello';
(function(foo){
  console.log(foo);  => 私有变量hello
  var foo=foo||'world'; hello||world => 返回私有变量hello 
  console.log(foo);  => 私有变量hello
})(foo); => 这里是把全局变量下的foo的值'hello'当作实参传递给函数的形参
console.log(foo);  => 全局变量hello

构造原型模式(正统面向对象)

自己能够创造出自定义类和对应的实例,构建起一套完整的面向对象模型

function CreatePerson(name,age){
  this.name=name;
  this.age=age;
}


=> CreatePerson('张三',18) 这里是普通函数执行


let person1=new CreatePerson('李四',20);
=> new CreatePerson()执行和普通函数执行的联系
=> 1.new这种执行方式叫做'构造函数执行模式',此时的CreatePerson不仅仅是一个函数名,被称为'类',而返回的结果(赋值给person1的)是一个对象,我们称之为'实例',而函数体中出现的this都是这个实例

构造原型模式

instanceof:用来检测某个实例是否属于这个类

实例 instanceof类 属于返回true,否则返回false
局限性:1.要求检测的实例必须是对象数据类型,基本数据类型是无法基于它检测出来
typeof能检测基本数据类型,但是引用数据类型无法具体区分,null检测为object等,刚好与instanceof互补

字面量创建方式(也是number类的实例,也可以调取内置的共有方法)

let n=10;
console.log(n.toFixed(2));
console.log(typeof n);   => 'number'

构造函数创建模式(创建出来的实例是对象类型的)

let m=new Number('10');
console.log(typeof m);  => 'object'
console.log(m.toFixed(2));
=> 和实例有关的操作一定是this.xxx=xxx,因为this是当前类创造出来的实例
=> 私有变量和实例没有必然联系

function Fn(n){
  let m=10;
  this.total=n+m;
  this.say=function(){
    console.log(this.total);
  };
}
let f1=new Fn(10);
let f2=new Fn(20);
let f3=new Fn;
console.log(f1.m);
console.log(f2.n);
console.log(f1.total);
f2.say();
console.log(f1===f2);

构造函数创建模式

原型及原型链模式

=> 执行步骤(new执行也会把类当作普通函数执行,当然也有类执行一面)
=> 1.创建一个私有栈内存
=> 2.形参赋值&变量提升
=> 3.浏览器创建一个对象出来(这个对象就是当前类的一个新实例),并且让当前函数中的this指向这个实例对象(构造函数模式中,方法中的this是当前类的实例)
=> 4.代码执行
=> 5.在我们不自己设置return返回情况下,浏览器会把当前创建的实例对象默认返回


function Fn(){
  this.x=100;
  this.y=200;
}
let f1=new Fn();
let f2=new Fn();

原型和原型链模式

1.每一个函数数据类型的值,都有一个天生自带的属性:prototype(原型),这个属性的属性值是一个对象(‘用来存储实例公用属性和方法’)
函数数据类型

  • 普通函数
  • 类(自定义类和内置类)

2.在prototype这个对象中,有一个天生自带的属性constructor,这个属性存储的是当前函数本身

Fn.prototype.constructor===Fn  => true

3.每一个对象数据类型的值,也有一个天生自带的属性:proto,这个属性指向’所属类的原型prototype’
对象数据类型

  • 普通对象、数组、正则、Math、日期、类数组等
  • 实例也是对象数据类型的值
  • 函数的原型prototype属性的值也是对象数据类型
  • 函数也是对象数据类型的值

原型链查找机制

1.先找自己私有属性,有则调取使用,没有就继续找
2.基于__proto__找所属类原型上的方法(Fn.prototype),如果还没找到则继续基于__proto__往上找,直到找到Object.prototype为止

原型链查找机制

hasOwnProperty

'hasOwnProperty’用来检测某个属性名是否为是否为当前对象的私有属性
'in’用来检测这个属性是否属于某个对象(不管是私有属性还是公有属性,只要是它的属性,结果就为true)

let ary=[1,2,3];
console.log('0' in ary); => true
console.log('push' in ary); => true
console.log(ary.hasOwnProperty('0')); => true
console.log(ary.hasOwnProperty('push')); => false push是公有属性而不是私有属性

console.log(Array.prototype.hasOwnProperty('push')); => true 公有还是私有得看是相对谁来的  自己堆中有的就是私有的属性,需要基于__proto__(__proto__这个属性在IE浏览器中不能使用,edge除外)查找的就是公有的

封装一个检测公有属性的方法

=>扩展一个hasPubProperty方法用来检测属性是否为公有

        =>在object上绑定一个hasPubProperty的方法
        Object.prototype.hasPubProperty=function(prop){
            =>首先得先判断传入的属性是否为number、string、boolean等
            let propAry=['number','string','boolean'];
            let resProp=typeof prop;
            if(!propAry.includes(resProp)){
                return false;
            }

            =>再判断传入的属性是否为该对象的属性
            let x=prop in this;

            =>再判断传入的属性是否为该对象的私有属性
            let y=this.hasOwnProperty(prop);

            =>如果满足是该对象的属性且不是私有属性则返回true,否则返回false

            if(x===true&&y===false){
                return true;
            }else{
                return false;
            }
        };

        console.log([].hasPubProperty('push')); //true

扩展内置类方法(之前的数组去重把他写到类的原型上))

!function (){
  function myUnique(){
  //创建一个空对象,用来接收循环数组中的内容
  let obj={};

  //循环遍历传入的数组,定义一个item将传入数组中的内容作为属性名和属性值
  for(let i=0;i<this.length;i++){
    let item=this[i];
    //如果属性名不等于undefined说明重复
    if(obj[item]!==undefined){
      this[i]=this[this.length-1];  //把这一项与最后一项互换位置
      this.length--;   //数组长度减一
      i--;
      continue;
    }
  obj[item]=item;
  }
  //保证返回的结果依然是Array的一个实例,这样我们可以链式使用方法(链式写法需要保证每一次返回的结果必须是当前类的一个实例)
  return this;
}

Array.prototype.myUnique=myUnique;//把该方法放入Array这个类的原型上
}() 

Function中的call\apply\bind

它们都是原型上提供的三个公有方法
每一个函数都可以调用这些方法
这些方法都是用来改变函数中的this指向

call

window.name='WINDOW'
let obj={name:'OBJ'};
function(){
  console.log(this.name);
}
fn() =>  this:window(严格模式下是undefined,非严格模式下是window)

fn.call(obj); => this:obj

语法:函数.call([context],[params1]…)
函数基于原型链查找时,找到Function.prototype.call这个方法,并且把它执行,在call方法执行的时候,完成了以下一些操作

  • 让当前函数执行
  • 把函数中的this指向改为第一个传递给call的实参
  • 把传递给call的余下实参当作参数信息传递给当前函数
  • 如果执行call方法时,没有传递任何参数,则函数中的this指向在非严格模式下为window,严格模式下为undefined
自己写一个基于原生JS实现内置call方法(跟浏览器自带的有差别)
~function(){
    function call(context){
      context = context || window;
        let args=[],
        result;
        for(let i=1;i<arguments.length;i++){
            args.push(arguments[i]);//把传递的参数中的每一项给args这个数组
        }
        //我们需要让fn中的this变成context
        //就给context这个对象添加一个属性名(自定义一个),使得这个属性名对应的属性值为fn1这个方法
        context.myIndex=this;//这个this,谁把call执行,this就是谁
        result=context.myIndex(...args);//展开运算符,先将该方法执行,然后使得数组中的每一项分别当作参数传递
        delete context.myIndex
        return result;
    }
    Function.prototype.call=call;
}();

apply

和call方法一样,都是把函数执行,并且改变里面this的指向,唯一区别就是传递的参数形式不同
call是一个个传递,apply则是传递一个数组

bind

和call和apply一样,也是用来改变this的指向,只不过bind改变指向时并没有执行该函数

ES6基础语法

let/const(它们之间的区别)

let与var的区别:let不存在变量提升(当前作用域中,不能在let声明前使用)
同一个作用域中,let不能重复声明
let解决了typeof暂时性死区
全局作用域中,使用let声明变量不会给window添加属性
let会存在块级作用域(除对象外的大括号都可以被看作块级私有作用域)

箭头函数(es6中新添加的创建函数方式)
const fn=([形参])=>{
  //函数体
}
fn([实参])

箭头函数的创建都是函数表达式的方式,所以不存在变量提升,函数只能在创建完成后被执行
形参只有一个,则小括号可以不加
大括号内只有一条语句且为return,则可以省略大括号
箭头函数没有arguments,但是可以基于剩余运算符(…)获取实参集合,而且ES6中支持给形参设定默认值
箭头函数中没有自己的this,它的this都是自己所处的执行上下文中的this

ES6中的解构赋值

让左侧出现和右侧值相同的结构,以此快速获取到我们需要的内容(项目中用的比较多的是对数组和对象的解构赋值)

const ary=[10,20,30,40,50];
const [n,m,...x]=ary;  => ...x拓展运算符:把剩下的内容存储到x数组中,但是这个表达式只能出现在最后
console.log(n,m,x); => 10 20 [30,40,50]
======================================================

const ary=[10,[20,30,[40,50]]];
const [n,[,,[,m]]]=ary;
console.log(n,m); => 10 50
======================================================

const obj={
  name:'老王',
  age:18,
  sex:'boy',
  friends:['老李','老刘','老陈','老张']
}

const {name,age,xingbie}=obj
console.log(name,age,xingbie)  => 老王 18 undefined  对象的结构赋值中,左边的属性名必须跟右边保持一样(顺序可以随意)才能取到对应的值,如果非要自己定义属性名可以sex:xingbie

const {name,sex:xingbie,age}=obj; 
console.log(name,xingbie,age) => 老王 boy 18
======================================================
const {
  name,
  friends:[firstFrineds]
}=obj;  
console.log(name,firstFrineds) => 老王 老李
======================================================

const data={
  'code':0,
  'data':{
      'today':'2021-07-02',
      'data':[{
          'date':'2021-07-01',
          'number':'10',
          'weekday':'\u661f\u671f\u4e09'
      },{
          'date':'2021-07-02',
          'number':'20',
          'weekday':'\u661f\u671f\u56db'
      }]
  }
}

const {
  code,
  data:{
    today,
    data:calenderData
  }
}=data;

console.log(code,today,calenderData); => 0 "2021-07-02" 后面对象中的两条数据

calenderData.forEach((item)=>{
  let weekday=item.weekday,
  date=item.date;
  console.log(weekday,date);
})

ES6中的’…’

拓展运算符(多用在解构赋值中)
展开运算符(多用在传递实参中)
剩余运算符(多用在接收实参中)

=> 拓展运算符
const ary=[10,20,30];
const [n,...m]=ary;
console.log(n,m) => 10 [20,30]


=> 展开运算符
const ary=[10,20,30];
let min=Math.min(...ary);  =>相当于Math.min(10,20,30)

=> 数组克隆(浅克隆)
const cloneAry=ary.slice(0);

const cloneAry=[...ary]

=> 对象克隆(浅克隆)
const obj={
  name:'老王',
  age:18,
  sex:'男'
}

const cloneObj={...obj,age:20,sex:'女'}
console.log(cloneObj); => {name: "老王", age: 20, sex: "女"}


=> 剩余运算符
const fn=(n,...m)=>{
  //n:10   ...m:[20,30]
}
fn(10,20,30);

ES6中的class创建类

=> 传统方式创建类
function Fn(){
  this.x=100;
}
Fn.prototype.getX=function(){
  console.log(this.x);
}
var f1=new Fn();
f1.getX();

// 也可以当作普通函数执行
Fn();

// 还可以把Fn当作普通的对象
Fn.queryX=function(){}
Fn.queryX();


=>ES6中class类创建
 class Fn{
   constructor(n,m){
     this.x
   }

   getX{
     console.log(this.x)
   }  //=> 写在原型上的公有方法

   static queryX(){};//=> 前面加上static,把当前Fn当做普通对象设置键值对
   static z=100;
   y=100; //给实例设置私有属性
 }

ES6中的模板字符串

反引号括起来${}

let year=2021,
month=07,
day=02;

let res=`今天是${year}年${month}月${day}日`;
console.log(res);  =>今天是2021年7月2日

类数组借用数组的方法

=> 实现一个求和方法
function sum(){
  const ary=[];
  let total=null;
  ary.forEach.call(arguments,(item)=>{   //将数组中的this指向arguments
    total=total+item; 
  })

  return total;
}

sum(10,20,30,40); //100

客户端与服务端的交互

当用户在浏览器窗口的地址栏输入网址,到最后看到页面,中间都经历的哪些东西?

  • 1.URL地址解析
  • 2.DNS域名解析
  • 3.和服务端建立TCP连接
  • 4.把客户端信息传递给服务端(发送HTTP请求)
  • 5.服务器得到并处理请求(HTTP响应内容)
  • 6.服务端渲染服务器返回的内容
  • 7.和服务器断开TCP连接

URI/URL/URN

URL(Uniform Resource Locator): 统一资源定位符,根据地址找到相对应的资源
URI(Uniform Resource Identifier): 统一资源标识符,URN和URL是URI的子集
URN(Uniform Resource Name): 统一资源名称,一般指国际上通用的(标准的)一些名字(例如:国际统一发版的编号)

一个完整的URL应该包含的内容

https://www.bilibili.com/video/BV1rV411n72v?p=294&spm_id_from=pageDriver

  • 协议(http://)

    • http 超文本传输协议,除了传递文本,还可以传递媒体资源文件(或者流文件)以及XML格式数据
    • https 更加安全的http,一般涉及支付的网站都会用该协议(s:ssl加密传输)
    • ftp 文件传输协议,一般应用于把本地文件上传到服务器端上
  • 域名(www.bilibili.com)

    • 顶级域名 qq.com
    • 一级域名 www.qq.com
    • 二级域名 sports.qq.com
    • 三级域名 kbs.sports.qq.com
    • .com 国际域名
    • .cn 中文域名
    • .com.cn
    • .edu 教育
    • .gov 政府
    • .io 博客
    • .org 组织
    • .net 系统类
  • 端口号(:80):端口号的取值范围0-65535,用端口号来区分同一台服务器上的不同项目

    • http 默认端口号为:80
    • https 默认端口号为:443
    • ftp 默认端口号为:21
    • 如果项目采用的就是默认端口号,我们在书写地址的时候,不用加端口号,浏览器在发送请求的时候会默认帮我们加上
  • 请求资源路径名称(/video/BV1rV411n72v)

    • 默认的路径或者名称(xxx.stu/不指定资源名,服务器会找默认的资源,一般默认资源名是default.html、index.html等,这些都可以在服务器端自己配置)
    • 注意伪URL的地址处理(URL重写技术是为了增加SEO搜索引擎的优化,动态的网址一般不能放在搜索引擎搜索,所以我们要把动态网址静态化,此时需要重写URL)
      https://item.jd.hk/2688449.html => https://item.jd.hk/index.php?id=2688449
  • 问号传参信息(?p=294&spm_id_from=pageDriver)

    • 客户端想把信息传递给服务端有很多种方式
      • URL地址问号传参
      • 请求报文传输(请求头和请求主体)
    • 也可以不同页面之间信息交互,例如从列表到详情
  • HASH值(#…)

    • 也能充当信息传输的方式
    • 锚点定位
    • 基于HASH实现路由管控(不同的HASH值,展示不同的组件和模块)

    DNS服务器域名解析

    DNS服务器:域名解析服务器,在服务器上存储着域名 <=> 服务器外网IP 的相关记录
    而我们发送请求的时候,所谓的DNS解析,其实就是根据域名,在DNS服务器上查找对应服务器的外网IP

    DNS优化
    • DNS缓存(一般浏览器在第一次解析后,默认建立缓存,时间很短,只有大概一分钟)
    • 减少DNS解析次数(一个网站中我们需要发送请求的域名和服务器尽可能的减少即可)
    • DNS预获取(dns-prefetch):在页面加载开始的时候,就把当前页面中需要访问的其它域名(服务器)的信息进行提前DNS解析,以后加载到具体内容部分就可以不用解析了

    建立TCP连接(三次握手)

    HTTP报文

    • 请求报文:所有经过传输协议,客户端传递给服务端的内容,都被称为请求报文
    • 起始行
    • 请求首部
    • 请求主体
    • 响应报文:所有经过传输协议,服务器返回给客户端的内容,都被称为响应报文
    • HTTP状态码
    • 响应首部
    • 响应主体
    • HTTP报文:请求报文+响应报文

    HTTP状态码

    1-5开头,三位数字

    • 200 OK:成功
    • 201 CREATED:一般用于告诉服务器创建一个新文件,最后服务器创建成功后返回的状态码
    • 204 NO CONTENT:对于某些请求(例如:PUT或DELETE),服务器不想处理,可以返回空内容,并且用204状态码告知
    • 301 Moved Permanently:永久重定向(永久转移)
    • 302 Moved Temporary:临时转移,很早以前基本用302来实现,但是现在主要用307来做
    • 304 Not Modified:设置http的协商缓存
    • 307 Temporary Redirect:临时重定向,主要用于服务器的负载均衡等
    • 400 Bad Request:传递给服务器的参数错误
    • 401 Unauthorized:无权限访问
    • 404 Not Found:请求地址错误
    • 500 Internet Server Error:未知服务器错误
    • 503 Service Unavailable:服务器超负荷

AJAX的四个步骤

1.创建AJAX实例
let xhr=XMLHttpRequest //=> IE6以下用的是 new ActiveXObject()

2.打开URL(配置发送请求的信息)
method:http请求方式
URL:请求地址(API接口地址)
ASYNC:设置同步或者异步,默认true是异步,false是同步
user-name:传递给服务器的用户名
user-pass:传递给服务器的密码
xhr.open(‘GET’,’./json/xxx.json’,true)

3.监听ajax状态,在状态为X的时候,获取服务器响应的内容
Ajax状态码: 0 1 2 3 4

4.发送请求(send中放的是请求主体的内容)

http请求方式

  • get系列请求(从服务器上拿的多,给的少)
    • get
    • delete:一般用于告诉服务器,从服务器上删除信息
    • head:只想获取响应头的内容,告诉服务器响应主体的内容不想要
    • options:试探性请求,发个请求给服务器看看能不能接收到,能不能返回
  • post系列请求(从服务器上拿的少,给的多)
    • post
    • put(和delete对应):告诉服务器把我给的信息存储到服务器上(一般用于文件和大型数据内容)

真实项目中最好使用对应的请求(语义化)

get系列一般用于从服务器获取信息,post一般用于给服务器发送信息,但是两个都能把信息传递给服务器和从服务器上获取信息,只是两者谁多谁少而已

客户端是如何把信息传递给服务端?

  • 问号传参 xhr.open(‘GET’,’./getdata?xxx=xxx&xxx=xxx’)
  • 设置请求头 xhr.setRequestHeader([key],[value])
  • 设置请求主体 xhr.send(主体内容)

服务端是如何把信息返回给客户端?

  • 设置响应头
  • 设置响应主体(大部分信息是通过响应主体返回的)

get系列和post系列本质区别
get系列传递给服务器的方式一般为:问号传参
post系列传递给服务器的方式一般为:设置请求主体
1.get传递给服务器的内容比post少,因为URL有最大长度限制(IE:2k google:4-8K),超出的部分被浏览器截取了
2.get会产生缓存(这个缓存是不可控的),因为请求的地址(尤其是问号传递的信息),浏览器有时候会认为你要和上次请求的信息一样,就会拿取上一次的信息,因此我们需要去除这个缓存,方法是在地址后面加随机数
3.get相比post不安全(虽然两个都不安全):get基于问号传参,容易被URL劫持,而post是请求主体,不容易被篡改

AJAX状态码

获取状态码:xhr.readyState

  • UNSEND 0 :未发送(创建一个xhr,初始状态为0)
  • OPEND 1 :已经打开(执行了xhr.open)
  • HEADERS_RECEIVED 2 :响应头信息已经返回给客户端(发送请求后,服务器会依次返回响应头和响应主体的信息)
  • LOADING 3 :等待服务器返回响应主体信息
  • DONE 4 :响应主体信息已经返回给客户端

Ajax中的7个方法

  • abort()中断请求信息
  • getAllResponseHeaders()获取所有响应头信息
  • getResponseHeader()获取响应头信息
  • open()
  • send()
  • setRequestHeader()设置请求头信息
  • overrideMimeType()重改Mime类型

Promise

let promiseExample=new Promise((resolve,reject)=>{
  //这里一般存放我们要操作的异步任务,任务成功执行resolve函数,失败执行reject函数
})

=> promise的第一个参数必须传递一个函数,我们把它称作executor
=> 当我们new一个promise的实例对象的时候就会把这个executor执行
=> promise不仅会把executor执行,还会给executor传递两个参数,resolve函数和reject函数
=> resolve函数代表promise处理的异步事件是成功的,然后把promise的状态改为fulfilled
=> reject函数代表promise处理的异步事件是失败的,然后把promise的状态改为rejected
=> promise.prototype上有三个常用方法,分别为then,catch,finally
=> then是在执行异步操作完成之前,往事件池中添加两个方法(成功和失败需要做的事情),等到异步操作完成时,再根据resolve或者reject去执行对应的事件,catch代表捕获错误,finally是不论成功还是失败他都会执行里面自己设置的事情

let promiseExamp=new Promise((resolve,reject)=>{
setTimeOut(()=>{
let ran=Math.random();
ran<0.5?reject(ran):resolve(ran)
},1000)
})

promiseExamp.then((success)=>{
console.log(‘成功’+success)
},(error)=>{
console.log(‘失败’+error)
})

promiseExamp.catch((message)=>{
console.log(‘失败’+message)
})

promiseExamp.finally(()=>{
console.log(‘不论成功失败我都会出现!’)
})

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值