JavaSrcipt基础

329 阅读1小时+

一、JavaScript介绍

概念:JavaScript简称JS,是一个运行在【浏览器】的【解释型】【弱类型】【面向对象】的脚本语言 JS由三部分组成:ECMScript(核心语法,版本3/5/6/7/8/9/10/11/12)+DOM(JS操作HTML网页)+BOM(JS操作浏览器)

1、浏览器

JS的运行环境是浏览器
(1)浏览器自带JS解释器,打开浏览器就可以运行
(2)在学习node.js是会安装单独的js解释器

2、编译型/解释型

(1)编译型:在程序执行前会检查程序的语法是否正确,如果不正确,不会运行-严格:eg:Java、C、C++、C#...
(2)解释型:在程序执行前不会检查程序的语法,直接运行,当碰到错误时,就停止运行-自由:eg:JS、php...

3、强类型/弱类型

(1)强类型:变量保存的数据,严格按照数据类型 -比如:eg:Java -严格
(2)弱类型:变量保存的数据是随意的,数据类型由数据来决定 -比如:eg:JavaScript -自由

4、面向对象

对象名.属性名; 对象名.方法名();

5、特点

(1)可以使用一切编辑工具编写js代码
(2)解释型
(3)弱类型
(4)面向对象
(5)可以做一切CSS完成不了的效果(轮播、选项卡、购物车、验证...)

二、如何使用JS

1、两种方式

方式一

在HTML页面上写一个<script></script>标签,中间写上js代码。一般放在HTML页面的中<body></body>中的最后面,html代码的后面

<script>
    //js代码
</script>

方式二

单独创建一个xx.js文件,在HTML中使用<script src="../xx.js"></script>引入,放的位置与方式一相同

<script scr="../xx.js">
    //引入的方式,一定不能在这里面写代码
</script>

2、输出方式/打桩输出

输出是为了方便定位错误,找出错误,输出有三种方式
(1)在控制台输出。console.log("输出内容")

console.log("输出内容");
console.log(123456);

(2)在页面中输出。document.write("输出内容")。此方法还支持标签的写法,但是搭配点击事件,会把页面中其他内容全部替换掉,不推荐使用

document.write("输出内容");
document.write(123456);

(3)在弹出框中输出内容。alert("输出内容")。有时候会卡住整个页面,导致用户只能看到一个白板

alert("输出内容");
alert(123456);

3、*变量

方式一:var

创建后可以修改
JS何时使用:当一个值会被反复使用时,要提前将它保存在一个变量中,以后使用其变量名,相当于就是在使用变量的值
语法:var 变量名=值;

var test1=123;
var test2="这是一个测试变量";

特殊
  (1)变量名不是随意取的
    a、不能以数字开头
    b、建议驼峰命名或下划线命名法
    c、命名要尽量的见名知意
  (2)如果变量名是name,不管你保存的数据类型是什么,都会悄悄的给转成一个字符串
  (3)变量名不能是关键字
  (4)变量可以只创建,不赋值,默认为undefined,但是undefined什么操作都做不了,后面需要给它赋值
  (5)多个变量可以连续创建,简写:var 变量名1=值1,变量名2=值2,变量名3=值3,变量名4=值4...;

var 变量名1=值1,变量名2=值2,变量名3=值3,变量名4=值4...;

方式二:let

let 变量名=值;
  作用:
    (1)解决了声明提前
    (2)带来了块级作用域,一个{}就是一个代码块,此变量只能在那个所在的{}里面使用
    (3)如果用let去当下标绑定事件,那么他会记录着你当前元素的下标,不在需要自定义下标了 - 其实forEach的那个形参i就是let创建的

4、常量

常量创建后,不允许修改
  生活中的常量:
    PI
    一个小时60分
    一分钟60秒
    一天24个小时
    一年365/366天
  语法:const 常量名=值;
  其余特殊点和变量一样

5、数据类型

分为两大类
(1)*原始/基本/值类型:5个
  a、*number - 数字:取值有无数个,数字直接写,不需要加引号,在控制台中显示的颜色是蓝色
  b、*string - 字符串:取值有无数个,但是必须加上:""或''(颜色是黑色)
  c、*boolean - 布尔:取值只有2个,分别叫 true(真|对) 或 false(假|错) - 一般用作条件判断(控制台中显示的颜色是蓝色)
  d、null - 空,取值只有1个,就是null,唯一的作用就是用于释放变量释放内存的,节约内存空间,提升网页的性能,以后会有更好的方式(颜色是灰色)
  e、undefined - 取值只有1个,就是undefined,什么都做不了(颜色是灰色)
(2)引用/对象类型:有11个。以后学习

6、*运算符

(1)*算术运算符:+   -   *   /   %
  特殊
    a、%:取余,俗称模,两个数相除,不取商,而是取除不尽的余数
      作用:
        i、*任意数%2 - 判断奇偶数
        ii、取出某个数字的后n位
          1234%10 -> 4
          1234%100 -> 34
          1234%1000 -> 234
    b、带有隐式转换:悄悄地会将数据类型转换,发生变化,默认:都是左右两边转为数字再运算
      true -> 1
      false -> 0
      undefined -> NaN
      null -> 0
      "1000" -> 1000
      "1000px" -> NaN - 需要用强制转换
      NaN -> Not A Number:不是一个数字,但是确实是数据类型,不是一个有效数字,有两个缺点:
        i、参与任何的算数运算结果都是NaN
        i、参与任何的比较运算结果都是false
    c、+运算特殊:如果碰上一个字符串,左右两边都会隐式转换为字符串,+运算就不再是+运算,而是拼接操作

(2)*比较运算符:>   <   >=   <=   ==   !=   ===   !==
  结果:一定是一个布尔值
  带有隐式转换:默认左右两边都会悄悄转为数字再进行比较大小
    特殊
      a、如果参与比较的左右两边都是一个字符串,则会按位PK每个字符的十六进制的uniccode号(十六进制ASCII码)
        数字0-9<大写A-Z<小写a-z<汉字
        常识:汉字的第一个字:是"一" unicode号是 4e00 ascii是19968。汉字的最后一个字:是 龥 unicode 号是 9fa5 ascii是40869
      b、NaN参与任何的比较运算结果都是false,带来了一个问题:如何判断x是不是NaN,我们不能使用普通的比较运算去判断!isNaN(x)。
        结果是一个布尔值:true -> 说明是有效数字 false -> 说明是NaN
      c、undefined==null/true
        解决:全等:不带隐式转换的等值比较===,要求值相同并且数据类型也要相同         !==:不带隐式转换的不等比较

(3)*赋值运算符:=   +=   -=   *=   /=   %=
  a、=:赋值:将=右边的值,保存到=左边的变量名之中   b、后面5个可以理解为是一种升级写法,运算后再保存回变量本身,举例:i=i+1 ==> i+=1;

(4)*逻辑运算符:综合比较 - 用于写条件
  &&:与(并且)
    全部条件都满足,结果才为true
    只要一个不满足,结果就为false
  ||:或者
    全部条件都不满足,结果才为false
    只要一个满足,结果就为true
  !:颠倒布尔值

(5)*自增自减运算符:
  ++ --
  i++ ===> i+=1 ===> i=i+1

  自增:固定的每次只能+1
  累加:+=每次加几由程序员自己决定

  前++和后++的区别?
    前++,返回的是加了过后的新值
    后++,返回的是加了之前的旧值

(6)位运算:
  左移:m<<n,读作m左移了n位,翻译:m*2的n次方
  右移:m>>n,读作m右移了n位,翻译:m/2的n次方

扩展:用户输入框:var 变量=promp("提示文字");

三、*****分支结构

1、程序的三种流程控制语句

(1)顺序结构 - 默认:程序从上往下执行每一句话
(2)分支结构 - 通过条件判断,选择部分代码执行
(3)循环结构 - 通过条件判断,选择要不要重复执行某块代码

2、三种分支结构

(1)if...else...分支

a、一个条件,一件事,满足就做,不满足就不做

if(条件){
    //操作;
}

b、一个条件,两件事,满足就做第一件,不满足就做第二件

if(条件){
    操作;
}else{
    操作;
}

c、多个条件,多件事,满足谁,就做谁

if(条件1){
    操作1;
}else if(条件2){
    操作2;
}else{
    默认操作;
}

(2)switch...case分支

  语法:

switch(变量/表达式){
    case1:
        操作1;
        break;
    case2:
        操作2;
        break;
    default:
        默认操作;
}

  特殊
    a、问题:默认只要一个case满足后,会将后续所有的操作全部做完
      解决:break;
      建议:每一个case的操作后面都跟上一个break
        有的地方也可以不加break;
          i、最后一个操作default可以省略break
          ii、如果中间多个条件,做的操作是一样的,可以省略中间部分
    b、case在做比较的时候是不带有隐式转换的
    c、default可以省略不写的,但是不推荐,如果不写,条件都不满足的情况,则什么事儿都不会发生

面试题:if vs switch的区别?
  a、switch...case:优点:执行效率较高,速度快(他比较时,case做的不是范围查找,而是等值比较)
     缺点:必须要知道最后的结果是什么才可以使用
  b、if...else...:优点:可以做范围判断
     缺点:执行效率较慢,速度慢(他做的是范围查找)
  建议:代码开发完过后,要做代码优化,要尽量的少用if...else...,多用switch...case和三目运算

(3)三目运算符

简化分支的
语法:
  条件?操作1:默认操作; === if...else...
  条件1?操作1:条件2?操作2:默认操作 === if...else if..else
注意:
  a、默认操作不能省略,省略了会报错
  b、如果操作复杂,不能使用三目运算:操作只能有一句话,如果操作有多句话还是推荐使用switch或if

四、*****循环结构

1、循环结构

反复执行 相同相似 的操作
  循环三要素:
    a、循环条件:开始 - 结束,循环的次数
    b、循环体:做的操作是什么
    c、循环变量:记录着我们当前在哪一次,而且他会不断的变化

2、三种循环

(1)while循环

语法:

var 循环变量=几;
while(循环条件){
    循环体;
    循环变量变化;
}

执行原理:首先创建出循环变量,判断循环条件,如果条件满足,则做一次循环体操作,并不会退出循环,而会回过头再次判断循环条件满不满足,如果满足,则做一次循环体操作,...直到条件不满足,才会退出循环

宏观上感觉循环好像一瞬间就结束了,但是微观上循环其实是一次一次执行的

特殊
  a、有的时候可能真的需要使用死循环:默认永远不会停下来的循环
    何时使用:不确定循环次数的时候
    while(true){死循环}
  b、死循环其实也会有一天停下来,如何停下来呢?
    break; - 退出整个循环,多半都是用来搭配死循环的
    continue; - 退出本次循环,下一次依然会执行

(2)*for循环

和while的原理是一样的,但是他比while看上去更加的简洁,更加的舒服 语法:

for(var 循环变量=几;循环条件;循环变量变化){
    循环体;
}

特殊
死循环:for(;;){}

面试题:while 和 for的区别?

  • while和for在原理上几乎没有区别?
  • 一般来说我们不确定循环次数的时候,会使用while循环 - 死循环
  • 一般来说我们确定循环次数的时候,会使用for循环 - 大部分情况都是他

(3)do...while循环

一般不使用 语法:

var 循环变量=几;
do{
    循环体;
    循环变量的变化
}while(循环条件)

面试题:while 和 do...while的区别?

  • 区别只看第一次,如果第一次条件都满足,那么两者没有任何区别。
  • 但是如果第一次条件不满足,那么while一次都不会执行,do...while至少会执行一次

五、***强制(显示)数据类型转换

******页面上一切数据JS获取到后都是字符串类型

1、转字符串(两种方法)

(1)var str=x.toString();//x不能undefined和null,会报错,undefined和null不能使用.做任何操作,因为他们不是对象
(2)var str=String(x);//万能的,任何人都可以转为字符串,但是绝对不要手动使用,完全等效于隐式转换,还不如+""
    不重要:页面上一切数据js获取到后都是字符串类型

2、***转数字:3种方法

(1)*parseInt(str/num); - parse解析 Int整型 - 专门用于将【字符串转为整数】
    执行原理:从左向右依次读取转换每个字符,碰到非数字字符就停止转换,如果一来就不认识则为NaN,不认识小数点。

console.log(parseInt(35.5));//35
console.log(parseInt("35.5"));//35
console.log(parseInt("3hello5"));//3
console.log(parseInt("hello35"));//NaN
console.log(parseInt("35px"));//35
console.log(parseInt(".35px"));//NaN
console.log(parseInt(true));//NaN
console.log(parseInt(false));//NaN
console.log(parseInt(undefined));//NaN
console.log(parseInt(null));//NaN

(2)*parseFloat(str); - parse解析 Float浮点型 - 专门用于将【字符串转为小数】
    执行原理:几乎和parseInt一致,认识第一个小数点

console.log(parseFloat(35.5));//35.5
console.log(parseFloat("35.5"));//35.5
console.log(parseFloat("3hello5"));//3
console.log(parseFloat("hello35"));//NaN
console.log(parseFloat("35.5px"));//35.5
console.log(parseFloat(".35px"));//0.35
console.log(parseFloat("35.5.5"));//35.5

(3)Number(x);//万能的,任何人都可以转为数字,但是绝对不要手动使用,完全等效于隐式转换,还不如 -0   *1   /1

3、转布尔

  • Boolean(x);//万能的,任何人都可以转为布尔,但是绝对不要手动使用,完全等效于隐式转换,还不如 !!x

  • ***只有6个为false:0,"",undefined,NaN,null,false,其余全部都是true。

  • 我们绝对不会手动使用,但是再分支或循环的条件之中,不管以后代老师写什么,他都会悄悄的转为一个布尔值,自带此方法

  • 不管条件中写什么,只需要判断不为上面那六个,不为那六个就是true,是那六个就为false

六、函数(Function)

1、概念

Function 也叫函数也叫方法,【先预定义】好,以后可以【反复使用】的【代码段】

2、如何使用函数:(2步)

(1)***定义/创建/声明:

function 函数名(){
    函数体/代码段;
}

注意:函数创建后,不会立刻执行,需要我们去调用函数
(2)调用函数:2种
a、在js内部写:函数名(); - 程序员写几次就会调用几次
b、在HTML上面绑定事件:   <elem onclick="函数名()"></elem> - 什么元素都可以绑定事件

3、何时使用函数

(1)不希望打开页面立刻执行
(2)希望用户来触发提升用户的体验感
(3)以后每一个独立的功能(作业)都要封装为一个函数
函数在js里的地位极高,函数是js的第一等公民

4、带有形参的函数

(1)创建带有形参的函数
  形参 - 其实就是一个变量,只不过不需要加var,而且不需要赋值,所以称之为叫做形式参数 - 简称形参

function 函数名(形参,...){
    函数体/代码段;
}

(2)使用带有形参的函数
必须传入实参 - 实际参数,就是你传递过去的值

函数名(实参,...)

注意:传参的时候顺序是不能乱的,必须和形参的顺序一一对应,数量不多不少

5、**********自定义函数Function

(1)创建方式

a、*【声明方式】创建函数

function 函数名(形参列表){
    操作;
    return 返回值/结果;
}

b、【直接量方式】创建函数: - 不推荐

var 函数名 = function(){
    操作;
    return 返回值/结果;
}

函数名其实就是一个变量名

(2)调用

var 接住返回的结果=函数名(实参列表);

  • 其实return的本意是退出函数,但是如果return后面跟着一个数据,顺便将数据返回到函数作用域的外部,但是return只负责返回,不负责保存,所以调用函数时要自己拿个变量来接住它
  • 就算省略return,默认也有返回值,返回一个undefined
  • *具体需不需要得到函数的结果,看程序员自己:如果有一天在全局中希望拿着函数结果去做别的操作,那么就需要return
  • 前辈们提供的方法,大部分基本上都加了return

(3)***作用域

a、全局作用域
  全局变量全局函数,在页面的任何一个位置都可以使用
b、函数作用域
  局部变量局部函数,在【当前函数调用时,内部可用】

  变量的使用规则:优先使用局部的,局部没有找全局要,全局也没有那就会报错

特殊点(缺点):

  • 千万不要再函数中对着未声明的变量直接赋值 - 全局污染:全局本身没有这个东西,但是被函数作用域给添加了
  • 局部可以使全局的,但是全局不能使用局部的 - 解决:函数中使用return

(4)***声明提前: - 只会出现笔试题中

规则
  在程序正式执行之前,将var声明的变量(轻)和function声明的函数(重),都会悄悄的集中定义在当前作用域的顶部,但是赋值留在原地

强调:

  • 声明方式创建的函数会完整的提前(第一种)
  • 直接量方式创建的函数不会完整提前,只有变量名部分会提前(第二种)

何时使用
  永远不会自己使用,干扰我们判断的 - 只会在笔试题中遇到

开发时只需要遵守以下规则:

  • 变量名和函数名尽量不要重复
  • 先创建后使用

(5)***重载

  相同的函数名,根据传入的实参的不同,自动选择对应的函数去执行,但是JS不支持,函数名如果重复了,后面的肯定会覆盖掉前面的
  目的:减轻我们程序员的压力,记住一个方法就可以执行很多的操作
  解决:在【函数内部】自带一个arguments的对象(类似数组对象):不需要我们去创建 - 哪怕没有写任何形参,他也可以接受住任何实参,所以默认你看他length长度为0
固定套路:

  • 通过下标去获取传入的某一个实参:arguments[i] - i从0开始
  • 通过length去获取到底传入了几个实参:arguments.length,通过判断传入的实参的不同,在内部去写判断,从而变相的实现重载
function calc(){
    var num=arguments.length;
    var result;
    switch(num){
        case 1:
            return result=arguments[0]*arguments[0];
        case 2:
            return result=arguments[0]+arguments[1];
        case 3:
            return result=arguments[0]*arguments[1]*arguments[2];
    }
}
console.log(calc(3,1,2));

6、ES6的箭头函数

简化一切匿名回调函数
固定公示:
  function去掉,()和{}之间添加=>,如果形参只有一个,那么()可以省略,如果函数体只有一句话,那么{}也可以省略,如果函数体只有一句话并且是return,那么return和{}都可以省略。

7、绑定事件(3种方式)

(1)在HTML上书写事件属性

<elem on事件名="函数名(实参)"></elem>   缺点:
    a、不符合内容与样式与行为得分离原则
    b、无法动态绑定,一次只能绑定一个元素
    c、不支持绑定多个函数对象

(2)*在js中使用事件处理函数属性

elem.on事件名=function(){操作} 优点:
  a、符合内容与样式与行为得分离原则
  b、动态绑定,一次能绑定多个元素
缺点:
  a、不支持绑定多个函数对象

(3)*在js中使用事件API:如果不用考虑老IE,他也不错

  主流:elem.addEventListener("事件名",callback);
  老IE:elem.attachEvent("on事件名",callback);
  兼容:

if(elem.addEventListener){
    elem.addEventListener("事件名",callback);
}else{
    elem.attachEvent("on事件名",callback);
}

  优点:
    a、复合内容与样式与行为得分离原则
    b、动态绑定
    c、支持绑定多个函数对象   缺点:
    有兼容性问题

8、事件的取消绑定

(1)如果使用elem.onclick=()=>{},那么elem.onclick=null
(2)如果你使用elem.addEventListenter("事件名",回调函数);那么:
  elem.removeEventListener("事件名",回调函数); - 事件名和回调函数,必须和添加时的一模一样

七、数组

创建一个变量可以保存【多个数据】
  数组都是线性排列,除了第一个元素,每个元素都有唯一的前驱元素;除了最后一个元素,每个元素都有唯一的后继元素
***每个元素都一个自己的位置,叫做下标,下标都是从0开始,到最大长度-1结束

1、创建数组(两种方式)

(1)*直接量方式:var = arr[];//空数组
         var arr = [数组1,...];
(2)构造函数方式:var arr = new Array();//空数组
         var arr = new Array(数组1,...);
         new Array(num);//第二个方法有坑:这句话的意思是:创建一个长度为num的空数组,里面没有任何东西,只有无数的undefined
(3)如何释放一个引用类型:
一定要看清楚有几个变量引用着这个类型,每个变量都要释放后才能释放干净
  在JS底层有一个垃圾回收器,只有垃圾回收器的计数器(记录着这个数据有几个人引用着)为0的时候才会删除不要的数据
  建议:我们的代码都要封装为一个函数,函数中的一切变量都会自动释放

2、***面试题:按值传递

var a=x; var b=a;修改a,b变不变,或者 修改b,a变不变?
(1)传递的如果是原始类型
  其实是复制了一个副本给对方,两者互不影响
(2)传递的如果是应用类型
  js中不是原始类型,就是引用类型(函数、数组,都是引用类型)- 引用类型都是地址传递 -- 浅拷贝/深拷贝
  因为引用类型很大,比原始类型大得多,不可能保存在变量本地,只是保存了一个地址值而已,其实是赋值了自己的地址值给对方,两者用的是同一个地址值,一个修改另一个也会变化

3、获取数组之中的元素

  数组名[i]

4、后续添加/替换元素

  数组名[i] = 新数据;   如果下标处没人则为添加,如果下标处有人则为替换

5、数组具有三大不限制

(1)不限制元素的类型
(2)不限制元素的长度
(3)不限制下标越界 - 不是一个好东西
    如果获取元素时,下标越界,返回的是一个undefined     如果添加元素时,下标越界,会得到要给稀疏数组,如果我们搭配上我们学过循环去遍历获取每个元素,那么会得到很多很多undefined

6、数组的唯一属性:length

语法:数组名.length 作用:获取到数组的长度,长度是从1开始的

三个固定套路:

  • 向末尾添加元素:arr[arr.length]=新数据;
  • 获取数组的倒数第n个元素:arr[arr.length - n];
  • 缩容:删除倒数n个元素:arr.length-=n;

7、***遍历数组

往往很多情况,我们不会拿出某个元素来使用,而是拿出每个元素来进行 相同相似 的操作 - 搭配上循环 固定公示:

for(var i = 0; i< arr.length;i++){
    arr[i];//当前次元素
}

8、***hash数组(关联数组):下标是可以自定义的

(1)如何使用

  • 创建空数组:var arr=[];
  • 为数组添加自定义下标并且赋值:arr["自定义下标"]=新值;

(2)访问元素:arr["自定义下标"];

(3)强调:hash数组的length失效了,永远为0

  遍历hash数组:不能再使用for循环,必须使用for in 循环 - 不需要设置从哪里开始到哪里结束,纯自动化。专门为了遍历hash数组存在的

for(var i in 数组名){
    i;//下标
    数组名[i];//当前次元素
}

不止能遍历hash数组,也能遍历索引数组
建议:索引数组依然是for,hash数组再使用for in

(4)***hash数组的原理:

  hash算法:将字符串,计算出一个尽量不重复的数字(地址值)
    字符串内容相同,则计算出来的数字也一定是相同的
  添加元素:将自定义下标交给hash算法,得到一个数字(地址值),直接将你要保存的数据放到此地址保存起来
  获取元素:将指定的自定义下标交给hash算法,得到一个和当初保存时一样的数字(地址值),通过此地址找到你当初保存的数据,取出来使用

(5)JS里面一切的东西都是对象,万物皆对象,除了undefined和null,【一切对象的底层都是hash数组】

9、*****数组的API

(1)*arr转str

var str=arr.join("自定义连接符");

固定套路:
  a、笔试题:将数组里面的内容拼接为一句话/单词 - 无缝拼接,其实就是拼接了一个空字符串

var arr=["h","e","l","l","o"," ","w","o","r","l","d"];
var str=arr.join("");
console.log(str);

  b、将数组拼接为DOM页面元素 - 第一次遇到数据渲染页面

//拿数据
var arr=["-请选择-","北京","南京","西京","东京","重庆","北京","南京","西京","东京","重庆","北京","南京","西京","东京","重庆","北京","南京","西京","东京","重庆","北京","南京","西京","东京","重庆","北京","南京","西京","东京","重庆"];
//将数组拼接为页面标签字符串
var str=<开始标签>"+arr.join("</结束标签><开始标签>")+"</结束标签>";
//巧了:innerHTML能够识别标签
sel.innerHTML=str;

(2)*数组拼接:添加元素的新方式

  将你传入的实参全部拼接到arr的末尾

var newArr=arr.concat(新值1,arr1...);

特殊:

  • 不修改原数组,只会返回一个新数组
  • concat支持传入数组参数,悄悄的将传入的数组打散为单个元素再拼接

(3)*截取子数组:

  根据你传入的开始下标截取到结束下标

var subArr=arr.slice(starti,endi+1);

特殊

  • 不修改原数组,只会返回一个新数组
  • 含头不含尾
  • endi可以省略不写,如果省略,会从starti位置一直截取到末尾
  • starti也可以省略,如果两个实参都省略,那么会从头到尾完全的赋值一份:此操作也叫做深拷贝 - 复制了一个副本给对方
  • 支持负数参数,-1代表倒数第1个,-2代表倒数第2个...

以上三个 ,join()、.concat(新值1,arr1....)、.slice(starti,endi+1)都不会修改原数组。需要新变量来接收返回结果,会开辟一个新空间,与原数组地址不同,是深拷贝


(4)*删插替

  删除:var desl=arr.splice(starti,n);//n代表删除的个数
    特殊:
      虽然他直接修改原数组,但是也有返回值,返回的是被删除的数据组成的一个新数组,因为前辈们考虑到有可能删除的东西刚好是需要的东西,哪怕没有删除也会返回一个空数组
  插入:arr.splice(starti,0,新值,...);
    特殊:

  • 原starti位置的元素以及后续元素都会向后移动
  • 尽量的不要插入一个数组,会导致我们的数组一些是一维,一些是二维,遍历的时候就非常不方便

  替换:var dels=arr.splice(starti,n,新值...);
    特殊:
      删除的个数和插入的个数不必相同

(5)翻转数组:arr.reverse();

(6)数组排序

  • 笔试题:冒泡排序:前一个元素和后一个元素进行对比,如果前一个>后一个,两者就交换位置,但是做完一轮发现只有最大的一个数字到了最后,所以再开循环反复使用,固定公式:
var arr=[31,21,54,4376,69,8,8,65,643,52,3,321,5,47,69,87,643,524];
for(var j=1;j<arr.length;j++){
    for(var i=0;i<arr.length-j;i++){
        if(arr[i]>arr[i+1]){
            var m=arr[i];
            arr[i]=arr[i+1];
            arr[i+1]=m;
        }
    }
}
console.log(arr);
  • 正式开发时:arr.sort();
      默认:将数组中的元素转为字符串后,再按位PK每个字符的unicode号(ASCII码)
      问题一:希望按照数字升序排列:
arr.sort(function(a,b){//此函数叫做匿名回调函数,回调函数不需要我们程序员调用,由前辈们创建好,我们学习如何使用即可,其实sort方法悄悄的调用了
//console.log(a);//后一个数字
//console.log(b);//前一个数字
return a-b;//如果a-b>0,说明后>前
            //如果a-b<0,说明后<前
            //如果a-b=0,说明后=前
            //而sort方法会根据返回的正数、负数、0,来自动考虑要不要交换位置
})

  问题二:希望按照数字降序排列

arr.sort(function(a,b){
    return b-a;
})

  强调:切记:

  • 1、以后只要网页上有功能带有排序,他的底层一定是数组,因为js中只有数组可以排序
  • 2、以后只要网页上有随机的功能,那么他的底层一定用到了随机数公式

(7)栈和队列

栈和队列:添加元素和删除元素的新方式:

a、栈:其实就是数组,只不过是一端封闭了,只能从另一端进出

  • 何时使用:现实生活中,情况不多
  • 如何使用:
      开头进:arr.unshift(新值,...);//添加元素的方式,向前添加。缺点:导致其余元素的下标都发生变化
      开头出:var first=arr.shift();//删除元素的新方式,向前删除,一次只能删除一个。缺点:导致其余元素的下标都发生变化
      结尾进:arr.push(新值,...);//添加元素的新方式,向后添加
      结尾出:var last=arr.pop();//删除元素的新方式,向后添加

b、队列:其实就是数组,只不过一端进,从另一端出

  开头进:arr.unshift(新值,...);
  结尾出:var last=arr.pop();
  结尾进:arr.push(新值,...);
  开头出:var first=arr.shift();

(8)判断(2个)

a、every()

每一个,要求所有元素都满足条件才会true,只要有一个不满足则为false,非常类似于我们的&&

var bool=arr.every(function(val,i,arr){
    //val - 当前的值
    //i - 当前的值的下标
    //arr - 当前数组本身
    return 判断条件;
})

b、some()

有一些,要求只要有一个元素都满足条件会为true,所有元素都不满足则为false,非常类似于||

var bool=arr.some(function(val,i,arr){
    return 判断条件;
})

(9)遍历(2个)

拿到数组中的每个元素做相同 或 相似的操作
forEach - 直接修改元素组

arr.forEach(function(val,i,arr){
    //操作
})

map - 不修改原数组返回一个新数组

var newArr=arr.map(function(val,i,arr){
    return 操作;
})

(10)过滤和汇总

a、过滤

筛选出你需要的部分,但是和现实不一样的是元数组并不会发生变化

var subArr=arr.filter(function(val,i,arr){
    return 判断条件;
})
var arr=[1,2,3,4,5,6,7];
var subArr=arr.filter(val=>val%2==0)

console.log(subArr);
console.log(arr);

b、汇总

var result=arr.reduce(function(prev,val,i,arr){
    return prev+val;
},基础值)
var arr=[1,2,3,4,5,6,7];

以上every()、some()、forEach()、map()、filter()、reduce()都是在简化for循环操作,以后数组可能就很少写for循环了

(11)类数组转为普通数组

let 变量接住=Array.from(类数组对象); let 接住=Array.prototype.slice.call/apply(类数组对象)

10、二维数组

数组的元素,又引用着另一个数组
何时使用:在一个数组,希望再次细分每个分类
创建:

var arr=[
        ["兰大帅",18,900],
        ["劳伦斯",19,1000],
        ["Lawrence",20,700]
    ];

访问:arr[行下标][列下标];
特殊:列下标越界,返回undefined
  行下标越界,得到的是一个报错,因为行下标越界已经得到一个undefined,undefined没有资格再加[]做操作
遍历二维数组:必然两层循环,外层循环控制行,内层循环控制列

for(var i=0;i<arr.length;i++){
    for(val j=0;j<arr[i].length;j++){
        console.log(arr[i][j]);
    }
}

八、**********DOM

Document Objuect Model:文档对象模型:专门用于操作HTML文档的,提供了一些方法
在某一个升级后,为了方便各类程序员将DOM分为3方面:
  (1)核心DOM:无敌的,既可以操作HTML又可以操作XML,但是语法相对比较繁琐
  (2)HTML DOM:只可以操作HTML,不能访问一切自定义的东西,但是语法简单
  (3)XML DOM:只可以操作XML,被淘汰了,被JSON数据格式代替了

1、DOM树概念

DOM将我们的HTML看作是一个倒挂的树状结构,但是树根不是html标签,而是document的对象

  • document对象:不需要我们程序员创建,由浏览器的js解释器自动创建,一个页面只有一个document树根
  • 作用:可以通过树根找到我们想要的任何一个DOM元素/节点/对象(属性和方法)
  • DOM会将页面上的每个元素、属性、文本、注释等等都会被视为一个DOM元素/节点/对象

2、查找元素(两大方面)

(1)直接通过HTML的特点去查找元素

a、通过ID查找元素

var elem=document.getElementById("id值");

特殊

  • 返回值,找到了返回当前找到DOM元素,没找到返回的一个null
  • 如果出现多个相同id,只会找到第一个
  • 记住控制台输出的样子,这个样子才叫做一个DOM元素/节点/对象才可以去做操作
  • 忘记此方法,不允许使用,id不好用,一次只能找到一个元素。id留给后端用
  • 其实根本不用查找,id直接可用。id名不要和js中的变量名相同,会被覆盖掉,导致id名找不到

b、*通过 标签名 查找元素

var elems=document/已经找到的父元素.getElementsByTagName("标签名");

特殊:

  • 返回值,找到了返回一个类数组DOM集合,没找到得到空集合
  • js只能直接操作DOM元素,不能直接操作DOM集合,解决:要么下标拿到某一个,要么遍历拿到每一个
  • 不一定非要从document开始查找,如果从document去找,会找到所有的元素,可以换成我们已经找到的某个父元素
  • 返回的是一个动态集合

c、*通过class查找元素

var elems=document/已经找到的父元素.getElementsByClassName("class名");

特殊:

  • 返回值,找到了返回一个类数组DOM集合,没找到得到空集合
  • Js只能直接操作DOM元素,不能直接操作DOM集合,解决:要么下标拿到某一个,要么遍历拿到每一个
  • 不一定非要从document开始查找,如果从document去找,会找到所有的元素,可以换成我们已经找到的某个父元素
  • 返回的是一个动态集合

d、可以通过选择器去查找

(a)

var elem=document.querySelector("任意CSS选择器的");//query - 查询selector - 选择器:查询css选择器

缺陷:
  只能找到单个元素,如果匹配到了多个,也只会返回第一个,没找到null
  一次只能操作一个元素,不舒服
(b)

*** var elems=document.querySelectorAll("任意css选择器的");

优点:
  找到了是一个集合,没找到是一个空集合
  复杂查找时,非常简单
  返回的是一个静态集合NodeList

面试题:document.getXXX和document.queryXXX的区别?   后者更适合复杂查找
  动态集合和静态集合的区别?
    (a)动态集合:每一个DOM发生变化,它都会悄悄的再次查找,让页面和数据保持一致,但是效率也就低下了 - 不支持forEach
    (b)静态集合:每一次DOM发生变化,它不会悄悄的再次查找,让页面和数据没有保持一致,但是效率也就高了 - 支持使用forEach

(2)*通过关系去获取元素

前提条件:必须找到一个元素才可以使用关系
  父元素:elem.parentNode; - 单个元素
  子元素:elem.children; - 集合
  第一个子元素:elem.firstElementChild; - 单个元素
  最后一个子元素:elem.lastElementChild; - 单个元素
  前一个兄弟:elem.previousElementSibling; - 单个元素
  后一个兄弟:elem.nextElementSibling; - 单个元素

当不希望影响到别人,只希望影响到自己的关系网时就可以使用关系去获取元素

3、操作元素

前提:先找到元素,才能操作元素(3个方面)

(1)内容

   a、*elem.innerHTML - 获取和设置开始标签到结束标签之间的内容,支持识别标签
      获取:elem.innerHTML;
      设置:elem.innerHTML="新内容";

   b、elem.innerText - 获取和设置开始标签到结束标签之间的纯文本,不识别标签
      获取:elem.innerText;
      设置:elem.innerText="新内容";

以上两个都是专门为双标签准备,而有一个单标签也是可以写内容,叫做,如何获取?
   c、input.value - 专门获取/设置input里的内容
    获取:input.value;
    设置:input.value="新内容";

(2)属性

  获取属性值:elem.getAttribute("属性名");
  设置属性值:elem.setAttribute("属性名","属性值");

简化版:
  获取属性值:elem.属性名;
  设置属性值:elem.属性名="属性值";
  简化版的缺点:
    a、class必须写为className - ES6(2015年)class变成了一个关键字
    b、不能操作自定义属性

(3)样式

内联样式

使用样式的方式(3种):
  a、*内联样式
  b、内部样式表
  c、外部样式表

二阶段使用JS来操作【内联样式】
  a、不会牵一发而动全身
  c、优先级最高

获取样式:elem.style.css属性名;
设置样式:elem.style.css属性名="css属性值";
特殊点:
  a、css属性名,有横线的地方,去掉横线,变为小驼峰命名法
    border-radius ---> borderRadius
  b、小缺陷:获取时,我们只能获取到内联样式,因为我们目前学的就是内联样式的操作

样式表样式 - 此生不见

    //获取你想要操作的样式表
    var sheet=document.styleSheets[i];
    //获取此样式表中所有的样式规则
    var rules=sheet.cssRules;
    //数出你想要操作的那个规则的
    var rule=rules[i];
    //操作
    console.log(rule.style.css属性名);
    rule.style.css属性名="css属性值";

(4) 绑定事件

elem.onclick = function(){
    操作;
    \*\*\*\*\*关键字this - 这个
    如果单个元素绑定事件,this -> 这个元素
    如果多个元素绑定事件,this -> 当前触发事件元素
}

一切的获取,往往都是为了判断
一切的设置,可以说是添加也可以说是修改

4、操作属性

(1)*获取属性值

  核心DOM:elem.getAttribute("属性名");
  HTML DOM:elem.属性名;

(2)*设置属性值

  核心DOM:elem.setAttribute("属性名","属性值");
  HTML DOM:elem.属性名="";

(3)删除属性

  设置属性值为空字符串,确实某些属性可以算是删除,但是只是删除了属性值,属性名还在,而有的属性哪怕只有一个属性名,也会具有作用(比如:href、disabled、readonly)
  *核心DOM:elem.removeAttribute("属性名");
  HTML DOM:elem.属性名=""; - 属性节点删不干净

5、判断有没有

垃圾,只能判断有没有,不能判断是什么,推荐用elem.getAttribute("属性名");去获取到值,自己再写比较运算
  核心DOM:elem.hasAttribute("属性名");
  HTML DOM:elem.属性名!="";

以后使用建议:优先使用HTML DOM,HTML DOM实现不了再用核心DOM补充!
  缺陷:
    a、class必须写为className
    b、自定义的东西操作不了

6、如何创建元素以及上树(3步)

  (1)创建空标签:
    var elem=document.createElement("标签名");
  (2)为其设置必要的属性和事件
    

elem.属性名="属性值";
elem.on事件名=function(){}

  (3)上树(3种)
    * 父元素.appendChild(elem);//在父元素末尾处追加一个子元素elem
    父元素.insertBefore(elem,已有子元素);//在父元素追加一个子元素elem,但是放在已有子元素的前面
    父元素.replaceChild(elem,已有子元素);//在父元素追加一个子元素elem,但是会替换掉已有子元素

7、删除元素

elem.remove();

九、***引用/对象类型(11个)

1、*****String对象

String的概念

什么是字符串:多个字符串组成的【只读】字符【数组】(只读:所有的字符串API都不会修改原字符串,都会返回一个字符串)
和数组的相同点
  (1)字符串中的个数:str.length;
  (2)获取字符串中的某个字符:str[i];
  (3)遍历字符串
  (4)所有数组不修改原数组的API,字符串也可以使用(concat、slice)
和数组的不同点
  (1)所有数组的直接修改原数组的API,字符串都不可以使用,比如排序只有数组可以使用,但是字符串也有很多很多属于自己的API

***引用/对象类型:11个

  *String  Number  Boolean->包装类型
  *Array  *Function  Date(日期)  Math(数学)  *RegExp(正则:验证)
  Error(错误)
  *Object(面向对象开发方式)
  Global(全局对象) - 只有在浏览器中被window对象代替了,自然保存着全局变量和全局函数,只不过window可以省略不写,后面node.js这个后端语言中全局对象就叫做global

***包装类型

  概念:专门用于将原始类型的值封装为一个引用类型的对象
  为什么:原始类型的值原本就是没有任何属性和方法,意味着原始类型本身是不支持,去做任何操作的。但是程序员经常用字符串来进行操作,就提供了三个包装类型的属性和方法(String,Number,Boolean)
  何时使用:只要试图使用原始类型的变量调用属性或方法的时候,自动包装
  何时释放:方法调用完毕后,自动释放包装类型,又变成了原始类型
因为没有给null和undefined提供包装类型,所以它俩不能使用

*****StringAPI

(1)转义字符:\

作用:

  • 将字符串中和程序冲突的字符转为原文
      "\""
  • 包含特殊功能的符号
      换行:\n
      制表符:\t -> 大空格,和按tap键一样的效果
  • *输出unicode编码的字符
      \u4e00 - ascii码:19968
      \u9fa5 - ascii码:40869

(2)*大小写转换

将字符串中每个字符统一的转为大写 或 小写
何时使用:只要程序不区分大小写,就要【先统一】的转为大写 或 小写,再比较(做验证码)
如何使用:
  大写:var upper=str.toUpperCase();
  小写:var upper=str.toLowerCase();

(3)获取字符串中指定位置的字符

str.charAt(i) === str[i]

(4)*获取字符串中指定位置的字符的ASCII码

var ascii=str.charCodeAt(i);

*通过ASCII码转回原文:
var 原文=String.fromCharCode(ascii);

(5)***检索字符串

检查索引,检查下标:获取关键字的下标
var i=str/arr.indexOf("关键字",starti);
从starti位置开始,查找右侧第一个关键字的第一个字符的位置
starti可以省略,默认从0位置开始查找
*返回值:找到了,返回第一个关键字的第一个字符的下标位置
  没找到,返回-1,其实我们根本不关心下标为多少,我们只关心下标为不为-1
作用:判断有没有
强调:数组也能使用此方法,数组这个方法是后期才添加的,原本此方法只有字符串可用,比如老IE的数组就没此方法
笔试题:默认只能获取到第一个关键字的下标,如何才能获取到所有的关键字的下标?

var str="no zuo no die no can no bibi";
var index=-1;
while((index=str.indexOf("no",index+1))!=-1){
    console.log("找到了,下标为:"+index);
}

(6)拼接字符串

var newStr=str.concat("新字符串",...)
不如使用+""运算

(7)*截取字符串(3个)

var subStr=str/arr.slice(starti,endi+1);//用法和数组的用法完全一直
str.substring(starti,endi+1);//用法几乎和slice一致,但是不支持负数参数
str.substr(starti,n);//n代表的是截取的个数,不必考虑含头不含尾

(8)*替换字符串

这个方法很强大,但是需要搭配正则表达式
var newStr=str.replace("固定关键字"/正则表达式,"新内容");

(9)*****切割/分割/分隔字符串

作用:将字符串转为数组
var arr=str.split("自定义切割符");
注意:
  (1)切割后,切割符就不存在了
  (2)如果你的切割符写的是"",就是切散每一个字符,装入数组

(10)去掉空白字符

str.trim()去掉字符串头和尾部的空字符串
str.trimStart()去掉字符串头部的空字符串
str.trimEnd()去掉字符串尾部的空字符串

扩展:JS如何创建元素并上树(3步)

  (1)创建空标签:var elem=document.createElement("标签名");
  (2)为其设置必要的属性和事件:
    elem.属性名="属性值";
    elem.on事件名=function(){操作}
  (3)挂载上树/渲染页面:父元素.appendChild(elem);

2、Math对象

专门提供了数学计算的API
强调:不需要创建,直接使用
属性:Math有一些属性,涉及到科学计数法,但是几乎用不到,只有Math.PI有可能用到===3.1415926...这一串数字我们不需要自己创建,浏览器自带

API

(1)取整(3种)

a、上取整

超过一点点,就取下一个整数
var num=Math.ceil(num);//小数位数不能超过15位,否则此方法只能取整,不能上取整

b、下取整

无论超过多少,都会省略小数部分
var num=Math.floor(num);

c、四舍五入取整

var num=Math.round(num);//只看小数位数的第一位


以上三个方法都只能取整


d、取整的方式

以上三个+*parseInt(str 去掉单位)+*num.toFixed(d)
num.toFixed(d)
优点:a、可以四舍五入,并且保留指定小数位数,d其实就是保留的小数位数  b、解决浏览器带来的舍入误差,2-1.6=0.39999999999999这种情况就可以使用toFixed来解决 缺点:结果是一个字符串,建议搭配上parseFloat()使用
笔试题:不允许使用toFixed的情况下,自己封装一个函数,由用户传入数字和保留位数,实现四舍五入操作:

function toFixed(num,d){
    num*=(10**d);
    num=Math.round(num);
    num/=(10**d);
    return num;
}
var result=toFixed(Math.PI,2);
console.log(result);

(2)乘方和开方

  *乘方:Math.pow(底数,幂); -> 更简化:底数**幂   开放:Math.sqrt(num); - 仅仅只能开平方

(3)*最大值和最小值

var max/min=Math.max/min(a,b,c,d,e,f,g...);//自动在你传入的数字中比较出最大值或最小值
  问题:本身不支持数组参数
  解决:固定用法:Math.max/min.apply(Math,arr);//具有打散数组的功能

(4)绝对值

把负数转为正数
Math.abs(-1);//1

(5)***随机数

Math.random();//在0-1之间取一个随机的小数
搭配上parseInt,只能取到0,但是不可能取到1,意味着取不到最大值
公式:parseInt(Math.random()*(max-min+1)+min);
强调:只要以后网页中某一块有随机的功能,他的底层一定用到了随机数

3、Date对象

日期对象,提供了操作日期和时间的API

创建(4种)

(1)*创建一个当前日期

var now=new Date();

(2)*创建一个自定义时间

var birth=new Date("yyyy/MM//dd hh:mm:ss");

(3)创建一个自定义时间

var birth=new Date(yyyy,MM,dd,hh,mm,ss);//修改月份,从0-11,0代表1月。这种创建方式计算时间时,结果会有问题

(4)复制一个日期

为什么:日期的所有API都是直接修改原日期的,无法获得修改之前的日期,所以,在执行API之前先进行复制,然后再操作复制后的日期
var end=new Date(start);

使用(2类)

(1)时间差

两个日期对象之间,可以相减(大-小),得到一个毫秒差,换算出自己想要的任何一部分 - 日期的本质其实就是保存了一个毫秒数 - 做到倒计时的关键

创建日期的最后一种方式,绝对没人用:var date=new Date(毫秒数);//计算机元年:1970年1月1日8点整

(2)API

分量:时间的单位
FullYear(年) Month(月) Date(日) Day(星期)
Hours(小时) Minutes(分) Seconds(秒) Milliseconds(毫秒)
每一个分量都有一对儿getXXX/setXXX的方法
  其中getXXX负责获取一个分量的值
  其中setXXX负责设置一个分量的值
特殊:
  a、取值范围:
    FullYear - 当前年份的数字
    Month - 0-11
    Date - 1-31
    Day - 0-6:0代表是星期天,外国人的眼里星期天才是一周的第一天
    Hours - 0-23
    Minutes、Seconds:0-59
    日期是一个取值范围,如果设置超过了范围,会自动进制
  b、Day,没有set方法
  c、如果希望对某个分量进行加减操作
    date.setXXX(date.getXXX()+/-n);
  d、格式化日期为本地字符串
    date.toLocalString(); - 垃圾:具有兼容性问题,我们一般会选择自己创建一个格式化方法来格式日期
    用了此方法,会失去一些东西,比如:日期的自动进制,日期的API
    但是也会得到一些东西:字符串的API

十、BOM

1、概念

BOM:Browser Object Model - 浏览器对象模型:专门用于操作浏览器的。
但是它使用的不多,远不如ES和DOM,浏览器很多操作都是自带的,而且BOM没有标准,各个浏览器都有自己的定义,但是大部分浏览器的都是一致规范的除了老IE(8及以下)

2、window对象

扮演着两个角色:

(1)全局对象:保存着全局变量和全局函数

(2)指代当前窗口本身

属性:

  1、获取浏览器的完整大小:outerWidth/outerHeight
  2、*获取浏览器的文档显示区域的大小:innerWidth/innerHeight - 获取每台电脑的浏览器的文档显示区的大小
  3、获取屏幕的完整大小:跟window没关系:screen.width/height; - 我们目前学习的都是浏览器应用(网页),并不会去做桌面应用

方法

(1)*打开链接的新方式

  a、当前窗口打开,可以后退:
    HTML:<a href="url">内容</a>
    JS:open("url","_self");
  b、当前窗口打开,禁止后退:使用场景:比如电商网站,结账后不允许后退
  HTML做不到了,只有JS可以,也不是window能做到的,而是另外一个:

history对象

  history:当前【窗口的历史记录】,它其实可以做的事儿就是前进后退
    前进:history.go(1);
    后退:history.go(-1);
    刷新:history.go(0);

***location对象

  location:当前【窗口正在打开的url】,有一个API:
    location.replace("新url");//叫做替换,不叫跳转,不会产生历史记录,自然也不能后退,但是网址替换了,网页必然会发生变化
  ***程序员常识:一个url由几部分组成?分别每个部分有什么作用?-以后再学习服务器端和数据库的时候会有很大的帮助
    (a)协议:*https(加密)/*http(未加密)/ftp(传输文件)/ws(直播)... 前两个都属于叫做请求-响应模型
    (b)主机号|IP地址|域名:域名是需要花钱购买的,主机号|IP地址是免费,127.0.0.1才真的是叫做主机号,只能自己访问自己
    (c)端口号:https默认端口为443,http默认端口为80,只有默认端口可以省略不写
    (d)***文件的相对路径|路由:百度加密了
    (e)***查询字符串|请求消息:前端传输到后端的东西,前端对后端说的话,就是form表单提交带来的东西
  属性:
    获取url的5个部分的内容,但是不需要记忆,直接输入location对象即可查看
      协议:location.protocal;
      域名:location.hostname;
      端口:location.port;
      路由:location.pathname;
      请求消息:location.search;

  跳转:location="新url" - 替换当前窗口,可以后退
  跳转后,禁止后退:location.replace("新url"); - 替换当前窗口,禁止后退
  刷新:location.reload();

  c、新窗口打开,可以打开多个
  HTML:<a href="url" target="_blank">内容</a>
  JS:open("url","_blank");
  d、新窗口打开,只能打开一个:使用场景:比如电商网站,只允许用户打开一个结账页面
  HTML:<a href="url" target="自定义一个name">内容</a>
  JS:open("url","自定义一个name");
其实窗口的底层都是有一个名字的,如果打开了一个已经开着的名字的窗口的,他会把他关掉,再次打开

总结:
  a、以后的跳转,任何标签都可以
  b、提升用户的体验感
  c、a标签的其他用途
    i、跳转
    ii、锚点
    iii、下载按钮:<a href="xx.exe/rar/zip/7z">下载</a>
    iiii、打开图片和txt文档:<a href="xx.png/jpg/jpeg/gif/txt">打开图片和txt</a>
    iiiii、直接书写js - 不需要绑定点击事件:<a href="javascript:js代码;">打开图片txt</a>

(2)打开新窗口/新链接

newW=open("url","target","width=?,height=?,left=?,top=?");
  t特殊:a、如果没有加第三个参数,那么窗口会和浏览器融为一体
    b、如果你加入了第三个参数,那么窗口会脱离浏览器独立存在

(3)关闭窗口

window/newW.close();

(4)改变新窗口的大小

newW.resizeTo(新宽,新高);

(5)改变新窗口的位置

newW.moveTo(新X,新Y);

(6)*window提供了三个框

  警告框:alert("提示文字");
  输入框:var user=prompt("提示文字");
  确认框:var bool=confirm("提示文字")

(7)*****定时器也是window的

(8)事件

  a、window.onload事件 - load - 加载:等待其他所有的资源加载完毕后才会执行的代码,放在里面的代码其实要最后才执行
  b、*window.onresize事件 - 窗口如果大小发生了变化,就会触发,搭配上判断innerWidth可以理解为是js版本的css媒体查询
  c、***window.onscroll事件 - 滚动事件,一旦滚动就会触发
    i、获取滚动条当前的位置:window.scrollY
    ii、获取元素距离页面顶部有多远:elem.offsetTop/offsetLeft

(9)*****本地/客户端存储技术

  cookie:淘汰了,存储的大小只有2kb,而且操作极其麻烦,尤其要到处切割只能最多保存30天
  webStorage:H5带了一个新特性,存储的大小有8mb,永久保存,而且非常简单
    分类(2种)
      sessionStorage - 会话级,只要浏览器一旦关闭,数据就会死亡了
      localStorage - 本地级,只要你不清空,就会永久存在
      两者的用法是一模一样的,不用创建,直接可用
    操作:
      1、添加:xxxStorage.属性名="属性值";
      2、读取:xxxStorage.属性名;
      3、删除:xxxStorage.removeItem("属性名");
      4、清空:xxxStorage.clear();

3、*****定时器

(1)周期性定时器

每过一段时间就会执行一次,先等后做
  开启:timer=serInterval(callback,间隔毫秒数);
  停止:clearInterval(timer);

(2)一次性定时器:等待一段时间,只会做一次就结束了

  开启:timer=setTimeout(callback,间隔毫秒数);
  停止:clearTimeout(timer);
同步技术:代码必须一行一行的执行,前面没做完,后面就等着。定时器就是目前见到第一个异步技术:无论这一块代码多么的耗时,也不会卡住后续代码。Ajax也是异步技术

4、event(事件对象)

(1)事件周期:从事件发生,到所有事件处理函数执行完毕的全过程
  3个阶段
    a、捕获阶段:由外向内,记录要发生的事件有哪些
    b、目标优先触发:目标元素->当前点击的实际发生事件的元素
    c、冒泡触发:由内向外,依次执行我们之前记录着的要发生的事件
(2)*****获取事件对象event:   主流:会自动作为事件处理函数的第一个形参传入
  老IE:event; - 老IE全局有这个变量
  兼容:event;//第1次见到小三上位,不光老IE可用,主流也可用

evet事件可做的操作

(1)获取鼠标的坐标

  获取鼠标相对于屏幕的坐标:e.screenX/e.screenY
  获取鼠标相对于窗口/客户端/文档显示区域的坐标:e.clientX/e.clientY
  *获取鼠标相对于网页的坐标:e.pageX/e.pageY

(2)阻止事件冒泡:- 笔试中

  主流:e.stopPropagation();
  老IE:e.cancelBubble=true;
  兼容:e.cancelBubble=true;//第2次见到小三上位,不光老IE可用,主流也可用

(3)*****利用冒泡 - 事件委托

开发中常用,提升网页性能,有了它我们的事件函数也可以换为箭头函数了
  优化:如果多个子元素定义了相同 或 相似的事件操作,最好只给父元素定义一次
  为什么:每一次绑定一个事件函数,其实都是创建了一个事件对象,创建的事件对象越多,网站性能就越差
  淘汰了this,表示当前元素
认识一个目标元素target:你点击的是哪一个,他永远就是那一个,不会变化
  主流:e.target
  老IE:e.srcElement;
  兼容:e.srcElement;//第3次见到小三上位,不光老IE可用,主流也可用

(4)阻止浏览器的默认行为

比如:a标签默认就可以跳转,提交按钮可以提交表单,右键自带一个弹出框,F12自带一个控制台,F11自带全屏功能,F5自带刷新功能
  主流:e.preventDefault();
  老IE:e.returnValue=false;
  兼容:e.returnValue=false;//第4次见到小三上位,不光老IE可用,主流也可用

新事件:
  (1)右键事件 - window.oncontextmenu
  (2)键盘事件:一般来说用于游戏开发较多+都要搭配上键盘的键码
    window.onkeydown - 按住和按下,任何键盘按键都可以触发
    window.onkeypress - 按住和按下,只有字母、数字、回车、空格可以触发,其他按键不行
    window.onkeyup - 松开,任何键盘按键都可以触发(比手速的游戏)

(5)获取键盘的键码

  e.keyCode; - 可以获取到你按了哪个键,每个键都有自己对应的键码,但是不需要记忆,要么输出看,要么百度搜个表
  event可以说BOM之中最最重要的一个点,其他点就算你不用,你要知道,笔试面试只要考BOM多半都是event
  老IE:不支持HTML5和ES5+

十一、递归

简单来说就是再函数之中再一次调用了函数自己,迟早有一天会停下来
何时使用:专门用于【遍历层级不明确的情况】 - DOM树和数据(children只能找到儿子层,找不到孙子层)
如何使用:(2步)

function 函数名(root){
    //1、第一层要做什么直接做
    //2、判断有没有下一层,如果有下一层则再次调用此方法,只不过传入的实参是自己的下一层
}

调用:函数名(实际的根);
算法:深度优先!优先遍历当前节点的子节点,子节点遍历完毕才会跳到兄弟节点
缺陷:不要过多使用,性能相对较差,同时开启大量的函数调用,良妃内存,我们只在一个情况下使用:【层级不明确】
递归VS纯循环
  递归:优点:简单易用
     缺点:性能低
  纯循环:优点:几乎不占用性能
     缺点:难得一批

十二、*****this

1、this的指向

单个元素绑定事件

  这个元素

多个元素绑定事件

  当前元素

箭头函数中的this

  外部对象

***函数中的this

  当前正在调用函数的对象

定时器的this

  window

2、******ES5强制改变this的指向

call/apply

call/apply:临时的替换了函数的this - 借用
  语法:函数名.call(借用的对象,实参...); - 单独传入每个实参
     函数名.apply(借用的对象,arr); - 只能传入一个实参要求是一个数组,apply其实会悄悄的打散数组
  强调:call/apply:相当于立刻调用函数,立即执行

bind

bind:永久替换了函数中的this - 买
  3件事:
    (1)创建了一个和原函数功能完全相同的新函数
    (2)将新函数的this永久绑定为了指定对象,别人都借不走
    (3)将新函数的部分参数永久固定
    语法:var 新函数=函数名.bind(永久对象,永久实参,...); - 不是立刻执行,需要自己调用
    强调:bind绑定的新函数没办法被call/apply再次借走

推荐使用call/apply

三个固定套路:

  (1)Math.max/min.apply(Math,arr) - 也能支持数组参数
  (2)Object.prototype.toString.call/apply(x)=="[object Array]";//笔试题:判断xx是不是一个数组
  (3)类数组转为普通数组:
    1、接住=Array.prototype.slice.call/apply(类数组对象)
    2、接住=Array.from(类数组对象)

十三、ES6

1、学过了let、const关键字、箭头函数

2、模板字符串

可以直接识别变量,不再需要+运算去拼接了,而且实现了一个简单的js环境,甚至支持再里面书写API
  `我的名字${name}`,需要加这两个模板符号``

3、*****解构赋值

顾名思义:解析结构再进行赋值 - 赋值的新方式,并且得到了增强
如果赋值符号,左右两边的结构一样的,就会悄悄的解开/脱掉结构再一一的进行赋值
语法:

(1)类似数组的解构赋值

let [a,b,c]=[1,2,3];
console.log(a);
console.log(b);
console.log(c);

(2)类似对象的解构赋值

let {a,b=默认值,c}={c:3,a:1,b:2};
console.log(a);
console.log(b);
console.log(c);

(3)调用函数时,传递实参的顺序无所谓了

unction zwjs({name,age,hobby="女"}){
	return `我的名字叫${name},今年${age}岁,喜欢${hobby}`;
}
console.log(zwjs({hobby:"学习",age:18,name:"袍哥"}));

(4)函数的返回的结果,可以有多个

function f1(){
	var a=1;
	var b=2;
	return [a,b];
}
var [a,b]=f1();
console.log(a,b);

只要以后见到:方法名({里面放着键值对就是使用了ES6的解构赋值})

4、Set和Map新的数据类型

(1)*Set

类似于数组的一种数据格式 - 【去重数组,然后再转回数组】
var s=new Set(arr);
...S - 三个点扩展运算符,可以脱掉数组的外套
一句话完成:[...new Set(arr)] - 不用记忆任何API

(2)Map:类似于对象的一种数据格式

var m=new Map(); 添加:m.set("键","值"); 获取:m.get("键"); 清空:m.clear(); 删除:m.delete("键");

5、新的循环 - 垃圾

for(var v of arr){
    v;
}

缺陷:
  (1)没有提供过下标,意味着不能修改原数组
  (2)只能遍历索引数组,不能遍历hash数组,意味着也不能遍历对象

十四、*****正则表达式

概念:定义字符串中字符出现规则的表达式
何时使用:切割 替换 【验证】
如何使用:语法:/正则表达式/

1、正则表达式基础语法

(1)最简单的正则

最简单的正则就是关键字原文 "no" -> /no/后缀
  后缀:
    g :找全部    i:忽略大小写

(2)备选字符集

格式:/^[备选字符集]$/
  强调:a、一个中括号,只管一位字符
     b、问题:正则表达式默认只要满足就不管后续了,而我们做验证的人,希望的使用户从头到尾按照我们的要求来,希望从头到尾完全匹配:
       解决:前加^,后加$,两者同时使用,代表要求从头到尾完全匹配/^[备选字符集]$/ - 只要做验证必加
    特殊:如果备选字符集中的ascii码是连续的,那么可用-省略掉中间部分
      比如:
        a、一位数字:[0-9];
        b、一位字母:[A-Za-z];
        c、一位数字、字母、下划线:[0-9A-za-z_];
        d、一位汉字:[\u4e00-\u9fa5];
        e、除了字符集之外的:[^0-9] - 表示除了0-9的数字以外的其他字符。很少使用,范围太广了

(3)预定义字符集

前辈们提前定义了一些字符集,方便我们程序员 - 简化了备选字符集

  • 一位数字:\d ===> [0-9]
  • 一位数字、字母、下划线:\w ===> [0-9A-Za-z]
  • 一位空白字符:\s
  • 一位除了换行外的任意字符: .  - 很少使用,范围太广了 建议:优先使用预定义字符集,预定义满足不了我们再用备选字符集补充
    问题:不管是备选字符集,还是预定义字符集,一个都只管一位

(4)量词

规定一个字符集出现的次数

  • 有明确数量:
    • 字符集{n,m}:前边相邻的字符集,至少n个,最多m个
    • 字符集{n,}:前边相邻的字符集,至少n个,多了不限
    • 字符集{n}:前边相邻的字符集,必须n个
  • 无明确数量:
    • 字符集? : 前边相邻的字符集,可有可无,最多1个
    • 字符集* : 前边相邻的字符集,可有可无,多了不限
    • 字符集+ : 前边相邻的字符集,至少一个,多了不限

(5)选择和分组

  选择:在多个规则中选一个
    规则1 | 规则2
  分组:将多个字符集临时组成一组子规则
    (规则1 | 规则2)

(6)指定匹配位置

  ^:开头
  $结尾
  特殊:两者同时使用,前加^,后加$,表示从头到尾要求完全匹配 - 只要做【验证】

(7)密码强度验证

2-4位,可以输入数字、字母,但是必须出现一位大写和一位数字的组合:/^[0-9A-Za-z]{2,4}/;

  预判公式:
    (?![0-9]+$) ->不能全由数字组成,可能有大写、小写、汉字、日文、特殊符号...
    (?![a-z]+$) ->不能全由小写组成,可能有数字、大写、汉字、日文、特殊符号...
    (?![0-9z-a]+$) ->不能全由数字组成、也不能全由小写组成、也不能全由数字和小写的组合组成

比如:
  /(?![0-9a-z]+$)(?![A-Za-z]+$)[0-9A-Za-z]{2,4}///2-4位,可以输入数字、字母。但是必须出现一位大写和一位数字的组合
  /(?![0-9z-a]+$)(?![A-Za-z]+$)(?![A-Z0-9]+$)[0-9A-Za-z]{2,4}///必须三者都有
  /(?![0-9A-Za-z]+$)[0-9A-Za-z_]{2,4}///至少要有下划线

2、*****支持正则表达式的字符串API

(1)切割

var arr=str.split("固定切割符"/RegExp);

(2)*****替换(笔试题中可能出现)

a、基本替换法

str=str.replace(/正则表达式/后缀,"新内容");
replace支持正则,并且搭配上后缀g就可以找到全部
缺陷:替换得新内容是一个固定得

b、高级替换法

str=str.replace(/正则表达式/后缀,function(a,b,c){
    console.log(a);//正则匹配到得关键字
    console.log(b);//正则匹配到得关键字的下标
    console.log(c);//原字符串
    return 判断a关键字的长度,而返回不同的星号数量
});

c、格式化:身份证

var id="513111129602226826";
var reg=/(\d{6})(\d{4})(\d{2})(\d{2})(\d{4})/;
id.replace(reg,function(a,b,c,d,e,f,g,h){
    //再replace的时候,正则出现了分组,我们会得到更多的形参
    //再形参a的后面就会出现n个形参,就看你有多少个分组
    //第1个分组获得的内容会保存在第2个形参之中
    //第2个分组获得的内容会保存在第3个形参之中
    //...
    //倒数第二个一定是下标
    //倒数第一个一定是原文本身
})

d、格式化:手机号

var phone="12345678912";
var reg=/(\d{4})\d{4}(\d{3})/;
phone=phone.replace(reg,(a,b,c)=>{
    return b+"****"+c;
})
console.log(phone);

总结:
  何时前加^后加$,何时又该添加后缀g?
    a、前加^后加$ - 验证
    b、替换,你希望替换所有 - 必须加g

(3)正则对象

创建:
  a、直接量:var reg=/正则表达式/后缀;
  b、构造函数:var reg=new RegExp("正则表达式","后缀");
API:
  验证:var bool=reg.test(用户输入的);

十五、animaite.css动画库

1、如何使用

  • 百度搜索:animate.style/
  • 下载
  • 引入下载的文件
  • 挑选需要的动画,把class放到元素上
  • 并且设置设置上animation-duration:2s;执行时长
  • 根据不同的动画,设置不同的初始效果,才会更好看

2、淡入淡出轮播

所有图片通过定位重叠在一起,通过设置opacity为0或1来显示图片,淡入淡出

3、滚动轮播

所有图片横向排列在一个div中,溢出的都隐藏,通过移动div的位置来显示图片

4、无缝轮播

页面上只加载两张图片,点击右按钮或者右边小圆点,就在右边生成一张图片,两张图片同时往左移动,移动完毕后删除左边的img标签。反之。显示的都是下标为0的,生成的都是下标为1的

5、swiper插件

一个专门的轮播插件,提供了HTML/CSS/JS,只需要复制
网站链接:www.swiper.com.cn/

十六、面向对象

面向对象的三大特点:封装、继承、多态
Array/String/RegExp/Date...对象具有属性和方法,都是预定义好的,现在可以学习自定义对象 - js是基于原型的面向对象语言

1、*****Object

(1)***开发方式

面向过程:过程 - 开始 ->结束,其实我们一直的开发方式都是面向过程:先干什么再干什么最后干什么
面向对象:对象(属性和方法),js有一句话万物皆对象,假设一个人是一个对象的话:
  属性:身高、体重、姓名、性别、爱好、智商、情商...
  方法:吃饭、睡觉、跑步、打字、上课...
何时使用面向对象:以后任何操作都要封装在一个对象之重 - 但是新手并不推荐,难度较大
为什么要面向对象:现实生活中所有的数据都必须包含在一个事务之中才有意义

(2)*****封装/创建/定义:封装自定义对象(3种)

a、*直接量方式

var obj={
    "属性名":属性值,
    ...,
    "方法名":function(){操作},//可以简化为箭头函数
    ...
}

强调:

  • 其实属性名和方法名的""可以不加 - 暂时建议加上,以后学习一个数据格式JSON,他必须在键上加上""
  • 访问对象的属性和方法
    • obj.属性名;===obj["属性名"];
    • obj.方法名();===obj"方法名";
    • 建议使用 . 去访问对象的属性和方法,更简单
    • ***js中一切都是对象,除了undefined和null,一切对象的底层都是hash数组
  • 访问到不存在的属性,返回undefined
  • 可以随时随地的添加新属性和新方法
    • obj.属性名=新值;
    • obj.方法名=function(){};
  • 如果希望遍历出对象所有的东西,必须使用for in,obj[i]才能拿到。不要使用 . 会出问题
  • ***如果希望在对象的方法里,使用对象自己的属性,写为this.属性名
    • *****难点:this的指向:
      • 单个元素绑定事件,this->这个元素
      • 多个元素绑定事件,this->当前元素
      • 定时器中的this->window
      • 箭头函数this->外部对象
      • 函数中this->谁在调用此方法,this就是谁
      • 构造函数之中this->当前正在创建的对象

b、预定义构造函数方式

var obj=new Object();//空对象
//需要自己后续慢慢添加属性和方法
obj.属性名=新值;
obj.方法名=function(){};

以上两个都有一个缺陷:一次只能创建一个对象,适合创建单个元素的时候(第一种方法),第二种方法完全是垃圾,如果你要批量创建多个对象,那么我推荐第三种方法

(3)自定义构造函数方式(2步)

a、创建自定义构造函数

function 类名(name,age,hobby){
    this.name=name;
    this.age=age;
    this.hobby=hobby;
}

千万不要在里面创建方法,每个对象都会创建出一个相同的方法,浪费内存 - 学习继承后可以解决

b、调用构造函数创建对象

var obj=new 类名(实参,...);

面向对象
  优点:
    (1)所有的属性和方法都保存在一个对象之中 - 更符合现实更有意义
    (2)每个功能特地分开写 - 便于以后维护
    (3)铁锁练舟 - 一个方法触发多个方法联动
  缺点:
    对新手不友好,尤其是this的指向问题

2、*****继承

父对象的成员(属性和方法),子对象可以直接使用
为什么要继承:代码重用!提高代码的复用性,节约了内存空间!提升了网站的性能!
何时继承:只要多个子对象共用的属性和【方法】,都要集中定义在父对象之中

(1)***如何找到原型对象(父对象)

保存了一类子对象共有属性和共有方法

  • 对象名.proto;//必须先有一个对象
  • 构造函数名.prototype;//构造函数名几乎人人都有,除了Math和Window,new构造函数名();//Array、String、Date、RegExp...

(2)*面试题

a、两链一包

作用域链和[原型链]和闭包
  每个对象都有一个属性:proto,可以一层一层的找到每个人的父亲,形成的链式结构,就称之为原型链
  可以找到父对象的成员(属性和方法),作用:找共有属性和共有方法
  最顶层的是Object的原型,上面放着一个我们眼熟的方法toString,所以人人都可以使用toString
  JS万物皆对象

b、*****判断是自有还是共有的属性或方法

判断自有

obj.hasOwnProperty("属性名"); 如果结果为true,说明是自有属性,如果结果为false,有两种可能,说明可能是共有,也可能是没有

判断共有
if(obj.hasOwnProperty("属性名")==false&&"属性名" in obj){//in 关键字,会自动查找整条原型链上的属性,找到了结果为true,找不到结果为false
    共有
}else{
    没有
}

完整公式:

if(obj.hasOwnProperty("属性名")){
    自有
}else{
    if("属性名" in obj){
        自有
    }else{
        if("属性名" in obj){
            共有
        }else{
            没有
        }
    }

c、修改和删除自有和共有属性或方法

自有

修改:obj.属性名=新属性值; 删除:delete obj.属性名;

共有

修改:原型对象.属性名=新属性值;//千万不要觉得,自己能拿到,就能直接修改这样很危险,并没有修改原型的东西,而是在本地添加了一个同名属性
删除:delete 原型对象.属性名;//如果对着本地直接删除,那么此操作直接无效

d、*如何为老IE的数组添加indexOf方法(如果为一类人创建某个方法)

if(Array.prototype.indexOf===undefined){//判断是否为老IE
    Array.prototype.indexOf=function(key,starti){
        starti===undefined&&(starti=0);
        for(var i=starti;i<this.length;i++){
            if(this[i]===key){
                return i;
            }
        }
        return -1;
    }
}

let arr1=[1,2,3,4,5];
let arr2=[2,4,6,8,10];

arr1.indexOf(3);//找到值为3的下标,从头开始找
arr1.indexOf(8,1);//找到值为8的下标,从下标为1出开始找

e、*如何判断x是不是一个数组(4种方法)

千万别用typeof(),只能检查原始类型,不能检查引用类型,如果检查引用类型得到的结果都是一个object

let arr=[],
    obj={},
    date=new Date(),
    reg=/\s/;
(i)判断x是否继承自Array.prototype

Array.prototype.isPrototypeOf(x);
结果为true,说明是数组,结果为false,说明不是数组

console.log(Array.prototype.isPrototypeOf(arr));
console.log(Array.prototype.isPrototypeOf(obj));
console.log(Array.prototype.isPrototypeOf(date));
console.log(Array.prototype.isPrototypeOf(reg));
(ii)判断x是不是由Array这个构造函数创建的

x instanceof Array;
结果为true,说明是数组,结果为false,说明不是数组

console.log(arr instanceOf Array);
console.log(obj instanceOf Object);
console.log(date instanceof Date);
console.log(reg instanceof RegExp);
(iii)Array.isArray(x)
  • ES5新增的方法,只有数组可以这么使用
    结果为true,说明是数组,结果为false,说明不是数组
    console.log(Array.isArray(obj));
(iiii)*输出【对象的字符串】形式
  • 在Object的原型上保存着最原始的toString方法
  • 原始的toString输出形式:[object 构造函数名]
  • ***多态:子对象觉得父对象的成员不好用,就在本地定义了同名函数,覆盖了父对象的成员,不严格定义:同一个方法,不同的人使用,效果不同,有多种形态 固定套路:Object.prototype.toString.call(x)==="[object Array]"
console.log(Object.prototype.toString.call(arr));
console.log(Object.prototype.toString.call(obj));
console.log(Object.prototype.toString.call(date));
console.log(Object.prototype.toString.call(reg));

f、实现自定义继承

(i)两个对象之间设置继承

子对象.__proto__=父对象;

(ii)多个对象之间设置继承

构造函数名.prototype=父对象;
时机:应该在开始创建对象之前就设置好继承关系

(3)有了原型对象,可以设置共有属性和共有方法

  原型对象.属性名=属性值;   原型对象.方法名=function(){}

3、class关键字

简化面向对象(封装、继承、多态)

class 类名 extends 老类{
    constructor(name,age,hobby,...){//放在constructor里面的都是自有属性
        super(name,age);
        this.hobby=hobby;
    }//放在constructor外面的都是共有方法
    //还会继承到老类所有的API,也可以添加新的
}

4、*****Function:闭包

(1)作用域(2种)

  • 全局:随处可用,可以反复使用,缺点:容易被污染
  • 函数:只能在函数调用时内部可用,不会被污染,缺点:一次性的,是会自动释放的

(2)***函数的执行原理

a、程序加载时

  • 创建执行环境栈(ESC):保存函数调用顺序的数组
  • 首先压入全局执行环境(全局EC)
  • 全局EC引用着全局对象window
  • window中保存着我们全局变量

b、定义函数时

  • 创建函数对象:封装代码段
  • 在函数对象之中有一个scope(作用域)属性:记录着函数来自己的作用域是哪里
  • 全局函数的scopt都是window

c、调用前

在执行环境栈(ESC)压入新的EC(函数的EC) 创建出活动对象(AO):保存着本次函数调用时用到的局部变量 在函数的EC中有一个scope chain(作用域链)属性引用着AO AO有一个parent属性是函数的scope引用着的对象

d、调用时

正是因为有前面三步,才带来变量的使用规则:优先使用自己的,自己没有找全局,全局没有就报错

e、调用完

函数的EC会出栈,没人引用AO,AO自动释放,局部变量也就释放了

(3)*****闭包

希望保护一个可以【反复使用的局部变量】的一种词法结构,其实还是一个函数,只是写法比较特殊
何时使用:希望保护一个可以【反复使用的局部变量】的时候
如何使用:
  两个函数进行嵌套
  外层函数创建出受保护的变量
  外层函数return出内层函数
  内层函数再操作受保护的变量

强调:
a、判断是不是闭包:有没有两个函数嵌套,返回了内层函数,内层函数再操作受保护的变量
b、外层函数调用了几次,就创建了几个闭包,受保护的变量就有了几个副本
c、同一次外层函数调用,返回的内层函数,都是再操作同一个受保护的变量

缺点:
受保护的变量,永远都不会被释放,使用过多,会导致内存泄漏 - 不可多用

问题:
应该在哪里去使用?
  三个事件需要防抖节流 - 共同点:触发的速度飞快
    elem.onmousemove - 鼠标移动事件
    input.oninput - 每次输入/改变都会触发
    onresize - 每次窗口改变大小都会触发

防抖节流的公式:

function fdjl(){
    var timer=null;
    return function(){
        if(timer!==null){
            clearTimeout(timer);
            timer=null;
        }
        timer=setTimeout(()=>{
            操作;
        },500)
    }
}

let inner=fdjl();

elem.on事件名 =function(){
    inner();
}

总结

两链一包:
  作用域链:以函数的EC的scope chain属性为起点,经过AO,逐级引用,形成的一条链式结构,我们就称之为叫做作用域链
    作用:查找变量,带来了变量的使用规则:优先使用自己的,自己没有找全局,全局没有就报错
  原型链:每个对象都有一个属性叫做.proto,可以一层一层的找到每个对象的原型对象,最顶层的就是Object的原型,形成的一条链式结构,我们就称之为叫做原型链
    作用:查找属性和方法,哪怕自己没有也会顺着原型链向上找,怪不得人人都能用toString(),因为他在最顶层
  闭包:希望保护一个可以【反复使用的局部变量】的一种词法结构,其实还是一个函数,只是写法比较特殊
    作用:专门用于防抖节流

5、保护对象

保护对象的属性和方法

(1)四大特性

{
    value:1001,//实际保存值的地方
    writable:true,//开关:控制着是否可以被修改 - 默认值:都为true
    enumerable:true,//开关:控制着是否可以被for in 循环遍历到 - 默认值:都为true
    configurable:true,//开关:控制着是否可以被删除 - 默认值:都为true,总开关:一旦设置为false,其他特性不可以在修改,而且它本身也是一旦设置为false,不可逆
}

修改四大特性:

a

Object.defineProperty(对象名,"属性名",{
    修改四大特性
})

调用一次方法只能保护一个属性的四大特性

b

Object.defineProperty(对象名,{
    "属性名":{修改四大特性},
    ...
})

至少方法只调用了一次

四大特性甚至不能防止添加

(2)三个级别

a、防扩展

防止添加
Object.preventExtension(obj);

b、密封

防止添加和删除
Object.seal(obj);

c、冻结

防止添加和删除和修改
Object.freeze(obj);

保护对象不重要

  • 如果不用面向对象开发,那也没有对象可保护
  • 前辈们都没保护,我们也不用
  • 别人一般来说不可能知道你取的对象名叫什么

***四大特性,其实应该叫六大特性 - 可以帮助我们做出动态数据

Object.defineProperty(obj,"name",{
    get:()=>{
        console.log("获取数据会进行拦截");
    },
    
    set:v=>{
        console.log("设置数据会进行拦截");
        v;//拦截到的新数据
        d1.innerHTML=v;
    }
})

理解到这一块即可,以后三阶段有一个框架叫做vue,vue中的所有数据都是动态数据,意味着他的底层就是做了拦截操作

6、*对象的深浅拷贝

a、浅拷贝

var obj1={"name":"obj1"};
var obj2=obj1;

b、深拷贝

var obj1={"name":"obj1"};
var obj2={...obj1}

c、以后如何脱掉后端传来的数据

后端穿衣服:var jsonText=JSON.stringify(obj1); - Node.js就是这句话,Node.js也是"js",历史上第一次一门语言可以通吃前后端
前端脱衣服:var jsonObj=JSON.parse(jsonText);
此方法也能实现深拷贝

7、*Error对象

错误对象

(1)以后工作/学习的目的

  • 快速找到错误
  • 记得要放用户

(2)***浏览器自带4种类型:可以快速找到自己的错误

  • 语法错误:SyntaxError - 一定是你的符号写错了
  • 引用错误:ReferenceError - 没有创建就去使用了
  • 类型错误:TypeError - 不是你的方法,你却去使用了
  • 范围错误:RangeError - 只有一个API会碰到:num.toFixed(d);//d取值范围:0~100之间

(3)只要发生错误,就会报错,会导致后续代码终止(闪退)

错误处理:就算发生错误,我们也不希望报错,而是给出一个错误提示,让后续代码可以继续执行
语法:

try{
    只放入你可能出错的代码
}catch(err){
    发生错误后才会执行的代码
    alert(err);//err就是我们的错误提示:只不过是英文的
    alert("中文的错误提示,来提示用户");
}

try...catch...的性能非常差,几乎是所有代码里最差的,放在try中的代码效率会被降到最低
*可以用一个技术代替他:分支结构
*开发经验:记住一切的客户端输入/用户输入都是坏人 - 但是你不必担心,只要你做好该做的防护就不会出错(!isNaN、正则)

(4)抛出自定义错误,只要是错误,后续代码都不会执行

throw new Error("自定义错误消息");

(5)ES5:严格模式

开启:"use strict"; - 写在任何一个作用域的顶部都可以
作用:
  a、禁止了全局污染,使用变量之前必须先创建变量
  b、将静默失败升级为了错误

8、柯里化函数

function add(a){
    return function(b){
        return function(c){
            console.log(a+b+c);
        }
    }
}
add(3)(5)(7);

9、百度/高德地图

(1)定位技术有哪些?(不重要,吹牛用)

  • GPS - 美国(卫星定位)
  • 北斗 - 中国自主:最初并不是民用,而是军用,使用的是惯性定位(定位并不精准)
    • 后期发展为了民用,添加了卫星定位了(更精确了)
    • 物联网、车联网...
  • 基站定位 - 信号的范围发射,可以检测到你现在的信号属于哪个范围
  • IP定位 - 在网不要乱说话,乱造谣,要坐牢

(2)网站定位技术都是使用GPS

不用学习如何使用GPS,只需学习如何使用百度/高德地图

(3)开发者如何使用百度/高德地图

  • 打开百度:搜索百度/高德地图开放平台
  • 注册、登录百度账号
  • 拉到最下面、点击立即注册成为开发者
  • 进入控制台
  • 应用管理->我的应用->创建应用->实名认证->获得密钥(AK)
  • 鼠标移动到导航条->放到开发文档上->web开发->JavaScript API->示例DEMO
  • 挑选出你喜欢的地图,然后复制全部代码(HTML/CSS/JS),到你需要的位置
  • 查询经纬度:api.map.baidu.com/lbsapi/getp…
  • 百度地图你看上的每一个都可以混搭在一起,但是一定要注意版本:普通版(老) 和 webGL(新),是不可以混搭的

10、匿名函数

没有名字的函数,有两种用法

(1)自调

只能执行一次,好处:函数中的没用的变量是会自动释放的,他可以用于代替全局代码写法,两者很相似:都只会执行一次,但是自调会释放

(function(){
    console.log(1);
})();

(2)回调

匿名函数不是自调,就是回调

elem.on事件名=function(){}
arr.sort(function(){})
var obj={
    "方法名":function(){}
}

一切的回调函数,都可以简化为箭头函数

11、设计模式

不仅仅局限于前端,它是一种编程思想,越来越复杂,对于我们前端人员要求也越来越高了
有21种设计模式

(1)单例模式

也称之为单体模式,保证一个类仅有一个实例对象,并且提供一个访问它的全局访问点,为三阶段的vue,new Vue();
举例:一个班级只有一个班主任,只有一个太阳,一个国家只有一个主席,"唯一" "便于访问(全局访问的)",行为对象变成单例
如何实现:
  最简单的单例模式:利用ES6的let不允许重复声明的特性,刚好就符合了单例的特点

let obj={
    "name":"袍哥1",
    "getName":()=>this.name,
}

不推荐这种写法:
  1、污染命名空间(容易变量名冲突)
  2、维护时不容易管控(搞不好就直接覆盖了)

推荐写法:

let h52301=(function(){
    let state=null;
    return function(name,age){
        this.name=name;
        this.age=age;
        if(state){
            return state;
        }
        return state=this;
    }
})();

h52301.prototype.sayHello=function(){
    console.log(this.name);
}

var llc=new h52301("兰淋川",18);
var yxw=new h52301("尹星文",19);

console.log(llc);
console.log(yxw);
xonsole.log(yxw==llc);

llc.sayHello();
yxw.sayHello();

何时使用:我们的页面只有一个弹出框

(2)发布订阅模式

为三阶段vue的bus总线用到的底层原理就是我们的发布订阅模式

let obj={};
//创建订阅者
function on(id,fn){
    if(!obj[id]){//判断有没有此id(有没有这个人),没有我就创建了一个空数组
        obj[id]=[];
    }
    obj[id].push(fn);
}
on("老袍",(msg)=>{console.log("小兰来了",msg)});
on("老袍",(msg)=>{console.log("小尹来了",msg)});
on("老袍",(msg)=>{console.log("小张来了",msg)});
on("老袍",(msg)=>{console.log("小李来了",msg)});
on("老袍",(msg)=>{console.log("小赵来了",msg)});
console.log(obj);//{"老炮":[fn,fn,fn,fn,fn]}

//发布者的操作
function emit(id,msg){
    obj[id].forEach(fn=>fn(msg));//obj[""老炮].forEach
}
btn.onclick=()=>{
    emit("老炮","一支穿云箭");
}

12、事件轮询

js其实是单线程引用,代码必然是从上向下,一步一步的执行,如果某一块代码非常耗时,可能会导致整个页面卡住,尤其是把js放在head之中,会看到页面是一个白板

(1)宏任务

不会再卡住我们的单线程应用,可以让后续代码先走,我们慢慢跟着来,但是问题在于,多个宏任务同时存在,到底谁先执行谁后执行,分不清

a、定时器:setInterval和setTimeout

b、Ajax

(2)微任务

ES6提供的Promise对象 - 可以控制异步代码,依然是异步代码,但是可以控制执行的顺序了

function ajax1(resolve){
    setTimeout(()=>{
        console.log(1);
        resolve();
    },Math.random()*5000);
}

function ajax2(){
    return new Promise(resolve=>{
        setTimeout(()=>{
            console.log(2);
            resolve();
        },Math.random()*5000);
    })
}

function ajax3(){
    return new Promise(resolve=>{
        setTimeout(()=>{
            console.log(3);
            resolve();
        },Math.random()*5000);
    })
}

new Promise(ajax1).then(ajax2).then(ajax3);//promise的回调函数提供了一个形参函数,可以用于放行
console.log("后续代码");