JavaScript

JavaScript

解决小数运算的精确问题的方法

逻辑运算符的短路讲不讲 . 短路和运算符的优先级有冲突。。。

var a=10;
3-2 || a++   //1  a为10

ASCII 码 发音 as key码

一个编码,用来表示计算机输入的所有内容,最早把所有的可以输入的内容全部转换成二进制保存,给电脑上的每一个键,每一个输入都编了一个二进制码,一共128个,起了个名字叫做ASCII码

unicode编码

随之计算机的发展,128个ASCII码不够用,万国码(统一码)取名叫做unicode编码,里面前128个就是ASCII码,顺序添加了世界上大部分国家的文字,随着web技术的发展,在浏览器中统一使用一种八进制的unicode编码,我们叫做UTF-8

js 备课

  • 计算机语言 :机器语言 汇编语言 高级语言

  • 机器语言 :计算机执行的二进制命令,都是0和1表示的。计算机最终执行的都是机器语言

  • 汇编语言和机器语言一样实质上是相同的,都是直接对硬件操作,只不过指令采用了英文缩写的标识符,容易识别和记忆 即用助记符代替机器指令的操作码(如:ADD表示加法)

  • 高级语言:更接近自然语言,更符合人类的习惯,更容易理解和修改,高级语言经过编译器编译后,可以得到目标程序。 如 高级语言:js java php c++

  • 机器语言,汇编语言,高级语言的区别:语言越高级越接近自然语言,越远离硬件,效率也越低

  • 机器语言,汇编语言,高级语言的联系:高级语言要经过编译程序翻译成汇编代码,汇编语言要经过汇编得到机器语言,计算机才能执行

    即机器语言是给机器看的,高级语言是给人看的,汇编语言,介于两者之间是一个中间转换的产物

  • 计算只能识别机器语言需要使用翻译器将高级语言翻译成二进制语言,目前有两种翻译类型,根据翻译的方式和时间节点不同分为了编译和解释

    • 编译语言 编译器在代码执行之前先把代码翻译成计算机可以理解的文件,比如EXE文件。这个EXE文件只需要一次编译就可以运行了, 除非修改代码,否则都不需要重新编译,所以编译型语言的程序执行效率高。(编译生成中间代码文件)

      由于编译型语言在运行前进行了编译,编译器对有所得代码都进行了检查,这样就不会产生一些低级错误,比如使用了不存在的名字

    • 解释型语言不需要在代码执行之前编辑,而是在运行过程中才翻译,解释器在代码运行时及时解释,并立即执行。解释一行执行一行,这样的解释型语言每执行一次就要翻译一次,效率比较低。

      (当编译器在以解释的方式运行被称为解释器)

    js属于解释型语言,这就表示每句代码在运行时,系统才知道这句代码有没有错误,换句话说,

  • js与java没有关系

  • js是编程语言,html是标记语言

    • 编程语言有强的逻辑性和行为能力,是主动的,编程语言向计算机发出指令,命令计算机执行操作
    • 标记语言是被动的,被计算机读取。不会像计算机发出指令,常用作格式化和链接
  • 计算机的组成

    • 硬件 :输出设备,输入设备,cpu,硬盘,内存,主板

      • cpu 是处理数据和运算
      • 硬盘和内存用来存储数据(二进制数据)。硬盘是永久储存,内存是暂时储存数据
    • 软件:

      • 系统软件
      • 应用软件
  • 数据存储

    • 计算机内部使用二进制0和1表示数据
    • 所有的数据,包括图片,视频等都是以二进制的数据的形式存储在硬盘中的
    • 所有程序,包括操作系统,本质都是各种数据,以二进制的形式存储在硬盘中,我们平时所说的安装程序软件都是将程序文件负复制到硬盘中
  • 数据存储单位:

    最小的单位是位bit,1字节=8bit,千字节:1kB=1024B;1MB=1024B,1GB=1024MB,1TB=1024GB

  • 程序运行

    • 打开某个程序时,先将程序从硬盘中加载到内存中,cpu执行内存中的代码,这样做的原因是因为cpu运行速度太快了,而从硬盘中直接读取数据的速度很慢很浪费cpu的性能,所以才使用读取速度更快的内存来保存运行时的数据
    • 内存使用的是电,硬盘是机械
    • js是网景,后来和sun公司(sun公司开发的java)合作改名为javascript,想借用其名气
  • js是运行在客户端的脚本语言,脚本语言不需要编译。运行过程中js解析器(js引擎)逐行解析并执行

  • 产生js的最初目的是进行表单的动态校验

  • 浏览器分为两部分,渲染引擎和js引擎

    • 渲染引擎俗称内核,用来解析html和css。谷歌浏览器现在是blink内核,老版本的是webkit内核
    • js引擎:负责读取网页中的js代码,对其处理后运行。比如谷歌浏览器的v8引擎,是js引擎中运行最快的
      • fireFox spiderMonkey
      • Ie jscript/Chakra
      • safari javaScriptCore
      • chrome v8
      • carakan carakan
    • 浏览器本身并不执行js代码,通过js引擎来执行代码,js引擎在执行代码时逐行解析每一句源码(转换成机器语言),然后由计算机去执行。所以js语言归位脚本语言,会逐行解析执行
  • js的简史

    javascript是由网景公司发明的,最初起名为livescript,后来由于sun公司(发明了java)的介入更名为javascript,后来微软公司在最新的IE3浏览器中引入了自己对javascript的实现jscript,由于市面上存在两个版本的javascript,一个网景的javascript一个微软的jScript,为了确保在不同的浏览器上运行的javascript的标准一致,所以几个公司共同制定了js的标准,命名为ECMAScript

  • 一个完整的javascript的组成

    • ECMAScript js语法
    • DOM 页面文档对象模型
    • BOM 浏览器对象模型 通过bom可以对浏览器窗口进行操作
  • js引入方式

    • 行内式的js 直接写在开始标签内部

      <div onclick="alert('123')"></div>
      

      可以将少量js代码写在html标签的事件属性中(以on开头的属性)

      在html中我们推荐使用双引号,在js中我嫩推荐使用单引号,注意引号多层嵌套使用的方式

      结构与行为耦合,不便于维护,不推荐使用行内js,只在特殊情况下使用

    • 可以将js代码写在a标签的href属性中,这样当点击超链接的时候,就会执行js代码

      <a hreft="javascript:alert('让你点你就点');">点一下试试<>
      
    • 内嵌式的js

      在页面中使用script标签,在标签的标签体中直接写js代码

      多在练习时使用此方式

    • 外部js 推荐使用

      <script teyp="text/javascript" src="new.js"></script>
      

      将js代码写在外部的js文件中,然后再通过script标签引入,这样可以在不同的文件中使用相同的js代码,也可以利用浏览器的缓存机制

      采用引入外部js文件时的script标签中不可以在写js代码了,即便写了浏览器也会自动忽略

      如果需要可以另建一个script标签写

script标签放的位置

有的将script标签放在head标签里,有的放在body闭合标签的前面

js执行的特点:加载后马上执行

将script标签放在head标签内,浏览器看到js文件会先下载完所有的js文件再解析其他的html。且一次只能加载两个js文件,这样会影响后面html的渲染造成页面内容呈现的滞后

且在js文件中可能会有对页面中元素的操作,但是html文件还没有解析

所以推荐将js文件放在页面body闭合标签之前,这样才能保证js加载前页面已经完成了DOM树的渲染

script标签放在body标签的闭合标签后面也可以加载执行,但是这是不规范的写法,不推荐

js中的注释

  • 单行注释 //被注释的内容

  • 多行注释 /* 被注释的内容 多行 */ 快捷键 alt+shift+A

alt+shift+鼠标选择多个

control+shift+左右健 按单词的边界选中

js输入输出语句

  • alert(123); 在浏览器中弹出提示框 只要不是纯数字都用引号括起来,单引号双引号都可以

  • prompt(‘提示信息’); 浏览器弹出输入框,用户可以输入,返回值就是用户输入的内容,且不论用户输入的是什么内容,返回结果都是一个字符串,用户点击取消返回值为null

  • confirm(‘需要用户确认的信息’) 弹出一个确认消息对话框,有确认和取消两个按钮,用户点击后会分别返回布尔值true和false

    alert,comfirm ,prompt弹出框会阻断程序的继续执行,因为js是单线程的,弹出框弹出后,如果用户没有点击按钮表示弹出层没有结束,直到用户操作之后,才会继续向下执行代码

  • console.log(在浏览器控制台打印的输出信息);

  • document.write() 可以向页面的body中字写入一个内容

    直接把内容书写在页面上,所以可以解析html标签

js的书写规则

  • js中是严格区分大小写的 在html中没有严格区分 在html中```a标签``可以正常显示,单是js中不可以

  • js中的每一条语句后面以;结尾

    如果不写分号,浏览器会自动添加,但是会消耗一些系统化资源,有时候也会加错分号,所以我们必须写分号

  • js中会忽略多个空格和换行,所以我们可以利用空格和换行对代码进行格式化

字面量

作用:字面量都是一些不可改变的值,是用于表达一个固定值的表示法,又叫做常量

通俗的讲字面量就是所见即所得,当我们看到字面的时候就会知道他是什么类型的数据,值是多少

比如说:数字字面量:1,2,3

​ 字符串字面量:“你好吗”

​ 布尔值字面量:true false

字面量都是可以直接使用的,但是我们一半都不会直接使用字面量

语句和表达式

js程序执行单位为行,也就是一行一行的执行,一般情况下,每一行就是一个语句

语句是为了完成某种任务而进行的操作,比如var a=1+3;就是一行赋值语句,这条语句先用var命令声明了变量a,然后再将1+3的运算结果赋值给变量a

1+3叫做表达式,是一个为了得到返回值的计算式,语句和表达式的区别语句是为了执行某种操作,一般情况下不需要返回值;表达式式为了得到返回值,一定会返回一个值。凡是js中预期为值的地方,都可以使用表达式。比如,赋值语句的等号右边预期是一个值,因此可以放置各种表达式;

语句以分号结尾,一个分号就是一个语句结束,多个语句可以写在一行如var a=1+3;var b='abc';

分号前面可以没有任何内容,js引擎将其视为空语句;;;,这表示3个空语句,表达式不需要分号结尾,一但在表达式的后面添加分号,则js引擎就将表达式视为语句,这样就产生了一些没有意义的语句。如1=3;‘abc’;这两行语句只是单纯的产生一个值,并没有任何实际的意义

表达式 返回值

  • 表达式是由数字,变量,运算符所组成的式子

  • 表达式最终都会有一个结果,这个结果就是返回值

  • 在js中,我们是将左边表达式计算出的结果返回给右边

    var userName=1+1;

变量

变量就是在内存中开辟一块存储空间用来存放数据,变量在程序运行中保存一个中间值使用

通俗的将变量就像个存储东西盒子,变量可以用来存储字面量,而且变量的值是可以直接改变的

变量可以更加方便我们使用字面量,所以在开发中我们都是通过变量去保存一个字面量,而很少使用字面量

(可以通过变量对字面量进行描述,即给变量起名时就可以知道是干啥用的)

  • 声明变量 使用关键字 var 变量名;

    //声明单个变量
    var box;  声明了一个名为box的变量本质是在内存中为box变量开辟一块存储空间
    //声明多个变量只使用一个var关键字,多个变量之间使用英文逗号,分隔
    var box1,box2,box3;
    
  • 关键字:系统规定有特殊意义的字,我们不能再使用关键字和保留字声明变量

    ​ 保留字:系统现在没有用上但是留作日后再用的字,等同于关键字

    标识符:我们为变量,函数,属性,参数等所起的名就是标识符(标识符不能使用关键字或保留字命名,表示要有意义)

  • 变量的命名规则,必须遵守,不遵守会报错

    • 由字母(a-zA-Z),数字(0-9),下划线,美元符号$符号组成
    • 不能以数字开头
    • 严格区分大小写 即 box 和 Box是俩个不同的变量
    • 不能使用关键字和保留字 如var
  • 变量命名规范,建议遵循,因为大家都这样nj

    • 由多个单词组成的变量名采用驼峰命名法,即第一个单词的首字母小写后面单词的首字母要大写myNameIsApple

    • 不要使用中文命名

    • 见名知意

    变量名虽然是任意起的,但是要尽量通俗易懂有意义,即 userName passWord

    可以通过变量对字面量进行描述,即给变量起名时就可以知道是干啥用的

  • 尽量不要使用name命名,name虽然不是关键字但是在许多系统中都有特殊的作用(这是扩展)

    name在没有声明且没赋值的情况下使用不报错,返回结果为空

    • window是js的全局对象,有一个天生自带的name属性,作用是:在iframe标签和name属性合作进行跨域的,特点是:被固定为字符串类型了,不管 你给name属性赋值为什么数据类型,他都会自动转换成字符串
  • 变量赋值 使用赋值符号=
    box=123; 表示将=右边的值放入到box变量中

  • 变量的初始化 就是声明一个变量并赋值,声明和赋值同时进行

    var box=123;  声明变量box,并给box变量赋值为123;
    var box1=123, 同时      mkk
    
  • 我们通过变量名获取变量

  • 变量声明与赋值的特殊情况

    • 只声明不赋值,使用变量,返回结果是undefined
    • 不声明直接赋值 使用变量可以正常使用,不会报错,弱类型,不推荐这样使用,建议先定义后使用
    • 不声明不赋值直接使用变量的情况下,会报错
  • 更新变量

    新赋的值会覆盖原来的值,原来的值就没办法获取了

    box=456; box变量的最终结果是456,原来的123已经被覆盖掉了

    作业:两个变量交换值

  • 当使用输出语法的时候,只要不用引号,就表示输出一个变量

    console.log(box1)

数据类型

数字 文字 (汉字,英文,日文)

ji中数据也是有分类,不同的数据类型所展的内存空间大小不同,且可执行的操作不同,比如数字类型的数据可以进行比较运算,字符串类型的数字可以进行字符串的链接

  • js的是弱类型的语言,我们在声明变量时并没有指定变量的类型,变量的类型由所赋给他的值的类型决定

  • 基本数据类型

    • 数字类型 Number 包含整形值和浮点型值
    • 字符串类型 String
    • 未定义类型 Undefined undefined
    • 空类型 Null null
    • 布尔类型 Boolean true false
  • 复杂数据类型(地址数据类型/引用数据类型)

    Object

    ​ Function

    ​ Array

typeof 检测数据类型

使用typeof 运算符来判断给定变量或数据的数据类型,返回结果是操作数的数据类型名称,是一个字符串类型的结果

  • 有两种格式

    • typeOf 要检测的数据 (空格) 只能检测紧跟着的一个变量

    • typeof(要检测的数据) 先运算小括号里的结果,然后再使用typeof 去检测结果的数据类型

需求:想要计算100+200 的结果的数据类型
var a=100,b=200;
var res=typeof a+b; //先匀速typeof a ,然后用返回值去和b进行加法运算
console.log(res)
var res2=typeof(a+b); //先运算a+b的值,然后再用typeog去检测300的数据类型
  • typeof的返回值

    • 返回值是一个字符串

    • 当两个及以上typeof 连用的时候,一定得到string

    var res3=typeof typeof b;
    //从右向左进行运算 ,先检测b的数据类型,再检测右侧检测后的返回值的数据类型
    
    • 只能准确的检测基本数据类型

      • 数值 Numbe console.log(typeof 122)

      • 字符串 String

      • 布尔值 Boolean

      • undefined undefined

      • null Objet *

console.log(typeof 12);  //"number"

数据类型总结:

  • 简单数据类型 null ,
var a=null;
typeOf(a)   //返回结果是object

如果一个变量打算以后存储为对象,暂时没有想好放啥,就可以先将变量赋值为null

  • 简单数据类型即基本数据类型或者值类型,复杂类型又叫做引用类型

    • 值类型:基本数据类型在存储时变量中存储的是值本身,因此叫做值类型

    string number boolean undefined null

    • 引用数据类型:复杂数据类型,在存储时变量中存储的仅仅是地址(引用),因此叫做引用数据类型

    通过new关键字创建的对象(系统对象,自定义对象),如Object,Array,Date等

数值型Number

  • 一切十进制表示的数字

  • 一切浮点数(小数)

  • 其他进制表示的数字 16 8 2进制

  • 科学计数法

    比较大的数字使用科学计数法表示 2e5 2*10的5次方

  • NaN

    not a Number 非数字

  • 数字型

    包含整形值和浮点形值。2 和2.3

  • 数字型进制

    • 十进制

      • 0~9 逢10进1
    • 二进制

      • 0和1 逢2进1
      • js中2进制的数字在前面加0b 0b100
    • 八进制

      • 0~7 逢8进1
      • 在js中表示八进制在数字前加0
      • 010 转换为十进制就是8
    • 十六进制

    • 0~9 a~f

      • 在js中表示十六进制在数字前加0x

      • 常见的颜色取值#dddddd就是十六进制

        var a=0xff a变量存储的是一个十六进制的ff

        在控制台输出的都是转为十进制的数值

  • 数字型的范围.

    js中数值的最大值和最小值

    Number.MAX_VALUE

    Number.MIN_VALUE

  • 数字型的三个特殊值.

    Infinity 正无穷

    -Infinity 负无穷

    NaN 待代表一个非数字

    ​ NaN不和任何数相等,包含它自身 NaN==NaN 返回false

    isNaN现在不讲

    • isNaN(参数) 这个函数用来判断参数是否为非数字,如果是非数字的数据返回true,是数字的话返回false

    • isNaN(参数) 参数要先通过Number()进行数据转换再判断

字符串

  • 在js中使用引号包围的文本就是字符串,引号可以是双引号也可以是单引号,反引号

    因为在html中的标签属性值使用的是双引号,所以我们推荐在js中的字符串使用单引号

  • 字符串嵌套的原则是:外单内双,或外双内单

  • 字符串是一段文本内容,是一个字符一个字符链接起来的内容

    当引号包围的是纯数字是,也不是数值类型,而是字符串类型 ‘123’ 是1,2,3三个字符的链接在一起

  • 在字符串里空格也是占位的 ”hello word“ 11个字符的链接

    var a='123',b=123;
    console.log(a,b) 不同的数据类型在控制台打印出来的颜色是不同的
    
  • 字符串转义字符

    在字符串中有些字符是没有办法直接使用的,需要使用转义字符,转义字符是以\开头的字符

    \\ 在衣字符串内输入\前要使用转义字符转义一下
    \n 在字符串中换行  n是值newline
    
    \t tab缩进
    \b 空格 blank
    
  • 字符串拼接

    字符串拼接使用+

    字符串与任何类型的数据拼接时,都是将其他类型的数据先转为字符串,再进行拼接,返回一个拼接后的新字符串

    console.log('你好'+'小明')  // 你好小明
    console.log('你好'+12) 			//你好12
    console.log('你好'+true)		//你好true
    console.log('100'+100)	   //100100
    console.log(100+100)			//200
    console.log(100+true);     *//101
    console.log(100+false);     *//100
    console.log(100+null) //100
    console.log(100+undefinde) //NaN
    console.log(10+10+'20');  //2020
    console.log('10'+10+20);	//101020
    console.log(10+''); //10加上一个空的字符串 返回一个字符串 '10'
    console.log(1+2+'0'+2*3);//'306'
    

加号+,两端都是数值或者布尔值的时候则执行相加的操作

加号+,两端只要有一个是字符串就执行字符串拼接的操作

  • 字符串与变量拼接

    变量不能写在引号内

    var userName='小明';
    console.log('你好'+userName);
    console.log('你好'+userName+',你吃饭了吗');
    

    经常遇到字符串与变量拼接,因为变量可以动态改变里面的值

  • 字符串的长度。

    • 字符串的长度就是字符串里面字符的个数 包含空格

    • 使用字符串的length属性获取

    var userName='小明';
    console.log(userName.length); //2
    

布尔值

  • 布尔数据类型有两个值,true 和 false true为真在计算机存储是为1 false为假在计算机存储是为0

  • 布尔值在运算过程中的意义:主要用来做判断

    • a>b 如果得到true 表示这个表达式成立 真的

    • a>b 如果得到false 表示这个表达式不成立 假的

布尔值有字符串相加,进行字符串的拼接

console.log(true+"你好") //true你好

布尔值和数值进行运算时,true为1,false为0

console.log(true+1) //1
console.log(false+1) //0
console.log(false-1) //-1
console.log(true-1) //0

Undefined

undefined表示未定义

变量被声明但是没有被赋值,变量会有一个默认值就是undefined,表示未知数据类型即未定义类型

即这里本该有一个值,但是没有,就是undefined

undefined与字符串相加,任何类型的数据与字符串相加都是执行字符串拼接的操作

console.log(undefined+'你好') //undefined你好

undefined与数值相加最后结果是NaN。 undefined 转为数字时为NaN

console.log(undefined+11) //NaN

undefined与布尔值相加

console.log(undefined+true) //NaN

Null

null表示空值,即该处现在的值为空,即这里有一个空值

一个变量声明后赋值为null,表示变量里面存储的值是空的

var b=null;

null要赋值为null才能得到空值

console.log(true+null)  //1
console.log(2+null)  //2

null转为数字时为0

字面量

作用:字面量是用于表达一个固定值的表示法,又叫做常量

通俗的讲字面量就是所见即所得,当我们看到字面的时候就会知道他是什么类型的数据,值是多少

比如说:数字字面量:1,2,3

​ 字符串字面量:“你好吗”

​ 布尔值字面量:true false

数据类型转换

js是弱类型的语言,变量没有数据类型的限制,可以随时赋任何类型的值。变量的类型只有在运行时才知道。

虽然变量的类型是不确定的,但是各种运算符对运算子的数据类型是有要求的,当运算符发现运算子的类型与预期的不符,就会自动转换运算子的类型。

"66"-"2"   减法运算符预期左右两侧的都是数字类型,发现不是,就会将他们自动转换为数值类型
  • 转换分为:强制转换 自动转换

    • 强制转换是使用Number(),String(),Boolean(),这三个函数将各种类型的值分别转为数值,字符串,布尔值
    • 自动转换,比如"123"+123数值与字符串相加,js是自动发生的数据转换,用户是不可见的

    常见的三种方式转化:

转为字符串类型
  • 你要转换的数据.toString() var userName=17; console.log(userName.toString())

    • undefined和null不能转换

      (在js中.表示的)

  • String(你要转换的数据) 强制转换 String(234)

    • 任何数据类型都可以转换
  • 使用加号拼接字符串。 (自动转换)“你好”+234

转为数值类型 (重点)
  • Number() Number()函数可以将任意类型的值转换为数值类型

    格式:Number(你要转换的数据); 返回转换好的数据

    特点:

    • 会把你要转换的内容当作一个整体来看待
    • 能转换成数字结果,就转换成数字结果
    • 不能转换成数字结果就返回NaN ,NaN也是数值类型,表示非数字
    Number(null);//0
    Number(undefined);//NaN
    Number(true);//1
    Number(false);//false
    Number(NaN);//NaN
    
  • paseInt(string)函数 解析一个字符串,返回一个整数

    (先看参数字符串的首个字符是否是数字,如果是数字就对该字符串进行解析,直到到达数字的末端为止然后以数值的形式返回数字)

    • 只有字符串的一个数字会被返回 parseInt('12dr5') //12
    • 如果字符串的第一个字符不能被转换为数值,则返回NaN parseInt('dr5') //NaN
    • 开头和结尾的空格是允许的 parseInt(' 88 99')

    (千)语法:parseInt(你要转换的数据),返回转换好的数据

    特点:把你要转换的任何内容一位一位的看,如果第一位不能转换为数字,那么直接返回NaN

    如果第一位可以转换为数字,就继续向后看第二位,第一位保留,以此类推直到一个不能转换成合法数字的位置为止

    案例:一个数字,只保留三位小数,就是从小数点后数三位,剩下的不要,使用paseInt()方法,能取整,首先将数字扩大1000倍,然后再取整,取整后,再缩小1000倍(不考虑四舍五入的情况)

    作业:

    // 把一个四位数字,拆成一位一位的,即通过计算得到4个变量
    var num=2345;
    // 方法1
    var a=parseInt(num/1000);//得到千位的数字
    var b=parseInt((num-a*1000)/100)//num-a*1000得到百位数再除以100后取整,得到百位的上数字
    var c=parseInt((num-a*1000-b*100)/10)//得到十位的上数字
    var d=parseInt((num-a*1000-b*100-c*10))//得到个位上的数字
    console.log(num,a,b,c,d);
    // 方法2
    var a2=parseInt(num/1000);//得到千位的数字
    var b2=parseInt(num%1000/100)//num和1000取余,剩下的就是不能整除1000的部分,再除以100,取整得到百位的数字
    var c2=parseInt(num%100/10)//得到十位的数字
    var d2=parseInt(num%10)//得到个位的数字
    console.log(num,a2,b2,c2,d2);
    
    给定一个随机秒数,换算成多少天多少小时多少分钟多少秒
    结果为xx天xx小时xx分钟xx秒
    
    var time=1234567;//要进行计算的总s数
    // 一天有多少秒 :24*60*60 一小时有60*60秒 一分钟有60秒
    // 得到天数
    var d=parseInt(time/(24*60*60));  //用变量除以一天的总秒数,然后取整,得到一共有多少天
    // 得到小时
    var h=parseInt(time%(24*60*60)/(60*60));//用取余的方式去掉整天的秒数后,在计算
    //得到分钟
    var s=parseInt(time%(60*60)/60);//用取余的方式去掉整天和整小时的秒数后小时后,再计算
    // 得到秒钟即得到不够一分钟的秒数
    var m=time%60;//直接取余60,得到不够一分钟的秒数
    console.log(time+"s为:"+d+"天"+h+"小时"+s+"分钟"+m+"秒")//1234567s为:14天6小时56分钟7秒
    
    
  • parseFloat(string)函数 解析一个字符串,返回一个浮点数

    ```parseInt(‘234.4’) //234`

    parseFloat('234.4') //234.4

    (千)和parseInt()的解析模式一样,只不过多认识一个小数点

  • 除了加法运算符有可能会将运算子转换为字符串,其他非加法的数学运算符都会把运算子自动转为数值

    减 乘 除 取余,和Number的解析规则一样将数据看作一个整体解析

    '5' - '2' // 3
    '5' * '2' // 10
    true - 1  // 0
    false - 1 // -1
    '1' - 1   // 0
    '5' * []    // 0
    false / '5' // 0
    'abc' - 1   // NaN
    null + 1 // 1
    undefined + 1 // NaN
    
    • 一元运算符也会把运算子转成数值(扩展??)

      取正负值 ,和Number的解析规则一样 (千)

      格式:+变量 -变量

    +'abc' // NaN
    -'abc' // NaN
    +true // 1
    -false // 0
    

注意:null转为数值时为0 undefined转为数值是为NaN

转为布尔类型

Boolean(参数 你要转换的数据) 函数可以将任意类型的值转为布尔值

它的转换规则是:

  • 代表空,否定的值会被转换为false ;undefined ,null, NaN, 0, “”(空字符串)

  • 其他的值都会被转换为true

  • true和false这两个布尔值不会发生变化

  • !取反 返回数据所对应的布尔值的取反后的布尔值

  • 双取反 返回数据所对应的布尔值

运算符

做算术运算时的符号

运算符的运算子个数不同分为:一元运算符 ,二元运算符,三元运算符

运算符的分类:赋值运算符,算术运算符,比较运算符,逻辑运算符,递增递减运算符,三元运算符

书写规范:除了一元运算符外,运算符左右两侧要有空格

一元运算符:一个运算符只有一个匀运算子就能运算

  • 取正运算符 +

    • 取负运算符 -
    • 取反运算符 !
    • 自增运算符 ++
    • 自减运算符 –

算术运算符

  • + — * / %(取余,返回除法的余数)

  • +运算符有两个含义 字符串拼接,数学运算

  • %取余的主要用途是判断一个数是否可以被整除,余数为0就是可以被整除

    被除数/除数=商…余数,得到不能被整除的部分

    3%10 得到3 ;10%3 得到1

  • ** 取幂 a**b就是a的b次方

  • 浮点数在运算里会有精确度的问题,这是因为小数本身就很小了在准换为二进制时容易出现误差

console.log(0.1+0.2); //0.300000000004
0.1+0.2==0.3; //false

不要判断两个浮点数是否相等

算术运算的顺序,先乘除后加减,如果有括号先算括号里的

自增和自减运算符

  • 自增自减运算符只有一个运算子是一元运算符
  • 自增自减运算符必须和变量配和使用
  • 自增自减运算符是仅有的可以改变变量值的运算符,其他的运算符都不会改变变量的值
自增运算符

之前我们想要给一个变量加1时要

var a=1;
a=a+1;

有了自增运算符我们可以 ++a或a++。最终a都是2 他们等同于a=a+1;

自增运算符放在变量的前面叫做前置自增运算符(++变量),放在变量的后面叫做后置自增运算符(变量++)

前置自增和后置自增单独运行时的结果是一样的,但前置或后置自增运算符和其他变量或数字一起参与运算时所起的效果就不同了

var a=1;
console.log(++a + 2); //4 a为2
console.log(a++ + 2); //3 a为2

前置自增是,变量先自增返回自增后的变量再参与运算(先己后人)

后置自增是,变量先返回原值参与运算,再自增(先人后己)

练习

var a=10,b=20;
console.log(++a + b++);
console.log(a++ + ++b);


var n=5;
var res=n++ + ++n + ++n +n++
console.log(res);//28 n此时为9
.....
....多练

推荐在开发时使用后置自增(自减),并且代码独占一行。a++或1–

比较运算符

是两个数据进行比较时所用的运算符,比较后会返回一个布尔值(true或false)作为结果

小于< 左边小于右边,返回true,左边不小于右边返回false

console.log(10<20);
console.log(10<10);
console.log(10<5);

大于>;<=;>=;(等于);!=(不等于);=(全等);!==(不全等);

  • == 等于会转换数据类型,将其他数据类型转换为数值 ,只要求值相等,不管数据类型是否一样

    true==1 //true
    '2'==1 //false
    '1'==1 //true
    ''==0 //true
    null==undefined //true
    null===undefined //false
    NaN==NaN //false
    NaN===NaN //false
    
  • === 全等要求数值和数据类型都一致

  • !== 不全等,比较两边的数据是不是不等,值和数据类型都要比较,当数据类型不一样的时候,返回false

    console.log(1!=='1'); //false

  • = 赋值运算符,将右边的值赋值给左边

逻辑运算符 或且非

逻辑运算符对数据进行逻辑运算

(阮一峰:

布尔运算符用于将表达式转为布尔值,一共包含四个运算符。

  • 取反运算符:!
  • 且运算符:&&
  • 或运算符:||
  • 三元运算符:?:

取反运算符
  • 符号是! (非),用来取一个布尔值的相反值
!true //false
!false //true
  • 对于非布尔值,取反运算符会将其转为布尔值取反

  • 将表示否定或空的值取反操作后为true

​ undefined

​ null

​ false

​ 0

​ NaN

​ “” 空字符串

  • 其他的值取反后都为false

  • !! 两个取反操作等于将其转为对应的布尔值

且运算符

&& 简称与,和的意思

语法:表达式1 && 表达式2

  • 且运算符在左右两侧都是布尔值时的规则:

    两侧都为true才返回true,否则返回false;即只要有一侧是false则返回false

    true && true
    false && false
    true && false==false //true
    3>2 && 2<4 //true
    3<2 && 2<4 //false
    
    
  • 左右两侧不是直接的布尔值,的规则:(数字,字符串,表达式参与运算)

    如果第一个运算子的布尔值是true则返回直接返回第二个运算子的值(注意是值,不是隐性转换后的布尔值)

    (如果第一个表达式的值为真,则返回表达式2)

    如果第一个运算子的布尔值是false则直接返回第一个运算子的值(不是隐性转换后的布尔值),且不在对第

    二个运算子求值(短路)

    (如果第一个表达式的值为假,则返回表达式1)

    3-1 && '2' //'2'
    3-3 && '2' //0
    
    var x=1;
    3-3 && x++  //0 此时X为1
    

    上面代码的最后一个例子,由于且运算符的第一个运算子的布尔值为false,则直接返回它的值0,而不再对第二个运算子求值,所以变量x的值没变。

    这种跳过第二个运算子的机制被称为段路,即只通过第一个运算子的值,控制是否运行第二个表达式的机制就是短路

    短路:当有多个表达式(值),左边表达式的值可以确定结果时,不在继续运算右边表达式的值了(bz)

    且运算符可以多个连用,这时返回第一个布尔值为false的表达式的值。如果所有表达式的布尔值都为true,则返回最后一个表达式的值。(如果有空值或表示为否定的值,返回第一个表示 空值或表示为否定的值。没有的话则返回最后一个表达式的值)

    true && 'foo' && '' && 4 && 'foo' && true
    // ''
    
    1 && 2 && 3
    // 3
    

    上面代码中,例一里面,第一个布尔值为false的表达式为第三个表达式,所以得到一个空字符串。例二里面,所有表达式的布尔值都是true,所以返回最后一个表达式的值3

或运算符

符号为:||

  • 或运算符在左右两侧都是布尔值时的规则:

    两边都是false时才返回false,即只要有一个为true则返回true

  • 左右两侧不是直接的布尔值,的规则:

    • 如果第一个运算子的布尔值为true,则返回第一个运算子的值,且不在对第二个运算子求值(短路)
    var a=10;
    3-2 || a++  //1   a此时为10 ,第二个运算子没有求值 造成短路了
    3-2 || (a=a*100) //1 a此时为10
    

    如果第一个运算子的布尔值为false,则返回第二个运算子的值(运算后的值)

    var a=10;
    3-3 || a*10 //100
    

    扩展:不讲

    或运算符可以多个连用,这时返回第一个布尔值为true的表达式的值。如果所有表达式都为false,则返回最后一个表达式的值。

    false || 0 || '' || 4 || 'foo' || true
    // 4
    
    false || 0 || ''
    // ''
    

赋值运算符

  • = 直接将右边的值赋值给左边

  • += -= 变量自身加减一个数后再赋值给自身,在自身的基础上进行叠加,是加法和赋值的合作

  • *= /= %= 变量乘 ,除,取余 后再赋值给自身

    var a=10;
    		a += 10;   等同于 a=a+10;
    		a -= 10; 	 等同于 a=a-10;
    		a *= 10; 	 等同于 a=a*10;
        a+="10";         a=a+"10";
    

运算符优先级

优先级由高到低:

.点运算符 []方括号运算符 new
()小括号

 一元运算符   ++  --  !  一元运算符里的逻辑非优先级很高

算术运算符  先* / %后+ -   (* / %的优先级是一样的,从左向右计算)

关系运算符  > >= < <= 

相等运算符  == != === !==

逻辑运算符  先&& 后||   逻辑与比逻辑或优先级高

三元运算符

赋值运算符 = += -= *= /= %=

逗号运算赋 ,

var value="3";
var index=3;
var flag=value==index?1:2;
console.log(flag);   //1

多练习,找练习题

流程控制

流程控制就是控制我们所写的代码按照什么的结构顺序执行,代码执行的顺序对最后返回的结果起着很关键的作用

流程控制分为3类:

顺序结构

​ 就是按照代码书写的先后顺序依次执行

分支结构:

是根据不同的条件,代码按不同的路径执行,返回相对应的结果

if语句

​ (1)if语句的格式 书写规范,()两端要有空格

if (条件表达式) {
	当条件表达式成立时所执行的代码
}

if语句是当条件成立(条件表达式为true)时执行大括号内的代码,条件不成立(false)则不执行{}里的语句

案例:输入成绩,根据不同的成绩,弹出不同的提示框

​ (2)if else语句,即双份支语句

if(条件表达式){
	当条件表达式成立时所执行的代码1
}else{
	当条件表达式不成立时所执行的代码2
}

if else语句是当条件成立时执行代码1,否则执行else后面大括号内的代码2

代码1和代码2必然且只能有一个被执行,即2选一

案例

​ (3) if else if 多分支语句

多分支语句是利用多个不同的条件选择不同的路径执行,最后返回不同的结果,是多选一,最多只能执行一个

格式

if(条件表达式1){
	代码1
}else if(条件表达式2){
	代码2
}else if(条件表达式3){
	代码3
}else{
 上面条件都不成立时执行代码4
}

执行路径是:若条件1成立则执行代码1,后离开该语句,后面的就不执行了

若条件1不成立,条件2成立,则执行代码2,后离开该语句

若条件1不成立,条件2也不成立,条件3成立,则执行代码3,后离开该语句

若条件1,条件2,条件3都不成立,则执行代码4,后离开该语句

即从第一个条件开始,哪一个条件满足,就执行哪一个{}里面的代码,前面的满足了,后面的就不在执行了,所有条件都不满足时则执行else里的代码,若干个{}必然会有一个执行

注意:

​ else if可以有任意多个

​ else if之间有空格,else后面不能有括号

​ if else if多分支语句的本质是多选一,只能有一条语句被执行

案例:

// 判断一个数是奇数还是偶数,数字%2取余,偶数返回值为0,即为false,返回值不为0,则为奇数即为true
var n=2;
if(n%2){
  console.log('奇数')
  }else{
  console.log('偶数')
}
//判断一个数字是不是在某个范围
// 判断n是不是在15和20之间  15 <= n <=20
//先比较15<=n 返回结果要么是true,要么是false,再和20比较,一定是小于20的,即返回true
var n=17;
if(15 <= n <=20){
  console.log('这样写总是会被执行')
}
// 正确写法是
if(15<=n&&n<=20){
	console.log('这是逻辑运算符,做了一个交集判断,两个两件必须同时满足')
}
判断闰年的条件
普通闰年:公历年份是4的倍数,且不是100的倍数的,为闰年(如2004年、2020年等就是闰年)。
世纪闰年:公历年份是整百数的,必须是400的倍数才是闰年(如1900年不是闰年,2000年是闰年)。
判断条件:年份可以被4整除且整百年份不能被100整除或者整百年份可以被400整除
条件1%&&条件2||条件3
if(year%4===0 && year%100!==0 || year%400===0){
	console.log('我是闰年')
}

(4) 三元表达式

格式:条件?表达式1:表达式2;

三元表达式就是一个简化了的if else语句

执行路径:条件成立时执行表达式1,条件不成立执行表达式2. (表达式都是有返回值的)

var a=39;

var age=39;
var result=age>20?'你已经中年了':'你还年轻着呢';
switch语句 switch 开关 转换 case 选项

switch语句也是多分支语句,也是多选一

  • 格式:
switch (表达式) {   //在实际开发中我们常用变量作为表达式
	case value1:
		执行语句1;
		break;
   case value2:
    执行语句2;
    break;
   case value3:
    执行语句3;
    break;
    ....
   default:
    	所有的case值都不满足的时候执行最后的语句;
}
  • 执行路径:根据switch后表达式的返回值与case后的值相匹配,如果匹配上就执行该case后面的语句,遇见break则退出该switch语句,如果和所有case后的值都没有匹配上,就执行default后的语句

    在执行case后的执行语句后遇到break后会退出该switch语句,如果没有遇到break则会不管下一个case是不是满足就继续执行下一个case后的语句,直到遇到break为止,或者到switch结束

    • break的穿透,是从第一个满足条件的case开始进行穿透,直到遇到break为止,或者到switch结束

      var n=7;
      switch(n){
        case 1:
        case 3:
        case 5:
        case 7:
        case 8:
        case 10:
        case 12:
          console.log('31天');
          break;
        case 4:
        case 6:
        case 9:
        case 11:
          console.log('30天');
          break;
        case 2:
          console.log('28天');
          break;
      }
      
  • switch语句的表达式匹配的是case后面特定的值,且必须是全等才能匹配上,switch语句,只能判断准确的某一个字面量值,不能判断范围

  • default可以写也可以不写,作用类似于if语句里的else,当所有条件都不成立的时候,执行default

  • switch语句与if语句的区别,使用时如何选择(用自己的语言再总结一下)

    • 一般情况下他两是可以相互替换的
    • switch语句通常处理case值比较明确的情况,if else语句更加灵活,常用于范围判断
    • switch语句是先进行条件判断后直接执行到匹配的程序语句里去。效率更高,if else语句会依次判断所有的条件,直到条件成立,效率低
    • 当分支比较少时,if else语句的执行效率比switch语句高
    • 当分支比较多时,switch语句的执行效率比较高,而且结构更清晰
循环结构

分支:根据条件来决定是不是执行某一段代码

循环:根据条件来决定一段代码重复执行几次

  • 循环成立的条件:
    • 初始值,作为循环的开始
    • 条件判断:决定要不要继续循环
    • 重复执行的代码
    • 改变初始值,为了让循环结束
while循环

变量初始化就是用var 声明一个变量,这个变量通常用作于计数器

条件表达式就是用来判断循环是继续执行还是终止的条件

操作表达式 就是每一次循环最后执行的代码,经常是对计数器进行更新操作(递增或者递减)

循环体 被重复执行的代码就是循环体

格式: while…时候

var n=1;//声明一个变量,这个变量通常用作于计数器
while(条件表达式){
	循环体
	n=n+1;
}

wile循环是在当条件成立的前提下执行循环体的代码,直到条件表达式是不成立时结束循环

循环执行的是{}里的语句

注意:while循环依然要有初始化变量(计数器)和操作表达式(更新计数器)

var num=1;
while(num<=100){
	console.log(num)
	num++
}
如果没有更新变量,会造成死循环

案例:

计算1至100的和

var i=1;
var sum=0;
while(i<=100){
 sum += i;
 i++
}
console.log(sum)
// 案例
// 6的阶乘 6*5*4*3*2*1
// 第一步 准备一个变量存错阶乘的结果
var and=1;
// 循环的初试变量
var n=6;
while (n>=1) {
  // 重复执行的循环体
  // and=and*n; 把每一个数字乘到and上
  and *= n;
  // 更新变量
  n--;
}
console.log('6的阶乘的结果为:',and);
用户输入人数及每一一个人的成绩
var sduentNum=prompt("请输入学生成绩");//学生人数
var sum=0; //成绩总和
var i=1;

while(i<=sduentNum){
	sum += prompt("请输入学生"+i+的成绩);
	i++;
}
console.log("学生的平均成绩为"+sum/sduentNum);
do while循环

格式:

do{
	循环体
}while(条件表达式)

do while循环是不管条件是否成立先执行一次循环体的代码再判断条件表达式真假,如果为真则继续下一次循环,直到条件为假时结束循环

var n=10;
do{
	console.log('我执行了');
	n++;
}while(n<10)
//此只会执行一次

do while是while循环的变体。do while循环至少执行一次循环体的代码

do while 循环依然要有初始化变量(计数器)和操作表达式(更新计数器)

案例:计算1至100的和

var i=1,sum=0;
do{
 sum += i;
 i++;
}while(i<=100)
cosole.log(sum)
for循环

for循环,应用最多,是循环的语法糖,用起来方便,写起来方便,看起来不方便

  • 作用:可以重复执行一段代码,通常和计数有关

格式: 书写规范:()左右要有空格,后面的闭合花括号要于for对齐

for (变量初始化;条件表达式;操作表达式) {//变量初始化;条件表达式;操作表达式这些所要用到的一次写完
  循环体
}
//标准语法
var i=0;
for(;i<10;){
	重复执行的代码
	i++
}

变量初始化就是用var 声明一个变量,这个变量通常用作于计数器

条件表达式就是用来判断循环是继续执行还是终止的条件

操作表达式 就是每一次循环最后执行的代码,经常是对计数器进行更新操作(递增或者递减)

循环体 被重复执行的代码就是循环体

案例:打印多次。。

案例:打印100以内所有3的倍数

​ 通过循环找到100以内所有的数字,判断每一个数字,如果是3的倍数,打印,不是则不打印

for(var i=1;i<=100;i++){
	if(!(i%3)){
		console.log('我是3的倍数:',i)
	}
}

案例1:求1至100的和。

var sum=0;  存储和的变量,和的初始值是0
for (var i=1; i<=100; i++) {
	// sum=sum+i;
  sum += i;
}
cosole.log(sum)

案例2:求1至100的偶数和 奇数和

​ 求100以内所有能被3整除的数字的和pa

案例:

/* 

水仙花数字
三次自幂数(取值范围100~999)
即一个三位数字的每一位数的三次方之和,如果和这个数字一样就是三次自幂数
例子:153  即1*1*1+5*5*5+3*3*3即1+125+27=153


*/
for(var i=100;i<=999;i++){
    // 拆数字
    var a=parseInt(i/100);
    var b=parseInt(i%100/10);
    var c=i%10;
    if(a**3+b**3+c**3===i){
        console.log(i+'是三次自幂数')
    }
}
/* 
153是三次自幂数
370是三次自幂数
371是三次自幂数
407是三次自幂数 
*/

嵌套的for循环 ,双for循环

双层for循环,外层每执行一次,内层for循环全部执行

//吃9个馒头,每个馒头三口吃完
for(var i=1;i<=9;i++){
    console.log("这是我吃的第"+i+"馒头")
    for(var j=1;j<=3;j++){
        console.log('    第'+i+"个包子我吃的第"+j+"口");
    }
    console.log('老板再来一个');
}
打印正方形的星,即每一行星的个数都以一样

// 打印三角型,第一行一个*,第二行两个***,第三行三个*...以此类
// 外层循环控制行数,内层循环控制每一行有多少个*
for(var i=1;i<=9;i++){
    for(var j=1;j<=i;j++){
        document.write("* ");
    }
    //输出一行的*后另起一行
    document.write('<br>')
}
在页面中打印和在控制台打印

打印9*9乘法表
var str='';
for(var i=1;i<=9;i++){
    for(var j=1;j<=i;j++){
    str=str+"*";//打印星
    str+= i+"X"+j+"="+i*j;  //99乘法表
    
        document.write("* ");
        
    }
    //输出一行的*后另起一行
    document.write('<br>')
    str+="\n";//换行
}
//打印梯型
// 打印三角型,第一行一个*,第二行两个***,第三行三个*...以此类
// 外层循环控制行数,内层循环控制每一行有多少个*
for(var i=1;i<=9;i++){
    for(var j=1;j<=i;j++){
        // 打印梯型 前三行的不输出*,直接退出本次循环,继续下一次循环
        if(i<=3) continue;
        document.write("* ");
    }
    //输出一行的*后另起一行
    document.write('<br>')
}



// 打印倒三角型,第一行9个*,第二行8个***,第三行7个*...以此类
// 外层循环控制行数,内层循环控制每一行有多少个*
for(var i=1;i<=9;i++){
   //从1到9是9个,从2到9是8个,从3到9是7个...
    for(var j=i;j<=9;j++){
        document.write("* ");
    }
    //输出一行的*后另起一行
    document.write('<br>')
/}
9gfor(var i=1;i<=9;i++){
//第一行有9个空格一个*,第二行有8个空格2个*,第三行有7个空格3个*....
    for(var j=i;j<=9;j++){
        document.write("&nbsp;");
    }
    for(var k=1;k<=i;k++){
        document.write("* ");
    }
    //输出一行的*后另起一行
    document.write('<br>')
}
循环控制语句

continue break,使用在循环里的关键字

  • Continue

在执行循环时遇到continue(继续),continue后的代码不在执行,直接就退出本次循环,直接跳到i++的位置,继续执行剩余次数的循环

  • break

在执行循环时遇到break,直接结束当前循环,break后的代码不再执行,也不在继续以后的循环

  • 标记语法

    自己命名一个标记,可以控制遇见break时跳到哪里,直接在循环开始的地方做一个标记格式是 名字:

​ 当准备跳出的时候语法为 break 名字;直接跳转到标记代表的循环结束位置

here:
for(var i=1;i<=9;i++){
  
    for(var j=1;j<i;j++){
        document.write("&nbsp;");
    }
   
    for(var j=i;j<=9;j++){
        document.write("* ");
       if(i=2) break here;
    }
  
    //输出一行的*后另起一行
    document.write('<br>')
}

数组

数组是js的一种数据类型,也是复杂数据类型 Array ,一个盒子,存储一堆数据,不是按照键值对存储的,是按照索引存储的(序号),基本数据类型都是单一的值,值和值之间没有没有任何联系,使用基本数据类型,我们创建的变量都是独立的,不能成为一个整体

数组:数组是按次序排列的一组值,每个值都有序号(从0开始的),整个数组用[]表示

[1,2,3,"55",true]

[]是数组的标致,里面的每个值是数组的元素,数组的元素可以是任意类型的值,数组中可以放另外的数组

  • 创建数组的方式

    var arr3=[1,3,'你安静点'];  //数组可以在创建时直接添加一些数组元素
    var arr2=[]; //用数组字面的方式创建一个空的数组,并赋值给arr2,也先定义后赋值
    
    数组里的数值一定要用逗号隔开
    
    //使用内置构造函数创建数组,js给我们提供了一个内置的构造函数Array
    var arr=new Array(); 创建一个空数组并赋值给变量arr
    
    	不传递参数的时候:创建一个空数组
    	传递一个正值,这个正值就是数组的长度
    	传递两个及两个以上参数的时候,传递进去的参数则是数组的内的元素
    

    (扩展不讲:数组不能使用点语法来操作,因为数组的下标是数字,点语法不能直接后接数字)

  • 数组的索引(下标)就是用来访问数组元素的序号

    数组的索引是从0开始的 0 1 2 3 4

  • 获取数组元素:格式 (多案例,比如循环遍历出数组中的元素,比较大小)

数组名[index] ;// index是数组的索引

console.log(arr3[index])

案例:

使用for循环遍历一个数组,遍历就是依次访问数组里的每一个数据
var Arr=['a','b','c','d','f'];

for(var i=0;i<Arr.length;i++){
    //i是for 循环的计数器,可以作为数组的索引,Arr[i]则是对应索引位置上的数据
    console.log(Arr[i])
}
  • 数组的长度,即数组中元素的个数

    数组的长度可以通数组的length属性获取,数组的length属性是一个可读写的属性

    读取格式

数组名.length ;		最终会返回数组中元素的个数

可以通过length属性来修改数组的长度

newArr.length=数值; 
//数值大于原数组的长度可以给数组扩容,多出来的就用空位补齐,读取空位时返回undefined
//数值小于原数组的长度,会原数组中多出来的元素删除
//清空一个数组就可以使用:  数组.length=0;
  • 替换数组中的元素,新增数组中元素

    索引也是一个可以读写的属性:

    • 读:读到指定索引位置的数据如果有就返回,没有就返回undefined
    • 写:
var newArr=[1,3,6,8];
newArr[1]=2; //指定的索引位置有数据,就是替换原数据
console.log(newArr[1]); //2  
newArr[5]=9;  //没有这个索引位置,就是新增数组中的元素,这个数字超出了数组的原长度,那么中间位置的空位补齐
console.log(newArr); //[1, 2, 6, 8, empty, 9]

数组循环组合多案例,筛选数组中元素,颠倒数组中的元素等。。。

案例:

求数组中元素的和及平均值
// 将数组中的每一个数据获取出来求和 
//和除以数据的个数得到平均值
var num=[1,3,4,7,8,9,6,5,2];
var add=0;
var average;
for(var i=0;i<num.length;i++){
//   add=add+num[i];
add+=num[i];
}
average=add/num.length;
// 求数组中的最大值
var num=[1,3,4,7,8,9,6,5,2,24];
// 假设元素中的第一位是最大值赋值给一个变量max,然后依次和数组中每一个数组元素相比较
//如果元素大于这个最大值,则就用这个较大的值更新max
var max=num[0];
for(var i=1;i<num.length;i++){
    if (num[i]>max) {
        max=num[i];
    }
}
console.log('数组中的最大值为',max)
将数组转换为字符串,并使用符号-分隔
var num=[null,'66',4,'undefined',8,9,6,5,2,24];
var str='';//定义一个新的变量来存储拼接后的字符串
for(var i=0;i<num.length;i++){
   
    // str += num[i]; //str为字符串,所以加号执行的是字符串拼接操作
    str += num[i]+'-';//拼接的每一个数据之间使用-分隔
}
console.log('11111',str)
创建一个数组,里面存放1-100的整数

var num=[];
for(var i=0;i<100;i++){
    num[i]=i+1;
}
console.log("数组为",num)
//筛选数组中的数据,将小余10的元素选出来放入一个新的数组
var arr=[12,4,5,3,66,3,232,22,1]
var newArr=[];//声明一个新的数组,存放选出来的元素
// var index=0;
for(var i=0;i<arr.length;i++){
    if(arr[i]<10){
        // newArr[index]=arr[i];
        // index++;
        newArr[newArr.length]=arr[i]
    }
}
console.log('>>',newArr)
颠倒数组中元素的顺序
var arr=[12,4,5,3,66,3,232,22,1]
var newArr=[];
for(var i=arr.length-1;i>=0;i--){
    newArr[newArr.length]=arr[i]
}
console.log('>>',newArr)

案例:冒泡排序,即给乱序的数组排列顺序,从小到大排列

目的不是为了上班用的,而是为了锻炼逻辑思维

口诀:双层for循环,一层减一次,里层减外层,变量相交换

前提知识:学会交换数组里的两个数据的位置, 遍历数组

var arr=[12,500,4,5,2,66,3,232,22,1]
for(var j=0;j<arr.length-1;j++){ //外部循环管理趟数
    for(var i=0;i<arr.length-1-j;i++){ //内部循环管 每一趟交换多少次
    //交换元素的值,让前一个值和后一个值做比较,如果前一个值比后一个值大,就交换一下,执行一遍以后,最大的数字一定在最后
        if(arr[i]>arr[i+1]) {
            var middle=arr[i];
            arr[i]=arr[i+1];
            arr[i+1]=middle;
        }
    }
}
console.log('>>',arr)

扩展不讲:数组的本质也是一个对象,还可当做对象使用,使用对象的.语法存储数据。。

  • 算法:就是观察执行过程,找到规则,转为代码

函数

  • 函数封装了一段可以被重复执行的代码,使代码可以重复使用,大大简化了代码的结构

    在js里函数就像是一个箱子,用来承载一段代码,当你要执行这段代码的时候只要呼唤这个箱子就可以了

    步骤:1:把代码装箱子里 2:使用箱子里的代码

定义函数

  • 声明式函数

    使用function关键字 声明函数,function要全部小写

    function 函数名() {  //空格必须要有分隔关键字和函数名
     	//函数体
    
    函数名要遵循变量的命名规则和命名规范
    函数是执行某些操作,函数名最好使用可以表示动作的名字 getName。。。
    

    这种方式声明的函数有名字,所以这样的函数是命名函数

  • 赋值式函数

    var 变量名= function() {
    	函数体
    }
    

    这是将一个匿名函数赋值给一个变量。这个匿名函数又被称为函数表达式,因为赋值符号(=)右边只能放表达式

  • Function 构造函数(讲不好就不讲,知道即可)

  • 函数的重复声明

    如果同一个函数被多次声明,后面的声明就会覆盖前面的声明

    function fn(){
        console.log(1)
    }
    fn(); // 2
    function fn(){
        console.log(2)
    }
    fn(); //2
    

    由于函数名的提升,前一次的声明在任何时候都是无效的

调用函数

函数在声明后不调用是不会自己执行的,需要调用才可以执行;

定义函数的方式不一样,但是调用函数的方式是一样的,使用圆括号运算符

函数名();

**注意:**函数名 和 函数名() 是不一样的,单写一个函数名是一个变量,表示这个函数(函数对象),函数名()是要调用执行这个函数,会得到函数调用后的返回值

  • 使用函数封装代码的优点:可以一次书写,多次使用,代码简介

  • 两种声明函数的方式,调用函数的方法一样,但是调用的时机不一样

    • 声明式函数,可以在声明之前调用,也可以在声明之后调用
    • 赋值式函数,只能在声明后调用,声明之前不能调用否则会报错

参数

函数内某些值不能固定,我们可以在声明函数时使用参数来代表这些不能确定的值,在调用函数时通过参数传入不同的值进去。可以使函数通过参数来执行不同的代码

function 函数名(参数1,参数2...) {
	函数体
}
  • 函数可以有多个参数,中间使用,分隔

  • 函数的参数分为两种

    • 在函数声明时定义的参数是函数的形参(形式上的参数)

      • 形参像是一个只能在函数内部使用的不用声明的变量,用来接收实参
      • 形参的值由函数调用时传进来的实参来决定
      • 起名遵循变量的命名规则和规范
    • 在函数调用时传入的参数是函数的实参(实际参数)

      • 实参就是一个准确的值,就是为了给函数的形参进行赋值的

      • 函数调用时实际参与执行的是实参

      • 实参可以是任意的数据类型,也可以是一个对象,当我们的参数过多时,可以讲参数封装到一份对象里面,然后通过对象传递

      • 实参也可以是一个函数,传入函数后可以调用

        function hi(){
         cosnole.log('hi');
        }
        function fun(a){
        	a();    //这等同于调用了hi函数,若是hi函数需要参数的话,也可以在这传参
        }
        fun(hi)
        

        案例展示

  • 函数的 参数不是必须的

  • 实惨本应该是和形参从左往右一一对应的,但是实际调用函数时无论我们传入多少个实参,程序都不会报错(案例展示)

    • 当传入的实参与形参一样多时,从左向右一一对应
    • 当实参个数大于形参个数即实参多 只取形参的个数

      前面的按照顺序一一对应,多出的来实参在函数内部没有形参接收,所以多出来的实参不能直接使用

    • 当实参小于形参即实参少

      前面的按照顺序一一对应,但是多出来的形参因为没有实参赋值会采用默认值undefined

      (js中省略的参数默认值undefined,结果为NaN)

      在调用函数时我们没办法只省略前面的参数而传入后面的参数。如果非要只省略前面的参数只能显示的传入undefined

      function f(a, b) {
        return a;
      }
    
      f( , 1) // 直接省略第一个参数会报错
      f(undefined, 1) // undefined
    
    @param {参数类型} 参数名 - 对参数的描述信息
    @return {函数返回值的类型} 函数返回值的描述
    作用:对函数的参数,返回值进行解释
    

函数的arguments(参数)

案例:使用函数计算任意个数字的和,即无论传入多少个实参求和,我们的形参就没办法提前预测会传入多少个实参了,所以有一个新的知识点 arguments

  • 在js函数中允许传入任意多个参数,那具体传入了多少参数,可以使用arguments对象来读取

  • arguments是函数的内置对象,包含了函数运行时所有的参数即实参

    (arguments是在函数内部天生自带的变量,表示所有实参的集合是一个伪数组)

  • 每个函数都有arguments对象

  • arguments是一个类似于数组的伪数组,他有数组的一些特性但他不是真正意义上的数组

    • 具有数组的length属性

    • 按照索引的方式存储数据. ”序号“从0开始,依次加1,这序号叫做下标

      • 我们可以按照遍历数组的方式遍历arguments

      • 我们可以依靠索引来操作arguments里面的某一个数据

        • 读:arguments[索引]; 表示获取对应索引位置上的数据

        • 写:arguments[索引]=你要设置的值 ; 表示把arguments里面对应索引位置的数据改变

          ​ 写入时:如果你写的索引是arguments里面没有的一个索引,那么就是添加,如果有这个索引,那么就是修改对应索引位置上的值

    • 数组特有的一些方法在arguments上不能使用(push,forEach等)·

function f() {
  return arguments.length;
}

f(1, 2, 3) // 3
f(1) // 1
f() // 0


function newf(){
	for(var i=0;i<arguments.length;i++){
		console.log(arguments[i]);
	}
}

newf(1,2,3);
newf(4,5,6,7);

return语句

引入案例:在一个函数内的计算结果打印到控制台或者alert出来,或者documen.write到页面上,我们要不断的修改代码,我们可以利用函数把结果返回到函数外面,在函外面怎么处理都可以,就不需要一次次的修改函数内的代码了

在函数内我们可以使用return返回特定的值

function 函数名(参数1,参数2...) {
  函数体
	return 返回值;
}
  • js在遇到return是就会返回return后面那个表达式的值并终止函数,即使后面还有其他代码也不再执行
  • return只能返回一个值,如果逗号隔开多个值,只能返回最后一个值。 return 表达式;
  • 如果想让返回多个值,可以利用数组
  • 谁调用就将返回值返回给谁。函数名()=返回值。常用变量来接收函数的返回结果 var a=函数名();
  • return语句不是函数必须的,但函数都是有返回值,如果有return则返回return后的表达式的值,如果没有return则返回undefined
  • 注意返回值如果是一个变量的话,返回的是这个变量所代表的值,而不是这个变量(在函数内定义的变量只能在函数内使用,外部获取不到)
function tt (){
  var ee=1;
  return ee; //return 作为返回值关键字来使用,返回的是retun 后的表达式
}
console.log(tt());
console.log(ee);
function tt (){
var ee=1;
return; //return进行打断函数的操作,return后面的语句不再执行了 此时测返回值是默认值undefined
console.log("6666");
}
console.log(tt());
  • return 返回值可以是任意类型的值,也可以是函数(闭包知识点)

    function fn3(){
    	//在函数内部再声明一个函数
    	function fn4(){
    		console.log(‘我是fn4’)
    	}
    	//将fn4函数作为返回值返回
    	return fn4;
    }
    var a=fn3();
    console.log(a);
    a();
    
    等同于
    fn3()();
    
    
  • break,continue(继续),return的区别

    break:结束当前循环体,不在执行后面的循环(主要针对循环。for循环,while循环…)

    continue:结束本次循环,继续执行下一次的循环 (主要针对循环。for循环,while循环…)

    return只能出现在函数中

​ 在函数内的循环中是退出循环,并且返回return语句中的值. (自己再操作确定一下)

​ 在函数中是退出当前函数并返回return语句中的值

打断循环用break,打断函数用return

  • 立即执行函数,函数定义完,立即被调用,这种函数就是立即执行函数(下有详细的)
  • 立即执行函数往往只会执行一次

函数内可以调用其他函数

第一等公民

JavaScript 语言将函数看作一种值,与其它值(数值、字符串、布尔值等等)地位相同。凡是可以使用值的地方,就能使用函数。比如,可以把函数赋值给变量和对象的属性,也可以当作参数传入其他函数,或者作为函数的结果返回。函数只是一个可以执行的值,此外并无特殊之处。

由于函数与其他数据类型地位平等,所以在 JavaScript 语言中又称函数为第一等公民。

作用域

作用域是指变量(变量名,函数名)存在的范围

es5中只有两个作用域:全局作用域和局部作用域

  • 全局作用域:整个script标签或者一个单独的js文件

    在全局作用域定义的变量就是全局变量,在全局内任何地方都可以使用

    全局作用域在页面打开时创建,在页面关闭时销毁

    在全局作用域中有一个全局对象window,它代表的是一个浏览器的窗口,它由浏览器创建,我们可以直接使用,在全局作用域中创建的变量都会作为window对象的属性保存,创建的函数都会作为windoe对象的方法保存

  • 局部作用域:函数内部就是局部作用域 又称为函数作用域(私有作用域,每一个函数就是一个私有作用域)

    • 在调用函数时创建函数作用域,函数执行完毕以后,函数作用域销毁,每调用一次函数就会创建一个新的函数作用域,他们之间是互相独立的

    es6中有新增了块级作用域,现在Es5先不涉及

function f(){
	//局部作用域
	var p=1; //局部变量,只能在函数内使用
	a=10; //全局变量 如果一个变量在局部作用域内没有声明直接赋值了,那这个变量则是全局变量
}
console.log(p)

在局部作用域(函数内)内定义的变量就是局部变量,只有函数内可以使用,外部不能读取,但在函数作用可以访问到函数作用域的变量

当函数内和全局有相同的变量时(作用域链),在函数作用域内要访问全局变量,可以使用window对象。 Window.变量

函数的形参也可以看作是在函数作用内声明了局部变量

特例:如果一个变量在局部作用域内没有声明直接赋值了,那这个变量则是全局变量

全局变量和局部变量的区别

  • 全局变量在全局都可使用,只有在浏览器关闭的时候才会销毁,比较占内存资源
  • 局部变量只能在函数内使用。当其所在代码块执行时会被初始化,当代码块执行完毕就会被销毁,比较节约内存资源

(不讲)块级作用域是es6新增的,块级作用域是{}. 比如For{} if{},在外面不能使用块级作用域内的变量

作用域的上下级,你的函数写在那个作用域下,你就是谁的子级作用域

作用域上下级的关系的作用是为了确定变量的使用范围

三个机制:

  • 变量定义机制

    • 有var关键字 var 变量
    • 声明式函数 function fn(){}

    一个变量(函数)定义在哪一个作用域,只能在当前的作用域或者下级作用域内使用,上一级作用域不能使用

  • 变量使用机制

    • 当需要使用以个变量(函数)会首先在自己作用内查找,如果有,就直接使用,如果没有去上一级作用域查找,如果有就直接使用,如果还没有就继续向上一级作用域找直到全局作用域都没有,那就会报错
  • 变量赋值机制

    • 一定要有赋值符号

    • 当你需要给一个变量(函数名)赋值的时候,会首先在自己的作用域查找,如果有直接赋值,如果没有去上一级作用域查找,有就赋值还没有就再去上一级作用域查找直到全局作用域都没有,就把这个变量定义为全局变量,再进行赋值

      function fn(){
          // var num=100;  //声明了私有变量num,只能在fn内使用,fn外部不能使用
          num=100; //不能声明,只是赋值
          console.log('****',num)
      }
      fn(); //这个函数执行的时候会定义一个全局变量num
      console.log(num)
      //最终顺序  100 100
      
      function fn(){
      
          var a
          function fun() {
              a=200;  //给fn的私有变量a赋值
          }
          console.log(a) //undefined
          fun() //这个函数执行完,才给fn私有a赋值
          console.log(a)
      }
      // console.log(a) //报错
      fn()
      // console.log(a) //报错
      // 最终的顺序为:报错  undefined  200 报错 
      

作用域链

如果函数内还有函数就在这外部函数的作用域内又生成一个作用域

内部函数可以访问外部函数中的变量

作用域链的案例展示:

<script>
	var a=7;
	
	function getNum() { //外部函数
		var a=8;
		function getNum2() { //内部函数 可访问外部函数里的变量
			console.log(a);
		}
		getNum2();
	}
	getNum(); //8

</script>

作用域链:在局部作用域获取一个变量,系统会先在当前作用域查找var声明语句,如果找到了就直接使用,找不到则继续向上一级作用域查找,直到找到全局作用域中(????给变量赋值的时候若还是没有找到则自动在全局作用域中声明该变量。)我们把这种链式的结构称为作用域链

案例:多案例练习

<script>
	var a=100;
	function fn(a){
	//形参就相当于在函数内部定义的私有变量
		console.log(a)
  }
  fn(); //输出undefined

</script>
<script>
 var a=100;
	function fn(a){
		a=200;
		console.log(a)
  }
  fn()
  console.log(a)
  //输出顺序为200 100
</script>
var a=100;
    function fn(a){
        console.log(a);
		a=200;
		console.log(a)
  }
  console.log(a) //100 只会输出以个100,因为fn函数没有调用
  
var a=100;
  fn();
    function fn(a){
        console.log(a);//unde
		a=200;
		console.log(a)//200
  }
  console.log(a) //100
  
  //输出顺序为undefined 200 100
  • 函数的作用域是在函数定义时就已经确定了

闭包

  • 闭包就是能够用读取其他函数内部变量的函数,由于在js中,只有函数内部的子函数才能读取内部变量,因此可以将闭包理解成定义在函数内部的函数

    function f1() {
      var n = 999;
      function f2() {
        console.log(n);
      }
      return f2;
    }
    
    var result = f1();
    result(); // 999
    
    
    //等同于
    f1()();
    //---------------------f2函数就是闭包函数与有没有return没有关系
    function f1() {
      var n = 999;
      function f2() {
        return n
      }
      window.fn3=f2; //将闭包函数f2赋值给了全局对象window,这样就可以在函数f1外部获取f1内的局部变量了
    }
    f1();//调用f1,目的是给windown.fn3赋值
    
    console.log(window.fn3())
    console.log(window)
    
  • 闭包的最大的特点就是它可以记住诞生环境,比如f2记住了它的诞生环境f1,所以可以从f2可以得到f1的内部变亮,在本质上,闭包就是将函数内部和函数外部链接起来的一座桥梁

  • 闭包的最大的两个用处有两个,一个是可以读取外层函数内部的变量,另一个就是让这些变量始终保存在内存中,即闭包可以使它的诞生环境一直存在

  • 闭包消耗内存很大,所以不要滥用闭包,否则会造成网页的性能问题,在ie浏览器中可能导致内存泄漏,解决方法就是,在退出函数前,将不使用的变量全部删除

  • 闭包会在父函数外部改变父函数内部的变量,所以如果你把父函数当做对象(object)使用,把闭包当做它的公用方法,把内部变量当做它的私有属性,这时一定会要小心,不要随便来改变父函数内部的变量的值

预解析

js代码是由浏览器的js解析器(引擎)解析执行的,js引擎在运行js代码是分为两步。分别是预解析和代码执行

1:预解析:

在当前作用域下,js代码执行之前,浏览器会把当前作用域内所有的var和function声明的变量提升到当前作用域的最前面(全局作用域,函数作用域),它会在所有代码执行之前就被创建(所以我们可以在函数声明前来调用函数,使用函数表达式创建的函数,不会被声明提前,所以不能在声明前调用)

  • 函数内部的变量也会发生变量提升

预解析分为:预解析又叫做变量,函数的提升

​ (1)变量预解析 将作用域内所有变量声明提升到当前作用域的最前面 ,变量的赋值不会提升

​ (2)函数预解析 将所有采用funchtion命令声明的整个函数提升到代码的最前面,但不会调用函数

​ 注意:匿名函数没有function声明,所以不会提升

var 变量名= function() {
函数体
}//这是声明一个变量,并将一个匿名函数(函数表达式)赋值给一个变量,不是函数声明。所以变量会提升,匿名函数不会提升

等同于

var 变量名;
变量名=function(){
 函数体
}
var f = function () {
  console.log('1');
}

function f() {
  console.log('2');
}

f() // 1


等同于
 var f;               //有变量的预解析,有声明式函数的预解析,问题是变量名和函数名重名了,当函数和变量重名
 function f() {       //时,在预解析阶段以函数为准
  console.log('2');
};
 f = function () {
  console.log('1');
}
f();//1
 
fn()
var fn=100
fn()
function fn(){
	console.log('我是 fn 函数')
}
fn()

等同于 
//预解析阶段
var fn;   //声明fn变量 
function fn(){       //声明fn变量,并赋值为一个函数,在预解析结束的时候,浏览器记录的fn变量是一个函数   
	console.log('我是 fn 函数')
}

//代码执行阶段
fn()   //调用fn,此时的fn为函数
fn=100  //给fn赋值为100
fn()  //调用fn,但此时的fn为100,报错,程序中断
fn()
fn()
function fn(){
	console.log('我是fn函数')
}
fn()
var fn=100
fn()


等同于
 预解析阶段
  function fn(){
    console.log('我是fn函数')
  }
  var fn;
  
  代码执行阶段
  fn() 
  fn() 
  fn=100
  fn()
预解析无节操
- if条件不管是不是成立,里面的代码都会进行预解析
- return 后面的代码虽然不执行,但是会进行预解析

案例1
console.log(num) //undefined
if(true){
	var num=100
}
console.log(num)  //100

案例2
console.log(num) //undefined
if(false){
	var num=100
}
console.log(num)  //undefined

案例3:
function fn(){
  var num=100
  console.log(num)   //100
  console.log(n)   //undefined
  return           // return 后面的代码虽然不执行,但是会进行预解析
  var n=200
}
fn()

经典案例:

f1();
console.log(c);
console.log(b);
console.log(a);
function f1() {
  var a = b = c = 9;
  console.log(a);
  console.log(b);
  console.log(c);
}

var a = b = c = 9;
相当于:
var a=9;b=9;c=9;b和c是直接赋值,没有声明,在函数内没有声明直接赋值当全局变量看

https://blog.csdn.net/weixin_43887184/article/details/105512242

2:代码执行:按照代码书写的顺序从上到下执行js代码

多案例练习

  • 对于解析的总结使用技巧
    • 函数名 不要和 变量重名
    • 声明式函数可以先调用,但是尽量不要先调用
    • 函数尽量使用赋值式函数来定
    • 建议变量名命名以名词为主,尽量可能使用多个单词命名
    • 函数名 以功能区分(使用动词) getUserName()

函数案例,翻转数组,for循环遍历数组中的元素倒序的放入一个新的数组中

function reverse(arr){
	var newArr=[];
	for(var i=arr.length-1;i>=0;i--){
		newArr[newArr.length]=arr[i];
	}
	return newArr;
}

this

解析器在调用函数每次都会向函数内部传递进一个隐含的参数,这隐含的参数就是this,它在函数体内部指代函数当前的运行环境

this指向的是一个对象,这个对象我们称为函数执行的上下文对象,根据函数的调用方式不同,this会指向不同的对象

this是一个使用在作用域内部的关键字,全局很少用,大部分都是在函数内部使用

this指向:

  • 在全局使用this指向window
  • 函数内使用,不管函数怎么定义,不管函数在哪定义,只看函数的调用(箭头函数除外)

- 以普通函数的形式调用时,this永远都是window(全局的函数就是window的方法)
- 以对象的方法的形式调用时,this就是调用方法的那个对象,点前面是谁就指向谁
	多层this嵌套的时候,内层的this指向window对象,解决方法可以使用中间变量固定this
- 当以构造函数的形式调用时,构造函数体内部的this关键字,代表所要生成的那个对象实例
- 定时器处理函数  this指向window
	setTimeout(function(){...},0) this指向window
	setInterval(function(){...},0) this指向window
-事件处理函数   this指向事件源,谁绑定的事件就指向谁 
	元素.onclick=function(){...}
	元素.addEventListener('click',function(){...});  
-自执行函数   this指向window
	(function(){})(); 
  • 函数的作用域在函数定义时就已经确定
var name="你好";
function fn() {
    console.log('name'+name);
}
var obj1={
    name:'布布',
    age:1.10,
    fn:fn
};
var obj2={
    name:'麻麻',
    age:1.10,
    fn:fn
};
obj1.fn();  //name你好
obj2.fn();  //name你好
console.log('外面的',name);  //外面的 你好
var name="你好";
function fn() {
    console.log('name'+this.name);
}
var obj1={
    name:'布布',
    age:1.10,
    fn:fn
};
var obj2={
    name:'麻麻',
    age:1.10,
    fn:fn  //将fn存储的地址赋值给了obj的fn成员,现在obj.fn和全局便变量fn指向一个函数空间
};
fn();  
obj1.fn();  //name布布
obj2.fn();  //name妈妈
console.log('外面的',name);   //外面的 你好

--------
setTimeout(fn,0);  定时器处理函数 this指向window

setTimeout(obj1.fn,0);  定时器处理函数 this指向window

div.onclick=obj1.fn;     事件处理函数this指向事件源

div.addEventListener('click',obj1.fn);  事件处理函数this指向事件源

setTimeout(function(){
		fn()  //this->window
},0);

div.onclick=function(){
	console.log(this);    //this->事件源 div
	fn()                  //this->window
	function newFn(){
		console.log(this)
	};
	newFn();  //this-->window
}; 
//由于对象的属性是可以赋值给另一个对象的,所以属性所在对象是可变的,即this的指向是可变的
var A = {
  name: '张三',
  describe: function () {
    return '姓名:'+ this.name;
  }
};

var B = {
  name: '李四'
};
B.y=A.describe;
console.log(B)

console.log(B.y())  //"姓名:李四"  以对象的方法调用,this指向调用它的对象
var A = {
  name: '张三',
  describe: function () {
    return '姓名:'+ this.name;
  }
};

var name = '李四';
var f = A.describe;
f() // "姓名:李四"
 
 //this所在方法不在对象的第一层,这时this只是指向当前一层的对象,而不会继承更上面一层
 var a={
 	p:"hello",
 	b:{
 		m:function(){
 			console.log(this.p)
 		}
 	}
 };
 a.b.m() //undefined
 
 --------------------------------------------
 var b = {
            a: 23,
            c: 3,
            d: {
                a: 78,
                e: {
                    a: 100,
                    f: function() {
                        console.log(this.a);
                    }
                }
            }
        }
        var fn = b.d.e.f;
        fn(); //undefined
        b.d.e.f(); //100  
        
        
     var myObject = {
            foo: "bar",
            func: function() {
                var self = this;
                console.log(this.foo);
                console.log(self.foo);
            }
        };
        myObject.func(); 
        // bar bar
        var x = 3;
        var y = 4;
        var obj = {
            x: 1,
            y: 6,
            getY: function() {
                var y = 7;
                return this.y;
            }
        }
        console.log(obj.getY()); 
        //6
  function a(xx) {
            this.x = xx;
            return this;
        }
        var x = a(5);
        var y = a(6);
        console.log(x.x);
        console.log(y.x);
        // window.x = 5;
        // window.x = window
        // window.x = 6
        // window.y = window
        
        最终输出是:undefined 6
     var $ = {
            name:'haha',
            fn: function() {
                console.log(1);
                return this;
            },
            fn2: function() {
                console.log(2);
                console.log(this.name)

            }
        }
        $.fn().fn2();
        
        // 1 2 haha
        运算符的优先级 先成员访问再 再函数调用
var obj={
	foo:function(){
		console.log(this)
	}
}
obj.foo();//obj
(false||obj.foo)(); //window对象

避免多层this嵌套

多层this嵌套时内层的this指向window

var obj={
    f1:function(){
        console.log(this)
        var f2=function(){
            console.log(this)
        }();
    }
}
obj.f1(); //obj window

解决方法:
在内层使用一个变量固定外层的this,然后在内层使用变量,就不会发生this指向的改变了
var obj={
    f1:function(){
        console.log(this)
        var that=this;
        var f2=function(){
            console.log(that)
        }();
    }
}
obj.f1(); //obj obj
var name='mama'
function fn(){
    var name='bubu'
    console.log('111',this.name,name);                   // 111 mama bubu
    function fun(){
        console.log('22222',this.name,name);             //22222 mama bubu 内层this指向window
    }                       
    fun()
}
fn()
数组的map和foreach方法,允许提供一个函数作为参数,这个也是多层this嵌套

---------------------------
var arr=[1,3,6],name='mama';
arr.forEach(function(){
    console.log(this.name)  //mama   内层this指向window
})

---------------------------------
var obj={
    name:'sun',
}
var name='bubu'
var arr=[1,3,6]
arr.forEach(function(){
    console.log(this.name) //sun 通过第二个参数绑定参数函数的运行环境(this指向)
},obj)

----------------------
var obj={
    name:'sun',
    arr:[1,3,6],
    f:function(){
        this.arr.forEach(function(){
            console.log(this.name)
        })
    }
}
var name='bubu'
obj.f();// bubu  内层this指向window 
--------
解决方法1
var obj={
    name:'sun',
    arr:[1,3,6],
    f:function(){
        var that=this; //使用变量固定this
        this.arr.forEach(function(){
            console.log(that.name)
        })
    }
}
var name='bubu'
obj.f();
--------
解决方法2 
使用map,foreach的第二个参数指定参数函数的this指向

        var point = {
            x: 0,
            y: 0,
            moveTo: function(x, y) {
                this.x = x;
                console.log(this.x);
                console.log(this);

                var moveX = function(x) {
                    this.x = x;
                };
                var moveY = function(y) {
                    this.y = y;
                }
                moveX(x);
                moveY(y);
            }
        };
        point.moveTo(1, 1);//1 point 
        console.log(point.x);//1
        console.log(point.y);//0
        console.log(x); //1
        console.log(y); //1

绑定this的方法

this的不确定,js提供了三个绑定this的方法

  • call

函数实例的call方法,this指定函数内部的this指向(即函数执行时所在作用域),然后在作用域中立即调用函数

会立即调用函数,不适合用作定时器处理函数或者事件处理函数

两个作用:改变函数的this指向,调用函数

Call 方法的参数应该是一个对象,如果参数为空,unll,undefined ,则默认传入全局对象

var obj={
    name:'mama'
}
var name='bubu'
function fn(){
    console.log(this.name)
}
fn();                                 // bubu this 为window
fn.call(obj);                         // mama 绑定了this的指向为 obj,然后在对象obj的作用域内运行函数fn
fn.call();  //bubu
fn.call(unll);
fn.call(undefined);
fn.call(window);
  • call方法可以接收多个参数。第一个参数是this指向,后页的参数则是调用函数时所需的参数
var obj={
    name:'mama'
}
var name='bubu'
function fn(a,b){
    console.log(this.name,a+b)
}
fn(1,2); //this 为window
fn.call(obj,1,2);//绑定了this的指向为 obj  调用函数并给函数传参

-----
fn.apply(obj,[1,2]) // mama 3 
  • call方法可以使伪数组使用数组的方法

           function fn(){
                console.log(arguments);
                
                //使用一个空数组[]将foreach方法调用起来,然后再使用call方法的第一个参数来改变foreach的           			this指向为arguments
                //foreach方法遍历查看arguments
                //call的第二个参数是传给foreach的方法里的参数函数(回调函数)
                
                [].forEach.call(arguments,function(item){
    
                    console.log('call方法的第二个参数是数组的forEach方法的参数函数');
                    
                    console.log(item); //item为arguments的每一项
    
                })
            }
            
            fn(1,2,3,4,5,6);
    
  • applay方法

    使用方法:直接连接在函数名后面使用

    applay方法与call方法类似,也是改变this指向,然后在调用该函数唯一的区别是,它可以接收一个数组(伪数组)作为函数的参数,该数组的所有成员依次作为函数的参数,传入函数,而call方法给函数传参要一个一个的传入,applay给函数传参必须要以数组的形式传入

    第一个参数:就是函数内的this指向

    第二个参数:是一个数组或 伪数组,所有成员依次作为函数的参数,传入函数

    特点:会立即调用函数,不适合用作定时器处理函数或者事件处理函数

  • 作用:可以以数组的形式给某些功能函数传参

    在js中不提供找出数组中最大元素的函数,结合apply方法和Math.max方法可以,就可以返回数组中最大元素

      var arr=[1,2,3,33,24,56,3,4,6767,3323,36,-1]
      console.log(Math.max.apply(null,arr))
      console.log(Math.min.apply(null,arr))
    
  • bind()

    bind()方法用于将函数体内的this绑定到某个对象,并不调用函数,而是返回一个新函数,一个已经改变好this指向的函数

    作用:该变事件处理函数或者定时器处理函数的this指向

    var obj={
        name:'mama'
    }
    var name='bubu'
    function fn(a,b){
        console.log(this.name,a+b)
    }
    var sunFn=fn.bind(obj,1,2);   //将this绑定obj后,返回一个新的函数
    sunFn(); //调用函数
    
    
    
    ------
    <div>88<div>
    --js
    function fn(a,b){
    	console.log(this)
    	console.log(a,b)
    }
    var div.documen.querySelected('div');
    //其实一般不会事件处理函数的this指向,给事件处理函数使用bind的目的是给事件处理函数传参的,事件处理函数不接受传参,当我们想要给事件处理函数传递一些参数可以使用bind方法传参
    div.onclick=fn.bind(null,100,200);//这样内部的this指向window,内部不用this的话,不用考虑this,只为给事件处理函数传参
    
this指向

this的指向再函数定义的时候是确定不了的,只有函数执行的时候才能确定this的到底指向谁,一般情况下this的最终指向的是那个调用它的对象

  • 全局作用域或者普通函数中的this指向全局对象window,定时器里的面的this指向也是window,因为window.setTimeout() window省略了

    console.log(this)
    function(){
     console.log(this)
    }
    window.fn();  全局作用域定义的变量和方法。。。
    
  • 方法调用中谁调用指向谁

    var o={
      sayHi:function(){
       console.log(this); 
      }
    }
    o.sayHi();  //在对象o调用,this指向o对象
    
    btn.addEventListener('click',function(){
    	console.log(this);  //this指向的是btn这个按钮对象
    }) 
    
  • 构造函数中this指向构造函数的实例

    function Fun(){
    	console.log(this);  
    }
    var fun=new Fun();  //this指向的fun这个实例对象
    

立即执行函数

根据js语法:圆括号() 跟在函数名的后面,表示调用该函数,但我们需要在定义函数之后,立即调用函数,

function关键字既可以当做语句,也可以当做表达式

我们不能在声明式函数后加上圆括号,这样会报错

function fn(){}();//报错

当做表达式的时候,函数可以定义后直接加圆括号调用

var a=function f(){}()

为了避免解析的歧义,js规定如果function关键字出现在行首,一律解释成语句,因此,引擎看到行首的是function关键字之后,认为这一段都是函数的定义,不应以圆括号结尾,所以就报错

函数定义后立即调用该的解决

立即执行函数,函数调用的一种方式

立即执行函数不需要单独调用,立马能过自己执行的函数,也可以传递参数

立即执行函数最大的作用就是独立创建一个作用域,里面的所有变量都是局部变量 不会有命名的冲突,防止外界变量污染内部数据方法,就是不要让function出现在行首,让引擎将其理解成一份表达式,最简单的处理就是将其放在一个圆括号里面

通常情况下,只对匿名函数使用这种立即执行函数表达式,目的有两个:一是不必为函数命名,避免污染全局变量,二是 在立即执行函数内形成一个单独的作用域,可以封装一些外部无法读取的私有变量

格式:

(function(){})();
(function(){}());
//注意:语句后的分号是必须的,如果没有分号,遇到两个连个着的立即执行函数就会报错,上面两行之间若是没有分号,js会将他们连在一起解释,第二行解释为第一行的参数

//或
~function(){}();
!function(){}();


(function(a,b){console.log(a+b)})(1,2);
//----
(function(a,b){console.log(a+b)}(1,3)); 第二个小括号可以看作是调用函数
//---function(){
console.log('111')
}()
//----
!function(){
console.log('111')
}()

一般作用是在单独书写js文件的时候使用

为了保护变量不污染全局,每一个js文件里面初始化使用一个自执行函数包裹

将一些需要别的文件使用的变量挂在全局(window.a=11)

-------------------------- 一个需要单独引入的js文件
;(function(){--------------------每一行以(),[],''开头时要写分号
        var mama=100*100;
        //-----------------将一些需要别的文件使用的变量挂在全局
        window.bubu='加油';
        console.log(mama)
})()

对象

在现实生活中最;万物皆对象,是一个看得见摸得着的事物

在js中对象是一个容器,封装了属性和方法,属性是对象的状态特征,方法是对象的行为(完成某种任务),

所有的事物都是对象,如字符串,数组,函数等

对象是一种复合的数据类型,在对象中可以保存多个不同的数据类型的属性

(对象:一个盒子,承载一堆数据;函数,一个盒子,承载一段代码)

如何创建对象

使用字面量创建对象

对象用{}表示

var cat={}; //创建了一个空对象


function song(){
	console.log('i can song')
}

var dog={    //使用字面量创建对象的时候可以直接添加一些数据
 name:"欢欢",
 age:'1',
 run:function(){
 	console.log("it can run")
 },
 sing:song
}

console.log(dog.sing==song); //true

  • 对象里的属性(和方法)是采用键值对的形式(key:value),键 是属性名,值 是属性值

    对象的键值可以是任意类型的数据,如果键值是函数,我们通常称这个属性为方法,它可以像函数一样调用,

    调用函数就说调用对象的方法(method),但它和函数只是名称上的区别没有其他的区别(函数是全局对象window的方法)

    方法冒号后面跟的是一个匿名函数

    多个属性或方法之间用逗号隔开

    es5中,对象的所有键名都是字符串,加不加引号都可以,如果键名是数值会被自动转化为字符串

var obj={
 2:'hh',
 3:function(){
 console.log('我是数字3')
 }
}

​ 如果键名不符合标识名的规则(第一个字符是数字,有空格或运算符等)且也不是数字,则必须给键名加上引号,否则会报错

​ 对象的属性名不强制要求遵守标识符的规范,什么乱七八糟的名字都可以使用,但是我们还是尽量按照标识符的规范去做

  • 操作对象的属性有两种方法

    • 使用点运算符

      对象.属性名=属性值;       向对象中添加一个成员或者修改对应属性或方法的值(原来有就修改,原来没有就添加)
      delete 对象.属性名        删除对象里的成员
      对象名.属性名             获取对象内的某个成员的值 当访问一个对象里没有的成员的时候返回undefined
      
      对象名.方法名()  调用方法要加小括号
      

      如果键名是数字不能使用点运算符,只能使用方括号运算符,即写入和读取的时候都只能使用方括号运算符

      var obj={};
      obj[123]='77';  //最好使用引号括着纯数值123
      console.log('obj',obj)
      console.log(obj[123]) 
      
    • 使用方括号运算符

      对象名['属性名']=值;        添加或修改一个成员
      对象名['属性名']            获取(访问) 当访问一个对象里没有的成员的时候返回undefined
      delete 对象名['属性名'];    删除一个成员
      

      注意如果使用方括号运算符,键名必须要放在引号里

    使用点运算符和方括号运算符操作对象的区别:

    使用点运算符添加对象成员的时候:不能使用变量,不能拼接字符串,点后面是什么,这个属性或方法的健名就是什么
    
    使用方括号运算符可以使用变量,可以使用字符串拼接,这样变量值是多少就会读取那个属性
    
    var name='abc';
    var obj={};
    obj.name=123;//给变量添加一个键名为name的属性,和变量name没有关系
    console.log(obj);//{name:123}
    // console.table(obj);
    obj[name]=456;//将name变量的值拿来作为新添加的对象的属性的属性名 等价于 obj['abc']=456;
    obj[name+1]=666; //方括号运算符采用字符串的拼接
    console.log(obj);//{name: 123, abc: 456, abc1: 666}
    // console.table(obj);
    

    注意:对象在控制台打印的时候,会出现两种情况,现在的样子和最终的样子

    			- 在控制台上,不展开对象数据类型的时候,是当前的样子
    			- 在控制台,展开对象的时候数据类型的以后,就是最终的样子
    			- 解决方法:打印你想看到的值或者使用console.table()
    

变量和属性的比较

变量和属性的相同点是都是用来存储数据的,不同是:

变量要单独声明,使用时直接写变量名,单独存在

对象里的变量称为属性, 在对象里不需要声明,使用时必须要 对象名.属性名

函数和方法的比较

  • 相同点:都是实现某种功能,做某件事

  • 不同点:函数是单独声明并且调用的, 单独存在。 调用: 函数名()

    ​ 对象里的函数称为方法,方法在对象里不需要声明,调用时要 对象名.方法名()

    对象的键值可以是任意类型的数据,如果键值是函数,我们通常称这个属性为方法,它可以像函数一样调用,

    调用函数就说调用对象的方法(method),但它和函数只是名称上的区别没有其他的区别(函数是全局对象window的方法)

  • 在全局作用域中有一个全局对象window,它代表的是一个浏览器的窗口,它由浏览器创建,我们可以直接使用,在全局作用域中创建的变量都会作为window对象的属性保存,创建的函数都会作为windoe对象的方法保存

使用new Object 创建对象

js给我们提供了一个内置的构造函数叫做Object

var obj=new Object(); //创建一个空对象,使用内置对象的方法不好直接添加成员,后面通过对对象的操作语法来进行增删改查
obj.name='欢欢';,
obj.age=1;
obj.run=function(){
	console.log('run a run');
}

我们使用等号赋值的方法添加对象的属性和方法

每个赋值语句后使用分号结束

获取对象的属性和方法同上

构造函数

以上两种方法创建的对象时一个个创建的,由于对象是单个实物的抽象,通常需要一个模版,表示某一类实物的共同特征,然后对象根据这个模版生成。

js语言可以使用构造函数作为对象的模版,所谓构造函数就是专门用来生成实力对象的函数,它是是对象的模版,将对象共同的属性和方法封装在构造函数里,它范指一大类,类似于java、c++里的类

一个构造函数可以生成多个实例对象,生成的这些实例对象都有相同的结构

var 构造函数名=function(){
	this.属性名=属性值;
	this.方法名=function(){
		执行代码
	}
}

var Dog=fuction(dogName){
	this.name=dogNmae;
	this.run=function(runWhere){
		console.log(runWhere);
	}
}

调用构造函数

new 构造函数名();

var xdog=new Dog('欢欢');
获取生成的实例对象的属性和方法
console.log(xdog.name);
xdog.run("花园);

new关键字

new关键字的作用就是执行构造函数,返回一个实例对象(使用new关键字调用构造函数的时候如果不需要传参可以不写后面的()运算符,new关键字就是在调用构造函数,但是为了表示是函数调用所以推荐调用构造函数的时候写上圆括号运算符)

new关键字的执行过程:

1:new 构造函数 在内存中创建一个空对象

2:将构造函数中的this指向刚创建的空对象

3:执行构造函数里的代码,给这个空对象添加属性和方法,所有针对this的操作,都会发生在空对象上

4:返回这个对象

构造函数的特点:

  • 构造函数就是一个普通的函数,创建方式和普通函数没有区别,不同的是为了让构造函数和普通函数的区别开,构造函数名字的第一个字母要大写

  • 一个函数作为普通函数调用的时候使用圆括号运算符,作为构造函数函数调用的时候使用new调用

  • (调用构造函数和普通函数的调用方式不同,构造函数必须使用new命令调用,普通函数直接使用函数名调用)

  • 构造函数体内部的使用了this关键字,代表所要生成的对象实例

  • 构造函数不需要使用return,就可以返回结果,返回结果是一个实例对象

  • 使用new命令时,根据需求,构造函数也可以接受参数

  • 使用同一个构造函数创建的对象,我们称之为一类对象,也将一个构造函数称为一个类

  • 通过new关键字创建对象的过程我们称为对象实例化

  • 使用instanceof 可以检查一个对象是否是一个类(构造函数)创建的实例对象

    对象 instanceof 构造函数
                           //返回值:true 或者false
    

in运算符

通过该运算符可以检查一个对象中是否含有指定的属性,如果有则返回true,没有则返回false

格式:“属性名” in 对象

检查obj中是否含有name属性
console.log('name' in obj);

for…in

For…in循环大部分时侯是用来遍历对象里的所有属性,不直接使用for循环是因为对象里的成员名是没有什么规律的

var obj={a:1,b:2,c:3};
for(var k in obj){
	console.log("键名:",k);
	console.log("键值:",obj[k]); //注意不能使用点语法,因为点语法不能使用变量
} 
根据对象内有多少个属性,循环体就会执行多少次(每次执行时,就会将对象的一个属性名字赋值给变量K)
遍历对象的判断条件是k在对象里
每循环一次,k分别是对象的成员的名称(键名)(数据类型是:字符串类型)
// 键名: a
// 键值: 1
// 键名: b
// 键值: 2
// 键名: c
// 键值: 3

(For…in循环有两个使用注意点:

1:它遍历的是对象所有可遍历的属性,不会跳过不可遍历的属性

2:它不仅遍历对象自身的属性,还遍历继承的属性

)

  • 使用in语法,判断一个成员是不是在对象里

    格式: 键名 in 对象名

    对象里的每一个成员名称都必须是字符串,所以键名要以字符串的格式书写

    var obj={
    	name:'lili';
    	age:18
    }
    console.log(name in obj)
    //false 此处返回false是因为这里的name是值的一个变量,window 对象自带一个name
    console.log("name",obj)
    //true 
    

数据类型存储的区别

  • 数据类型分为两种-基本数据类型 和复杂数据类型

  • 复杂数据类型-function Object

  • 存储上的区别是:

  • js打开的内存空间,js是一个脚本语言,依赖于浏览器执行,本质是依赖于浏览器里的js引擎,js本身不打开内存空间,因为浏览器在你的电脑上运行的时候,会占用一段内存空间,js就是在这一段内存空间里运行的,数据类型的存储,就是存储在浏览器分配给js存储的一段空间里

    浏览器的一段存储空间

    • 栈内存

      存储机制,先来进栈底

    • 堆内存

      存储机制,随机存储

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fmuekyhZ-1635337966073)(/Users/wei/Desktop/markdown/js备课/img/数据存储1.jpeg)]

  • 数据类型的存储

    • 基本数据类型

      直接把值存储在栈内存里面,先来的进栈底,值与值之间的存储是独立的,改变一个变量不会影响其他变量

    • 负杂数据类型(地址数据类型/引用数据类型)

      对象是保存在堆内存中的,每创建一个新的对象,就会在堆内存中开辟出一个新的空间

      把数据放在了堆内里面,把在堆内存中的地址放在了栈内存的变量里面,我们管这个地址叫做引用

  • 代码执行时,我们只能访问栈里面的内容,你要想访问一个对象里的成员,因为对象本身在堆内存里,我们无法直接访问,需要利用栈里的地址找到堆里面的空间然后访问内部的成员

    var num=100;
    var str="hello"
    var obj={
    	name:'jack',
    	age:18
    }
    console.log(obj.name)
    
  • 当比较两个基本数据类型的时候就是比较值,而比较两个引用数据类型的时候,它比较的是对象的内存地址,如果两个对象是一模一样的,但是地址不同,它会返回false

    var obj3=new Object();
    var obj4=new Object();
    obj3.name="小明";
    obj4.name="小明";
    console.log(obj3==obj4);
    
  • 数据类型赋值的区别

    • 基本数据类型

      就是把变量存储的值直接赋值给另一个变量,赋值后两个变量就没有 关系了,一个变量改变不会影响另一个变量

    • 复杂数据类型

      因为复杂数据类型中,变量存储的是地址,赋值的时候,实际是把一个变量的地址给了另一个变量,赋值后两个变量操作一个空间(就像复制了两把钥匙一样,可以进一个房间)

    • 函数的形参和实参的关系

      实参就是在函数调用的时候给形参赋值,实参和形参的交互和变量赋值是一个道理,也就是说给形参赋值为基本数据类型和复杂数据类型是不一样的

      function fn(a,b){
      	在函数内部修改a和b的值
      	a=200;// 因为a赋值的是基本数据类型,所以形参a和全局的num没有关系,修改的只是形参a,全局的mun没有改变
      	b.name="abc";//因为给参数b赋值的是复杂数据类型,实际赋值的是地址,所以形参b和全局的obj指向一个对象空间,使用b修改空间里的数据,全局obj空间里的数据也被修改了
      }
      
      var num=100;
      var obj={
      	name:'jack',
      	age:18
      }
      
      fn(num,obj);
      //调用fn函数给a和b形参赋值,a赋值的是基本数据类型,b赋值的是复杂数据类型
      
    • 函数返回值也是变量赋值的一种

      函数的返回值是把函数内的数据return出去,在函数外面有一个变量接收

      function fn(){
      	var a=100;
      	var obj={
      		name:'jack',
      		age:18
      	}
      	return obj
      }
      
      var res=fn()
      //res接收到的fn函数里面定义的那个obj的地址。。。(没有讲完)
      
    • 函数本身也是一个对象,定义一个函数后函数就有两个功能,一是作为函数保存一段代码,另一个时作为对象时保存一段数据

      • 把函数当作一个函数来执行调

        函数名()

      • 函数当做对象用来存储数据(函数本身已是一个对象,可以保存一堆数据,函数的存储空间)

        函数名.键名=‘值’

      ​ 这两个功能互不干扰,也没有关系

      function fn() {
          console.log(123);
          console.log(fn.age); 
          console.log(age); //报错。age未定义
          // 访问age变量是栈内存里的age成员,我们栈内存里没有age这个变量,只有fn这个变量,指向一个存储空间,这个存储空间里面才有age成员
      }  
      fn()
      //把函数当做对象来使用,使用对象的方法来存入数据
      fn.age=19;
      fn['gender']="男"
      console.log(fn);  //只能打印出fn作为方法的信息
      console.dir(fn); //详细打印,也可以打印出函数作为对象使用时存储的信息
      

原型链

使用new 构造函数函数创建的对象都有一个对应的方法,很占内存,所以使用可以在全局定义一个函数赋值给构造函数函数的方法,这样构造函数创建的对象里的方法都是指向一个在全局作用域内的地址,但是这个地址在栈内变量里存储,全局如果重名的话修改的话,就被污染了,所以不能使用这种方法


  • prototype:每一个函数天生自带的一个prototype属性,它是一个对象,是函数的原型对象

    ​ prototye的作用:就是为了书写一些方法给构造函数的实例对象使用,因为构造函数的每一个实例都可以反问它

  • ——proto——:每一个对象天生自带的一个属性叫做——proto——,指向所属的构造函数的prototype(原型对象)

​ 实例化对象也是对象,也有——proto——属性

  • 当一个对象没有准确的构造函数来实例化的时候,我们都看作是内置构造函数的Object的实例

    var arr=[],Array实例
    var obj={} Object的实例
    var p1=new Person; Person的实例
    var time=new Date() Date的实例
    var fn=function (){}  Function的实例
    Person.prototype Object的实例
    Array.prototype  Object的实例
    

  • 每一个函数天生自带的一个prototype属性,这个属性作为普通函数调用时prototype没有任何作用,当函数以构造函数函数的形式调用的时候,构造函数所创建的对象都有一个隐含的__ proto _ _属性指向所属构造函数的原型对象,我们可以通过——proto——来访问该属性

  • 对象访问机制

    原型对象就相当于一个公共的区域,所有同一类的实例都可以访问到这个原型对象,我们可以将对象中共有的内容,统一设置到原型对象中,当我们访问对象的一个属性或方法时,它会现在对象自身中寻找,如果有则直接使用,如果没有则会去——proto——中寻找,如果找到则直接使用,没有再去——proto–上找,一直找到顶级对象的——proto–都没有,就返回undefined(顶级对象象是内置构造函数Object)

  • 原型链结论:任何一个对象开始出发,按照——proto——开始向上查找,最终都能找到Object的原型对象Object.prototype,我们管这个使用——proto——串联起来的对象链状结构叫做原型链

    原型链的作用:为了对象访问机制服务

  • 以后我们创建构造函数的时候,可以将这些对象共有的属性和方法统一添加到构造函数的原型对象中,这样不用分别为每一个对象添加,也不会影响到全局作用域,就可以使每个对象具有这些属性和方法了

(利用prototype和——proto–和对象的访问机制,解决构造函数的不合理,属性直接写在构造函数体内,方法书写在构造函数的prototye上,这样才可以使用构造函数创建一个有属性,有方法的合理的对象)

function Person(name,age){
    this.name=name;
    this.age=age
}
Person.prototype.skill=function(){ console.log("你好")}
var a=new Person('66',12);
console.log(a)
a.skill();//对象访问机制
  • 使用in检查对象中是否含有某个属性时,如果队形中没有但是原型中有,也会返回true

    可以使用对象的hasOwnProperty()来检查对象自身中是否含有该属性,使用该方法只有当对象中含有属性时才会返回true

    原型对象也是对象它也有原型,当我们使用一个对象的属性或方法的时候,会先在自身中查找,自身如果有就直接使用,如果没有则去原型对象中查找,如果原型对象有就用,没有则去原型对象的原型中寻找,直到找到Object对象的原型,Object对象的原型没有原型了,如果object中依然没有找到,则返回undefined

  • 什么是原型:原型就是每一个函数天生自带的prototype属性(它是一个原型对象),为了存放一些方法,给这个构造函数的所有实例对象使用,多个实例共享方法

  • 什么是原型链,原型链就是使用下划线proto串联起来的对象链状结构,为了对象访问机制服务的

  • 原型和原型链是完全的两个概念,他们两个的关系就想java和javascript的关系,虽然名字看起来很像但是他们没有必然的联系

  • 如果想给数组扩展一个方法,写在Array.prototype上

  • 如果想给函数扩展一个方法,写在Function.prototye上

  • constructor属性(构造器)

    只有函数天生自带的那个prototype上才有,表示我是哪一个构造函数所自带的原型对象

    作用:判断数据类型

Json格式

json格式是一种固定的字符串格式

'ahdsjkaf ahd' 普通字符串
'121232423' 纯数字字符串
'<h1>hello word</h1>'     html格式的字符串

json也是一种字符串的格式,和普通字符串本质上没有什么区别,只是多了一些固定的格式

json字符串的作用:在电脑网络传输的过程中,只能传递字符串,不能传递对象和数组类型的的数据,如果想要传递数组或者对象,那么需要转换成字符串的格式传递

json格式就是满足对象和数组数据结构的一种字符串

  • JSON.parse(要转换的json格式的字符串)

    作用:把json格式的字符串转换成js的数组或者对象

    返回值:js格式的数组或对象

    (我们可以手写一个JSON格式的字符串)

  • JSON.stringify(要转换的数组或者对象)

    作用:把js格式的数组或者对象转换成json格式的字符串

    返回值:一个json格式的字符串

json数据格式

  • 描述数组或者对象数据类型

  • 对象中的key和value都是用双引号包裹,数值和布尔值不需要引号

  • 数组里面可以放多个对象

  • 当多个数据的时候,最后一个数据后面不能有逗号,

  • 一个json格式中,在符号位可以使用符号,只有{},[],"",逗号

    “”*(&(*))" 引号内可以有特殊符号,表示的是一个字符串

  • 转换json格式字符串的时候,函数会被自动过滤掉

var obj={
  name:'小明',
  ha:'^*&^*',
  age:18,
  fn:function(){
    console.log('bubumamm')
  }
}
var newObj=JSON.stringify(obj);
console.log('>>>>>>',newObj);   //  {"name":"小明","ha":"^*&^*","age":18}
  • json文件
    • 我们有一种文件格式 ,以.json为扩展名
    • 一个完全的json文件,里面只能写json格式的内容

本地缓存

作用:把一些数据记录在浏览器中,是浏览器提供给我们的一些本地存储数据的机制

  • localStorage 永久缓存,除非手动删除

  • sessionStorage 会话缓存,关闭浏览器就没有了

    共同点:只能存储字符串格式的数据

  • localStrorang

    • localStrorage.setItem(‘名字’,‘值’)

      • 存储一条数据
      • 重复设置同一个名字的时候,就是修改数据
      localStorage.setItem('name',{name:8});
                      // 设置的值是一个对象,设置进入本地的实际值为 [object Object]
      console.log(localStorage.getItem('name'));
                      //  [object Object]   存入数据时值为对象,获取的时候是没有办法正确获取的
                      
      localStorage.setItem('name',JSON.stringify({name:8}));
      //存储对象类型的数据的时候可以先将其转换为josn格式的字符串
      
      console.log(localStorage.getItem('name'));
      //{"name":8} 获取后获得是json格式的字符串,然后在使用JOSN.parse()转换为数组或者对象类型
      
    • localStorage.getItem(‘名字’)

      • 获取数据,如果获取的是没有设置过的数据,返回null
    • localStorage.removeItem(‘名字’)

      删除一条数据

    • localStorage.clear()

      清除所有的数据

  • sessionStorage

    • sessionStorage.setItem(‘名字’,value)

    • sessionStorage.getItem(‘名字’)

    • sessionStorage.removeItem(‘名字’)

    • sessionStorage.clear()

回调函数

回调函数非常有用,因为他们可以将调用函数委托给其他函数,可以使用组合来构建项目,从而写出更佳简洁,高效的代码

js执行机制

js是单线程的,也就是说在同一个时间中能做一件事,这是因为js这们脚本语言诞生的使命所导致的,js是处理页面中的用户的交互,以及操作DOM而诞生的,比如我们对某个DOM元素的进行添加和删除,不能同时进行,要先添加再删除

单线程就意味着,所有的任务需要排队,前一个任务结束才会执行下一个任务,如果一个任务的执行时间过长,这样就会导致线程阻塞,导致页面的渲染不连贯。

为了解决这个问题,利用多核cpu的计算能力,html5提出,web worker标准,允许js脚本创建多个线程,于是js中出现了同步和异步

  • 同步

    前一个任务结束后再执行后一个任务,程序的执行顺序与任务的书写顺序一致

  • 异步

    处理一个任务花费很常的等待时间,在做这件事的同时可以去处理其他的事情

    同步和异步的本质区别是:这条流水线各个流程的执行顺序不同

  • 同步任务

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

  • 异步任务

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

    异步任务有以下三种类型

    • 普通事件的回调 如 click resize

    • 资源加载 如 load error

    • 定时器 setInterval setTimeout等

      异步任务相关回调函数添加到任务队列中 (任务队列也称为消息队列)

执行顺序

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

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

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

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

定时器

单线程,js只在一个线程上执行,代码从上到下一行一行执行,同时只能做一件事情,其他任务必须在后面排队等待

js是有多个线程的,但是js只在一个线程上运行,称为主线程,其他线程都是在后台配合

程序里的任务分为两类:同步任务和异步任务

  • 同步任务:同步任务是指没有被js引擎挂起,在主线程上排队执行的任务,只有前面的任务执行完了才会执行后面的任务,前面的写一个死循环,后面的代码就全部不执行了

  • 异步任务:不会立即执行的代码

    当代码从上到下的执行,遇到异步代码的时候会把它放到队列里先不执行,等到所有的同步代码执行完毕,再从队列里面拿到代码来执行

    异步任务:

    webapi给我们提供了一个队列的机制,用来模拟多线程,队列里面是各种需要当前程序处理的异步任务,我们叫做单线程的异步

    js是单线程,解决方式是异步,异步的解决方案是事件轮询,事件轮讯的核心是–回调函数

  • js的定时器,js中的window对象提供两个异步定时器机制

    • window.setTimeout(函数,时间) 延时定时器/ 炸弹定时器

      作用:设置一个定时器,在指定时间后调用回调函数,只调用一次,就结束了这个定时器

      调用函数可以直接写函数,或者函数名

      延迟时间默认为0,如果写单位必须是毫秒(不写单位ms,但是写入的数值表示的是毫秒)

      因为定时器有很多,我们经常给定时器赋值一个标识符,这样便于我们清除定时器

    • window.setInterval(函数,时间) 间隔定时器

      每隔固定时间,调用一遍回调函数,只要不清除定时器一直重复调用

    • 这两个定时器都可以接收多个参数,后面的参数作为回调函数的实参传入

  • 定时器里的回调函数是异步回调函数:

    普通函数是按照代码顺序直接调用的,而这个函数要等待时间,时间到了才调用该函数

    因此称为回调函数(干完上件事情后回头再调用这个函数)

    我们之前的onlick=function(){}.等里的函数也是回调函数

  • 异步机制案例

    consoel.log('start');
    setTimeout(function(){
    	console.log('timeout');
    },0)
    console.log('end')
    //start end timeout
    
    console.log('start');
    setTimeout(function(){
    	console.log('timeout');
    },0)
    console.log('end')
    while (true) {} //同步代码,它没有结束
    //start end
    
function a(){
    console.log('执行函数a');
    setTimeout(function(){
        console.log("执行函数a的延迟函数");
    },1000)
}
function b(){
    console.log("执行函数b");
}
a();
b();
//'执行函数a' '执行函数b' "执行函数a的延迟函数"
  • 定时器的返回值是一个number数据类型,不分定时器的种类只表示当前定时器是页面中的第几个定时器,

    定时器返回值的作用是用来关闭定时器使用的,我们一般会将定时器的返回值赋值给一个变量记录

  • 清除定时器有两种方式

    • window.clearInterval(要关闭的定时器的返回值);
    • window.clearTimeout(要关闭的定时器的返回值);

    清除定时器不分种类,随便关,只要你的定时器返回值是对的就可以了

    案例 :按钮倒计时 禁用 防止用户多次点击。。。倒计时60秒

//案例定时广告弹窗
     sBox.onclick=function(){
     //点击后让元素消失
        sBox.style.display='none'
      //消失3秒后再弹出
        setTimeout(() => {
            sBox.style.display='block'
        }, 3000);
      }
      //打开页面的时候不会出来,box隐藏,过一会显示使用setTimeout
      setTimeout(() => {
            sBox.style.display='block'
        }, 3000);

对象的分类

内部对象:(在任何Es实现中都可以使用)

​ 1:本地对象:ECMAScript提供的需要实例化(new)才能使用的对象 Object Array String Function Data 等

​ 2:内置对象:ECMAScript提供的不需要实例化就能使用的对象,只有Global(全局对象)和Math

自定义对象:由开发人员自己创建的对象

宿主对象:目前主要是指浏览器提供的对象比如BOM DOM是js独有的

内置对象

内置对象就是js语言自带的一些对象,这些对象供开发者使用,并提供了一些常用的或者最基本而必要的功能(属性和方法)

Math
  • Math对象是js的原生对象,提供各种数学功能,不是一个构造函数调用时不需要使用new生成对象实例,所有的属性和方法都必须在Math上调用
    • Math的静态属性提供一些数学常数,这些属性是只读的不能修改
      Math.PI //Math对象的属性,圆周率 3.1415926....
    • Math提供了一些静态方法
最大最小值 (参数为数组不好使)
Math.max(num1,num2) //返回若干个参数之际的较大的数 
Math.min(num1,num2)  //返回较小的数


Matn.max(1,2,"哈哈")  //NaN
Math.max() //-Infinity
在js中不提供找出数组中最大元素的函数,结合apply方法和Math.max方法可以,就可以返回数组中最大元素
  var arr=[1,2,3,33,24,56,3,4,6767,3323,36,-1]
  console.log(Math.max.apply(null,arr))
  console.log(Math.min.apply(null,arr))

绝对值(数字到坐标原点的距离)
Math.abs(-1) //-1
Math.abs("77") //77
Math.abs('nihao') //NaN

向下取整 floor 地板 是往最小了取整数值
Math.floor(3.4) //3
Math.floor(3.9) //3

向上取整 ceil 天花板 是往最大了取整数值
Math.ceil(4.6//5
Math.ceil(4.1//5
Math.ceil(-10.5//-10

四舍五入 
Math.round(1.5) //2
Math.round(-1.5)//-1    ??**.5**特殊直接舍???
Math.round(10.499)//10 取整只看小数点后一位来四舍五入

返回01之间的随机数x(0=<x<1),这个方法不需要参数,,每次调用自动返回一个随机数
Math.random()  

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/random

得到一个两数之间的随机整数,包括两个数在内

随机点名的案例 (bz)

  Math.random()  返回 01之间的随机数 含小数 
    010之间的随机整数: 
            使用Math.random()获取00.9999之间的随机数 *1009.9999

            取整:Math.floor() 向下取整 09
                 Math.ceill() 向上取整 110
                 Math.round() 四舍五入取整  010
                    00.49999 0
                    0.51.4999 1
                    8.5~9.4999 9
                    9.5~9.9999 10

    020之间的随机整数:
            使用Math.random()获取01之间的随机数 *20 取整
    20~30
            使用Math.random()获取01之间的随机数 *10+20
            
    3050
            使用Math.random()获取01之间的随机数*50-30+30
            。。。。
     n~m
     				使用Math.random()获取01之间的随机数* (m-n)+n
     				
//-------比较选取哪种取整方式
var obj={};
//   console.log(Math.random()); 
  for(var i=0;i<=1000;i++){ 
    var res=Math.floor(Math.random()*(10+1)); //使用向下取整获取的数字两端少,中间多
    // var res=Math.ceil(Math.random()*10); 
    // var res=Math.round(Math.random()*10); //差不多
    if(obj[res]){
        obj[res]++
    }else{
        obj[res]=1;
    }

  }
  console.log(obj)
  
  //封装范围内随机整数
  
  /* 
 @param {NUMBER}   n 数字1
 @param {NUMBER}   m 数字1
 @return {NUMBER}  随机数字
*/

function rangeRandom(n,m){
    var max=Math.max(n,m);
    var min=Math.min(n,m);
    return   Math.floor(Math.random()*(max-min+1)+min);
}
var res=rangeRandom(20,80)
console.log(res)

数字转换进制

  • 进制是一种数字的表示方法,进制有2~36进制

    js里进制转换分为两种

    • 十进制转换为其他进制

      使用方法:数字.toString(你要转换的进制)

      返回值:以字符串的形式返回给你转换好的进制数字,如果不以字符串的形式返回,那么在js里自动转换成十进制

      var num=100;
       console.log(num.toString(2));  //1100100
       console.log(num.toString(16)); //64
      
    • 其他进制转换为十进制

      方法:parseInt(要转换的数字,你把这个数字当做进制)

      返回值:转换好以后的十进制数字

       console.log(parseInt(101,8));// 65 将一个8进制的数字101转换为十进制后是65
      

      注意计算的时候要是同一进制的数值再计算

Date

js有一个内置的构造函数Date,是js的时间库,用来处理时间和日期。

Date是一个构造函数,需要先用new调用生成对象

格式:var time=new Date() 返回值:当前终端的当前时间,当把你电脑的时间调整后,得到的是调整以后的时间

Date对象以国际标准时间1970年的1月1日00:00:00作为时间的零点,可以表示的时间是前后各一亿天(单位为毫秒)

可以创建一个指定日期的时间对象

var today=new Date();
  • 如果不加参数,返回的实例代表的是当前时间的字符串

  • 可以接受多种格式的参数,返回一个该参数对应的时间

    • 参数为时间零点开始计算的毫秒数

      new Date(1378218728000)
      // Tue Sep 03 2013 22:32:08 GMT+0800 (CST)
      
    • 参数为日期字符串

      日期字符串的格式: yyyy-mm-dd HH:MM:SS yyyy/mm/dd HH:MM:SS

      年月日和时分秒之间有一个空格

      当使用字符串这个形式的时候,1表示1月,12表示12月

      new Date("2021-6-4")
      new Date("2021/6/4")
      new Date("06/04/2021")
      
      //Fri Jun 04 2021 00:00:00 GMT+0800 (中国标准时间)
      
    • 参数为多个整数,代表年、月、日、时、分、秒、毫秒

      new Date(2021,6,4,8,7,9,9) 
      //Sun Jul 04 2021 08:07:09 GMT+0800 (中国标准时间)
      

      参数为年月日多个整数时,年和月不能省略,其他参数可以省略,也就是说至少要有两个参数,因为如果只有一个参数的话,Date会将其解释为毫秒

      参数的取值:

      • 年:使用四位数的年份 2000
      • 月:从0开始计算 即 0至11,0是1月,11是12月
      • 日:从1开始计算,即1至31
      • 时:0到23
      • 分:0到59
      • 秒:0到59
      • 毫秒:0到999

      注意:1秒等于1000毫秒

      ​ 除了日的默认值是1,时分秒毫秒的默认值都是0

      ​ 除了年以外的每一个数字都会自动进位

      这些参数如果超出了正常范围,会被自动折算,比如月份设置为15,就会被折算为下一年的4月

      new Date(2013, 15)
      // Tue Apr 01 2014 00:00:00 GMT+0800 (CST)
      new Date(2013, 0, 0)
      // Mon Dec 31 2012 00:00:00 GMT+0800 (CST)
      上面代码的第二个例子,日期设为0,就代表上个月的最后一天。
      
  • 日期的运算

    两个日期实例对象进行减法运算时,返回的是他们之间间隔的毫秒数 即两个时间对象时间戳的差值

    两个日期实例对象进行加法运算时,返回的是两个字符串拼接后的新的字符串

    因为:类型自动转换,Date实例如果转为数值,则等于对应的毫秒数,如果转为字符串,则等于对应的日期字符串

    var d1 = new Date(2000, 2, 1);
    var d2 = new Date(2000, 3, 1);
    
    d2 - d1
    // 2678400000
    d2 + d1
    // "Sat Apr 01 2000 00:00:00 GMT+0800 (CST)Wed Mar 01 2000 00:00:00 GMT+0800 (CST)"
    
  • 日期格式化(获取时间对象信息的方法)

    Date对象提供了一系列get*放法,用来获取实例对象内部信息

    格式:

    • 时间对象.getFullYear() :返回时间对象的(4位)年份 Number数据类型
    • 时间对象.getMonth():返回月份(0表示1月,11表示12月),返回的月份比实际的月份小1,要返回值加1 Number数据类型
    • 时间对象.getDate():返回当天的日期(从1开始) Number数据类型
    • 时间对象.getHours():返回小时(0至23) Number数据类型
    • 时间对象.getMinutes(); 返回分钟(0至59) Number数据类型
    • 时间对象.getSeconds();返回秒(0至59) Number数据类型
    • 时间对象.getMilliseconds(): 返回当前时间的毫秒(0至999) Number数据类型

    修改时间对象的某一指定信息

    • 时间对象.setFullYear(你要设置的年) 作用:设置时间对象的年份信息

    • 时间对象.setMonth(你要设置的月))

      月份(0表示1月,11表示12月)

    • 时间对象.setDate(你要设置的日))

    • 时间对象.setHours(你要设置的小时))

    • 时间对象.setMinutes(你要设置的分));

    • 时间对象.setSeconds(你要设置的秒));

    • 时间对象.setMilliseconds(你要设置的毫秒))

    • 时间对象.setTime(时间戳)) 作用:根据时间戳,定位到指定的时间

      var d1 = new Date(2000, 2, 1);  返回值是对象的时间戳
      new Date(d1.setFullYear(1989));
      
    • –还有一套按照UTC时间的设置方法


  • 获取世界标准时间的时间信息

    时间对象.getUTCFullYear()

    时间对象.getUTCMonth()

    时间对象.getUTCDate()

    时间对象.getUTCHourse()

    时间对象.getUTCMinutes()

    时间对象.getUTCSeconds()

    时间对象.getUTCMilliSeconds()亲


时间对象.getDay(): 返回星期几 星期日为0,星期一为1… Number数据类型

var Day=new Date();
Day.getDay();

格林威治时间 1970年1月1日00:00:00 时间长河的起点,时间零点

时间戳:当前时间距离时间零点1970年1月1日00:00:00的过了多少毫秒数

获取指定时间的时间戳,可以带参数也可以不带参数,不带参数则是当前时间的时间戳,带参数则是指定时间的时间戳

  • 时间对象.getTime([指定时间])

  • 时间对象.valueOf([指定时间]))方法

  • +new Date([指定时间]))

    var ms = +new Date();
    
    var d1 = new Date(2020,10,2,2,2,2);
    
    console.log(d1.getTime());  //1604253722000
    console.log(d1.valueOf());   //1604253722000
    console.log(+new Date(2020,10,2,2,2,2));  //1604253722000
    
  • H5新增的方法

    Date.now() 返回当前时间距离时间零点(1970年1月1日 00:00:00 UTC)的毫秒数,相当于 Unix 时间戳乘以1000 返回的是当前时间的时间戳,带参数指定时间的话,参数会被省略

    Date.now()  //静态方法,不用实例化
    

案例:

封装一个函数,返回当前的时分秒,格式为09:09:10

function getTime(){
	var time=new Date();
	var h=time.getHours();
	h=h<10?'0'+h:h;
  var m=time.getMinutes();
	m=m<10?'0'+m:m;
  var s=time.getSeconds();
	s=s<10?'0'+s:s;
	return h+':'+m+':'+s;
}

倒计时案例

案例分析:

1:输入的未来时间剪去现在时间就是剩余的时间,但是不能使用时分秒来相减结果会是负数(06分减31分),要用时间戳来减,用输入的未来时间的时间戳减去当前时间的时间戳,就是剩余的时间的毫秒数

2:将剩余时间的总的毫秒数转化为天,时,分,秒

转换公式:

d=parseInt(总秒数/60/60/24) //计算天数
h=parseInt(总秒数%24/60/60) //计算小时
m=parseInt(总秒数%60/60) //计算分钟
s=parseInt(总秒数%60) //计算当前秒数
    <div id="dBox"></div>
    <div id="hBox"></div>
    <div id="mBox"></div>
    <div id="sBox"></div>
//---------------------------封装的返回时间差的插件
/* 
  @param {TIME} t1 时间节点1
  @param {TIME} t2 时间节点2
  @return {Object} 以对象形式返回时间差

*/
function time(t1,t2){
    //两个时间对象之间相减可以获得他们之间时间戳的差值,但是有兼容性问题,所以需要转换为时间戳后,用时间戳相减
    t1=t1.getTime();// 获取传进来时间的时间戳
    t2=t2.getTime();

    var ct=Math.round(Math.abs(t1-t2)/1000);   //两个时间戳相差的秒数
    //相减之后取绝对值,这样就不用考虑谁大谁小了,结果是ms/1000得到秒数,取整
  
    var d,h,m,s;
    d=parseInt(ct/(24*60*60));
    h=parseInt(ct%(24*60*60)/(60*60));
    m=parseInt(ct%(60*60)/60);
    s=ct%60
    // console.log(`${d}天${h} 时${m} 分${s} 秒`);// 距离目标时间的时间 字符串心形式
    return {
        d:d,
        h:h,
        m:m,
        s:s
    }
}

------------------------------
----------使用插件,实现秒杀倒计时 并渲染到页面中
var target=new Date(2021,9,1);// 2021 10 1 目标时间
function djs(){
   var current=new Date();  //每隔一秒钟就获取一些当前时间
   var res= time(target,current);//每秒钟调用一下函数      
   //处理时间格式
   if(res.d<10) res.d='0'+res.d;
   if(res.h<10) res.h='0'+res.h;
   if(res.m<10) res.m='0'+res.m;
   if(res.s<10) res.s='0'+res.s;
    //    向页面元素渲染数据
    dBox.innerText=res.d
    hBox.innerText=res.h
    mBox.innerText=res.h
    sBox.innerText=res.s
}
djs();//页面一进来就立即执行 填补有那一秒钟的空白
setInterval(djs,1000); //此处要写函数名,表示时间到了要执行的函数,如果写 djs()表示的是立即执行函数,得到是函数的返回值
var arr=['零','一','二','三','四','五','六','七','八','九','十'];
var time=new Date()
var year=time.getFullYear()
var month=time.getMonth()
var day=time.getDate()
var hover=time.getHours()
var minute=time.getMinutes()
var secend=time.getSeconds()
var week=time.getDay()
console.log(year,month,day,hover,minute,secend); //----------2021 6 19 0 50 10
function getYear(year){
    //把年份拆分成一个四位数
    var a=parseInt(year/1000);
    var b=parseInt(year%1000/100);
    var c=parseInt(year%100/10);
    var d=parseInt(year%10);
    return arr[a]+arr[b]+arr[c]+arr[d]+"年"
    //转换成中为,将这四个数字作为索引取数组里相对应位置上的数字
}

function getOther(num){ 

    //先拆数字
    var a=parseInt(num/10); //十位上的数字
    var b=num%10    //个位上的数字

    // 阅读方式
    // 个位: 一 二 三。。。十        num<=10
    // 十几:十一  十二 。。。十九    num<20
    //几十:二十 三十 四十           num%10===0
    //几十几: 二十九。。。 

    if (num<=10) {
        return  arr[num];  //直接使用传进来的参数作为数组的索引获取数组中的中文
    }else if(num<20){
        
        return "十"+arr[b];
    }else if(num%10===0){
        return arr[a]+'十'
    }else{
        return arr[a]+'十'+arr[b]
    }
}
//组装
var str=getYear(year)+' '+getOther(month)+'月'+' '+getOther(day)+'日'+ ' 周'+getOther(week)+' '+getOther(hover)+'点'+' '+getOther(minute)+'分'+' '+getOther(secend)+'秒';
console.log(str); //----------二零二一年 六月 十九日 周一 零点 五十分 十秒

作业:实现中文的倒计时

数组后补知识点

  • 使用new Array这种方式创建数组的时候,有一个很大的缺陷,不同的参数个数会导致的行为不同
    无参数时,返回一个空数组

var arr=new Array(); 创建一个空数组并赋值给变量arr
参数为单个正整数表示的时创建的数组的长度,每个值为空的数组元素
new Array(2) // [ empty x 2 ]
参数为多个时,所有参数都是返回的新数组的成员

new Array(1, 2) // [1, 2]  等见于使用字面量来创建数组,里面有两个数组元素分别是1和2
new Array('a', 'b', 'c') // ['a', 'b', 'c']

因为Array对象作为构造函数创建数组时行为很不一致,所以不推荐使用这种方式来生成新的数组,推荐直接使用字面量的方式创建数组

检测是否为数组的方法
  • Array.isArray(参数)方法返回一个布尔值,表示参数是否为数组

    var arr = [1, 2, 3];
    
    typeof arr // "object"
    Array.isArray(arr) // true
    

    这个方法是h5新增的方法,可以祢补typeOf的不足,ie9以上版本支持

  • instanceof 运算符,他可以用来检测是否为数组

    var arr=[];
    var obj={};
    console.log(arr instanceof Array);
    console.log(obj instanceof Array);
    

    函数案例,翻转数组和检测数组结合,for循环遍历数组中的元素倒序的放入一个新的数组中

    function reverse(arr){
    	var newArr=[];
    	if(Array.isArray(arr)){
        for(var i=arr.length-1;i>=0;i--){
          newArr[newArr.length]=arr[i];
        }
        return newArr;
    	}else{
    		return "你穿的参数不是一个数组"
    	}
    	
    }
    console.log(reverse(1,2,3));
    
添加删除数组中元素的方法
push

Push()方法用于在数组的最后面添加一个或多个元素,并返回添加新元素后的数组长度。

注意:该方法会改变原数组

参数为要添加进去的新元素,一个或多个

var arr = [];

arr.push(1) // 1  参数为要添加进去的新元素
arr.push('a') // 2
arr.push(true, {}) // 4
arr // [1, 'a', true, {}]
pop

pop()方法用于删除数组的最后一个元素,并返回该元素。

注意:该方法会改变原数组

没有参数,一次只能删除一个数组元素

var arr = ['a', 'b', 'c'];

arr.pop() // 'c'
arr // ['a', 'b']

空数组使用pop方法不会报错,而是返回undefined. (此句可不讲)

[].pop() // undefined

pushpop结合使用,就构成了“后进先出”的栈结构(stack)。(此句可不讲)

shift

shift()方法用于删除数组的第一元素。并返回该元素。

注意在;该方法会改变原数组

没有参数,一次只能删除一个数组元素

var a = ['a', 'b', 'c'];

a.shift() // 'a'
a // ['b', 'c']
unshift

unshift()方法用于在数组的最前面添加一个或多个元素,并返回添加新元素后的数组长度。

注意:该方法会改变原数组。

参数为要添加进去的新元素,一个或多个

var arr = [ 'c', 'd' ];

arr.unshift('a', 'b') // 4
arr // [ 'a', 'b', 'c', 'd' ]
concat

作用:用于多个数组合并 返回值:追加数据后的新数组 ,原数组不变

如果参数是数组,它将参数数组的成员拆开,将里面的每一个元素添加到数组的后面,返回一个新的数组,原数组不变

如果参数是单个数据,直接追加到数组的后面,返回一个新的数组,原数组不变

var arr=[1,3];
arr.concat({a:1,b:2},{name:"66"});  // [1,3,{a:1,b:2},{name:"66"}]
map

格式:数组.map(function(item[, index,arr]){})

这个函数会根据数组里有多少个元素就循环执行参数函数多少回

作用:将数组的元素依次传入参数函数(回调函数),在参数函数内对数组的元素进行操作,然后将每次循环操作后的元素组成一个新的数组返回 不会改变原数组

返回值:是一个新的数组,新数组的长度一定和原始数组的长度一样

var arr=[1,true,3,'hello',7];
var newArr=arr.map(function(item){
    return item+1;
})
console.log(newArr); // [2, 2, 4, "hello1", 8] 数据类型转换了

map方法的接受一个函数作为参数,该函数调用时,map()方法向它传入三个参数,分别是 当前的成员 当前的位置 和 数组本身

map()方法可以接受第二个参数,绑定参数函数(回调函数内部)的this变量

(map的底层封装(工作原理):1 准备一份新的数组 2遍历原始数组,分别执行函数 3:把每次函数执行的返回值放到新的数组里 4:把新的数组当做map的返回值给出来)

forEach

forEach方法不返回值,只是用来操作数据,所以,如果遍历数组的目的是为了得到返回值,那么使用map()方法,否则使用forEach()方法

这个函数会根据数组里有多少个元素就循环执行多少回

这个函数每一次执行的时候,item分别的是数组里的每一个元素,index分别是数组里的每一个元素的索引,arr每一次都是原始数组

var arr=[1,2,3,45,7];
arr.forEach(function(item,index,arr){
 	console.log(item,'-----',index,'-----',arr)
})
  • forEach()方法可以接受第二个参数,绑定参数函数的this变量
var arr=[1,true,3,'hello',7];
var newArr=[];
arr.forEach(function(item,index,arr){
 //  this.push(item);//按正序将数组arr的元素推送到空数组中
    newArr.unshift(item);
 // this.unshift(item); //颠倒数组到一个新的数组里
},newArr)
console.log('新数组',newArr)
console.log('原数组',arr)
//统计数组中各个元素出现的次数
var a=[2,34,'7',true,'7','7']
var obj={};
a.forEach(function(item,index){
    if(obj[a[index]]){
        obj[a[index]]++
    }else{
        obj[a[index]]=1; 
    }
})
console.log(obj);   //{2: 1, 7: 3, 34: 1, true: 1}  

**注意:**map和forEach无法中断执行,总是会将所有成员遍历完,如果希望符合某种条件时就中断遍历,要使用for循环

filter

作用:filter方法用于过滤数组成员,将满足条件的成员组成一个新的数组返回

他的参数是一个函数,所有数组的成员依次执行该函数,返回结果为true的成员组成一个新的数组返回

该方法不会改变原数组

var arr=[1,3,45,64,642,22,4]
var newArr=arr.filter(function(item,index,arr){
    return item>10
})
console.log(newArr);// [45, 64, 642, 22]
//返回数组的索引是偶数的位置上的元素
var arr=[1,3,45,64,642,22,4]
var newArr=arr.filter(function(item,index){
    return index%2==0
})
console.log(newArr);// [1, 45, 642, 4]
splice

作用:删除原数组中的一部分成员 返回值:被删除的数组元素 直接操作原数组

​ 格式1: 数组.splice(开始索引,n个)

​ 从开始索引的,删除n个元素

​ 如果只提供一个参数,第二个参数不写则表示直接到末尾等同于将原数组在指定位置拆分成了两个数组

var a=[1,2,3,4];
a.splice(2);      //[3,4]
a;                //[1,2]

​ 格式2: 数组.splice(开始索引,n个,替换数据1,替换数据2…)

​ 将替换的数据按照书写的顺序插入到你截取得位置,从哪个索引开始删除,替换数据的第一个就插入到哪个位置

var a=[1,2,3,4];
a.splice(2,0,'新来的');      //[]
a;                          //[1,2,"新来的",3,4]
// 数组去重
var arr=[1,true,1,3,3,'hello',7,7];
for(var i=0;i<=arr.length-1;i++){
  
    for(var j=i+1;j<=arr.length-1;j++){
        if(arr[i]===arr[j]){
            console.log(arr[i],arr[j])
            arr.splice(j,1);
            j--;

        }
    }
}

console.log(arr);  //[1, true, 3, "hello", 7]
var arr=[1,true,1,3,3,'hello',7,7];
var newArr=[];
arr.forEach(function(item,index){
    if(newArr.indexOf(item)===-1){
        newArr.push(item)
    }
})
console.log(newArr); //[1, true, 3, "hello", 7]
slice

作用:获取数组中的一部分元素,返回一个新的数组,原数组不改变

格式:数组.slice(开始索引,结束索引) 包前不包后

返回值:从数组中获取的元素组成的新数组,如果在原数组中没有获取元素则返回一个空数组

第二个参数可以不写,表示从开始索引一直返回到原数组的最后一个元素

var arr=[1,2,3,45,7];
console.log(arr.slice(1,3),'------',arr);   // [ 2, 3] -------- [1, 2, 3, 45, 7]
console.log(arr.slice(1),arr);  // [2, 3, 45, 7] --------- [1, 2, 3, 45, 7]

参数可以是一个负整数,表示倒数计算的第几个位置,表示 length+负整数

数组排序
reverse翻转数组

reverse方法用于颠倒排列数组元素,返回改变后的数组。

注意:该方法会改变原数组

var a = ['a', 'b', 'c'];

a.reverse() // ["c", "b", "a"]
a // ["c", "b", "a"]
  • 数组排序(冒排序,由小到大)
sort

作用:Sort方法对数组成员进行排序,默认是按字典ASCII码的顺序排序,

返回值:排序后原数组

​ 直接操作原数组

[11, 101].sort()
// [101, 11]     
['d', 'c', 'b', 'a'].sort()
// ['a', 'b', 'c', 'd']

[4, 3, 2, 1].sort()
// [1, 2, 3, 4]

[10111, 1101, 111].sort()
// [10111, 1101, 111]
返回结果有问题,对个位数值没有问题,超过个位的一些数组会有问题

想要让解决上述的问题,可以传入一个函数作为参数

[10111, 1101, 111].sort(function (a,b) {
 return a - b;    //升序的顺序排列
})
// [111, 1101, 10111]

sort的参数函数本身接受两个参数,表示进行比较的两个数组成员。如果该函数的返回值大于0,将第一个成员排在第二个成员后面;其他情况下,都是第一个元素排在第二个元素的前面。

[10111, 1101, 111].sort(function (a,b) {
 return b - a;    //降序的顺序排列
})
//[10111, 1101, 111]
[
  { name: "张三", age: 30 },
  { name: "李四", age: 24 },
  { name: "王五", age: 28  }
].sort(function (o1, o2) {
  return o1.age - o2.age;
})

// [
//   { name: "李四", age: 24 },
//   { name: "王五", age: 28  },
//   { name: "张三", age: 30 }
// ]
返回给定元素在数组中的索引号
indexOf

indexOf方法返回给定元素在数组中第一次出现的index索引号,如果在数组中没有找到该元素则返回-1

从数组的前面开始找(正向查找)

var a = ['a', 'b', 'c'];

a.indexOf('b') // 1
a.indexOf('y') // -1

indexOf方法还可以接受第二个参数,表示从哪一个索引开始向后查找

['a','b','c'].indexOf('a',1) //-1
lastIndexOf

lastIndexOf方法返回给定元素在数组中最后一次出现的位置,如果在数组中没有找到该元素则返回-1

从数组的后面开始找 ,注意:数组的索引依然是从前面第一位开始为0,1,2,3…(正常的索引)

lastIndexOf方法还可以接受第二个参数,表示从哪一个索引开始向前查找

var a = [2, 5, 9, 2];
a.lastIndexOf(2) // 3
a.lastIndexOf(7) // -1

注意这两个方法不能用来检索NaN的位置,即他们无法确认数组中是否包含NaN

[NaN].indexOf(NaN) // -1
[NaN].lastIndexOf(NaN) // -1

这是因为这两个方法内部,使用严格相等运算符(===)进行比较,而NaN是唯一一个不等于自身的值

重点案例,数组去重

案例分析:

目标将数组中的不重复的元素取出来,重复的元素只保留一个。放到新数组中去

方法:遍历旧数组,然后拿旧数组中的元素依次和在新数组中查找,如果没有查找到该元素就放进新数组,如果找到了就不放入新数组

要点:使用indexOf方法判断新数组中有没有目标元素 返回值为-1则表示新数组中没有该目标元素

function delCel(arr){
	var newArr=[];
	for(var i=0;i<arr.length;i++){
		if(newArr.indexOf(arr[i])===-1){
		 newArr.push(arr[i]);
		}
	}
	return newArr;
}
数组转化为字符串
toString() 将数组转化为字符串
join

join()方法以指定的参数作为分隔符,将所有的数组成员链接为一个字符串返回,如果不提供参数默认使用逗号分隔

var a = [1, 2, 3, 4];

a.join(' ') // '1 2 3 4'
a.join(' | ') // "1 | 2 | 3 | 4"
a.join() // "1,2,3,4"
  • 以上操作数组的方法中有不少返回值是数组,所以可以链式使用

案例看如阮一峰es5

案例:

      var a=  {
                "男明星": [
                        { "name":"梁朝伟","age":"40"},
                        { "name":"刘德华","age":"42"},
                        { "name":"彭于晏","age":"29"}
                    ],
                "女明星": [
                        { "name":"刘嘉玲","age":"40"},
                        { "name":"张敏","age":"41"},
                        { "name":"张柏芝","age":"30"},
                        { "name":"朱茵","age":"38"}
                    ]
             }

        /* 
        把下列json对象按要求遍历
        1.页面里实现
                男明星:
                name-->梁朝伟     age--->40
                name-->刘德华     age--->42
                name-->彭于晏     age--->29
                
                女明星:
                name-->刘嘉玲     age--->40
                name-->张敏        age--->41
                name-->张柏芝      age--->30
                name-->朱茵       age--->38

        2.过滤出所有40岁以上的明星
        3.求所有明星的年龄之和


        */
             
     //-------------------------------------------------
        for(var i in a){
            console.log(i+':')
            a[i].map(function(item){
                console.log(`name-->${item.name}   age--->${item.age}`);
            })
        }
        //-----   2.过滤出所有40岁以上的明星
        var arr=[];
        for(var i in a){
            
            arr= arr.concat(a[i]) 
        }
        console.log(arr);//将所有明星推到一个数组里
        var up40=arr.filter(function(item){
                return item.age>40
            })
        console.log('大于40岁的明星',up40)
        //----    3.求所有明星的年龄之和
                var sum=0;
        arr.forEach(function(item){
          sum +=(item.age-0)
        })
        console.log('所有明星之和',sum)

包装对象

js中Number 、String、Boolean这三个原生的函数(对象),作为普通函数调用时(不带new),常常用于将任意类型的值转为相对应的数值,字符串和布尔值。(即数值类型转换)

// 字符串转为数值
Number('123') // 123

// 数值转为字符串
String(123) // "123"

// 数值转为布尔值
Boolean(123) // true

Number 、String、Boolean这三个原生对象作为构造函数使用的时候(调用时带new),可以将原始类型的值转为(包装)对象

var v1 = new Number(123);
var v2 = new String('abc');
var v3 = new Boolean(true);

typeof v1 // "object"
typeof v2 // "object"
typeof v3 // "object"

v1 === 123 // false
v2 === 'abc' // false
v3 === true // false

可以看到基于原始类型的值,生成了三个对应的包装对象,且与原来的基本类型的值不在相等

将基本类型的值转为包装对象的原因是:基本类型的值是没有属性和方法的,只有对象才有属性和方法,为了方便操作基本类型的数据使得基本类型的值也可以调用自己(对象)的属性和方法

  • 包装对象各自提供了许多的属性和方法,他们共有的方法是:

    • valueOf()方法返回包装对象实例对应的基本类型的值
    new Number(123).valueOf()  // 123
    new String('abc').valueOf() // "abc"
    new Boolean(true).valueOf() // true
    
    • toString()方法返回对应的字符串形式

      数值,布尔值,字符串都可以使用toString(),因为数字,布尔值,字符串都是包装数据类型,undefined和null不是包装数据类型,不能使用

      new Number(123).toString() // "123"
      new String('abc').toString() // "abc"
      new Boolean(true).toString() // "true"
      
  • 基本数据类型与实例对象的自动转换

    在基本类型的值上直接调用包装对象的属性和方法时,js引擎会自动将基本类型的值先转换为包装对象,调用包装对象的属性和方法,并在使用后销毁实例

    数值,字符串,布尔值是包装数据类型,在使用的时候会自动转换成复杂数据类型,当使用完毕自动转换为基本数据类型

    基本数据类型在堆里没有空间,当使用字符串.length的时候,会自动转换为复杂数据类型,在堆内存里开辟一个空间,按照索引将字符串的每一个字符排列进去,等访问结束,拿到我们要使用的内容后,这个开辟的临时空间就销毁了

    基本数据类型是不会被改变的只能覆盖 var num=10;num+=20;

var str="abc";
str.lengrh //3

上面色代码等同于,先把str转换为其对应的包装对象,这是一个临时对象
var strObj=new String(str)
// String {
//   0: "a", 1: "b", 2: "c", length: 3, [[PrimitiveValue]]: "abc"
// }
在这个临时对象上调用length属性
strOj.length;
调用包装对象的方法或属性后再销毁这个临时对象
strObj=null;

自动生成的包装对象是只读的,无法修改,所以,字符串无法添加新的属性

var s = 'Hello World';
s.x = 123;
s.x // undefined

另一方面,调用结束后,包装对象实例会自动销毁。这意味着,下一次调用字符串的属性时,实际是调用一个新生成的对象,而不是上一次调用时生成的那个对象,所以取不到赋值在上一个对象的属性

操作字符串

操作字符串的案例

反转字符串
方法1:
var a='adgfshj gggggfsg';
var res=a.split('').reverse().join('')
console.log(res); // gsfggggg jhsfgda

方法2:
var a='adgfshj gggggfsg';
var s='';
for(var i=a.length-1;i>=0;i--){
    // console.log(a[i])
    // console.log(a.charAt(i))
    s += a[i];
}
console.log(s)

基本数据类型在堆里没有空间,当使用字符串.length的时候,会自动转换为复杂数据类型,在堆内存里开辟一个空间,按照索引将字符串的每一个字符排列进去,等访问结束,拿到我们要使用的内容后,这个开辟的临时空间就销毁了

基本数据类型是不会被改变的只能覆盖

基本数据类型在堆里没有空间,当使用字符串.length的时候,会自动转换为复杂数据类型,在堆内存里开辟一个空间,按照索引将字符串的每一个字符排列进去,等访问结束,拿到我们要使用的内容后,这个开辟的临时空间就销毁了

var str='啊函数的佛罗伦萨定时发个hasdf a'
console.log(new String(str))
//
0: "啊"
1: "函"
2: "数"
3: "的"
4: "佛"
5: "罗"
6: "伦"
7: "萨"
8: "定"
9: "时"
10: "发"
11: "个"
12: "h"
13: "a"
14: "s"
15: "d"
16: "f"
17: " "
18: "a"
length: 19

//---------------------------循环遍历字符串
var str='啊函数的佛罗伦萨定时发个hasdf a'
console.log(new String(str));//看看字符串包装对象怎么存储的
for(var i=0;i<=str.length-1;i++){
    console.log(str[i])
}

//-----------------------------统计字符串传中各个字符出现的次数
var a='11adgfshjf gggggfsg22';
var obj={}
for(var i=0;i<=a.length-1;i++){
    if(obj[a[i]]){       //a[i] 是字符串里每一个字符,判断对象里有没这个属性,没有就以字符作为属性名
        obj[a[i]]++       // 并赋值为1,如果有就自增1
    }else{
        obj[a[i]]=1;
    }
}

console.log(obj)


//--------------------------出现最多次的字符

var a='11adgfshjf gggggfsg22';
var obj={}
for(var i=0;i<=a.length-1;i++){
    if(obj[a[i]]){
        obj[a[i]]++
    }else{
        obj[a[i]]=1;
    }
}

console.log(obj)
var max=0;
var j;
for(var i in obj){
    if(obj[i]>=max){
        max=obj[i]
        j=i
    }

}
console.log('出现次数最多的字符是:',j,'出现多少次:',max)  //g 次

//-------------------------------统计指定字符出现的次数
//-----------先找出第一次出现的位置,然后只有返回值不是-1,就继续向后查找,因为indexof,只能查找到第一个,后面的查找可以使用indexof的第二个参数,设置下次查找从上次返回值的下一位开始查找

var a='11adgfshjf gggggfsg22';
var index=a.indexOf('s');
var num=0;
while (index!=-1) {
    console.log(index)               // 出现的索引位置 6 17
    num++
    index=a.indexOf('s',index+1);
}
console.log('出现多少次:',num);            // 2次

模版字符串

es5中使用+拼接字符串,es6中新增了以反引号定义字符串 dgfgd,我们将反引号定义的字符串叫做模版字符串

模版字符串和单双引号字符串的区别

​ 在单双引号字符串中不能换行,不能使用变量,模版字符串可以换行且换行效果可以输出,可以使用变量

var a=`<p>
      <a>不错不错</a>
      <span>你好<span>
     </p>`

模版字符串有兼容性问题,但是不用考虑,打包工具会转换为es5的语法

(…字符串的不可变

(字符串的不可变指的是里面的值不可变,变量的值虽然看上去可以改变内容,但其实是地址变了,在内存中开辟了新的内存空间)

var str="abc";
在内存中开辟一个内存空间存储字符串“abc”,将变量的指针指向这个字符串的内存空间
str="123";
当给str变量重新赋值为“123”字符串实际是,在内存中重新开辟了一个内存空间用来存储新的字符串“123”,并将变量str的指针指向这个新的内存空间,而原来的"abc"常量字符串依然在内存空间中。只不过是变量str的指针指向发生了改变

由于字符串的不可变,在大量拼接字符串时会有效率问题

var str='';
for(var i=0;i<10000000000;i++){
 str+=i;
}
console.log(str) //这个结果需要大量的时间来显示,因为会不断的开辟新的内存空间

由于字符串时不可变的,所以字符串的 所有方法都不会修改字符串本身,操作完成会返回一个新的字符串…)

字符串实例的属性

字符串实例的length属性返回字符串的长度,字符串的lengrh属性是一个制度属性,你如你要设置不会报错

基本数据类型是不会被改变的只能覆盖

字符串里的空格也算一个字符

'abc'.length

字符串实例方法

根据字符返回返回位置
indexOf

作用:indexOf方法用于确定一个字符串在另一个字符串中第一次出现的位置(从原字符串头部开始向后查找)。如果找不到就返回-1

格式:字符串.indexOf('要查询的字符',[开始的index索引])

开始的索引可以省略,省略即是在整个原字符串中查找

//可以匹配一个字符串片段,返回开始索引
var a='adgfshj gggggfsg';
console.log(a.indexOf('shj'))
lastIndexOf

字符串.lastIndexOf(‘要查询的字符’) 从原字符串尾部开始向前查找,返回第一个符号条件的index,没有找到则返回-1

字符串的这两个方法同数组的indexOf和lastIndexOf用法一样

案例:。。。

includes

格式:字符串.includes(‘字符串片段’)

作用:查找字符串里有没有匹配的字符串片段,返回 true或false

search

格式:字符串.search(‘字符串片段’)

作用:查找字符串里有没有匹配的字符串片段

返回值:有 返回指定索引

​ 没有 返回-1

和indexof的区别,search没有第二个参数,search可以使用正则

match

格式:字符串.match(‘字符串片段’)

作用:查找字符串里匹配的字符串片段

返回值:返回一个数组,里面是找到的字符串片段

实际应用:参数不是传入字符串而是传入正则

​ match可以使用正则

trim

去除首位空格

返回去除空格后字符串

trimStart() 去除开始空格。trimEnd() 去除尾部空格

根据位置返回字符
  • 字符串.charAt(index) index是字符串的索引号,返回该索引号位置上的字符

    如果没有该索引位置,返回空

    var a='adgfshj fsg';
    console.log(a.charAt(87))
    
    //循环遍历字符串
    var a='adgfshj gggggfsg';
    for(var i=0;i<=a.length-1;i++){
        // console.log(a[i])
        console.log(a.charAt(i))
    }
    
  • 字符串.charCodeAt(index) 返回该索引号上的字符的ASCII码(索引)

  • 字符串[index] 返回index位置上的字符 该方法有兼容性问题 ,IE8+才支持,与charAt方法等效

​ 案例:统计一个字符串中出现次数最多的字符

concat

concat方法用于拼接两个或多个字符串,返回一个新的字符串,不改变原字符串

字符串1.concat(字符串2[,字符串3,....])

'a'.concat('b','c') //'abc'

如果参数不是字符串会先将其转化为字符串再拼接

var one = 1;
var two = 2;
var three = '3';

''.concat(one, two, three) // "123"
one + two + three // "33"

作为比对,加号运算符在两个运算数都是数值的,不会转换类型,所以的到的是一个两个字符的字符串

slice

字符串实例的slice方法用于从原字符串取出子字符串并返回子字符串,不改变原字符串

字符串.slice(star,end),它的第一个参数是子字符串开始的位置(包含该开始的位置),第二个参数是子字符串结束的位置但不包含该结束的位置(包前不包后)

var one = 1;
var two = 2;
var three = '3';

''.concat(one, two, three) // "123"
one + two + three // "33"

如果省略第二个参数,则表示子字符串一直到原子符串的结尾

'JavaScript'.slice(4) // "Script"

如果参数为负值,则表示从结尾开始倒数计算的位置,即该负值加上字符串的长度

'JavaScript'.slice(-6) // "Script"
'JavaScript'.slice(0, -6) // "Java"
'JavaScript'.slice(-2, -1) // "p"

如果第一个参数大于第二个参数(正值的情况下),则slice方法返回一个空字符串、

'JavaScript'.slice(2, 1) // ""

substring方法和slice放法很像,但是substring方法有些规则很违反直觉,推荐使用slice方法,substring方法不讲了,感兴趣的自己查找

substring和slice的区别,substring可以使用负整数

substring 不建议使用,优先使用slice

substr

作用:从原字符串中取出子字符串并返回子字符串,不改变原字符串

格式:字符串.substr(star, length)

返回值:截取出来的字符串

第一个参数是子字符串开始的位置(从0开始计算)包含该开始位置,第二个参数是子字符串的长度

'JavaScript'.substr(4, 6) // "Script"

如果省略第二个参数则表示子字符串从开始位置一直到原字符串的结束

'JavaScript'.substr(4) // "Script"

如果第一个参数是负数则表示倒数计算的字符位置,如果第二个参数是负数则会自动被转化为0,表示子字符串的长度是0,因此会返回一个空字符串

'JavaScript'.substr(-6) // "Script"
'JavaScript'.substr(4, -1) // ""

split

作用:split方法按照给定规则分隔字符串,返回一个由分隔出来的子字符串组成的数组。

格式:字符串.split(分隔符,保留多少个)

第二个参数是选填,作用是设置切割完后,保留多少个,默认是全部

返回值:以数组的形式保存每一段的内容,返回值总是一个数组

'a|b|c'.split('|') // ["a", "b", "c"]

如果分隔规则为空字符串,按照一位一位的切割,则返回数组的成员是原字符串的每一个字符

'a|b|c'.split('') // ["a", "|", "b", "|", "c"]

如果省略参数,则返回数组的唯一成员就是原字符串

'a|b|c'.split() // ["a|b|c"]
replace

replace方法用于替换字符串中的字符,一般情况只替换第一个匹配的字符

格式:字符串.replace(‘被替换的字符’,‘要替换为的字符’)

"aaa".replace('a','b')  //"baa"

案例:将’abcabcabc’中的所有a替换为w(关键字隐藏)

方法1:
var str='abcabcabc';
while(str.indexOf('a') !== -1){
	str=str.replace('a','w'); //替换字符串后重新赋值给变量,否则字符串一直是原字符串,会死循环
}
console.log(str);

方法2:
// 使用正则
var a='adgfshj gggggfsg';
a=a.replace(/f/g,'*')
console.log(a)

方法3:
var a='11adgfshjf gggggfsg22';
console.log(a.split('f'));   //["11adg", "shj", " ggggg", "sg22"]
console.log(a.split('f').join('**')); //11adg**shj** ggggg**sg22
  • toUpperCase() //转换大写
  • toLowerCase() //转换小写

垃圾回收机制

程序在长期运行中也会产生垃圾,垃圾积攒过多会导致程序的运行过慢,我们需要一种垃圾回收机制来处理来处理程序在运行过程中产生的垃圾

  • 当一个对象没有任何的变量或者属性对他进行引用,此时我们将永远无法再对该对象进行操作,这个对象就是一个垃圾数据,这种对象过多会占用大量的内存空间,需要对这种对象进行清理,在js中拥有自动的垃圾回收机制,会自动将这种对象从内存中销毁,我们不需要也无法手动的进行垃圾回收操作,我们需要做的就是将不再使用的对象设置为null即可,就像是放到了垃圾箱一样,到时垃圾回收机制就会自动将垃圾清理掉

    var obj=new Object();  
    obj=null;  //对象不在使用后赋值为null
    

js的组成

javascript 组成

  • ECMAScript :js语法,js基础 我们学习的是ECMAScript标准规定的基本语法,无法实现页面交互效果

  • DOM 页面对象模型

  • BOM:浏览器对象模型

    DOM和BOM是web APIs,web APIs是W3C组织的标准

API与Web APIs

  • api就是给程序员提供的一种工具,以便能更轻松的实现想要的完成的功能,而又无需了解源码或内部工作机制的细节,直接使用即可

  • Web APIs是浏览器提供的一套操作浏览器功能和页面元素的api(DOM和BOM)

  • web API很多都是方法(函数),一般都有输入和输出(函数的传参和返回值)

学习web Api可以结合前面学习内置对象方法的思路学习

案例:网上查看详细的api

DOM

DOM,全称是Document Object Model文档对象模型,是w3c推荐的处理html的标准编程接口。

js是通过一系列DOM接口来对html文档进行操作的,可以改变网页的内容,结构和样式

一套操作页面元素的属性和方法

DOM树

dom是一个以树状结构存在的内容,dom的顶级是Document表示当前文档,因为在pc端的文档是插入浏览器里运行的,所以在pc端,document上面还有一个window

操作dom,从document,各种标签,文本,属性,样式的操作

window下有一个document对不对? 答案对

在手机端document上不一定是window,比如在微信里是wechat,但是window下有一个document是一定的

看dom树图片

  • 文档:一个html网页就是一份文档,在DOM中使用document表示

  • 元素:html文档中的所有标签都是元素,DOM中使用element表示

  • 节点:网页中的所有内容都是节点(标签,属性,文本,注释等),在DOM中节点使用node表示

    节点是构成html文档的最基本的单元,网页中的每一个部分都可以称为是一个节点,

    比如:html标签(元素),属性,文本,注释,整个文档都是一个节点

    根据节点的类型不同,对节点进行了分类:比如标签是元素节点,属性是属性节点,文本是文本节点(空格换行也是文本节点),文档是文档节点

    js中常用的节点分类

    • 文档节点:一个html网页就是一个文档,在DOM中使用document表示

      document是页面中最大的节点,只能有一个,是承载所有节点的容器,不属于元素,根节点

    • 元素节点:html文档中的所有标签都是元素节点

      • html是一个页面中最大的元素节点,承载所有其他节点,是根元素节点

      • 其他元素节点:

        head body div p ul table…都是元素节点,只是不同的标签在页面中表现形式不一样

    • 属性节点:元素的属性

      属性节点不作为独立节点出现,必须依赖于元素,没有元素,属性节点就是文本

    • 文本节点:html标签中的文本内容

      每一段文本内容都是一个文本节点,包含空格和换行

      一般作为元素节点的子节点存在,用来表示该元素节点在页面上显示的内容

    • 注释节点:页面中书写的注释内容

      作为独立节点出现,作为说明文本使用

  • 对象:DOM把文档,元素,节点都转换为一个对象

  • 模型:使用模型来表示对象之间的关系,这样可以方便我来获取对象(dom树)

  • 我们获取过来的DOM元素是一个对象,所以称为文档对象模型

节点
节点属性

​ 作用 :用来描述这个节点的信息,相同类型的不同节点,有相同的属性名,但是值不一样

  • 节点的类型不同,属性和方法也都不尽相同,一般节点至少拥有nodeType(节点类型),nodeName(节点名),nodeValue(节点值)这三个基本属性

    • 文档节点

      • 节点名(nodeName)为#document
      • 节点类型(nodeType)为9
      • 节点值(nodeValue)为null
    • 元素节点

      • 节点名(nodeName)为大写标签名(全大写)
      • 节点类型(nodeType)为1
      • 节点值(nodeValue)为null
    • 属性节点

      • 节点名(nodeName)为属性名
      • 节点类型(nodeType)为2
      • 节点值(nodeValue)为属性值
    • 文本节点

      • 节点名(nodeName):所有文本节点的名称全部为: #text

      • 节点类型(nodeType)为3

      • 节点值(nodeValue)为文本内容(包括文字,空格和换行)

    • 注释节点

      • 节点名:所有注释节点的名称都为 #comment
      • 节点类型(nodeType)为8
      • 节点值:注释内容 (包含换行和空格)

    在实际开发中,节点操作主要操作的是元素节点

节点操作:

不光操作元素还要操作 注释节点,文本节点,属性节点

获取节点(获取元素节点,文本节点,属性节点等…)

创建节点:通过js的语法来创建一个标签,文本,注释等

插入节点:把创建的节点插入到另一个节点,出现父子结构

删除节点:把一个已经存在的节点删除

替换节点:使用创建的节点去替换一个已经存在的节点

克隆节点:把一个已经存在的节点复制一份一模一样的

事件

  • 事件就是用户和文档或浏览器窗口中发生的交互行为,比如点击按钮,鼠标移动,按下键盘上的某个键,关闭窗口,简单的理解:触发响应机制
  • html和js之间的交互是通过事件实现的,事件是可以被js侦测到的行为
事件是由三部分组成的(事件三要素) :

事件源,事件类型,事件处理函数(程序)

  • 事件源: 在谁身上绑定事件 比如:按钮

  • 事件类型,如何触发,什么事件,比如:鼠标点击事件(click)鼠标经过事件

    on是绑定事件的方式 onclick

  • 事件处理函数(程序):当行为发生的时候,执行哪一个函数。通过一个函数赋值的方式

  • 执行事件是的步骤:1:获取事件源 2:注册(绑定)事件 3:添加事件处理程序(采用函数赋值的形式)

事件注册的方式

事件注册/事件绑定:即给元素添加事件

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


  • 传统注册事件的方式:利用on开头的事件 此方法没有兼容性问题

  • 特点:

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

    ​ 因为传统方式的注册事件实际是赋值的行为

  • 采用传统注册事件的方式注册事件时的绑定方式

    • 1我们可以开始标签中通过事件对应的事件属性设置一些js代码来绑定事件,这样当事件被触发时这些代码就会执行
<button type="button" id="btn" onclick="alert('今天天气很不错');">普通按钮</button>

onclik是鼠标点击时元素时触发

这种通过在标签上设置事件属性来执行代码的写法不符合结构,表现,分为相分离的原则,结构和行为耦合,不方便维护,不推荐使用

​ - 2:在js中获取对象,通过对对象的指定属性(onclick是元素的一个属性)绑定回调函数的形式来响应事件,这样当事件触发时其对应的函数将会被调用

	格式:事件源.事件类型=事件处理程序
var btn=document.getElementById('id属性的属性值');   //获取按钮对象
btn.onclick=function(){       //为按钮对象的单击事件绑定的函数,称为单击响应函数
	alert('今天下雨了')
}

  • 浏览器在加载一个html文件时,是按照自上向下的顺序加载的,读取到一行就执行一行,如果将script标签写在页面的上面(head内,body前)在代码执行时页面还没有加载,DOM对象也还没有加载,获取DOM对象时就会获取不到,解决这个问题的方法,

    • script标签写在前头时,给window对象绑定onload事件,onload事件会在整个页面加载完成之后再触发,该事件对应的响应函数将会在页面加载完成后执行

      window.onload=function(){
      	页面要执行的js代码
      }
      

      这种方法会先加载js再加载html元素,会造成元素渲染的延迟

    • 将script标签写在body结束标签的前面,页面元素的后面,这样会在页面元素加载完后再加载script

常见的事件

click 鼠标点击事件

focus 获取焦点事件

blur 失去焦点事件

。。。。。

**注意:**传统注册事件的方式:利用on开头的事件,特点:注册事件的唯一性,同一个元素的同一个事件只能设置一个事件处理函数,最后注册的处理函数将会覆盖前面注册的处理函数

获取页面中的元素

浏览器为我们提供了文档节点对象document,这个对象是Window对象的属性可以在页面中直接使用,文档节点代表的是整个网页 console.log(document)

获取元素节点要通过document对象调用

通过js获取页面中的元素分为两类标签

  • 获取特殊元素(body,html,head)

获取html,返回html元素对象 document.documentElement

head : document.head

获取body ,返回body元素对象 document.body

- 常规元素 
 - 元素的id名,可以直接当做一个变量来使用,这个变量就代表着这个元素
 
   <div id="sy">6666</div>
 
   ---js
   
   console.log(sy);//返回的就是这个div元素
	- 通过id属性获取一个元素节点对象

​```var element=document.getElementById('id属性的属性值')```?? 不能在元素范围内使用name属性获取

参数id是大小写敏感的字符串   

返回值:返回一个匹配到Id的DOM element对象即元素对象,若在当前document中没有找到,则返回null

console.log(typeOf element); //查看返回值类型
console.dir(element)
//consoel.dir方法打印我们返回的元素对象,可以更好的查看元素对象里的属性和方法

- 通过标签名获取一组元素节点对象

  - document.getElementsByTagName('标签名')     在document的范围下查找匹配条件的元素

  - 父元素A.getElementsByTagName('标签名');      在元素A的范围下查找匹配条件的子元素

    父元素必须指明是哪一个元素对象,获取到的元素不包含父元素自己

var divBox=document.getElementsByTagName('div');
var divA=divBox[0].getElementsByTagName('a');
console.log(divA)
```

返回值:是一个伪数组,数组的常用方法使用不了

将所有查询到元素对象都会封装到数组中返回,即使只查询一个符合条件的元素对象也会封装到数组中返回,如果在页面中没有匹配到的元素,返回的是空的伪数组

- 因为得到的是一个对象集合,所以在操作里面的元素时需要遍历   使用for循环遍历数组,使用索引获取元素

  ```
  var aBob=document.getElementsByTagName('a');
  for(var i=0;i<=b.length;i++){
  	console.log(aBox[i])
  }
  ```

​ - 得到的元素对象是动态的,即元素变化,返回值也会变化

  • 通过name属性获取一组元素节点对象,name属性大多是设置在表单元素上的,但其实所有的元素都可以设置name属性

    格式: document.getElementsByName(‘name属性的属性值’) ?? 不能在元素范围内使用name属性获取

    返回值:是一个伪数组,数组的常用方法使用不了

    ​ 将所有查询到元素对象都会封装到数组中返回,即使只查询一个符合条件的元素对象也会封装到数组中返回,如果在页面中没有匹配到的元素,返回的是空的伪数组

    ​ 表单元素,通过name属性获取一组元素节点对象,返回一个数组

  • 通过h5新增的方法获取(有兼容性问题,ie9以上才支持)

    • 根据类名获取某些元素集合

      ​ document.getElementByClassName(‘类名’)

      ​ 元素.getElementByClassName(‘类名’)

      返回值:是一个伪数组,数组的常用方法使用不了

      将所有查询到元素对象都会封装到数组中返回,即使只查询一个符合条件的元素对象也会封装到数组中返回,如果在页面中没有匹配到的元素,返回的是空的伪数组

      var bubu=document.getElementsByClassName('bubu')[0].getElementsByClassName('cell');
      console.log(bubu);
      //获取原属范围内的class类名为cell的元素
      
    • 根据选择器获取元素,选择器需要加符号 .className #id值 (ie低版本有兼容性问题)

      • 格式:ducument(或元素).querySelector(‘选择器’)

        ​ 元素.querySelector(‘选择器’)

        能css中写的选择器在这里都可以使用

      • 返回值:返回指定选择器的第一个元素对象,没有选择器匹配到的元素则返回null

      document.querySelector('.box')
      document.querySelector('#box')
      document.querySelector('div')
      
          <div id="dBox" class="bubu" name='66'>
              <a href="" class="cell" id="fan" name="66">33
                  <span>yy</span>
              </a>
              <a href="" id="sun" name='77'>33</a>
          </div>
      
          var a=document.querySelector('#dBox > #fan.cell')
          var b=document.querySelector('#fan ~ #sun')
          var c=a.querySelector('span')
          var d=document.querySelector('#dBox > a:nth-child(1)')
          console.log(a)
          console.log(b);
          console.log(c);
          console.log(d);
      
      • 查找与指定选择器匹配的所有元素

      • 返回值:是一个伪数组,数组的常用方法使用不了

        将所有查询到元素对象都会封装到数组中返回,即使只查询一个符合条件的元素对象也会封装到数组中返回,如果在页面中没有匹配到的元素,返回的是空的伪数组

      • 使用querySelectorAll方法获取的元素合集可以使用foreach,不是数组的foreach而是人家自带的,使用方式时一样的

      • document.querySelectorAll('.box')
        document.querySelectorAll('#box')
        document.querySelectorAll('div')
        

关于DOM操作,我们主要针对于元素的操作,主要有创建 增删改查

操作元素

  • 元素的属性

    • 原生属性

      id clss src href type name border(table标签)style(给元素设置行内样式)

    • 自定义属性

      index abc aaa

      自定义属性不是标签原生自带的属性,是我们自己定义的属性,是为了将数据保存在页面中而不需要将数据保存在数据库中,方便直接使用数据

    • H5自定义属性

      date-XXXX 以data-开头的属性

      h5规定自定义属性以data-开头作为属性名并且赋值(其实也可以不以data-开头,但是这样不容易区分是内置属性还是自定义属性

我们可以通过DOM(属性)操作元素来改变元素里面的内容,元素的属性等

操作元素的原生属性

格式:

读:元素.属性名 获取元素的该属性值

写:元素.属性名=值 设置

只能获取标签自带的内置属性不能获取自定义属性

class除外,操作类名使用 元素.className

  • 操作常见元素的属性

如果需要读写元素节点的属性,直接使用 元素.属性名src href id alt title等

var boj=document.getElementById('id属性的属性值');
console.log(obj.id)  
console.log(obj.name) 
console.log(obj.value)
var img=document.getElementByTagName('img');
img.src='.././a.jpg'

但是class属性不能采用这种方式,读写class属性时需要使用 元素.className

操做表单元素的属性

利用DOM可以操作如下表单元素的属性:type value checked selected disabled等

var input=document.getElementByTagName('input');
input.value='777'
//表单里的值通过value属性来读写
input.type='password'


var button=document.getElementByTagName('button');
button.onclick=function(){
  //this是事件函数的调用者
  //按钮禁用
	this.disable=true;	
}
----发送按钮倒计时

	<input type="text"><button>发送</button>
	
-----js
     //获取元素
      var fsbtn=document.querySelector('button');
      //给按钮绑定点击事件
      fsbtn.addEventListener('click',function(){
         //点击后禁用定时器
         this.disabled=true;
          //倒计时初始值
          var num=3;
          this.innerText=num;
          //设置定时器,每隔一秒调用一次回调函数
          var timeIn=setInterval(() => {

                if(num==0){
                  //当倒计时倒数倒0的时候,清除定时器
                    clearInterval(timeIn);
                    //按钮取消禁用
                    fsbtn.disabled=false;
                    //按钮内容为发送
                    fsbtn.innerText='发送';
                     //倒计时计数恢复初始值,已备下一次倒计时使用 
                    num=3;
                }else{
                    //倒计时 数字自减,并显示在页面上
                    this.innerText= --num; 
                };
                
          }, 1000);
      })
自定义属性操作

不能使用点语法操作自定义属性

<div data-index='1'></div>
或者使用js设置自定义属性
element.setAttribute('data-index',2)
  • 获取(写入)属性

    • 获取属性

      element.getAttribute(‘属性名’)

      //不论设置元素属性时设置的是什么类型的数据,获取到的都是字符串

    • 设置属性

      element.setAttribute(‘属性名’,‘属性值’);

    • 删除属性

    ​ element.removeAttribute(‘属性名’) 标签的原生属性也可以删除,比如a标签的href属性

这种方式主要用来操作自定义属性,H5自定义属性,但也可以操作内置属性

但推荐内置属性还是使用element.属性名的方式来操作,自定义属性推荐使用setAttribute

element.setAttribute('data-index','2'); 
element.setAttribute('class','abc'); //注意以这种方式设置class属性,写的是class而不是className

 d.href=10;
console.log(typeof d.getAttribute('href'));  //检测获取到的数据类型  string
//如果设置元素属性时设置的是什么类型的数据,获取到的都是字符串
H5自定义属性

dataset方法有兼容性问题,只有ie11才开始支持,所以不推荐使用,也可以不讲,可以使用上一套方法操作

每一个元素身上都有一个dataset属性,它是一个对象,里面包含了设置在元素上的所有H5自定义属性

key是除了data-以外的内容:vlue是这个属性的属性值

操作H5自定义属性,直接在dataset里面进行操作即可(使用的是操作对象属性方法)

格式:标签上写data-index=‘010’

​ 读 element.dataset.index Element.dataset[‘index’]

​ 写 Element.dataset['a] =“011” 原对象里有是修改属性的值,没有是向对象里增加属性,映射到标签上 是 data-a=“011”

​ 删除属性 delete element.datasert.index 删除dataset对象里的index成员

​ 这种方式设置的自定义属性里面有多个-链接的单词,我们获取时采用驼峰命名法

​ 如:element.dataset.myIndex``````element.dataset['myIndex']

等同于

element.setAttribute(‘data-index’)

操作元素的类名

作用:修改元素的class属性,来达到修改元素样式的目的,使用于样式较多或者功能复杂的情况

使用步骤:需要在css样式表中写入class类名所对应的样式,再通过该方式绑定在元素上

class是个保留字,所以使用className来操作元素的类名属性

  • 按照原生属性操作

​ 设置类名 元素.className=“box”

​ 修改类名 元素.className=“新值1 新值2”

​ 注意:clssName会直接更改元素的类名,会覆盖原先的类名

​ 删除类名 获取原类名,执行操作字符串的方法, 截取字符串等

​ 或者重新写一遍

  • H5标准提供的API

    元素身上都有一个classList属性 ,里面包含了该元素的所有的类型

    这个classList提供了一系列的方法来操作

    • add()

      元素.classList.add(‘你要添加的类名’)

    • remove()

      元素.classList.remove(‘你要移除的类名’)

    • toggle() toggle 开关

      元素.classList.toggle(‘你要切换到的类名’)

      当元素有这个类名时,就删除,当元素没有这个类名时就添加

    -----点击显示下来框,通过给元素添加删除属性名
    .top-box{
                width: 100%;
                height: 50px;
                position: absolute;
                top:0;
                left:0;
                background-color: aquamarine;
                transition:all 1s;
            }
            .show{
                top:-50px;
            }
            .btn{
                margin-top: 150px;
            }
    --------
    <div class="top-box">
    	下拉框
    </div>
    <div class="btn">按钮</div>
    -------
    var topBox=document.querySelector('.top-box');
    var btn=document.querySelector('.btn');
    btn.onclick=function(){
    	topBox.classList.toggle('show')// 给元素动态添加类名,有就删除,没有就添加
    }
    

案例:密码输入框,掩文和明文状态的切换(input标签的type属性为text或者password之间切换)

​ 关闭广告栏 (dispaly:none)

​ 下拉菜单在鼠标滑过时才显示

​ 页面开关灯

​ 排他按钮,点击一个按钮前先清除其他所有按钮的选中状态再设置点击按钮的样式

操作元素样式

我们可以通过js来操作元素的样式,来修改元素的大小,颜色,位置等

样式分为两大类:行内样式 非行内样式

  • 行内样式
    • 操作元素原生的样式属性

​ 获取格式:元素.style 操作元素的行内样式(可读写)

​ 获取到的内容是一个对象,里面包含元素所有的可设置样式,我们想要获取哪个样式就使用操作对象的方式直接获取就可以了,但是只能获取到行内,如果我们的样式是写在css样式表中的,样式可以起作用,但是通过元素.style的方式获取不到css样式表设置的样式

​ 设置:元素.style.指定样式=值

​ 实际是操作的元素的行内样式,行内样式权重(1000)比较高

​ 使用情况,样式比较少或者功能简单的情况下使用

​ js里的样式采用驼峰命名的方式

element.style.fontSize='16px';
//获取设置元素原生属性style里的样式
element.style.backgroundColor='red';
  • 非行内样式

    • 获取非行内样式

      • window.getComputedStyle(要获取样式的元素) 这是window对象里的一个方法

      • 返回值:一个对象,里面包含所有可设置的样式,每一个样式都有值,没有设置的样式采用默认值

      • 获取指定样式:需要获取哪个样式,直降使用访问对象的方式在返回的样式对象里访问即可

        只能获取不能设置或修改样式

      • 兼容性:标准浏览器适用

     var style=window.getComputedStyle(changeBox); 
     //获取id为changeBox的元素的非行内样式 得到的是一个对象里面是所有可以设置的样式
     console.log(style.width);
     //使用点运算符获取对象里的样式使,如果样式中带有中划线-,我们在js中要使用驼峰的形式书写样式,因为在js中-表示的是减法运算符,会执行减法的操作
     //   console.log(style.background-color);//报错
     console.log(style.backgroundColor);//得到的结果会给我们转换为rbg值
     console.log(style['background-color']);//使用方括号运算符获取css样式对象里的样式
    
    • 元素.currentStyle 属性

      • 兼容性:IE低版本适用

      • 返回值:返回值是一个对象,里面包含元素所有可设置的样式,每一个样式都有值,没有设置的采用默认值

      • 获取指定样式:需要获取哪个样式,直降使用访问对象的方式在返回的样式对象里访问即可

        只能获取不能设置或修改样式

注意: 使用点运算符获取对象里的样式使,如果样式中带有中划线-,我们在js中要使用驼峰的形式书写样式,因为在js中-表示的是减法运算符,会执行减法的操作

使用方括号运算符获取css样式对象里的样式,在字符串中可以写中划线-

这两种获取元素非行内样式的方式有兼容性问题,但是不能使用短路表达式来处理兼容性问题,因为他们是报错;

  • 什么情况下使用短路表达式

使用短路表达式兼容前提是,代码执行不能报错即方法或者属性执行没有问题,只是拿不到值的时候可以使用短路表达式

//----------封装一个函数获取元素的样式(处理兼容性问题)    
    /* 
      @param {ELEMENT} element - 要获取样式的元素
      @param {STRING}  style - 要获取的样式字符串
      @return {STRING} 获取到的元素样式的值

      */

     //-----处理获取元素样式的兼容性问题的函数
     function getStyle(element,style){
      //判断当前浏览器的window对象里有没有getComputedStyle属性,有则返回true,没有则返回false
      //在低版本的ie浏览器中的window对象里没有getComputedStyle属性
      if('getComputedStyle' in window){
        //有 则是标准浏览器
       return  window.getComputedStyle(element)[style]
      }else{
        //IE 低版本
        return element.currentStyle[style]
      }
  }
  getStyle(div,'width')//调用函数获取元素的width样式
@param {参数类型} 参数名 - 对参数的描述信息
@return {函数返回值的类型} 函数返回值的描述
作用:对函数的参数,返回值进行解释
  • 设置元素样式

    设置元素样式只有一种方式,即我们只能设置元素的行内样式,因为非行内样式css样式表是属于在html的外在文件,而我们js不能操作电脑上的文件

    而非要修改非行内样式,只能修改html文件里的style标签的内容或者css文件,不能使用js操作

    (我们js实际是可以通过操作内嵌样式表的style标签通过innerText的方式修改样式,但是一般没人这样做)

    所以我们修改设置元素的样式只能修改行内样式

    格式:元素.style.样式名=‘样式值’

操作元素内容

三种:innerHTML innerText value

  • innerHTML属性,通过这个属性可以获取到元素内部的html代码,即双标签的标签体,对于单标签而言这个属性没有作用

    可读写的属性,可以获取元素里的内容也可写入内容

    推荐使用

    格式:元素.innnerHTML 可读写的属性

    获取元素的超文本内容,获取标签内的所有内容:文本,html标签,保留空格和换行

    返回值:以字符串的形式返回

    写入时:当写入的字符串中包含htnl结构的时候,会自动解析

    var boj=document.getElementById('id属性的属性值');
    console.log(obj.innnerHTML)//获取
    obj.innerHTML='<em>我是</em>新的内容'
    
  • innerText属性

    作用:操作元素的文本内容 可读写的属性

    读: element.innerText获取元素内的所有文本内容

    ​ (包含后代元素内的文本,但不包含html标签,并去除空格和换行)

    写: element.innerText='hello

    ​ 完全覆盖式的写入,当写入内容的字符串中出现html的时候,不会自动解析,按原样输出

  • 元素的原生value属性

    读写属性 元素.value 元素.value=‘你要设置的值’

案例:

 -------选项卡切换,互斥选择
 <div class="change-box">
    <span class="left-btn">左侧</span> <span class="right-btn">右侧</span> <span class="right-btn">右侧</span> <span class="right-btn">右侧</span>
    
    <div class="change-page">11111</div>
    <div class="change-page">2222</div>
    <div class="change-page">3333</div>
    <div class="change-page">44444</div>
</div>
  
   -------------推荐使用
   			var allSpan=document.querySelectorAll('.change-box span');
        var allpage=document.querySelectorAll('.change-page');
        //页面一进来设置第一选项和卡片显示
        allSpan[0].className='bg-color'; 
        allpage[0].style.display='block';

        allSpan.forEach(function(item,index){
            //给每一个元素设置一个自定义属性,这样我们可以在点击之后知道点击的是哪个元素
            item.setAttribute('data-index',index);
            //给数组中的每一个元素给绑定点击事件
            item.onclick=function(){
                //给所有元素清除背景颜色
                allSpan.forEach(function(item){
                    item.className=''
                })
                //给点击的元素增加背景颜色
                item.className='bg-color'
                //在点击元素的时候获取元素的data-index属性就可以知道点击的是哪一个元素了
                console.log(
                '点击的元素索引',item.getAttribute('data-index')
                )
                //将所有的页面隐藏
                allpage.forEach(function(item){
                    item.style.display='none';
                })
                //将点击了对应索引的页面显示
                allpage[item.getAttribute('data-index')].style.display='block'

            }
        })
        
 --------不推荐
   var leftBox=document.querySelector('.left-btn')
   var rightBox=document.querySelector('.right-btn')
   leftBox.onclick=function(){
     rightBox.classList.remove('bg-color')
     leftBox.classList.add('bg-color')
   }
   rightBox.onclick=function(){
     leftBox.classList.remove('bg-color')
     rightBox.classList.add('bg-color')
   }
-----------表单全选
<div class="form">
    <div class="top">
        <label for="all">全选</label>
        <input id="all" type="checkbox"><br>
        <hr>
    </div>
    <div class="cell-input">
        <label for="red">红色</label>
        <input id="red" type="checkbox"> <br>
        <label for="green">绿色</label>
        <input id="green" type="checkbox"><br>
        <label for="green">绿色</label>
        <input id="green" type="checkbox"><br>
        <label for="green">绿色</label>
        <input id="green" type="checkbox"><br>
        <label for="green">绿色</label>
        <input id="green" type="checkbox">
    </div>
</div>
-----------------------------
var allBtn=document.querySelector('#all');// 全选按钮
var cellBtn=document.querySelectorAll('.cell-input input[type="checkbox"]');// 获取全部的选择
//给全选按钮绑定事件
allBtn.onclick=function(){
    //当点击按钮的时候遍历所有选项,给所有选项设置选中属性
    cellBtn.forEach(function(item){
        //遍历的每一个选项的选中状态与全选按钮的状态一样
        item.checked=allBtn.checked
    })
}
//给每一个选则绑定点击事件,每次选择后检测一下是不是所有的选择都是选中状态,如是全部选中就给全选按钮设置选中状态
cellBtn.forEach(function(item){
        //遍历的每一个选项,给每一个选项设置点击事件
       
        item.onclick=function(){
            var isAllChecked=true; //使用一个变量记录是否都选中,假设全都选中
            //点击每一个选项,就循环遍历查看一下每一个选项的选中状态
            cellBtn.forEach(function(item){
                //如果有一个选项的没有选中,就设置isAllChecked为false
                if(!item.checked) isAllChecked=false;
            })
            //循环遍历完之后就可以知道,是不是所有的选项都选中了
            //将最终的结果给全选按钮赋值
            allBtn.checked=isAllChecked;
        }
})

操作节点

获取节点的常用的两种方式

1:利用DOM提供的方法获取元素,这种方式获取节点逻辑性不强且很繁琐。(比较老的用法不太推荐)

​ ducument.getElementById();

ducument.getElementByTagName();

ducument.querySelector()等

2:利用节点层级关系通过节点的属性来获取元素

即利用父子兄弟节点关系来获取元素,这种方式获取节点更加简单,逻辑性强但是兼容性稍差

节点基础概念在上面

---------------------------------案例准备
<!-- 准备一系列节点 -->
<div id="box" class="container">
    <!-- 你好世界 -->
    <span>hello</span>
    hello world
</div>
节点层级
父子节点

根据DOM树,可以把节点划分为不同的层级,常见的是父子兄弟层级,

  • 父级节点

    格式:元素.parentNode

    作用:返回该元素的父节点(亲爸爸),如果指定的节点没有父级节点则返回null

    父节点:大部分都是元素,有特殊的document

console.log(div.parentNode.parentNode.parentNode);   
//找到document后的parentNode返回null
  • 父元素节点

    格式:元素.parentELement

    返回元素的父元素节点

  • 子节点

    • 格式:元素.childNodes

    作用:返回包含在指定节点内的子节点的伪数组,该集合是即时更新的集合

    注意:返回值里包含了所有的子节点,包括元素节点,文本节点等(文本节点包括文字,空格,换行),如果想要通过这个属性只获取元素子节点需要再做处理(遍历返回值,判断每一项的nodeType),所以不推荐使用该属性获取子节点

    • 格式:元素.firstChild

    作用:返回第一个子节点,没有则返回null,同样是包含所有的类型的节点

    • 格式:元素.lastChild

    作用:返回最后一个子节点,没有则返回null,同样是包含所有类型的节点

    以上三个属性不是值返回子元素节点,所以不推荐使用

    使用typeof 检测节点类型 返回的是object

    • 格式:parentNode.children 这是一个只读属性,没有兼容性问题,这个属性必须掌握

      作用:parentNode.children属性只返回parentNode节点内的所有的子元素节点(伪数组),其余类型的节点不会返回

      如果元素内没有子元素节点则返回的是[]

    • 格式:parentNode.firstElementChild 有兼容性为题,ie9以上才支持

      作用:返回第一个子元素,找不到则返回null

    • 格式:parentNode.lastElementChild 有兼容性为题,ie9以上才支持

      作用:返回最后一个子元素,找不到则返回null

    获取到元素节点后我们可以对元素进行操作

    总结:

    在实际开发中使用parenNode.children属性(没有兼容性问题且直接返回子元素节点)

    返回所有子元素节点:parenNode.children

    返回第一个子元素节点 :parenNode.children[0]

    返回最后一个子元素节点:parenNode.children[parenNode.children.length-1]

兄弟节点
  • 元素.nextElementSibling 有兼容性问题,ie9以上才支持

    返回当前元素下一个兄弟节点,找不到则返回null。 (弟弟元素)

  • 元素.previousElementSibling 有兼容性问题,ie9以上才支持

    翻译:previous 在…以前 sibling兄弟姐妹

    返回当前元素上一个兄弟元素节点,找不到则返回null。(哥哥元素)

  • 元素.nextSibling (弟弟节点)

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

  • 元素.previousSibling (哥哥节点)

    返回元素的上一个兄弟节点,包含元素节点或者文本节点

    获取兄弟节点的以上四个属性都不够完美,需要我们自己封装一个没有兼容性问题的函数

    function getNextElementSibling(element){
    	var el=element;
    	while(el=el.nextSibling){
    		if(el.nodeType===1){
    			return el;
    		}
    	}
    	return null;
    }
    
属性节点
  • 格式:元素.attributes

    返回该元素的所有属性节点

    属性节点是元素身上设置的属性,每一个属性都是一个节点

创建添加节点

想要在页面中通过js添加一个新的节点需要分两步:创建节点,添加节点

document.creatElement(‘标签名’)

作用:创建指定标签名的html元素

返回值:返回一个元素节点

  • 添加节点

    父节点.appendChild(要添加进去的子节点)

    作用:在父节点的末尾添加一个子节点,类似于class中的::aftetr伪元素

var li=document.creatElement('li');
var ul=document.querySelector('ul');
ul.appendChild(li);
在获取到的ul标签里添加一个新创建的li标签

---------

var div=document.createElement('div')
div.innerText='布布妈妈';
div.classList.add('77');
div.id='88';
box.appendChild(div)
console.log('>>>>',div)
      var img=new Image();
      img.src='../imgs/cat.png';
      img.onload=function(){
          console.log('图片加载成功');
      }
      img.onerror=function(){
        console.log('图片加载失败');
      }
      document.body.appendChild(img)
  • 在父元素中指定元素的前面添加子节点,类似于class中的::before伪元素

    父节点.insertBefore(子节点,指定元素)

ul.insertBefore(li,ul.children[0])
在ul的第一个子元素前添加新的节点li
删除节点
  • 父节点.removeChild(子节点)

作用:将子节点从父节点里移除

返回值是被删除的节点

该方法可以在创建的节点里删除,也可以直接从页面元素里删除父节点中的一个子节点

var ul=document.querySelector('ul');
ul.removeChild(ul.children[0]);  //删除ul中的第一个子元素
  • 节点.remove()

    作用:把自己从父节点中移除

替换节点

​ 用一个节点替换一个已经存在的节点,可以直接替换页面元素可以替换我们自己创建的节点

格式:父节点.replaceChild(新节点,旧节点);

作用:在父节点下,用新节点替换旧节点,所有的节点都可以替换,但是不能用元素节点替换属性节点

<!-- 准备一系列节点 -->

<div id="box" class="container">
    <!-- 你好世界 -->
    <span>hello</span>
    hello world
</div>

----js

 var p=document.createElement('p');
 p.innerText='我是新来的';
 box.replaceChild(p,box.firstElementChild);  
 //将div#box的第一个元素节点替换成新创建的p标签
复制节点(克隆节点,拷贝节点)

作用:把某一个节点复制一份一模一样的

格式:要被复制的节点.cloneNode(参数)

返回值:返回被复制节点的一个副本

参数:参数为空或者false,是浅拷贝,即只拷贝节点本身,不拷贝节点里的子节点等内容

​ 参数为true时,则是深拷贝,会复制节点本身以及节点里子节点等内容

<!-- 准备一系列节点 -->
<div id="box" class="container">
    <!-- 你好世界 -->
    <span>hello</span>
    hello world
</div>
-------------
  var newBox=box.cloneNode();//只赋值了box元素,不包含元素的内容
  var newBox=box.cloneNode(true);
  console.log(newBox);
  document.body.appendChild(newBox)

三种动态创建元素方式及区别

  • document.write()

  • element.innerHTML

  • document.createElement()

区别:

  • document.write

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

        <div id="box">
            布布妈妈
        </div>
        <button>write写入</button>
        -----------------------------------
        var p=document.createElement('p');
        p.innerText='希望赶快好起来';
        console.log(p)
        box.appendChild(p)
        var btn=document.querySelector('button');
        ----------------页面绘制完后点击按钮使用write写入内容,会导致页面的全部重绘
        btn.onclick=function(){
            document.write('<p>郑州加油</p>')
        }
    
  • innerHTML 是将内容写入某个DOm节点,不会导致页面的全部重绘

    innerHTML 创建多个元素效率更高(不要以拼接字符串的形式,而要采取数组形式拼接),结构稍微复杂

  • creatElement() 创建多个元素效率稍微低一点点,但是结构更加清晰

    创建节点,插入节点会频繁操作DOM,页面浪费性能最大的就是操作dom,所以要尽量减少操作DOM

  • 总结:在不同的浏览器下,innerHTML以数组形式效率要比creatElement高

        var box1=document.querySelector('#box1');
        // 方法1: innerHTML以拼接字符串的形式来创建添加元素,一次次的创建添加这样的效率很低
        for(var i=0;i<=100;i++){
            box1.innerHTML+='<span>你好<span>'
        }
        
        // 方法2:  innerHTML 先将要创建的元素通过数组将字符串拼接好,再一次性的使用innerHTML创建添加,效率最高
        var arr=[];
        for(var i=0;i<=100;i++){
            arr.push('<span>你好<span>')
        }
        console.log(arr)
        box1.innerHTML=arr.join('')
        
        // 方法3 :以document.creatElement()创建元素  效率比方法2要低
        for(var i=0;i<=100;i++){
            var a=document.createElement('a');
            a.innerHTML='你好';
            box1.appendChild(a)
        }
    

    案例作业:

        <table>
            <thead>
                <tr>
                    <th>姓名</th>
                    <th>年龄</th>
                    <th>性格</th>
                </tr>
            </thead>
            <tbody>
                <!-- 动态添加表格数据 ,原表格无数据-->
            </tbody>
        </table>  
        
        --------------------------
        
           var userList=[
                {name:'孙悟空',age:1800,gender:'男'},
                {name:'猪八戒',age:1800,gender:'男'},
                {name:'沙僧',age:1800,gender:'男'},
                {name:'唐三藏',age:1800,gender:'男'}
            ]
            var str='';
            userList.forEach(function(item){
                str+=`<tr>
                            <td>
                                ${item.name}
                            </td>
                            <td>
                                ${item.age}
                            </td>
                            <td>
                                ${item.gender}
                            </td>
                        </tr>`
            })
    
            var tbody=document.querySelector('tbody')
            tbody.innerHTML=str;
            console.log(str)
            console.log(div)
    
  • 文档碎片(筐)

    我们可以使用js创建一个文档碎片节点,它的作用是可以承载(其他)节点,当我们把筐向页面元素里添加的时候,筐是不会进入页面的,是把筐里的内容倒进去

    格式:document.createDocumentFragment()

    返回值:一个文档碎片节点

        ----
          <table>
            <thead>
                <tr>
                    <th>姓名</th>
                    <th>年龄</th>
                    <th>性格</th>
                </tr>
            </thead>
            <tbody>
             <!-- 动态增加表格数据, 保留原数据-->
             <tr>
                <td>bubu</td>
                <td>2</td>
                <td>nan</td>
              </tr>
            </tbody>
        </table>
        -----------
            var userList=[
                {name:'孙悟空',age:1800,gender:'男'},
                {name:'猪八戒',age:1800,gender:'男'},
                {name:'沙僧',age:1800,gender:'男'},
                {name:'唐三藏',age:1800,gender:'男'}
            ]
            //创建一个文档碎片节点 筐
            var frg=document.createDocumentFragment()
            userList.forEach(function(item){
               var tr=document.createElement('tr')
               tr.innerHTML= `<td>
                                ${item.name}
                            </td>
                            <td>
                                ${item.age}
                            </td>
                            <td>
                                ${item.gender}
                            </td>`
                //把每次循环创建好的tr标签添加到筐里
                frg.appendChild(tr)
            })
           console.log(frg)
           var tbody=document.querySelector('tbody')
           //将筐插入进tbody,实际插入的是筐内的内容,筐不进入
           tbody.appendChild(frg)
    
offset

offset翻译为偏移量,通过offset系列相关属性可以得到该元素的位置,大小等

offset系列常用属性,返回数值不能带单位

  • 获取元素自身的大小(宽高)

    • 元素.offsetWidth
    • 元素.offsetHeight

    返回值:得到元素的 内容+padding+border 区域的宽/高度

    返回值不带单位

    元素设置display:none后,返回值为0

  • 获取元素距离带有定位父元素的位置

    • E.offset.offsetParent

      返回元素带有定位的父元素,如果没有父元素或父元素没有开启定位则返回body

      (元素本身可以没有开启绝对定位,假设我们给这个元素开启绝对定位时,他会根据谁来定位,返回的offsetParent就是谁)

    • E.offset.offsetTop

      返回元素相对带有定位的父元素,上方的偏移量,

      如果没有父元素或父元素没有开启定位就相对于body

    • E.offset.offsetLeft

      返回元素相对带有定位的父元素左边框的偏移量,

      如果没有父元素或父元素没有开启定位就相对于body

    当我们在css中给元素设置定位时,采用的时right和bottom的时候,会自动给我们转换为left和top的值返回

  • offset和style的区别

    • offset可以得到任意样式表中样式值

    • offset获取到的数值没有单位

    • offsetWith和offsetHeight 为可见框尺寸 包含 conten( width height) padding border

    • offsetWidth,offsetHeight 属性为只读属性,不能赋值

    • style 只能得到行内样式表中的样式值

    • shyle.width 返回的是带有单位的字符串

    • style.width 返回的是不包含 padding 和border,可读写属性

      所以,我们想要获取元素的大小位置,使用offset更合适,修改元素的值,使用style改变

client

client(翻译 客户端)系列相关属性可以获取元素可视区域的相关信息,可以动态得到该元素的边框大小,元素大小

  • 元素.clientWidth

​ 返回值:自身content(width)+padding 的宽度,不包含边框. ???自己获取时包含了border??

​ 返回数值不带单位

获取浏览器窗口的尺寸:
- BOM级别的获取
	window.innerWidth
	window.innerHeight
		拿到的是包含浏览器滚动条的尺寸
- DOM 级别的获取
	其实就是获取页面的那一部分的尺寸
  通过DOM节点获取浏览器窗口尺寸,不包含滚动条
  本质是获取的时html的width
  格式:document.documentELement.clientWidth;
  • 元素.clientHeight

    返回值:自身包括content(height)+padding 的高度,不包含边框

    ​ 返回数值不带单位

  • 元素.clientTop

    返回元素上边框的大小 boder-top

  • 元素.clientLeft。

    返回元素左边框的大小 border-left

事件注册(绑定事件)

即给元素添加事件

事件注册的两种方式:传统方式(dom0级事件) 和 事件监听注册方式(dom2级事件)

传统方式 event.οnclick=function(){};此方法没有兼容性问题

事件监听注册方式:

  • addEventListenter() 方法

    标准浏览器使用

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

  • attachEvent()方法

    Ie低版本使用

    是一个很老的方法新版本的浏览器对此方法不兼容,试用其来处理老版本的浏览器下不能兼容addEventListenter()的问题

addEventListener 事件监听

  • 格式:eventTarget.addEventListener(type,listener[,usercapture])

    eventTarget.addEventListener()方法将指定的监听器注册到eventTarget(目标对象)上,当该对象触发指定的事件时,就会执行事件处理函数

  • 该方法接收三个参数

    • type :事件类型字符串,比如 ‘click’ ‘mouseover’ 不带on

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

    • usecapture :可选参数 布尔值 默认值为false 为执行冒泡事件流,再在事件冒泡阶段调用事件处理程序,true为执行捕获事件流,在事件捕获阶段调用事件处理程序

  • 格式:事件源.addEventListener(‘事件类型’,事件处理函数)

  • 此方法可以给同一个元素的同一个事件添加多个监听器(事件处理程序),会按照书写顺序依次执行

      box1.addEventListener('click',function(){
                console.log('元素被点击了')
            })
      box1.addEventListener('click',function(){
                console.log('元素被点击了22')
            })     
    

attachEvent() 事件监听器

格式: 事件源.attachEvent(‘on事件类型’,事件处理函数)

  • 该方法只能在ie低版本的浏览器使用

  • 此方法可以给同一个元素的同一个事件添加多个监听器(事件处理程序),会按照书写顺序倒序执行

  • 只用两个参数

var box1=document.getElementById('box1');
 box1.attachEvent('onclick',function(){
            console.log('后执行');
        })
  box1.attachEvent('onclick',function(){
            console.log('先执行');
        })       

—事件监听的兼容性处理

 /**
  * 为什么要封装:因为给元素绑定事件有兼容性问题
  * 必备三个参数:
  *  - 事件源
  *  - 事件类型
  *  - 事件处理函数
  * 
  * 手动抛出异常
  *   格式:  throw new Error("错误提示信息")
  * 
  */
  
-----

  /**
   * 事件绑定的兼容处理
   * @param {ELEMENT} ele 事件源
   * @param {STRING} type 事件类型
   * @param {FUNCTION} handler 事件处理函数
   * 
  */
 function on(ele,type,handler){
     //参数验证,首先要确保事件源ele 必传 ,如果没有传入,直接报错,可以很明确的知道错误,并终止了代码执行
     if(!ele) throw new Error('请传入事件源');
     //ele 事件源传入的类型要限制,必须要是元素节点 可以通过节点是类型来确定传入参数类型
     //元素节点的nodeType为1
     if(ele.nodeType!==1) throw new Error('事件源类型错误');

     //处理兼容性问题
    //判断元素上有没有addEventListener方法,有则是标准浏览器
     if (ele.addEventListener) {
         //标准浏览器
        ele.addEventListener(type,handler);
     }else if(ele.attachEvent){
         //判断元素上有没有attachEvent方法,有则是ie低版本浏览器
         ele.attachEvent('on'+type,handler);
     }else{
         //原始浏览器
         //ele.onclick-function(){} 对象的点语法
         //ele[] 对象的 数组关联语法
         ele['on'+type]=handler; 
     }
}

----

var div=document.querySelector('#box1');

---

// 函数调用
var fn=function(){
 console.log('加油') 
}
on(div,'click',fn)

删除事件(事件解绑)

  • 传统方法的删除事件

    因为是赋值行为,所以解绑时再次赋值为null即可

element.onclick=function(){

   console.log('nihau');

	element.onlick=null;   //删除元素element上的事件 传统绑定事件的方式删除方法为直接赋值为null

}

div.onclick=function(){
	...
}
//设置一个按钮,当点击按钮时将div的click事件解绑
btn.onclick=function(){
	div.onclick=null;
}

知识点铺垫:复杂数据类型比较时比较的是数据在堆内存中的地址

  • 事件监听器的删除事件

    格式:元素.removeEventLIstener(‘事件类型’,要解绑的事件处理函数)

    注意:如果想要解绑事件监听器注册事件,在事件绑定的时候一定要把函数单独书写成一个具名函数,以函数名的形式绑定事件处理函数

div.addEventListener('click',event1)   //此处为函数名 不需要加小括号。不要使用匿名函数 

Var event1=function(){

.....

  div.removeEventLIstener('click',even1)

}
  • IE低版本事件解绑

    detachEvent(‘on事件类型’,要解绑的事件处理函数)

    注意:如果想要解绑事件监听器注册事件,在事件绑定的时候一定要把函数单独书写成一个具名函数,以函数名的形式绑定事件处理函数

/**
 * 事件解绑的兼容处理函数
 * @param {ELEMENT} ele 事件源
 * @param {STRING} type 事件类型
 * @param {FUNCTION} handler 事件处理函数
*/

function off(ele,type,handler){

	  //参数验证,首先要确保事件源ele 必传 ,如果没有传入,直接报错,可以很明确的知道错误,并终止了代码执行
     if(!ele) throw new Error('请传入事件源');
     //ele 事件源传入的类型要限制,必须要是元素节点 可以通过节点是类型来确定传入参数类型
     //元素节点的nodeType为1
     if(ele.nodeType!==1) throw new Error('事件源类型错误');

    if(ele.removerEventListener){
        //标准浏览器
        ele.removerEventListener(type,handler)
    }else if(ele.detachEvent){
        //ie 低版本
        ele.detachEvent('on'+type,handler)
    }else{
        ele['on'+type]=null
    }
}

事件对象

当一个事件触发的时候,对本次事件的描述

​ 事件发生后,根事件相关的一系列信息数据的集合都放在这个对象里,他有很色属性和方法

例子:客服,接电话时会记录通话信息,地址,什么事,需要什么帮助

比如点击行为:

当点击浏览器触发点击行为,要执行事件处理函数,需要记录信息:谁绑定了这个事件,点击了哪个元素,点击坐标,按下哪一个按键,当前触发事件的类型

键盘事件:会得到键盘相关的信息,如按了哪个键,按下的是不是组合键

  • 标准浏览器:

    在事件处理函数中接收一个形参event,是系统帮我们设定事件对象,当我们注册事件时,event对象会被系统自动创建

    我们不需要传递实参过去,在事件触发的时候,由浏览器自动传递实参进去

    (在事件处理函数中可以直接使用event,我们可以不用显示设置形参)

    每一次事件触发,都会获取一个新的事件处理对象,是对当前这一次事件的描述

eventTarget.onclick=function(e){
	console.log(e)
	console.log(event)
}
eventTarget.addEventListener('click',function(e){
 
})   
//这个event即是事件对象 我们还经常写 e或者evt
  • Ie低版本

    事件对象也有兼容性问题,在标准浏览器中,是浏览器给方法传递参数,我们只需要定义形参就可以获取到

    在ie6/7/8 中,浏览器不会给方法传递参数,如果需要的话,需要到window.event中获取

    window.event 在标准浏览器中也可以使用,但官方推荐还是使用兼容性写法

    火狐浏览器中事件对象,只能e表示

    兼容写法 e=e||window.event;

//兼容性写法
div.onclick=function(e){
	e=e||window.event;
	console.log(e)
}
  • 常见的事件对象的属性和方法

    • e.target 返回的是触发事件的对象(元素) ,即点击了哪个元素就返回哪个元素

      this返回的是绑定事件的对象 (元素)

      触发事件的对象和绑定事件的对象不一定就是同一个,因为有事件流

      e.target也有兼容性

    • e.srcElement 返回触发事件的对象 非标准 ie6.7.8

      处理兼容性的写法

      div.onclick=function(e){
          e=e||window.event;  //处理事件对象的兼容性问题
          var target=e.target||e.srcElement;
          console.log(target);
      }
      
    • e.currentTarget与this一样,返回的事绑定事件的元素 只不过currentTarget 有兼容性问题 ie6/7/8不认识,所我们平时使用this就可以了

    • e.type 返回事件的类型

    • e.preventDefaule() 阻止默认事件(默认行为) 标准 比如阻止链接在点击后跳转,阻止提交按钮点击后提交

      var a=document.querySelector('a');
      a.addEventListener('click',function(e){
         //只有一种阻止默认事件的方式
      		e.preventDefaulet();
      })
      
      //传统注册事件方式 有三种阻止默认事件的方式
      a.onlick=function(e){
      	//普通浏览器 
      	e.preventDefault();
      	//低版本的浏览器 ie6/7/8
      	e.returnValue=false;
      	//return false也可以阻止默认行为 ,没有兼容性问题,但是return 后的代码就不在执行了,而且只限于传统注册事件的方式
      	return false}
      
    • e.returnValue=false 该属性 阻止默认事件 非标准 ,ie6/7/8使用(了解)

    • e.stopPropagation() 阻止冒泡 标准 propagation 传播

    • e.cancelBubble 阻止冒泡 ie6/7/8 (了解) cancel 取消 bubble 泡泡

    //阻止事件冒泡的兼容性解决方案
    if(e && e.stopPropagation){
    	e.stopPropagation();
    }else{
    	window.event.cancelBubble=true;
    }
    

事件流

事件的传播

当你在一个元素上触发行为的时候,会按照结构父级的顺序向上传播 行为 ,直到window为止

当触发事件的时候,会按照结构父级的顺序向上传递同类型事件

事件对象里有一个path属性,表示当前事件的传播路径

----课堂展示案例
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #out{
            width: 300px;
            height: 300px;
            background-color: rgb(43, 86, 226);
            border: 1px solid red;
            margin: 20px;
        }
     #center{
            width: 200px;
            height: 200px;
            background-color: rgb(43, 226, 98);
            border: 1px solid red;
            margin: 20px;
        }
        #inner{
            width: 100px;
            height: 100px;
            background-color: blueviolet;
            border: 1px solid red;
            margin: 20px;
        }
    </style>
</head>
<body>
    <div id="out">
        <div id="center">
            <div id="inner"></div>
        </div>
    </div>

    
    <script>
        //当我们点击inner的时候点击行为会从inner到window向上传播
        inner.onclick=function(e){
            console.log('点击了inner',e);
        }
        //给inner设置了移入的行为,只会触发同类型的事件,不会触发祖先元素绑定的点击事件
        inner.onmouseenter=function(){
            console.log('移入--了inner');
        }
        center.onclick=function(){
            console.log('点击了center');
        }
        center.onmouseenter=function(){
            console.log('移入--了center');
        }
        out.onclick=function(){
            console.log('点击了out');
        }

        document.body.onclick=function(){
            console.log('点击了body');
        }
        document.documentElement.onclick=function(){
            console.log('点击了html');
        }
        document.onclick=function(){
            console.log('点击了doucment');
        }

        window.onclick=function(){
            console.log('点击了window');
        }

    </script>

<script>
  //----事件监听绑定事件
   inner.addEventListener('click',function(e){
  		console.log('点击了inner',e.target,e.path);
		},true)

    center.addEventListener('click',function(e){
      console.log('点击了center',e.target,e.path);
    },true)

   out.addEventListener('click',function(e){
     console.log('点击了out',e.target,e.path);
   },true)

 		document.body.addEventListener('click',function(e){
      console.log('点击了body',e.target,e.path);
    },true)

   document.documentElement.addEventListener('click',function(e){
     console.log('点击了html',e.target,e.path);
   },true)

	 document.addEventListener('click',function(e){
     console.log('点击了document',e.target,e.path);
   },true)

	 window.addEventListener('click',function(e){
     console.log('点击了window',e.target,e.path);
   },true)

</script>

</body>
</html>
  • 事件目标. target属性

    • 准确触发事件的那个元素

    • 在事件对象有一个target属性表示事件目标,表示本次事件触发的时候,准确触发事件的的元素,而不是绑定事件的事件源

      特点:Ie低版本不支持target属性,在ie低版本使用srcElement

    处理兼容性:var target=e.target||e.srcElement

  • 事件流

​ 事件发生时会元素节点之间按照特定的顺序传播,这个传播过程即为DOM事件流

DOM事件流分为3个阶段

  • 捕获阶段 像是我们向水里扔一块石头,首先它会有一个下降的过程,从最顶层向事件发生的最具体元素的捕获过程

    ​ 从 window 到 目标 的顺序来执行所有的事件(ducument html body div (目标阶段))

  • (当前目标阶段???)

  • 冒泡阶段 像是小泡泡一样 再最顶层(具体元素)之后浮到水面上

    从 目标 到 window 的顺序来执行所有的事件

    (目标元素 body html document window)

注意:

  • js代码中只能执行捕获或者冒泡中的一个阶段

  • onclick 和 attachEvent只能得到冒泡阶段

  • addEventLister((type,listener[,usercapture]) 的第三个参数决定事件是按照冒泡还是捕获来执行

    第三个参数为可选参数 ,取值为布尔值 true或false 默认值为false

    false 表示:执行冒泡事件流,在事件冒泡阶段调用事件处理程序

    true表示:执行捕获事件流,在事件捕获阶段调用事件处理程序

  • 在实际开发中我们很少使用事件捕获

  • 有些事件没有冒泡如 blur focus mouseenter museleave ??

  • 事件冒泡 有时候会带来麻烦,有时可以帮助很巧妙的做某些事情。。。 ??

阻止事件传播

因为事件的传播,会导致在一个元素触发行为,会执行多个元素的绑定的事件处理函数

阻止事件传播的方式:

  • 事件对象.stopPropagation() propagation 传播

    标准浏览器

  • 事件对象.cancelBubble=true cancel 取消 bubble气泡

    IE浏览器

    处理兼容性:

inner.onclick=function(e){
    console.log('点击了inner');
    e=e||window.event
    //阻止事件传播
    //这行代码一代执行,从这个元素开始,事件行为就不在传播了
    //阻止事件传播的兼容性处理
    //--- 使用if else分支语句处理兼容性
    if(e.stopPropagation){
        e.stopPropagation()
    }else{
        e.cancelBubble=true;
    }
    //----使用try catch处理兼容性
    try{
        e.stopPropagation()
    }catch(err){
        //此处没有管err错误信息,直接执行备用代码
        e.cancelBubble=true;
    }
  
}
//-------try catch语句 语法
//如果try代码块里的语句没有报错,那么catch就不执行了
//如果try里面的语句报错了,那么就执行catch里面的代码
//在catch 接收的参数err是try里面报错信息

try{
	console.log('尝试代码1')
   // throw new Error('777');//这是我们手动抛出的错误
   console.log(a); //js抛出的错误也可以在catch的err参数接收
    console.log('尝试代码2')
}catch(err){
    console.log('***',err);
	console.log('备用代码')
}

// 尝试代码1  *** Error: 777  备用代码
 

事件委托

事件委托也称为事件代理

事件委托的原理:

不需要给每个子节点都添加绑定事件,而是将子元素的事件绑定到给他们共同的结构父节点上,这样在点击子节点时利用事件冒泡执行设置在父节点上的事件监听函数

可以通过函数的事件对象的target属性 获取被点击的是哪个节点

通过节点的nodeName和tagName判断是什么标签,注意标签名要大写

案例 ul li

​ 如果点击ul,event.target就是ul; 如果点击li,event.target就是li

---css
	    ul{
            padding: 20px;
            background-color: chartreuse;
       }
       ul li{
            border: 1px solid red;
       }
---html
	<ul>
    <li>1我是第1个li</li>
    <li>2我是第2个li</li>
    <li>3我是第3个li</li>
    <li>4我是第4个li</li>
  </ul>
---js
	      var ul=document.querySelector('ul'); //获取ul
	      //给ul绑定点击事件
        ul.addEventListener('click',function(e){
            console.log("点击li,通过事件冒泡将点击事件传播给ul,执行ul绑定的事件处理函数");
            e=e||window.event;//事件对象的兼容处理
            var target=e.target||e.srcElement;//事件目标的兼容性处理
            // taret
            // target就是事件目标,事件触发事件的元素,通过元素节点名可以知道点击哪个元素
           if(target.nodeName==="LI"){
               console.log('点击的是li标签')
           } 
           if(target.nodeName==="UL"){
               console.log('点击的是ul标签')
           } 

        })
  • 事件委托的优点:

1:减少元素事件的绑定

2:只操作一次DOM,提高了程序的性能

3:对新添加进来的子元素也可以执行事件,不需要在从新绑定事件

阻止浏览器默认行为

  • 浏览器默认行为:不需我们手动绑定,本身就带有的事件行为

    • a标签 自带点击行为
    • form标签 自带表单提交,当我们点击了submit按钮后自动表单提交
    • 在页面只能够框选文字,自带框选效果,背景颜色
    • 鼠标右键单击,自动弹出菜单
  • 阻止浏览器的默认行为要在同类型的事件里面阻止

    阻止a标签的自动跳转,那么就在a标签的点击事件里面阻止

    阻止form标签的表单提交,那就在form标签的submit事件里阻止

    方法:事件对象.preventDefault() 标准浏览器

    ​ 事件对象.returnValue=false IE低版本

    兼容性处理:

    <a href="http://www.baidu.com">点击跳转</a>
    --------js
    var a=document.querySelector('a');
            a.addEventListener('click',function(e){
                console.log('我被点击了');
                e=e||window.event;
                //阻止a标签点击事件的默认行为
                      //-------------------------阻止默认行为的兼容处理1
                if(e.preventDefault){
                    //标准浏览器
                    e.preventDefault()
                }else{
                    e.returnValue=false
                }
                       //---------------------------阻止默认行为的兼容处理2
                try{
                    e.preventDefault()
                }catch(err){
                    e.returnValue=false
                }
                //--------------------------------阻止默认行为方法
                //阻止默认行为 直接return flse也可以阻止a标签的默认行为,(有的情况下不好使)但一般情况下都好使
                return false; //在mac上测试不起作用
              
            })
    
       //---阻止在页面中选中文字
       document.onselectstart=function(e){
          e.preventDefault();//------阻止选中文字的默认行为,就无法选中文字了
          alert('请支付后在选择')
       } 
    
       //----右键单击事件
       window.oncontextmenu=function(e){
    	        e=e||window.event;
    	        //阻止右键单击事件的默认行为  菜单
    	        try{
                    e.preventDefault()
                }catch(err){
                    e.returnValue=false
                }
            }
    

js中事件分类:

鼠标事件 键盘事件 浏览器事件 表单事件 拖拽事件 触摸事件(移动端) 其他事件

js中的原生事件(js自带的事件)没有大写字母,全是小写

常用的鼠标事件

  • click 鼠标点击 鼠标左键单击

  • dblclick 鼠标左键双击

​ 如果同时绑定了鼠标单击和双击事件,每一个双击会触发两个单击行为

       var table= document.querySelector('table')
       table.ondblclick=function(){
           console.log('鼠标左键双击');
       }
       table.onclick=function(){
           console.log('鼠标左键单击');
       }
       
       ----使用js触发绑定在元素的click事件
        btn.onclick=function(){
            console.log('点击按钮了')
        }
        btn.click();//使用js触发绑定在元素的click事件
  • contextmenu 鼠标右键单击 鼠标右键触发菜单

  • mousewheel 鼠标滚轮事件

​ 给元素绑定鼠标滚轮事件后,在元素上滚动滚轮时触发,在元素外滚轮不会触发

  • mousemove 鼠标移动

  • mouseover 鼠标移入

  • mouseout 鼠标移出

  • mouseenter 鼠标移入

  • mouseleave 鼠标移出

    • mouseenter 和 mouseover的区别

      mouseenter和mouseleave 事件不会进行事件传播即移入移出子元素不会向祖先元素传播

    Mouseover鼠标经过自身盒子会触发,经过自身盒子内部的子盒子依旧会触发绑定的回调函数,因为会冒泡

    mouseenter只会在经过自身盒子会触发,经过自身盒子内部的子盒子不会触发回调函数,不会冒泡

    根mouseenter搭配鼠标离开mouseleave同样不会冒泡

  • focus 获取鼠标焦点

  • blur 失去鼠标焦点

  • mousedown 鼠标按下

    鼠标按下,不单指鼠标左键按下,鼠标右键,滚轮健,只要是鼠标上的健按下都会触发

  • mouseup 鼠标弹起

    ​ 一个click事件会触发一个down一个up

           var table= document.querySelector('table')
           table.ondblclick=function(){
               console.log('鼠标左键双击');
           }
      
           table.onmousedown=function(){
               console.log('鼠标按下')
           }
           table.onmouseup=function(){
               console.log('鼠标抬起')
           }
    
  • selectstart 开始选择

当想在页面或者元素中选中文档时触发

   document.onselectstart=function(){
      console.log('我要选中文字')
    }
   -----
   document.onselectstart=function(e){
      e.preventDefault();//阻止选中文字的默认行为,就无法选中文字了
      alert('请支付后在选择')
   } 
//禁用右键菜单
document.addEventListener('contextmenu',function(e){
 e.preventDefault();
})
//禁止选中文字
document.addEventListener('selectstart',function(e){
 e.preventDefault();
})

鼠标事件对象

event对象代表事件的状态,根事件相关的一系列信息的集合,现阶段我们主要是用鼠标事件对象mouseEvent和键盘事件对象 keyboardEvent

鼠标事件对象

  • 按下的是哪一个按键

    想要获取按下的是哪一个按钮要给元素绑定mousedown事件,不要绑定click事件,因为点击鼠标右键的时候不会触发click事件而是会出菜单

    鼠标事件对象里面有一个button属性,记录按下的是哪一个按键

    0 表示左键

    1 表示滚轮

    2 表示右键

    box1.onmousedown=function(e){
    	  e=e||window.event;// 处理事件对象兼容
        console.log(e);
    }
    
  • 光标的坐标

    • e.clientX e.clientY

      返回光标相对于浏览器窗口可视区域左边的位置 (X坐标)

      返回光标相对于浏览器窗口可视区域上边的位置 (y坐标)

      页面滚动展示
      
    • e.pageX e.pageY

      返回光标相对于页面文档的左边的位置(X坐标) ie9+ 支持

      返回光标相对于页面文档的上边的位置(y坐标) ie9+ 支持

    • e.screenX e.screenY

      返回光标相对于电脑屏幕的x坐标 Y坐标

    • e.offsetX和e.offsetX

      返回光标距离 触发事件的元素 的左上角的位置

      注意的是光标触发事件的元素(不是事件源,而是实际在身上点击的)

      如果不想按照里面的光标触发元素的左上角来计算坐标,而是按照事件来计算坐标,

      处理方式:给里面的元素添加css样式 pointer- events:none;

      ​ 如果不使用css,则需要使用js来计算,光标距离事件源的位置

      ​ offsetParent 是获取偏移量时的参考的父元素(如果父元素没有设置定位则相对于body)

      ​ offsetLeft ( 如果祖先元素没有设置定位则相对于body的x位置)

      e.pageX是光标距离页面左侧的距离,元素.offsetleft是元素距离页面

------css
    body{
        margin: 0;
        padding: 0;
        width: 2000px;
        height: 2000px;
    }
    .box1{
    
        /* 里面的元素:在你计算坐标的时候不哟啊考虑我 */
        pointer-events: none; 
        
        width: 200px;
        height:200px;
        background-color: aquamarine;
        border: 1px solid red;
        margin:50px;
        margin-top: 50px;


    }
    .box2{
        width: 500px;
        height: 500px;
        background-color: rgb(67, 63, 129);
        border: 1px solid red;
        margin:100px;
    }
------html
  <div class="box2" id="box2" >
        <div class="box1" id="box1">
        </div>
    </div>
------js   

box2.onmousedown=function(e){
    e=e||window.e;
    console.log('----------');
    console.log('clientX',e.clientX,'-----','clientY',e.clientY);
    console.log('pageX',e.pageX,'-----','clientY',e.pageY);
    console.log('offsetX',e.offsetX,'-----','offsetY',e.offsetY);
    console.log('screenX',e.screenX,'-----','screenX',e.screenX);
    
    //----不使用css,使用js来计算,光标距离事件源的位
    console.log(e.pageX,box1.offsetLeft,'----',e.pageX-box2.offsetLeft);(自己写的)
    
    console.log(e.offsetX+box1.offsetLeft-box2.offsetLeft);
    console.log(e.offsetY+box1.offsetTop-box2.offsetTop);
}
document.addEventListener('click',function(e){
	console.log(e.pageX)
})
 实时获取页面中光标的位置 显示到页面中
/* 
 
  获取光标的位置
  page 一套
  client 一套
  如果没有滚动条无所谓,一但有滚动条则拿到的都是相对窗口的
  offset一套
  因为你页面一定会有其他的元素,所以不合适

*/

document.onmousemove=function(e){
    console.log(e.pageX, e.pageY);
}



//----案例:跟随鼠标移动的图片
var img=document.querySelector('img')
document.onmousemove=function(e){
    e=e||window.event
    console.log(e.pageX, e.pageY);
    img.style.left=e.pageX+'px';
    img.style.top=e.pageY+'px';
}
图拍绝对定位,通过偏移量让元素动起来,且图片不会占位置
document.addEventListener('mousemove',function(e){
//在页面中移动,给document注册事件
//mouseover事件 只要我们的鼠标移动1px,就会触发这个事件
//只要我们的鼠标一移动,就会获取最新的鼠标坐标,给图片设置偏移量就可以让图片跟着鼠标动起来
	var x=e.pagex;
	var y=e.pagey;
	pic.style.left=x+'px';
	pic.style.top=y+'px';
	
})
   .text-box{
        width: 400px;
        height: 400px;
        background-color: chartreuse;
        border: 1px solid red;
        position: absolute;
    }
-----html
 <div class="text-box"></div>
------js
    var textBox=document.querySelector('.text-box');
    textBox.addEventListener('mousedown',function(e){
        //鼠标在盒子内点击,获取鼠标在盒子内的坐标
         //获取点击模态框时鼠标落下时光标在元素内的位置
         var x=e.offsetX;
         var y=e.offsetY;
        function fn(e){
            //使用光标在页面中的坐标减去光标在盒子内坐标就是盒子的定位
            textBox.style.left=e.pageX-x+'px';
            textBox.style.top=e.pageY-y+'px';
        }
        //给元素绑定监听事件,当鼠标移动的时候给元素重新定位
        textBox.addEventListener('mousemove',fn)
        //给元素绑定监听事件,当鼠标抬起的时候,清除鼠标移动事件
        textBox.addEventListener('mouseup',function(){
            textBox.removeEventListener('mousemove',fn);
        })
    })
//---在指定的元素中显示跟随者光标走的提示文本

     .text-box{
        width: 400px;
        height: 400px;
        background-color: chartreuse;
        border: 1px solid red;
        margin: 100px;
    }
    .text-del{
        width: 80px;
        height: 20px;
        background-color: cornflowerblue;
        position:fixed  //提示文本相对于浏览器窗口定位
        display: none;
    }

-----html

 <div class="text-box">
 </div>
 <p class="text-del">提示文本</p>
 
---js

    var textBox=document.querySelector('.text-box');
    var textDel=document.querySelector('.text-del');
    textBox.addEventListener('mouseover',function(e){
       textDel.style.display='block';
    })
    textBox.addEventListener('mouseout',function(e){
        textDel.style.display='none'
    })
    textBox.addEventListener('mousemove',function(e){
        e=e||window.event;
        //给坐标+5是为了处理闪烁的问题
       var x=e.clientX+5;
       var Y=e.clientY+5;
       //记住要带单位
       textDel.style.left=x+'px';
       textDel.style.top=Y+'px';
    })
-----------京东商品展示的放大镜效果,注意选择图片最大是正方形,知道尺寸
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        /* 图片展示框 */
        .goods-box{
            width: 300px;
            height: 250px;
            background-color: cyan;
            border: 1px solid red;
            position: relative;
        }
        /* 展示的图片 */
        img{
            width: 100%;
        }
        /* 遮罩层,相对于图片展示框定位 */
        .masker{
            width: 200px;
            height: 170px;
            background-color: rgba(244,39,123,0.3);
            position: absolute;
            display: none;
            cursor:progress;
            /* 设置计算光标在元素父元素内的位置时不要考虑我 */
            pointer-events: none;    
            
        }
        /* 大图片展示框,相对于图片展示框定位  */
        .big-goods{
            width: 400px;
            height: 300px;
            background-color: rgba(60, 52, 129, 0.3);
            position: absolute;
            top:0;
            left: 315px;
            display: none;
            overflow:hidden;
        
        }
        /* 大图,相对于大图展示框定位 */
        .big-goods img{
            width: 150%;
            position: absolute;
        
        }

    </style>
</head>
<body>
    <!-- 图片展示框 -->
    <div class="goods-box">
        <!-- 图片展示 -->
        <img src="../图片素材/by.jpeg" alt="">
        <!-- 遮罩层 -->
        <div class="masker"></div>
        <!-- 大图展示框 -->
        <div class="big-goods">
            <!-- 大图 -->
            <img src="../图片素材/by.jpeg" alt="">
        </div>
    </div>
    <script>
        // 获取图片展示框
var goodsBox=document.querySelector('.goods-box')
// 获取遮罩层
var masker=document.querySelector('.masker')
// 获取大图框
var bigGoods=document.querySelector('.big-goods')
//获取大图元素
var bigImg=document.querySelector('.big-goods img');

//鼠标进入商品展示框就显示遮罩和大图
goodsBox.addEventListener('mouseover',function(){
    masker.style.display='block';
    bigGoods.style.display='block';
})

//鼠标移除商品展示框就隐藏遮罩和大图
goodsBox.addEventListener('mouseout',function(){
    masker.style.display='none';
    bigGoods.style.display='none';
})
// 给图片展示框注册鼠标移动事件
goodsBox.addEventListener('mousemove',fn);

function fn(e){
         e=e||window.event;   
        //e.offsetX为光标在元素展示筐内的位置,本可以直接赋值给遮罩层的移动距离 
        //asker.offsetwidth 是msker盒子的宽高
        var x=e.offsetX-masker.offsetWidth/2;  //e.offsetX等同于 e.pageX-goodsBox.offsetLeft 
        var y=e.offsetY-masker.offsetHeight/2;//e.offsetY 等同于 e.pageY-goodsBox.offsetTop
        //遮罩层最大移动距离=图片展示框的尺寸-遮罩层的尺寸
        var maxX=goodsBox.offsetWidth-masker.offsetWidth;
        var maxY=goodsBox.offsetHeight-masker.offsetHeight;

 
        //设置遮罩层移动的范围
        if(x<=0){
            x=0;
        }else if(x>=maxX) {
            console.log(222);
            x=maxX
        }
        if(y<=0){
            y=0;
        }else if(y>=maxY) {
            console.log(222);
            y=maxY
        }

        //设置遮罩层的移动距离
        masker.style.left=x+'px';
        masker.style.top=y+'px';


        //-----处理大图移动
         //计算大图最大移动距离=大图的尺寸-大图框的尺寸
         var bigMaxWidth=bigImg.offsetWidth-bigGoods.offsetWidth;
         var bigMaxHeight=bigImg.offsetHeight-bigGoods.offsetHeight;
          //获取大图的移动距离=遮罩层移动距离*大图最大移动距离/遮罩层最大移动距离
         var  bigx=x*bigMaxWidth/maxX
         var  bigy=y*bigMaxHeight/maxY
         console.log(bigx,bigy);
        //给大图设置移动距离
         bigImg.style.left=-bigx+'px';
         bigImg.style.top=-bigy+'px';
    }



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

表单事件

表单事件绑定给表单元素和form标签

  • onchange

    表单内容改变时触发

    当表单失焦的时候比较如果和聚焦时内容不一样才叫做改变

       var ipt=document.querySelector('input');
       ---
       ipt.onchange=function(){
             console.log(
                '表单内容改变了'
             )
         }
       ---  
       ipt.oninput=function(){
             console.log('输入内容')
         } 
       --
       ipt.onfocus=function(){
             console.log('聚焦')
         }
       ipt.onblur=function(){
             console.log('失焦')
         }
    
  • oninput 表单输入事件

    只要在表单内输入内容就会触发

  • onfocus 表单聚焦事件

  • onblur 表单失焦时触发

  • onsubmit 表单提交事件

    事件是绑定给form标签使用的

    当点击form里的submit时侯触发

        <form action="">
            <input type="submit">
        </form>
        -----
            var inp=document.querySelector('input')
            var form=document.querySelector('form')
            //-------表单提交事件要绑定给form标签,而不是提交按钮
            form.onsubmit=function(){
                console.log('表单提交');
                return false; //阻止表单提交后刷新页面
            }
            //-------表单重置事件要绑定给form标签,而不是提交按钮
            form.onreset=function(){
                console.log('重置表单内容');
                // return false; //阻止表单提交后刷新页面
            }
    
  • onreset 表单重置事件

    事件是绑定给form标签使用的

    当点击reset按钮时才能触发

键盘事件

键盘事件不是所有元素都能触发,表单元素(有选中的效果),document,window可以触发

  • keydown

    键盘上某个按键按下时触发 能识别所有的按键,实际开发中经常使用

    中文输入法也好使

     var ipt=document.querySelector('input');
     		----
         ipt.onkeydown=function(){
             console.log('键盘按下了')
         }
         ---
           ipt.onkeyup=function(){
             console.log('键盘抬起了')
         }
         ---
         ipt.onkeypress=function(){
             console.log('键盘抬起了222')
         }
    
  • keypress

    必须要准确嵌入到文本筐里内才会触发,即出现在文本框里的内容要和按下的按键一致时才可以触发

    它不能识别功能键 ctrl shift 左右上下箭头等,但是keycode属性能区分大小写

  • keyup

    键盘上某个按键松开(抬起)时触发,任意按键

使用addEventListener 不要加on

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

键盘事件对象

键盘事件对象的信息:

  • 按下哪个键

    • 事件对象里有一个key属性

      作用:表示你按下的那个按键

      兼容问题: ie低版本不能使用

      键盘事件对象的key属性2018以后新增的属性,不经常用,一是因为兼容性问题,二是因为一些键不能表示出来比如:小键盘上的数字键不能表示出来,我们经常使用keycode属性

    • 事件对象里有一个keycode属性

      作用:表示你按下的那个按键,但是以编码的顺序返回(ASC II值中的顺序)

      兼容问题:在火狐FireFox<2的火狐浏览器不支持,但是现在低版本的火狐已经装不上了,市面很少,所以其兼容性可以不用考虑

      ​ 在火狐低版本可以使用键盘事件对象的which属性

      处理兼容:var code=e.keycode||e.which

  • 按下的是不是组合按键

    事件对象里的altkey属性:表示alt键 mac上 option键

    事件对象里的ctrlkey属性:表示ctrl键

    事件对象里的shirftkey属性:表示shift键

    事件对象里的metakey属性:表示win键(菜单键) mac上command键

    ie浏览器 不支持 键盘对象的metakey属性,在ie浏览器中只能靠键盘码去捕获了

    这四个属性的值都是布尔值

    表示你在按下其他按键的时候,如果这四个属性的某一个值为true,表示同时按下了这个按键

            var inp=document.querySelector('input');
            inp.addEventListener('keydown',function(e){
                    e=e||window.event;
                    //判断组合按键
                    //按下大写A,即shift+a
                  if(e.shiftKey&&e.keyCode==65){
                    console.log('你按下的是shift+a');
                  };
                  if(e.shiftKey&&e.ctrlKey&&e.altKey&&e.keyCode==65){
                    console.log('你按下的是shift+ctrl+alt+a');
                  }  
    
            })
    

onkeydown和onkeyup 不区分字母大小写,onkeypress 区分字母大小写

案例:检测用户按下s后使页面的输入框获取光标

        var inp=document.querySelector('input');
   //---此处不应用keydown因为按下后没有及时松开,input框获取光标后会将s字母输入在input框内,所以keyup
        document.addEventListener('keyup',function(e){
                e=e||window.event;
                console.log(e);
                //判断用户按下的是s键,就将页面中的input框获取光标
              if(e.keyCode==83){
                console.log('你按下的是s键');
                inp.focus();//页面input框获取光标
              };
        })
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
<style>
----------------css
.text-con{
    margin:200px 0 0 0;
    width: 300px;
    height: 30px;
    border: 1px solid red;
    position: relative;
}
.text-title{
    width: 300px;
    height: 30px;
    border: 1px solid blueviolet;
    position: absolute;
    display: none;
    top:-30px;
    left:0;
}
input{
    width: 300px;
    height: 30px;
    outline: none;
}
--------------------
</style>
</head>
------------html
<div class="text-con">
    <div class="text-title">
    </div>
  <input class="text-input" type="text">
</div>
<body>
    <script>
------------js
        var inp=document.querySelector('input');
        var textBox=document.querySelector('.text-title');
        //此处为什么用keyup而不是用keydown和keypress,是因为keydown和keypress当按键按下触发时,文字还没有落入文本框
        //keyup当事件触发时,文字已将落入文本框了
        inp.addEventListener('keyup',function(e){
                e=e||window.event;
                //判断用户按下的是s键,就将页面中的input框获取光标
              
                if(inp.value==''){
                   //如果输入框内容为空则隐藏提示框
                    textBox.style.display='none';
                }else{
                    textBox.style.display='block';
                    textBox.innerText=inp.value;
                }

        })
        //当input框失去焦点是就隐藏提示框
        inp.addEventListener('blur',function(e){
                e=e||window.event;
                //判断用户按下的是s键,就将页面中的input框获取光标
                    textBox.style.display='none';
        })
        //当input框获取焦点是就显示提示框
        inp.addEventListener('focus',function(e){
                e=e||window.event;
                //判断用户按下的是s键,就将页面中的input框获取光标
                if(inp.value!=''){
                    textBox.style.display='block';
                }           
        })
    </script>
</body>
</html>

浏览器事件

  • onload。页面加载完毕

  • onscroll 滚动

  • onresize 窗口尺寸改变

  • onoffline 网络断开

    一般会在网络断开时跳转到一个缓存页面,做用户提示

  • ononline 网络恢复 由没网到恢复

      //在控制台network调整网络状态展示
      window.ononline=function(){
             console.log('网络恢复')
         }
       window.onoffline=function(){
             console.log('没网了')
         }
    
  • onhasechange 当地址栏的地址hash值改变时触发

 //改变url的hash值测试
 window.onhashchange=function(){
         console.log('hash变化了')
     }

拖拽事件

drag翻译 拖拽

有一些标签天生可以拖拽,比如img标签,一般元素如果想触发拖拽行为,需要给被拖拽的元素添加一个属性

draggable=“ture”

  • 拖拽事件需要两个元素完成一个完整的拖拽

    • 拖拽元素
    • 目标的元素
  • ondragstrat 拖拽开始

    是绑定在被拖拽的元素上的 ,在按下鼠标开始拖拽的那一瞬间触发开始拖拽事件

  • ondrag 拖拽移动

    是绑定在被拖拽的元素上的

  • ondragend 拖拽结束

    是绑定在被拖拽的元素上的

  • ondragenter 拖拽元素进入目标元素

    绑定给目标元素

    当光标进入目标元素那一瞬间触发

  • ondragleave 拖拽元素离开目标元素

    绑定给目标元素

    当光标离开目标元素那一瞬间触发

  • ondragover 拖拽元素在目标元素里移动

    绑定给目标元素

  • ondrop 拖拽元素在目标元素内放手

    绑定给目标元素

    必须在dragover事件内阻止默认行为

   <div class="box1" id="box1"  draggable="true">
		  //拖拽元素
    </div>
    <div class="box2" id="box2" >
     //目标元素
    </div>
--------
   //------被拖拽元素上绑定的事件
        box1.ondragstart=function(){
            console.log(
            '拖拽开始'
            )
        }
         box1.ondrag=function(){
            console.log(
            '拖拽移动了~'
            )
        }
        box1.ondragend=function(){
            console.log(
            '拖拽结束了~wuwu~'
            )
        }

        //--------目标元素上绑定的拖拽事件
        box2.ondragenter=function(){
            console.log('拖拽元素进入目标元素');
        }
        box2.ondragleave=function(){
            console.log('拖拽元素--离开--目标元素');
        }
        box2.ondragover=function(e){
            e.preventDefault();//为了在目标元素内放手,要在dragover内阻止默认行为
            console.log('拖拽元素--在--目标元素--内移动');
            
        }
        box2.ondrop=function(){
            console.log('拖拽元素--在--目标元素--放手');
        }

触摸事件

(同下移动端事件)

移动端兼容性比较好,我们不需要考虑以前js所提到的那些兼容性问题,可以放心使用原生js书写效果,但是移动端也有自己独特的地方,比如,触摸事件 touch,andriod 和ios都有。

touch对象代表一个触摸点,触摸点可能是一根手指也可能是一根触摸笔,触屏事件可以响应用户手指对屏幕或触控板的操作

  • 常见的触屏事件:

    • touchstart 手指触摸到一个DOM元素时触发

    • touchmove 手指在一个DOM元素上滑动时触发

    • touchend 手指从一个DOM 元素上移动时触发

  • 触摸事件对象

touchEvent 触摸事件对象描述手指在触摸平面删的状态变化,可以描述一个或多个触摸点,使开发者可以检测触点的移动,增加和减少等

​ - 触摸事件对象.touchs

​ 正在触摸屏幕的所有手指的列表

​ - 触摸事件对象.targetTouches

​ 正在触摸当前DOM元素上的手指的列表数组

​ 因为我们一般都是触摸元素,经常使用的事件对象里的targettouches,可以获取到触摸元素的元素有手指

​ 通过获取数组成员的方法获取到某一个手指的指定信息

​ - 触摸事件对象.changedTouches

​ 手指状态发生了改变的列表,从无到有,从有到无的变化

​ 当我们手指离开屏幕时就没有了touches和targetTouches列表,但是会有changedTouches

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>

        #div{
            width: 300px;
            height: 300px;
            border:1px solid red;
        }
    </style>
</head>
<body>
    <div id="div">

    </div>
    <script>
        div.addEventListener('touchstart',function(e){
            console.log('触摸开始',e)
        //   console.log(e.touchs); //正在触摸屏幕的所有手指的列表
        //   console.log(e.targetTouches); //正在触摸当前DOM元素上的手指的列表
        //   console.log(e.changedTouches);//手指状态发生了改变的列表,从无到有,从有到无的变化
        })
      
        div.addEventListener('touchmove',function(e){
            // console.log('触摸移动',e)
        })
      
        //手指离开DOM元素事件
        div.addEventListener('touchend',function(e){
            //当我们手指离开屏幕时就没有了touches和targetTouches列表,但是会有changedTouches
            console.log('手指离开',e)
        })
    </script>
</body>
</html>

其他事件

  • transitioned 过渡结束事件

当给元素设置了过渡属性时,过渡结束时触发,有几个属性过渡,在结束时触发几次

     .box1{
        width: 200px;
        height:200px;
        background-color: aquamarine;
        border: 1px solid red;
        transition: all 1s;

    }
    .box1:hover{
        width:300px;
        background-color: chartreuse;
    }
 ----
    <div class="box1" id="box1">
    </div>
  -----
   box1.ontransitionend=function(){
   	console.log('过渡结束了' )
   }
  • selectstart 开始选择

当想在页面或者元素中选中文档时触发

   document.onselectstart=function(){
      console.log('我要选中文字')
    }
   -----
   document.onselectstart=function(e){
      e.preventDefault();//阻止选中文字的默认行为,就无法选中文字了
      alert('请支付后在选择')
   } 
  • visibilitychange 窗口显示和隐藏

    只绑定给document,当切换选项卡或者显示或缩小窗口时会触发

    document身上有一个属性,叫做visibilityState属性可以表示当前页面是显示还是隐藏状态 hidden或者visibilitySate

    document.onvisibilitychange=function(){
            console.log('窗口显示状态变化了')
   }
function changeDocument(){
    // 可以给document添加一个显示隐藏的监听器
    document.addEventListener('visibilitychange',function(){
        console.log('标签页状态变化')
        // document身上有一个属性,叫做visibilityState属性可以表示当前页面是显示还是隐藏状态 hidden或者visibilitySate
        console.log(document.visibilityState);
        if(document.visibilityState=='hidden'){
            //页面隐藏了
            

        }else{
            //页面显示了

        }
    })
}

BOM

bom(browser object Model)浏览器对象模型 它提供了独立于内容而与浏览器窗口进行交互的对象,其核心对象是window;

bom与dom的区别

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

Dom 文档对象模型,就是吧文档当作一个对象来看待,其顶级对象是document。dom主要学习的是操作页面元素,dom是w3c标准规范

Bom是浏览器对象模型,吧浏览器当作一个对象,bom的顶级对象是window,bom学习的是浏览器窗口交互的一些对象,bom是由浏览器厂商在各自浏览器上定义的,兼容性较差

bom比dom更大,它包含Dom

window (document location navigation screen history)

bom的构成

  • window对象是浏览器的顶级对象,当打开一个页面的时候就有一个window对象,它具有双重角色

    • 它是一个全局对象,定义在全局作用域内的变量,函数都会成为window对象的属性和方法,在调用时可以直接省略window,比如 window.alert()

      Window下一个特殊的属性 window.name,所以我们在给变量命名的时候不要使用name作变量名

    • 它是js访问浏览器窗口的一个接口

      浏览器给我们提供一套操作浏览器的属性和方法

      所有和BOM相关的api都是window.xxx;在js代码书写的时候可以省略window不写-

  • 浏览器尺寸

    指的是浏览器的可视窗口的尺寸,浏览器有可能会出现滚动条,在一般浏览器上滚动条算是浏览器的一部分,在MAC上,是不算的(mac上的滚动条是压在页面上的)

    window对象上有两个属性获取浏览器的尺寸(包含滚动条的尺寸)

    • window.innerWidth 属性 window可省略不写
    • window.innerHeight 属性
  • 窗口加载事件

    • window.onload是窗口(页面)加载事件

      当文档内容(包括图片 ,音视频,脚本文件 css文件)全部加载完毕就会触发该事件,调用处理函数

      window.onload=function(){
      	console.log('资源加载完毕')
      }
      window.addEventListerner('load',function(){})
      

    作用:当需要将js代码写在页面元素的上方时,可以加上window.onload给其设置事件处理函数,当页面元素加载完后再执行里面的代码

    window.onload传统注册事件方式只能写一次,如果有多个,后面的会覆盖前面的

    window.addEventListener 则可以写多个依次执行

    • window.addEventListener('DOMContentLoaded',function(){})
      

      DOMContentLoaded在页面Dom加载完成就会触发 ,不包括图片 css flash等,ie9+才支持

      优点:如果页面图片很多,从用户访问到onload触发可能需要较长的时间,交互效果就不能实现,影响用户体验,此时使用DOMContentLoaded就比较合适

  • 调整窗口大小事件

    window.onresize=function(){
    	console.log('频幕变化了')
    }
    window.addEventListerner('resize',function(){})
    

    浏览器可视窗口改变的时候就会触发该事件

    一般配合innerWidth和innerHeight来判断频幕的尺寸,来做响应视布局

    window.innerWidth为当前屏幕的宽度

    移动端:判断横屏竖屏

  • 定时器

    var time1=window.setTImeout(callback[,延迟时间])
    

setTImeout()用于设置一个定时器,在指定时间后调用回调函数,只调用一次,就结束了这个定时器

调用函数可以直接写函数,或者函数名

延迟时间默认为0,如果写单位必须是毫秒

因为定时器有很多,我们经常给定时器赋值一个标识符

  • 回调函数

    普通话函数是按照代码顺序直接调用的,而这个函数要等待时间,时间到了才调用该函数,因此称为回调函数(干完上件事情后回头再调用这个函数)

    我们之前的onlick=function(){}.等里的函数也是回调函数

  • 清除定时器

    window.clearTimeout(要清楚的定时器名称)
    

    作用:清除之前设置的定时器,window可以省略

  • setInterval

    window.setInterval(回调函数,延迟时间);

    每个一段时间都会调用设置的回调函数,只要不清除定时器一直重复调用

    • 清除定时器

      clearInterval(定时器名称)

      案例 :按钮倒计时 禁用 防止用户多次点击

location对象

  • url是互联网上的标准资源的地址,互联网上的每一个资源都有一个唯一的url,它包含的信息,指出文件的位置以及浏览器该怎么处理它

  • 一个url地址的组成部分:https://www.baidu.com?a=100&b=200#abc

    url格式 : protocol://host[:port]/path/[?query]#fragment

    • Portocol:传输协议 http ftp maito等

      作用:前后端交互的方式

    • host 域名 www.baidu.com

      作用:找到一台服务器电脑

    • port 端口

    ​ 可以省略,省略时使用方案的默认端口 如http的默认端口为80

    • path 路径 由零或者多个/ 符号隔开的字符串

      作用:一般用来表示主机上的一个目录或者文件地址

    • ?a=100&b=200 查询字符串(queryString)

      以键值对的形式 通过&符号分隔开

      作用:可有可没有,不影响打开网页;是打开这个网页时所携带的信息

    • #abc 哈希(hash)(fragmaent 片段)

      作用:锚点定位

window对象里有一个location属性,属性值是个对象,我们将这个属性称为location对象,里面存储着和网页地址相关的信息

  • location对象的属性

    • location.href 当前网页的地址 可读写属性

      window.location.href 获取当前网页的地址

      location.href =“新的网页地址” 设置当前打开页面的url地址,可以实现页面跳转 * 必须掌握

      案例:点击跳转页面. 页面加载后几秒后跳转到新的页面

    • location.host 返回主机域名

    • location.port 返回端口号 如果未写则返回空字符串

    • location.pathname 返回路径

    • Location.search 当前网页地址的查询字符串 *必须掌握

      获取:查询到的是一个字符串,访问网页所有带的信息,我们需要将这个字符串解析后才能使用

      (表单提交)

      https://www.baidu.com?a=88&b=99&c=22#abc

      地址中没有?或者?后没有参数信息的时候,window.location.href的值是空字符串 “”

           var urlSearch=window.location.search; //?a=88&b=99&c=22 获取url后的query参数
              //封装一个解析urlquery参数的函数
              function gerQuery(urlSearch){
                //地址中没有?或者?后没有参数信息的时候,window.location.href的值是空字符串""
                //先判断一些传入的参数,单独处理一下空字符的情况
                var obj={}
                if(urlSearch){
                    //参数不为空时,解析一下字符串后给obj对象赋值
                  var newSerch=urlSearch.slice(1);  //a=88&b=99&c=22
                  var a=newSerch.split('&');  // ["a=88", "b=99", "c=22"]
                  
                  a.forEach(function(item,index){
                      // console.log(item.split('=')[0],'------',item.split('=')[1])
                      obj[item.split('=')[0]]=item.split('=')[1]
                  })
                  console.log(obj); //{a: "88", b: "99", c: "22"}
                }
                return obj;//解析后,以对象的形式返回  {a: "88", b: "99", c: "22"}或{}
                
              }
             console.log( gerQuery(urlSearch));//调用该函数,打印返回值
      
    • location.hash 当前网页的hash值

  • location对象的方法

    • location.reload() 重新加载刷新页面 ,相当于按下浏览器左上角的刷新按钮 相当于f5

      可以有参数,参数为true 为强制刷新 重新从服务器获取数据 ctrl+f5

      注意此语句不要写在一打开页面就可以执行的地方,一进来就刷新,会死循环,多和按钮配合使用

    • location.assign(‘新地址’) 根href一样,可以跳转页面 (重定向页面)

    • location.replace(‘新地址’) 替换当前的页面 因为不记录历史 所以不能回退

navigator 对象

navigator航海家

widown对象下有一个成员叫做:navigator,它是一个对象,包含浏览器的版本信息

它有很多属性,常用的

  • userAgent属性:我们可以知道用户使用的浏览器的版本及型号信息

​ 获取:window.navigator.userAgent

  • appName

    所有的浏览器都是统一的名字 netscape(网景)致敬网景公司 ,ie低版本不是

    window.navigator.appName

  • platform

    浏览器所在操作系统

history对象

window下有一个叫做history的成员,是一个对象,里面包含着一些操作历史记录的属性和方法

作用:可以操作浏览器的前进后退

history包含用户(在浏览器窗口中)访问过的url

  • window.history.back();

​ 作用:回退到上一条历史记录,相当于浏览器左上角的后退按钮

​ 前提:需要有历史记录,否则无法回退

  • window.history.forward() 前进

    作用:前进到下一条历史记录,相当前进按钮

    前提:需要回退过以后才可以前进

  • window.history.go(参数)

    参数为正值 前进

    参数 负值为后退

    参数为0 刷新当前页面

scroll系列属性

scroll系列属性可以动态的获取到元素的大小,滚动距离等

  • E.scrollTop 返回元素被卷去的上侧距离 返回值不带单位

  • E.scrollleft 返回元素被卷去的左侧距离 返回值不带单位

  • E.scrollWidth 返回元素自身的实际内容的宽度,不含边框,返回值不带单位

  • E.scrollHeight 返回元素自身的实际内容的高度,不含边框,返回值不带单位

  • 浏览器scroll滚动事件

window.οnscrοll=function(){}

当浏览器滚动条滚动时触发的事件,不管横向还是竖向只要滚动就会触发

作用:

1: 楼层导航 2 :顶部通栏和回到顶部按钮按钮的显示 3:渐进显示页面 4:瀑布流

  • 获取页面被卷去的头部距离,有兼容性问题,因此被卷去的头部通常有以下几种写法

    • document.documentElement.scrollTop

      使用必须要有 DOCTYPE标签,如果没有则获取的滚动高度一直是0

      可读写属性:设置时直接赋值 ,直接改变浏览器滚动的位置

    • document.body.scrollTop

      使用必须要没有 DOCTYPE标签,如果有则返回的高度一直是0

  • 获取页面卷去的宽度

    • document.documentElement.scrollLeft

      使用必须要有DOCTYPE标签

      可读写属性:设置时直接赋值 ,直接改变浏览器滚动的位置

    • document.body.scrollLeft

      使用必须没有 DOCTYPE标签

 window.onscroll=function(){
      console.log('****',document.documentElement.scrollTop)    
      console.log('****',document.body.scrollTop)   
      //----------以上两种方式的兼容性写法 ,使用逻辑运算符的短路,当前面值为0,会执行后面的,前面的值不为0即转换为布尔值为true,则直接返回前面的值
      
      var scrollTop=document.documentElement.scrollTop||document.body.scrollTop
      console.log(scrollTop); //获取页面卷去的头部的高度
      //----------获取卷去的宽度的兼容性问题
      
      var scrollLeft=document.documentElement.scrollLeft||document.body.scrollLeft
      console.log(scrollLeft); //获取页面卷去的头部的高度
      
      
}

  • 新方法 window.pageYOffset和window.pageXOffset iE9+支持. 推荐
处理各种兼容性问题,封装函数
function getScroll(){
	return{
	 left:window.pageXOffset||document.documentElement.scrollLeft||document.body.scrollLeft||0,
	 top:window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0
	}
}
使用时 getScroll().top
  • 设置浏览器滚动到指定的位置

格式1:window.scrollTo(横向坐标,纵向坐标);

  • 书写不需要带单位,直接设置数字就可以了,参数是数值必须要设置两个参数,否则会报错
  • 特点:瞬间定位

格式2:window.scrollTo({top:纵向坐标,left:横向坐标});

  • 如果参数是对象,对象里的属性写几个都可以

  • 特点:可以依靠第三个配置项来决定是瞬间定位还是平缓移动

    behvaior:“smooth” 平滑或’instant’立即 不能决定滚动时间

    但是:如果设置的是平滑的滚动的时候,页面无论滚动的距离是多还是少,所花费的秒数是一样的,1s或者2s,这样滚动的速度是不一样的。若是我们想要当页面滚动的距离多时耗时稍微久一点(保持滚动的速度是一样的),通过api提供的功能是没有办法实现的,需要我们自己写

      btn.onclick=function(){
            //让浏览器平滑的滚动到指定位置
            window.scrollTo({top:0,left:2,behavior:"smooth"})
        }
       //当用户点击返回到顶部的时候,实现页面滚动
        var scrollTimer;//记录定时器的返回值的变量设置在外面(全局),事件里面只是赋值,这样在任何地方都可以使用了
        btn.onclick=function(){
            //使用定时器每隔一定时间改变一下滚动条的滚动的位置来实现页面的滚动
            //每30ms使让当前浏览器卷曲的高度-20
            //但是我们需要当页面滚动到顶部的时候使定时器停下来清除定时器,否则会一直执行,滚动就停不下来了
            
          scrollTimer=setInterval(() => {
                document.documentElement.scrollTop-=20;
                if(document.documentElement.scrollTop<=0){
                    //但是我们需要当页面滚动到顶部的时候使定时器停下来清除定时器,否则会一直执行,滚动就停不下来了
                    clearInterval(scrollTimer)
                }
                console.log(document.documentElement.scrollTop)
            }, 30);
        }
   -------------`	     
        //上面只是实现了设置滚动的时间(速度),实现了页面滚动,但是我们无法是滚动在中途停下来
        //我们可以设置一个浏览器的滚动事件来检测滚动的高度,如果滚动的高度出现变大则说明用户想要反向滚动我们就将上一步的滚动定时器清除 
        var topNum=0;//设置一个变量来存储浏览器上一次滚动的高度
        window.onscroll=function(){
            if(document.documentElement.scrollTop>=topNum){
                //如果滚动的高度出现变大则说明用户想要反向滚动我们就将上一步的滚动定时器清除
                clearInterval(scrollTimer)
            }
            //给浏览器滚动的高度赋值,随着滚动记录每一次滚动的位置
           topNum=document.documentElement.scrollTop;
        }
  • 案例

    滚动窗口至文档中特定的位置

    window.scroll(x,y);

  • 元素.scrollWidth/scrollHeight

​ 包含content padding 溢出内容的尺寸,如论这个一处内容是隐藏还是滚动条

//   如何判断元素是否滚动到了底部
//元素.scrollHeight-元素.scrollTop=window.innerHeight

window.onscroll=function(){
  
  //页面被卷去高度的兼容性写法
  var scrollTop=document.body.scrollTop||document.documentElement.scrollTop||window.pageYOffset;
  // console.log(document.body.scrollHeight-scrollTop,window.innerHeight)
  //页面的总长度-页面被卷曲的高度=浏览器可视窗口的高度  的时候就说明页面滚动到最底部了
  //document.documentElement.scrollHeight 用html的scrollHeight更加合适,因为body有默认的margin值
  if(document.documentElement.scrollHeight-scrollTop<=window.innerHeight-10){
    console.log('到底了')
  }
            
}

window.devicePixelRatio 获取物理像素比 dpr

在火狐浏览器中,有个”往返缓存“,这个缓存存中不仅保存着页面的数据,还保存着DOM和js的状态,实际上是将整个页面都保存在了内存中,所以在火狐中按后退按钮不能刷新页面,这种情况下就不能使用onload而应该使用pageshow

pageshow事件 在页面显示时触发,无论页面是否来在缓存,在重新加载的页面中,pageshow会在load事件触发后触发,根据事件对象中的persisted来判断,是否是缓存中的页面触发的pageshow事件,注意这个事件给window添加

他们offset client scroll三大系统的使用总结

  • offset系列

    经常用于获取元素的位置 offsetLeft offsetTop 返回相对于设置了定位的父元素上/左面距离

​ offsetWidth/offsetHeight:content padding border 滚动条

​ 滚动条的宽度:offsetWidth-clientWidth-border

  • client经常用来获取元素的大小 clientWidth clientHeight 包含:content padding

    document.body.clientWidth/document.body.clientHeight 页面可见区域的宽高,不包含滚动条

    元素.clientTop 返回元素的border-top-width ;元素.clientLeft 返回元素的border-left-width

  • scroll 经常用来获取元素的滚动距离(被卷去的距离) scrollTop scrollLeft

    scrollWidth/scrollHeigh 包含content padding 溢出内容的尺寸,如论这个一处内容是隐藏还是滚动条

  • 页面滚动距离 使用 window.pageXoffset 获取

  • window.innerwidth / window.innerheight 返回窗口的文档显示区的宽

  • outerWidth 和 outerHeight 属性获取加上 工具条与滚动条窗口的宽度与高度


案例:封装一个动画函数

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  <style>
*{
    margin: 0;
}
      .box1{
          width: 100px;
          height: 100px;
          background: chartreuse;
          /* 元素要定位,否则无法动起来 */
          position: absolute;
          
      }
      #box2{
          width: 200px;
          height: 200px;
          background-color: chocolate;
             /* 元素要定位,否则无法动起来 */
             position: absolute;
             top: 300px;
      }
      #btn1{
          margin-top:100px;
      }
      #btn2{
          margin-top:100px;
      }
  </style>
</head>
<body>
    <div class="box1">

    </div>
    <div id="box2"></div>
    <button id="btn1">300</button>
    <button id="btn2">500</button>
    <script>
        var div=document.querySelector('.box1');
        console.log(div.offsetLeft)//获取元素在页面中的位置
        //封装一个函数,将元素及停止的位置作为参数传进去
        function animation(ele,targetLocation,callBack){
            console.log('>>>>',typeof ele)
             //点击按钮让div元素动起来,多次快速点击会发现元素移动的原来越快,这是因为多个定时器重叠了,所以速度越来越快
             //解决方案,清除上一次的定时器,让元素只有一个定时器执行
             clearInterval(ele.time)
            //给传进来的ele元素对象增加一个time属性用来记录每一个元素自己的定时器的返回值
            ele.time=setInterval(function(){
                // 给元素设置一个停止的位置,清除定时器
                //这里要写等于==,不要大于等于,因为向回走的时候会清除定时器
                if(ele.offsetLeft==targetLocation){
                    clearInterval(ele.time)//清除存储在对象中指定的定时器的值,就停止了动画
                    //在定时器清除后,也就是动画执行完后,如果有回调函数就执行回调函数
                    if(callBack){
                        callBack();
                    }
                }
                // div.offsetLeft获取元素在页面中的位置
                // 给元素的left重新赋值使元素动起来
                //元素移动是每固定时间增加一个固定的值,所以元素是匀速移动的
                // ele.style.left=ele.offsetLeft+10+'px';
                //想让元素变速移动,每秒钟移动的距离递减,即实现滑动动画,让元素的速度慢慢的慢下来
                //每次移动的距离(步长)=(目标位置-当前位置)/10  这样每次移动的距离就是在递减的
                //(targetLocation-ele.offsetLeft)/10 这样写步长会有小数,而且一直到不了目标位置,所以我们要取整,而且要向上取整,因为只能朝着目标位置前进不能后退
                var step;
                if(((targetLocation-ele.offsetLeft)/10)>0){
                    //目标位置大于元素现在位置的时候,向上取整,朝着目标位置只前进不后退  
                    step=Math.ceil((targetLocation-ele.offsetLeft)/10);
                }else{
                    //目标位置小于元素现在位置的时候,向下取整,朝着目标位置只前进不后退
                    step=Math.floor((targetLocation-ele.offsetLeft)/10);
                }
                console.log('step',step,'-------',targetLocation-ele.offsetLeft)
                ele.style.left=ele.offsetLeft+step+'px';
     
          }, 15);

        }
      
        animation(box2,200);
        //点击按钮让div元素动起来,多次快速点击会发现元素移动的原来越快
        //这是因为多个定时器重叠了,所以速度越来越快
        btn1.onclick=function(){
            animation(div,300,function(){
                console.log('下雨了')
            });
        }
        btn2.onclick=function(){
            animation(div,500);
        }
    </script>
</body>
</html>
//------动画函数的使用        还可以使用css实现同样的效果
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #fater-box{
            width: 100px;
            height: 100px;
            background: chocolate;
            position: fixed;
            right: 0;
            bottom: 50px;
           
        }
        /* 使用css实现子元素的滑出 */
        /* #fater-box:hover #cell-box{
            left:-200px;
        } */
        #cell-box{
            width: 300px;
            height: 100px;
            background: rgb(30, 210, 105);
            position: absolute;
            top:0;
            left:0;
            /* transition:1s; */
        }
        span{
            display: block;
            background-color: pink;
            width: 100px;
            height: 100px;
            position: absolute;
            z-index: 9999;
        }
    </style>
</head>
<body>
    <div id="fater-box">
        <span>66</span>
        <div id="cell-box"></div>
    </div>
    <!--引入东画文件 -->
    <script src="./utils.js"></script>
    <script>
        var cellBox=document.querySelector('#cell-box')
        var faterBox=document.querySelector('#fater-box')
        var span=document.querySelector('span')
      
        faterBox.addEventListener('mouseenter',function(){
            animation(cellBox,-200,function(){
                //滑出后的回调函数
                span.innerText='滑出了'
            })
        })
        faterBox.addEventListener('mouseleave',function(){
            animation(cellBox,0,function(){
                 //收回后的回调函数
                span.innerText='收回了'
            })
        })
        //offetLeft返回的是相对于设置了定位的祖先元素的左侧的距离,如果没有设置了定位的祖先元素则返回的是相对于body的左侧的距离)
    </script>
</body>
</html>

移动端

触屏事件:触摸事件只能在移动端使用

移动端兼容性好,我们不需要考虑js的兼容性问题,可以放心的使用原生的js书写效果,但是移动端也有自己独特的地方,不如触屏事件touch,安卓和ios手机都有

touch对象代表一份触摸点,触摸点可能是一根手指,也可能是一个触摸笔,触摸事件可以响应手指或者触摸笔对屏幕或者触控版的操作

  • 常见的触屏事件(移动端只用这三个事件,其他事件都是在这三个事件的基础上发生的)

    触摸事件在pc端测试的时候必须要调到移动模式下才可以

    • tuochstart 手指触摸到一个dom元素时触发

    • touchmove 手指在一个dom元素上滑动时触发

    • touchend 手指从一个dom元素上移开时触发

            box1.ontouchstart=function(){
                console.log('触摸开始')
            }
            box1.ontouchmove=function(){
                console.log('触摸--在元素上移动')
            }
            box1.ontouchend=function(){
                console.log('触摸结束')
            }
    
  • 触摸事件对象

    touchevent是一类描述手指在触摸平面上的状态变化事件,这类事件用于描述一个或者多个触摸点,是开发者可以检测到触摸点的移动,触摸点的增减和减少等

    touchstart touchmove touchend 三个事件都各有事件对象

    常见的触摸对象

    touches 正在触摸屏幕的所有手指的一个列表

    targetTouches 正在触摸当前元素的上的手指的一个列表

    如果监听一个Dom元素targetTouches和touches是一样的,因为我们平时都是给元素注册触摸事件,重点记targetTouches2

    changedTouches 手指状态发生了改变的列表,从无到有,从有到无的变化,当我们的手指离开屏幕的时候,就没有了touches和targetTouches列表,但是会有changedTouches

移动端点击事件的延迟

移动端点击事件会有300ms的延迟,原因是移动端双击屏幕会缩放页面

解决方案:

  • 使用meta标签禁用缩放行为,并且去掉300ms的点击延迟

    <meta name="viewport" contnet="user-scalabe=no">
    
  • 利用touch事件自己封装这个事件来解决300ms的延迟

    原理:当我们触摸屏幕时,记录触摸时间,但我们手指离开屏幕,用离开时间减去触摸时间,如果时间小于150ms,并且没有滑动过频幕,那么我们就定义为点击

    封装的函数。。。

  • 使用插件 fastclick插件解决300ms的延迟,gitHUb地址:https://github.com/ftlabs/fastclick

    移动端要求快速开发,我们经常使用插件来帮助我们来完成特定的操作

    js插件就是js文件,它遵循一定的编码规则,方便程序展示效果,拥有特点的功能且方便调用,如轮播图和瀑布流插件,插件一半是为了解决某个问题而专门存在的,功能单一,且比较小

循环 一个题 用各种循环语法循环执行一遍

小鸡生蛋 即 100内数相加 偶数项之和

9*9乘法表 阶乘 水仙花花束

红任务

浏览器的反应时间是每秒钟60次,16毫秒内光标移出盒子,浏览器反应不过来的

ES6

  • es6官方名称叫做es2015,每年2月都会一些新增的语法

    是语法层面的更新,比如原来的var 声明变量,let声明变量

    我们书写代码不需要考虑语法层面的兼容性,因为会有一些工具可以将我们写的es6代码转换为es5的语法,babel:https://www.babeljs.cn/ 可以看到将es6转为es5

es6新增了两个定义变量的关键字:

let 和const

  • letconst一起和var的区别:

    • var会进行预解析,let和const不会进行预解析,必须先定义再使用
    • var可以重复声明变量,let和const不能声明重复的变量名
    var a=100;
    let a=200;
    //---这样也不可以,因为变量a已经用过了,let不能再使用a声明变量
    
    • var 没有块级作用域,let和const有块级作用域

      块级作用域:

      被代码块限制变量的使用方法

    var只有函数私有作用域才能限制使用的范围,let和const只要是能书写代码段的{}都能限制使用范围

    if(true){
    	//全局变量
    	var num=100; //全局变量
    	let num2=200;  //---let定义的变量num2离开这个if的{}就用不了了
    }
      console.log(num);    //100
    console.log(num2);   //报错
    
    if(true){
    	let num2=200;   本质是将{}内使用let声明的num转换为了_num ,换了个变量名,所以在外面就获取不到了
    	console.log(num2); //200
    }
      let num2=300;
      console.log(num2);//300
    
   <div class="box1">1</div>
    <div class="box2">2</div>
    <div class="box3">3</div>
----------------js
var div=document.querySelectorAll('div');

--使用var 声明变量
for(var i=0;i<=div.length-1;i++){
    console.log(i)
    // 给每一个div元素绑定一个点事件,当点击div元素的时候,打印出的i都是3
    // 这是因为var 声明的i是全局变量,在div元素被点击时,for循环已经执行完毕,i自增为了3
    //所以点击元素时打印出的是自增后的全局变量i 为3
    div[i].onclick=function(){
        console.log('点击————',i)
    }
}

本质
var i
{
    i=0;
    ...
}
{
    i=1;
    ...
}
{
    i=2;
    ...
}
i=3;//i自增为3
--------------------------

---使用let声明变量
for(let i=0;i<=div.length-1;i++){
    console.log(i)
    // 给每一个div元素绑定一个点事件,当点击div元素的时候,打印出的i分别是0 1 2
    //这是因为使用let声明的变量会有快级作用域只能在当次循环的{}内使用
    div[i].onclick=function(){
        console.log('点击————',i)
    }
}
--本质

/* 
    {
        let i=0;
        ....
    }
    {
        let i=1;
        ....
    }

    {
        let i=2;
        ....
    }
*/
  • let和const的区别

    • let可以在声明时不赋值

      let a;
      console.log(a)//undefined
      

    ​ const 声明是必须赋值,声明时不赋值会报错

    ​ const a; //报错

    • let 声明的变量可以被修改

      const修改的常量不能被修改,一旦修改就报错

      给定义的常量赋值为数组或者对象,对数组和对象元素的修改,不算对常量的修改,不会报错

      因为常量存储的是数组和对象的内存地址,地址没有发生变化

    • const 一般常亮命名采用大写(潜规则)

  • let声明变量

    • 使用let声明变量不能重复声明

      let str='adc';
      let str='adc'; //重复声明会报错
      
    • 块级作用域

      if(true){
          let str='122';
      }
      console.log(str);//报错   Uncaught ReferenceError: str is not defined
      //如果使用var 声明变量就不会报错
      
    • 不存在变量提升

      console.log(ab);
      let ab='23';
      
    • 不影响作用域链

      {
          let ab='12';
          function fn() {
              console.log(ab)
          }
      }
      fn();// 12
      
  • const 声明常量

    const STR='123';
    //一定要赋初始值,否则就会报错
    const AD; //报语法错误 Uncaught SyntaxError: Missing initializer in const declaration
    
    • 一般常亮命名采用大写(潜规则)

    • 常量的值不能修改

    • 块级作用域

      {
          const AD=2; 
      }
      console.log(AD);//报错
      
    • 给定义的常量赋值为数组或者对象,对数组和对象元素的修改,不算对常量的修改,不会报错

      因为常量存储的是数组和对象的内存地址,地址没有发生变化

      const TIME=[1.23,4,56,7];
      TIME[0]="步步高";
      console.log(TIME);//["步步高", 4, 56, 7]
      

箭头函数

一种新的函数定义方式:对于函数表达式的简写方式(匿名函数)

  • 匿名函数使用场景:
var fn=function(){};

var onj={
    fn:function(){}
};

setTimeout(function(){},1000);

setInterval(function(){},1000);

[].map(function(){});

div.onclick=function(){};

div.addEventListener('click',function(){});
  • 箭头函数的语法:
()=>{};

      (): 设置形参的位置
      =>: 箭头函数的标志
      {}: 代码段


----
var fn=(a,b)=>{
    console.log(999,a,b);
};
fn(1,2);//正常调用
  • 箭头函数的特性

    • 一个形参的时可以省略小括号不写
    • 一句代码执行语句的时候可以省略大括号,并且自动return返回那一句语句的结果
    var fn=a => a+4;  //将a+4的结果当做fn函数的返回值
    console.log(fn(2));   //6
    
    • 箭头函数里面没有arguments,在箭头函数内没有办法使用arguments

    • 箭头函数里面没有this

      官方解释:箭头函数里面的this是上下文(context),外部作用域的this就是箭头函数内的this

      私人解释:箭头函数的this就是,你的箭头函数写在哪一行,上一行的this就是箭头函数里面的this

    var div=document.querySelectorAll('div')[0];
    
    div.onclick=function(){
        console.log(this);      // this为div元素
    }
    
    div.onclick=()=>{
        console.log(this);      // this为window
    }
    
    var div=document.querySelectorAll('div')[0];
    div.onclick=function(){
        console.log(this);// this为div元素
        let fn=function(){
            console.log(this);
        }
        fn();// 以函数的形式调用this指向window
        let fun=()=>{console.log('***8',this);};  //---这个箭头函数内的this就是上一行的this
        fun();             // 上一行所在函数的this是div,所以箭头函数this为div
    }
    
    let obj={
        fn:function(){
            console.log('11111',this);
        },
        fun:()=>{
            console.log('22222',this);
        }
    };
    obj.fn();    //  this为obj
    obj.fun();   
    //  this为window 因为obj实际为 let obj={fn:function(){console.log('11111',this);},fun:()=>     {console.log('22222',this)}};
    // 定义箭头函数的哪一行的上一行的this为window
    
    var div=document.querySelectorAll('div')[0];
    div.onclick=function(){
        console.log('$$$$$',this);// this为div元素
        let obj={
            name:'我是obj对象',
            fn:function(){
                console.log('11111',this);
            },
            fun:()=>{
                console.log('22222',this);
            }
        };
        obj.fn();           //---this为obj
        obj.fun();         //--this为div
    }
    
    • 箭头函数内的this任何方法改变不了,因为箭头函数内没有this它用的是外部作用域的this

      call apply bind不能改变箭头函数的this指向

    var fn=()=>{
        console.log(this);
    }
    let obj={
        name:'sun',
        fun:fn
    }
    fn();         //---this为window
    obj.fun();    //---this为window
    fn.call(obj); //---this为window  箭头函数不能改变this指向
    

函数参数的默认值

作用:给函数的形参设置一个默认值

​ 如传递了实参,就用实参,如果没有传递实参就用形参的默认值

格式:直接在形参后面使用等号(=)进行赋值

function fn(a=100,b){
    console.log(a,b);
}
fn('你好','bubu');//传递实参使用实参
fn();//没有传递实参就使用形参设置的默认值,如果形参没有设置默认值,则默认值为undefined

箭头函数也可以书写参数的默认值

箭头函数只要你设置了参数默认值,不管多少个形参,都的写小括号

let fn=(a=100,b)=>{
    console.log(a,b);
}
fn('你好','bubu');//传递实参使用实参
fn();//没有传递实参就使用形参设置的默认值,如果形参没有设置默认值,则默认值为undefined

模版字符串

模版字符串就是使用反引号包围的字符串

  • 模版字符串可以直接换行书写

    let ab=`12345
    dfff
    ffff
    444`;
    console.log(ab);
    //打印输出的内容,也是换行的 
    12345
    dfff
    ffff
    444
    
  • 可以使用变量拼接字符串

    let name='安保';
    let newStr=`122${naem}`
    
  • 模版字符串可以调用函数

    模版字符串的内容就是函数的参数

    ${}把字符串切开,组合成一个数组当做函数的第一个参数

    从左到右开始依次是每一个${}里面的内容作为函数后面参数

function fn(a,b,c) {
    console.log(arguments);
    console.log('第一个参数',a); //['hello','word','你好']
    console.log('第二个参数',b); //10
    console.log('第三个参数',c); //20
}
var num1=10;
var num2=20;
fn`hello${num1}word${num2}你好`;
/* ----------
    执行步骤:
    1: 用${}切开字符串 将切开的字符串放到一个数组里['hello','word','你好']作为函数第一个参数传入函数
    2: ${num1} 里面的num1就是函数的第二个参数
    3: ${num2} 里面的num2就是函数的第三个参数

    作用:将模版字符串的各部分给你,你可以在函数内自由组合
    不经常使用,但是面试常出
*/

(…)点点点运算符

  • 展开运算符

    当在函数实参的位置或者数组或者对象里面使用的时候是展开

    作用:就是将包裹的内容全部打开

    let arr=[1,2,3,4,5];
    console.log(...arr);// 1 2 3 4 5
    
    console.log(Math.max(...arr));
    // Math.max()方法不接受数组作为参数,我们可以使用展开运算符,将数组的每一项展开后依次作为该方法的参数
    ---展开数组
    let arr2=[6,7,8];// 将arr和arr2合并为一个数组  原来可以使用数组的concat方法
        arr2=[...arr,6,7,8]
        console.log(arr2);// [1, 2, 3, 4, 5, 6, 7, 8]
        
    --- 展开对象
        
    let obj={
        name:'sun',
        age:18
    }
    let obj2={
         ...obj,
        skill:function(){console.log('你好')}
    }
    console.log(obj); //{name: "sun", age: 18}
    console.log(obj2);//{name: "sun", age: 18, skill: ƒ}
    
  • 合并运算符

    当你在函数的形参位置使用的时候是合并

    箭头函数没有arguments,我们可以使用合并运算符整一个

    function fn(...a){
        // 定义一个变量a,从第一个实参开始到最后一个实参全部获取,合并放在一个数组里面
        console.log(a);//[1, 2, 3, 4, 6, 7]
    }
    fn(1,2,3,4,6,7)
    
    function fn2(a,...b){
        // 第一变量a,接受第一个实参
        // 定义一个变量b,从第二个实参开始到最后一个实参全部获取,合并放在一个数组里面
        console.log(a);// 1
        console.log(b);// [2, 3, 4, 6, 7]
    }
    fn2(1,2,3,4,6,7)
    
    ....以此类推
    
    ----箭头函数
    
    var fn3=(...n)=>{
     console.log(n); //[1, 2, "nihao"]
    }
    fn3(1,2,'nihao')
    
  • 变量的解构赋值

    (解析结构并赋值)

    ES6允许按照一定模式从数组和对象中提取值,对变量进行赋值,这即是解构

    • 解构数组

      格式: let [变量1,变量2,…]=[数据1,数据2,…]

      也可以解构多维数组

      let [ni,hao,bei,jing]=['你','好','北','京'];
      console.log(ni);
      console.log(hao);
      console.log(bei);
      console.log(jing);
      
      //结构赋值获取数组里的成员,同时给4个变量赋值了
      //本质就是es5的
      
      let ni=['你','好','北','京'][0];
      ...
      
      // ----   结构多维数组
      let arr=['你','好','北',[1,2,[3]]];
      let [a,b,c,[d,e,[f]]]=arr;
      
      console.log(a,b,c,d,e,f);    // 你 好 北 1 2 3
      
      -----使用结构赋值交换变量,可以直接交换不用使用中间变量
      var a='你';
      var b='好';
      console.log(a,b); // 你 好
      [b,a]=[a,b];
      console.log(a,b); // 好 你
      
    • 解构对象(必须与对象属性名相同)

      语法: let {key1,key2,…}={键值对1,键值对1,…};

    const per={
        name:'小明',
        age:'12',
        run:function(){
        console.log('run666666')
        }
    };
    let {name,age,run}=per;   //解构赋值了3个变量
    // 等价于 
    //let name=per.name;
    //let age=per.age;
    //let run=per.run;
    -----
    
    let {run}=per; //也可以只解构赋值一个变量
    
    console.log(name); //小明
    console.log(age); //12
    console.log(run);
    //  ƒ (){
        console.log('run666666')
        }
    优点:可以直接调用方法 run()
     不用像之前一样调用 per.run()
    
    
    
    • 解构的时候可以给结构的变量起一个别名
      

    ​ let {key1:别名}={…}

    const per={
        name:'小明',
        age:'12',
        run:function(){
        console.log('run666666')
        }
    };
    //---------------------------------------------解构的时候可以给结构的变量起一个别名
    let {name:n}=per;    //等同于 let n=per.name
    console.log(name);//'小明'
    console.log(n);//'小明'
    
    • 解构多维对象

      let o1={
          a:100,
          b:200,
          o2:{
              c:300,
              o3:{
                  d:400
              }
          }
      };
      
      let {a,b,o2:{c,o3:{d}}}=o1;
      
      console.log(a,b,c,d);      //100 200 300 400
      
      /* -----案例分析
          o2定义一个别名{c,o3:{d}}=o1.o2;  这又是 一个结构赋值
          let {c,o3:{d}}=o1.o2;
          let c=o1.o2.c
          o3定义一个别名 let {d}=o1.o2.o3
          所以d=o1.o2.o3.d
      
      */
      

简化对象写法

在es6标准下,对象的简写格式

当对象的key和value一模一样的时候,可以只写一个

即在对象花括号里直接写入变量名和函数,作为对象的属性和方法,这样的写法更简洁

let name="小明",age="18",run=function(){console.log("666666")};
const per={
    name:name,  //------------属性值是name变量可以简写
    age:age,
    run:run,
    skill:'skill'//-----------属性值是字符串 是一个准确的值不能简写
}
console.log(per);//{name: "小明", age: "18", run: ƒ}
//-------------------------------------------------------es
const per2={
    name,
    age,
    run,
    song:function(){   
        console.log('77777')
    },
    //--------------------------------方法可以省略 :function
    song2(){
        console.log('77777')
    },
    song3:()=>{...}   //------------------箭头函数不能简写
    
}
console.log(per2);//{name: "小明", age: "18", run: ƒ, song: ƒ, song2: ƒ}

对象的新增方法 Object.assign

Object.assign()方法。 做对象覆盖

  • 作用:用于对象的合并,将源对象的属性,复制到目标对象

  • 格式:Object.assign(目标对象,源对象1,源对象2)

​ 第一个参数是目标对象,后面的参数都是源对象

  • 注意:如果目标对象与源对象,或者多个源对象有同名属性,后面的属性会覆盖前面的属性
 let target={a:1};
 let source1={a:2,b:8};
 Object.assign(target,source1)
 console.log(target);//{a:2,b:8}

扩展:

  • Object.assign()方法实行的是浅拷贝,而不是深拷贝,即如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用(地址)
let target={a:1,m:'haha'};
let source1={a:2,b:8,c:{d:99,e:77}};

Object.assign(target,source1)

console.log(target);        //{a:2,m:'haha',b:8,c:{d:99,e:77}}

target.c.d=0;	

console.log(source1.c.d);       //0 
  • 对于这种嵌套的对象,一旦遇到同名属性,Object.assgin()的处理方式是替换,而不是添加
  let target={a:{b:2}}
  let source1={a:{b:2,c:3}}
  Object.assign(target,source1)
  console.log(target);//{a:{b:2,c:3}}

使用案例:

     //-------没有使用Object.assign()方法
		function fn(options){
            console.log('type',options.type);
            console.log('url',options.url);
            //调用成功的回调函数
            options.succes();
             //调用失败的回调函数
             options.error();

        }

        //调用fn的时候传入一个对象作为参数,每一次调用对象里的每一个成员都要设置
        fn({
            type:'get',
            url:'****',
            succes:function(){
                console.log('成功')
            },
            error:function(){
                console.log('失败')
            }
        })
       //-----使用Object.assign()方法 进行代码优化
			function fn(options){
            //设置一个默认值
            var defaultValue={
                    type:'get',
                    url:'****',
                    succes:function(){
                        console.log('成功')
                    },
                    error:function(){
                        console.log('失败')
                    }
                }
            //用传进来的实参覆盖默认值
            //如果实参与默认值的同名属性不一样,会用实参的值覆盖默认值
            //这样我们就不用每次调用函数的时候对象里每一个成员属性都要设置了,没有设置的就采用默认值
            Object.assign(defaultValue,options)    
            
            console.log('type',defaultValue.type);
            console.log('url',defaultValue.url);
            //调用成功的回调函数
            defaultValue.succes();
             //调用失败的回调函数
             defaultValue.error();

        }
        
        //调用fn的时候传入一个对象作为参数
        //这样我们就不用每次调用函数的时候对象里每一个成员属性都要设置了,没有设置的就采用默认值
        fn({
          type:'post',
          url:'www.baidu.com'
        });

				//--调用函数 
        fn({
        	type:'@@@',
        })

类语法

在es5中,我们使用函数来充当 构造函数(类)

Es6 引入一个类的概念,就是使用一个新的关键字 class 来定义类, class 是构造函数的语法糖

注意:一个class定义的类,不能被当做普通函数调用,必须要和new关键字连用,不然就报错

​ 只能通过一个new来得到一个对象

语法:

--------------------定义一个类
class 类名{
    // 构造器 ,等价于我们的构造函数 ,定义这个类创造的对象里有哪些属性
  //一个类必须有constructor()方法,如果没有显式定义,一个空的constructor()方法会被默认添加。
    constructor(a,b){
        this.属性名1=a;
        this.属性名2=b;
    }
    // ------ 直接书写原型上的方法,不用function: 
    // ------  方法与方法之间不需要逗号 , 分隔,加了会报错
    方法名1(){
        console.log('***',this)
    }
    方法名2(){
        console.log('***',this)
    }
    方法名3(){
        console.log('***',this)
    }
}
-----------------定义一个名为Person的类
class Person{
    // 构造器 ,这个类创造的对象里有哪些属性
    constructor(a,b){
        this.name=a;
        this.age=b;
    }
    // 直接书写原型上的方法,不用function: 
    // 也不用,
    init(){
        console.log('***',this)
    }
}

//---使用Person类去创建对象
var newObj1=new Person('你好',100);   //----得到一个实例对象
newObj1.init1();                    //----调用实例对象的方法,方法中的this指向这个创建出来的实例对象


 //ES5 一样,类的所有实例共享一个原型对象。
//newObj1和newObj2都是Person的实例,它们的原型都是Person.prototype,所以__proto__属性是相等的。
newObj1.__proto__ === newObj2.__proto__

模块化开发

  • 为什么需要模快化开发:把一类方法放在一个单独的js文件里,为了方便使用,只引入这一类方法的文件

​ 比如:a.js全都是封装的操作DOM的方法

​ b.js全都是封装的格式化的时间对象的方法

​ c.js全都是封装的和数字相关的方法。c.js里面进行整合和组装

​ 我们管这样一个个封装好的js文件叫做一个模块

  • 什么是模块化开发

    多个js文件之间的相互配合来实现效果

在没有模块化的时候按照顺序引入文件,把整合文件放在最后面

问题:1:我们把一般把整合的js文件起名叫做main.js,我们并不知道依赖了哪一个文件

我们现在只能知道c.js用到了a.js和b.js文件里面的内容,但是b.js里面有没有依赖a.js里面的内容我也不清楚,所以这几个文件的引入顺序也不敢动

​ 2:全局变量污染

ES6 模块化

在2015年发布,es6语法里面自带了一个模块化标准,起初个大浏览器并不买账,2016年开始,vue出现了,人家出现了脚手架(开发的大框架直接给你搭建好,我们写项目的时候只需要在指定的位置写代码就可以了),搭建这个脚手架的时候,内置了es6模块化标准,大家都在使用vue,2018年各大浏览器厂商开始原生支持ES6模块化标准,2018年中,Chrom率先原生支持ES6模块化

语法:变成了js的语法,和关键字,不需要任何第三方文件的引入

特点:1: 浏览器支持不好,页面必须在服务器上打开。(不过我们项目上线后都是在服务器上的,这点也没有什么大问题)

​ vscode中可以安装 live server 插件,模拟在服务器上打开

​ Vscode 上使用live Sever 打开网页的设置 (vscode扩展设置)

浏览器地址栏:

http://你电脑的ip地址:5500/文件名.html

http://172.20.10.2:5500/index%E6%A8%A1%E5%9D%97%E5%8C%96.html

​ 2: 如果你想要使用模块化语法,script标签需要加一个属性type=“module”

使用:每一个文件都可以作为独立模块,也都可以作为整合文件

  • 模块功能主要由两个命令构成:exportimportexport命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。

如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字导出该变量(可以导出变量,函数,class类)

导出 格式

  • 直接在声明变量(函数)语句的前面使用export

    export var 变量1=变量的值;  
    
    export var 变量2=变量的值; 
    
    export function fn(){};
    
  • 在exprt 后面使用{}指定一组要输出的变量,推荐使用这种,因为这样就可以在脚本尾部,一眼看清楚输出了哪些变量。

     var 变量1=变量的值;  
    
     var 变量2=变量的值;
    
    export {变量1,变量2 as 别名};
    

导入:

使用export命令定义了模块的对外接口后,其他js文件就可以通过import命令加载这个模块,并接收导出的数据

  • imoport命令接受一对大括号,里面指定要从其他模块导入的变量名,大括号里面的变量名,必须要和被导入模块对外接口的名称相同 (本质是结构赋值)

如果想为输入的变量重新取一个名字,import命令要使用as关键字,将输入的变量重命名。

import {接受变量1,接受变量2 as 别名} from "哪一个文件"
----b.js
export var a='2020-7-31';
export var b=666;

----c.js
// 需要依赖b.js文件导出的时候,因为b,js是分别导出的,所以接受时必须要以结构赋值的方式分别接受
import {a,b} from './b.js'
console.log('b.js 文件----',a,b);
  • import 接收的变量都是只读的,,因为它的本质是输入接口。也就是说,不允许在加载模块的脚本里面改写接口
import {a} from './xxx.js'

a = {};//---报错
  • import语句会执行所加载的模块,因此可以有下面的写法。
import 'lodash';

上面代码仅仅执行lodash模块,但是不输入任何值。

  • import命令具有提升效果,会提升到整个模块的头部,首先执行。

    即依赖前置,先执行依赖文件内的语句,在执行整合文件的语句,与代码书写位置没有关系,都是先加载依赖文件

-----整合文件c.js  本是想要先执行c.js里的输出语句再导入a.js 和 b.js 但是是依赖前置

console.log('c.js---c 文件',ModeA);
console.log('b.js---c 文件----',a,b);


// 我是整合文件,我需要a.js文件里面的内容
// ModeA 接收的是a.js 文件里面export default 后面的一整个内容
import ModeA from "./a.js"
// 需要依赖b.js文件导出的时候,因为b,js是分别导出的,所以接受时必须要以结构赋值的方式分别接受
import  {a,b} from './b.js'


----输出结果


--------我是a.js的输出
bs------b.jsd的输出
c.js---c 文件 {af1: ƒ, af2: ƒ}
b.js---c 文件---- 2020-7-31 666
  • 模块整体加载
import  * as res from './b.js'
console.log('****',res.a,res.b)

使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。但是,用户肯定希望快速上手,未必愿意阅读文档,去了解模块有哪些属性和方法。

为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default命令,为模块指定默认输出。

  • 设置默认的导出

    • 导出语法:

      • export default {导出内容} 有没有{}都可以
    • 导入语法.

      import 变量 from ‘哪一个js文件’

      注意:这是import后面不能使用{}

      变量 接收的是指定 文件里面export default 后面的一整个内容

      变量名可以任意取,我们不需要知道导出数据的名字

      --------a.js
      // 我是一个模块,因为这个文件并不会通过script标签引入页面,所以这些变量不是全局变量,不在window上
      
      var num=100;
      var flag=true;
      
      function af1(){ console.log('我是a.js里面的af1 方法')};
      function af2(){ console.log('我是a.js里面的af2 方法')};
      
      // 一个文件只能有一个export default 
      
      // export default af1; //---可以只导出一个变量或方法
      //---如果需要导出多个,那么需要以对象或者数组的形式导出
      export default {
          af1,
          af2
      }
      --------c.js
      
      // 我是整合文件,我需要a.js文件里面的内容
      // ModeA 接收的是a.js 文件里面export default 后面的一整个内容
      import ModeA from "./a.js"
      console.log('c.js 文件',ModeA);
      
      
  • 同样也支持混合导出

/******************************导出**********************/
export default function(){
    return "默认导出一个方法"
}
export var myName="laowang";
/******************************引入**********************/
import myFn,{myName} from "./test.js";
console.log(myFn(),myName);//默认导出一个方法 laowang
默认导入和基本导入一起使用

语法:import 默认导入,{基本导入} from "文件路径"

基本导出直接把默认的也一起导出

语法 export {基本导出,默认导出 as default}

重命名export和import

如果导入的多个文件中,变量名字相同,即会产生命名冲突的问题,为了解决该问题,ES6为提供了重命名的方法,当你在导入名称时可以这样做:

/******************************test1.js**********************/
export let myName="我来自test1.js";
/******************************test2.js**********************/
export let myName="我来自test2.js";
/******************************index.js**********************/
import {myName as name1} from "./test1.js";
import {myName as name2} from "./test2.js";
console.log(name1);//我来自test1.js
console.log(name2);//我来自test1.js

Es6模块化的优点

  • 预声明的方式导入(在一开始确定依赖关系)
  • 多种导入导出方式
var a = require('./a');
var b = require('./b');

console.log(a.aNum, b.bStr);
  • 问题

1:浏览器支持不好,页面必须在服务器上打开。

(不过我们项目上线后肯定是在服务器上的,这点也没有什么大问题)

2:依赖前置

解决依赖前置:

Es2020 发布新的标准,引入import()函数,支持动态加载模块

语法: import(你要加载的文件).then(function(res){})

import()函数返回一个Promise对象

import()函数可以用在任何地方,不仅仅是模块,非模块的脚本也可以使用,它是运行时执行,也就是说,什么时候运行到这一句,就加载指定的模块

  • 条件加载

import()可以放在if代码块,根据不同的情况,加载不同的模块。

if (condition) {
  import('moduleA').then(...);
} else {
  import('moduleB').then(...);
}

import()加载模块成功以后,这个模块会作为一个对象,当作then方法的参数。因此,可以使用对象解构赋值的语法,获取输出接口。

import('./myModule.js')
.then(({export1, export2}) => {
  // ...·
});

上面代码中,export1export2都是myModule.js的输出接口,可以解构获得。

如果模块有default输出接口,可以用参数直接获得。

import('./myModule.js')
.then(myModule => {
  console.log(myModule.default);
});

模块化

面临的问题

技术的诞生是为了解决某个问题,模块化也是。在js模块化诞生之前,开发者面临很多问题:随着前端的发展,web技术日趋成熟,js功能越来越多,代码量也越来越大。之前一个项目通常各个页面公用一个js,但是js逐渐拆分,项目中引入的js越来越多:

<script src="zepto.js"></script>
<script src="jhash.js"></script>
<script src="fastClick.js"></script>

如果你是2015年前毕业的,项目中的js就是类似这样,这样的js引入造成了问题:

  1. 变量重名:不同文件中的变量如果重名,后面的会覆盖前面的,造成程序运行错误。
  2. 文件依赖顺序:多个文件之间存在依赖关系,需要保证一定加载顺序问题严重。

这些问题严重干扰开发,也是日常开发中经常遇到的问题。

举例:

// a.js
var aStr = 'aa';
var aNum = cNum + 1;

b.js

// b.js
var bStr = aStr + ' bb';

c.js

// c.js
var cNum = 0;

index.js

// index.js
console.log(aNum, bStr);

四份文件,不同的依赖关系(a依赖c,b依赖a,index依赖a b)在没有模块化的时候我们需要页面中这样:

<script src="./c.js"></script>    
<script src="./a.js"></script>
<script src="./b.js"></script>
<script src="./index.js"></script>

严格保证加载顺序,否则报错。

什么是模块化

​ 用乐高积木来比喻模块化再好不过了。每个积木都是固定的颜色形状,想要组合积木必须使用积木凸起和凹陷的部分进行连接,最后多个积木累积成你想要的形状。

模块化开发是一种管理方式,是一种生产方式,一种解决问题的方案,一个模块就是实现特定功能的文件,有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块,

模块化解决方法

1. 闭包与命名空间

这是最容易想到的也是最简便的解决方式,早在模块化概念提出之前很多人就已经使用闭包的方式来解决变量重名和污染问题。

这样每个js文件都是使用IIFE包裹的,各个js文件分别在不同的词法作用域中,相互隔离,最后通过闭包的方式暴露变量。每个闭包都是单独一个文件,每个文件仍然通过script标签的方式下载,标签的顺序就是模块的依赖关系。

上面的例子我们用该方法修改下写法:

// a.js
var a = (function(cNum){
   var aStr = 'aa';
   var aNum = cNum + 1; 
    
    return {
       aStr: aStr,
       aNum: aNum
    };
})(cNum);
// b.js
var bStr = (function(a){
   var bStr = a.aStr + ' bb';
    
   return bStr;
})(a);
// c.js
var cNum = (function(){
   var cNum = 0;
    
   return cNum;
})();
//index.js
(function(a, bStr){
    console.log(a.aNum, bStr);
})(a, bStr)

这种方法下仍然需要在入口处严格保证加载顺序:

<script src="./c.js"></script>    
<script src="./a.js"></script>
<script src="./b.js"></script>
<script src="./index.js"></script>

这种方式最简单有效,也是后续其他解决方案的基础。这样做的意义:

  1. 各个js文件之间避免了变量重名干扰,并且最少的暴露变量,避免全局污染。
  2. 模块外部不能轻易的修改闭包内部的变量,程序的稳定性增加。
  3. 模块与外部的连接通过IIFE传参,语义化更好,清晰地知道有哪些依赖。

不过各个模块的依赖关系仍然要通过加装script的顺序来保证。

2.ES6模块化

在之前的javascript中是没有模块化概念的。如果要进行模块化操作,需要引入第三方的类库。随着技术的发展,前后端分离,前端的业务变的越来越复杂化。直至ES6带来了模块化,才让javascript第一次支持了module。ES6的模块化分为导出(export)与导入(import)两个模块。

export的用法

在ES6中每一个模块即是一个文件,在文件中定义的变量,函数,对象在外部是无法获取的。如果你希望外部可以读取模块当中的内容,就必须使用export来对其进行暴露(输出)。先来看个例子,来对一个变量进行模块化。我们先来创建一个test.js文件,来对这一个变量进行输出:

export let myName="laowang";

然后可以创建一个index.js文件,以import的形式将这个变量进行引入:

import {myName} from "./test.js";
console.log(myName);//laowang

页面引用 ,需要在<script></script>里添加type='module'

语法:import {具名符号列表} from "模块路径"

由于使用的是依赖预加载,因此导入任何其他模块,导入代码必须当时到所有代码之前

如果要输出多个变量可以将这些变量包装成对象进行模块化输出:

let myName="laowang";
let myAge=90;
let myfn=function(){
    return "我是"+myName+"!今年"+myAge+"岁了"
}
export {
    myName,
    myAge,
    myfn
}
/******************************接收的代码调整为**********************/
import {myfn,myAge,myName} from "./test.js";
console.log(myfn());//我是laowang!今年90岁了
console.log(myAge);//90
console.log(myName);//laowang

如果你不想暴露模块当中的变量名字,可以通过as来进行操作:

let myName="laowang";
let myAge=90;
let myfn=function(){
    return "我是"+myName+"!今年"+myAge+"岁了"
}
export {
    myName as name,
    myAge as age,
    myfn as fn
}
/******************************接收的代码调整为**********************/
import {fn,age,name} from "./test.js";
console.log(fn());//我是laowang!今年90岁了
console.log(age);//90
console.log(name);//laowang
默认导出(default export)

一个模块只能有一个默认导出,对于默认导出,导入的名称可以和导出的名称不一致。

/******************************导出**********************/
export default function(){
    return "默认导出一个方法"
}
/******************************引入**********************/
import myFn from "./test.js";//注意这里默认导出不需要用{}。
console.log(myFn());//默认导出一个方法

可以将所有需要导出的变量放入一个对象中,然后通过default export进行导出

export default {
    myFn(){
        return "默认导出一个方法"
    },
    myName:"laowang"
}
/******************************引入**********************/
import myObj from "./test.js";
console.log(myObj.myFn(),myObj.myName);//默认导出一个方法 laowang

同样也支持混合导出

/******************************导出**********************/
export default function(){
    return "默认导出一个方法"
}
export var myName="laowang";
/******************************引入**********************/
import myFn,{myName} from "./test.js";
console.log(myFn(),myName);//默认导出一个方法 laowang
默认导入和基本导入一起使用

语法:import 默认导入,{基本导入} from "文件路径"

基本导出直接把默认的也一起导出

语法 export {基本导出,默认导出 as default}

重命名export和import

如果导入的多个文件中,变量名字相同,即会产生命名冲突的问题,为了解决该问题,ES6为提供了重命名的方法,当你在导入名称时可以这样做:

/******************************test1.js**********************/
export let myName="我来自test1.js";
/******************************test2.js**********************/
export let myName="我来自test2.js";
/******************************index.js**********************/
import {myName as name1} from "./test1.js";
import {myName as name2} from "./test2.js";
console.log(name1);//我来自test1.js
console.log(name2);//我来自test1.js

Es6模块化的优点

  • 预声明的方式导入(在一开始确定依赖关系)
  • 多种导入导出方式
var a = require('./a');
var b = require('./b');

console.log(a.aNum, b.bStr);

深浅拷贝

  • 赋值

    把一个对象的地址赋值给另一个变量

    两个变量操作同一个空间

    var o1={
    	name:'jack'
    }
    var o2=o1;
    o2.name='rose'
    console.log(o1,o2);//这是赋值和拷贝没有关系
    
  • 浅拷贝

    把对象里面的每一个成员,复制一份一模一样的内容,放到另一个对象里面

    当对象里有一个对象成员是复杂数据类型时,这个成员依旧是一样的

    浅拷贝只是操作对象里面的一层可以没有关系,如果再深层次就会出现问题了

         var o1={
                name:'jack',
                age:18,
                //o1.onfo存储的是一个地址
               //浅拷贝:当对象里有一个对象成员是复杂数据类型时,这个成员依旧是一样的
                info:{
                    a:1,
                    b:2
                }
    
            }
         var o2={};
    
        for(var i in o1){
        //将o1里的成员赋值过去的时候,其实是将的o1.info存储的地址赋值给了o2.info
    
          o2[i]=o1[i]
        }
        //o1和o2是两个对象空间,但是里面的内容是一样的
    		console.log(o1,o2)
     
    
    		o2.name='jiayou'
    		o2.info.a='new Value';  //o.info和o2.info操作的是一个内存空间
        //o1和o2没有关系,操作其中一个,另一个不受影响
    		console.log(o1,o2);
    
       var o1={
            name:'jack',
            age:18,
            info:{
                a:1,
                b:2
            }
        }
        
      let o2={...o1}
      o2.age=66;   //第一层深拷贝
      o2.info.a='哈哈哈';  //第二层没有实现深拷贝
      console.log(o1,o2) 
 var a=[1,2,[3,4]]
 var c=[];
 var b=c.concat(a);
 b[0]=5;
 b[2][0]=6;
 console.log(b[0]);        //5
 console.log(a[0])        //1
 console.log(b[2][0]);   //6
 console.log(a[2][0])    //6
  • 深拷贝

    对象空间里面不管有多少层,都是相对独立的,没有关系

    深拷贝的实现方式1:

    ​ 使用for in遍历赋值

    只要碰到某一个是复杂数据类型 对象或数组,再次进入到这个数据类型里面进行二次遍历,如果还有就在进入遍历。。(太麻烦,不推荐)

    深拷贝实现方式2:

    使用json

    不管多复杂的数据类型,转换为json以后就是字符串了,字符串的赋值是基本数据类型,赋值以后再转换回来

    **注意:**Json的方式实现深拷贝是有局限性的,函数在专为json字符串时是会被忽略

    var o1={
      name:'jack',
      age:18,
      info:{
        a:1,
        b:2
      }
    }
    var obj=JSON.stringify(o1);
    var o2=JSON.parse(obj);
    
    o2.info.a=88; //此时操作o2,o1就不再受影响了
    console.log(o1,o2)
    
--------------------------------轮播
<!DOCTYPE html>
<html lang="en">
<head>

    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        *{
            margin:0;
            padding: 0;
        }
        .lb-continer{
            width: 250px;
            height: 100px;
            border:1px solid red;
            /* position: fixed; */
            position: relative;
            left:300px;
            overflow:hidden;
        }
        ul{
            list-style: none;
        }
        ul.img-box{
            width: 600%;
            border: 1px solid rgb(29, 27, 27);
            overflow:hidden;
            position: absolute;
        }
    
        .img-cell:nth-child(1){
            background-color: cornflowerblue;
        }
        .img-cell:nth-child(2){
            background-color: rgb(100, 237, 184);
        }
        .img-cell:nth-child(3){
            background-color: rgb(207, 237, 100);
        }
        .img-cell:nth-child(4){
            background-color: rgb(237, 100, 184);
        }
       .img-box li{
            width: 250px;
            height: 100px;
            float: left;
        }
        .lb-continer>.left{
            display: block;
            background-color: brown;
            position: absolute;
            left: 0;
            top:50%;
            display: none;
        }
        .lb-continer>.right{
            display: block;
            background-color: rgb(42, 165, 93);
            position: absolute;
            right: 0;
            top:50%;
            display: none;
        }
        .click-ul{
            position: absolute;
            bottom: 0;
            left:50%;
            transform: translateX(-50%);
            display: flex;
        }
        .click-ridius{
            width: 10px;
            height: 10px;
            background-color: cornsilk;
            border-radius: 50%;
            
        }
        .click-action{
            background-color: crimson;
        }
        .img-cell img{
            width: 100%;
            height: 100%;
        }
    </style>
</head>
<body>
    <button id="btn">777</button>
    <div class="lb-continer">
        <ul class="img-box">
            <!-- <li class="img-cell">
                <img src="../图片素材/td.jpeg" alt="">
            </li> -->
            <!--  -->
            <li class="img-cell">
                <img src="../图片素材/by.jpeg" alt="">
            </li>
            <li class="img-cell">
                <img src="../图片素材/hb.jpeg" alt="">
            </li>
            <li class="img-cell">
                <img src="../张玮/屏幕快照 2021-03-27 下午12.41.55.png" alt="">
               
            </li>
            <li class="img-cell">
                <img src="../图片素材/td.jpeg" alt="">
            </li>
            <!-- 复制一份第一张轮播图,来实现无缝滚动的效果,我们看到最后一张图的时候会以为是第一张
            <li class="img-cell">
                <img src="../图片素材/by.jpeg" alt="">
            </li> -->
        </ul>
        <span class="left">1</span>
        <span class="right">2</span>
        <!-- 可点击的小圆圈定位 -->
        <ul class="click-ul">
            <!-- <li class="click-ridius"></li>
            <li class="click-ridius"></li>
            <li class="click-ridius"></li>
            <li class="click-ridius"></li> -->
        </ul>
    </div>
    <!-- js -->
    <script src="./utils.js"></script>
    <script>
      
        // 最外层的盒子
        var lbContiner=document.querySelector('.lb-continer')
        // 左侧按钮
        var left=document.querySelector('span.left');
        // 右侧按钮
        var right=document.querySelector('span.right');
        // 轮播图的盒子
        var imgBox=document.querySelector('.img-box');
        // 点击的小圆圈的盒子
        var clickUl=document.querySelector('.click-ul')
        //获取轮播图图片的宽度
        var imgWidth=document.querySelector('.img-cell').offsetWidth
        console.log(left,right,imgWidth);
        //动态生成可点击小圆圈
       //imgBox.childElementCount 为轮播图图片的个数
        var str='';
        for(var i=1;i<=imgBox.childElementCount;i++){
            str+='<li class="click-ridius"></li>'
        }
        // 向页面中渲染小圆圈
        clickUl.innerHTML=str; 
        // 获取可以点击的小圆圈
        var clickLiBox=document.querySelectorAll('.click-ul li')
        // 默认页面进来时第一个小圆圈为选中状态
        clickLiBox[0].className='click-ridius click-action'
        // 循环遍历所有可以点击的小圆圈给其绑定点击事件
        clickLiBox.forEach(function(item,index){
            //给每一个小园圈设置data-index属性记录这是第几个小圈圈
            item.dataset['index']=index;
            // item.setAttribute('data-index',index)
            console.log('***',item.dataset)
            // 给每一个小圆圈绑定一个点击事件
            item.addEventListener('click',function(){
                //取消所有小圈圈身上的背景色
                clickLiBox.forEach(function(item){
                    item.className='click-ridius'
                })
                // 给当前被点击的这个小圆圈添加背景颜色
                this.className='click-ridius click-action'
                // this.classList.add('click-action')
                //点击小圆圈让轮播滚动到指定的位置
                //通过this.dataset['index']知道当前点击的小圆圈是第几个小圆圈
                animation(imgBox,-this.dataset['index']*imgWidth)
                //我们的左右按钮是通过num来计算显示的图片,我们的点击小圆圈和左右按钮没有关系,我们应该将每次点击小圈圈后的小圆圈的索引给num重新赋值
                num=this.dataset['index'];
                // 给小圆圈的背景重新赋值
                circleNum=this.dataset['index'];
                console.log('>>>>>>',num)

            })
        })
        // 鼠标移入轮播图的外层盒子就显示左右按钮
        lbContiner.addEventListener('mouseover',function(){
            left.style.display='block';
            right.style.display='block'
            //鼠标进入时清除自动播放
            clearInterval(lunboAnimation);
        })
         // 鼠标移出轮播图的外层盒子就隐藏左右按钮
        lbContiner.addEventListener('mouseout',function(){
            left.style.display='none';
            right.style.display='none';
            lunboAnimation=setInterval(() => {
                right.click();
            }, 1000);
            
        })
        console.log('*****',imgBox.children[0].cloneNode(true))
        //imgBox.children[0].cloneNode(true) 为 克隆轮播图中的第一张图的li,参数true时为深层拷贝,还有复制li中的img标签
        // 向轮播图的盒子最后添加刚才复制的第一个里
        // 此在向页面渲染点击小圆圈后添加li元素,不会增加小圆圈的数量
        imgBox.appendChild(imgBox.children[0].cloneNode(true));
        clickLiBox[num]
       //  每点击一下右侧按钮,图片就滚动一张
       //  声明一个变量来记录我们点击了几次right按钮,来实现滚动
        var num=0;  //num控制了图片下一张的播放
        var circleNum=0;  //控制小圆圈的显示
        //设置截流阀,防止按钮点击过快,目的时当上一张轮播播放完了再开启截流阀,允许用户点击
        var flag=true;
        right.onclick=function(){
            if(flag){
                flag=false;//先关闭截流阀,当这张轮播动画播放完再开启
                // 当看到最后一张图片后点击的时候,将轮播图瞬间定位到第一张,然后再num+1,滚动到第二张图
                // mgBox.children.length-1为最后一张图
                if(num==imgBox.children.length-1){
                    imgBox.style.left=0+'px'
                    //瞬间移动到第一张图后,num从新开始赋值为0
                    num=0;      
                }
                num++; 
                animation(imgBox,-imgWidth*num,function(){
                    flag=true;
                });
                circleNum++;
        
                // 当显示最后一张克隆的图片时,没有可以显示的小圆圈,(小圆圈的个数比图片少,)其实最后一张图片在用户看来就是第一张图片,将小圆圈计数赋值为0
                // 给第一个小圆圈增加选中状态
                if(circleNum==clickLiBox.length){
                    circleNum=0
                }
                changeCircle() 
            }
          
        }
        left.onclick=function(){
            if(flag){
                flag=false;
                if(num==0){
                //当显示第一张图片后再点击左侧按钮在显示前快速移动到最后一张,给num赋值为最后一张图的索引,再num--
                    imgBox.style.left=-(imgBox.children.length-1)*imgWidth+'px'
                    num=imgBox.children.length-1;  

                }
                num--;
                
                animation(imgBox,-imgWidth*num,function(){
                    flag=true;
                })
            
                // 当显示最后一张克隆的图片时,没有可以显示的小圆圈,(小圆圈的个数比图片少,)其实最后一张图片在用户看来就是第一张图片,将小圆圈计数赋值为0
                // 给第一个小圆圈增加选中状态
            
                if(circleNum==0){
                    circleNum=clickLiBox.length
                }
                circleNum--;
                changeCircle()
            }
            
        }
       
    function changeCircle(){
           //取消所有小圈圈身上的背景色
           clickLiBox.forEach(function(item){
                item.className='click-ridius'
            })
            clickLiBox[circleNum].className='click-ridius click-action';  
    }
    // 轮播图自动播放,实际就是每间隔一定事件就触发一次点击right按钮的点击事件
    //我们要在鼠标经过轮播图时停止轮播,鼠标离开轮播时在开启轮播
   var lunboAnimation=setInterval(() => {
        right.click();
    }, 1000);
// -------------------------------------------------- 切换标签页
//当浏览器最小化或者离开当前页面的时候,定时器是继续走着的,但是Dom是不动的
//---解决方式:当离开页面的时候关闭定时器,回到页面的时候开启定时器

changeDocument()
function changeDocument(){
    // 可以给document添加一个显示隐藏的监听器
    document.addEventListener('visibilitychange',function(){
        // console.log('标签页状态变化')
        // document身上有一个属性,叫做visibilityState属性可以表示当前页面是显示还是隐藏状态 hidden或者visibilitySate
        // console.log(document.visibilityState);
        if(document.visibilityState=='hidden'){
            //页面隐藏了 清除定时器关闭自动轮播
            console.log("_____清除定时器");
            clearInterval(lunboAnimation)

        }else{
            //页面显示了 开启定时器自动轮播
            console.log("_____开启定时器");
            lunboAnimation=setInterval(() => {
                right.click();
            }, 1000);

        }
    })
}

    </script>
</body>
</html>
-------------------------渐变轮播
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        *{
            margin:0;
            padding: 0;
        }
        .lb-continer{
            width: 250px;
            height: 100px;
            border:1px solid red;
            /* position: fixed; */
            position: relative;
            left:300px;
            /* overflow:hidden; */
        }
        ul{
            list-style: none;
        }
        ul.img-box{
            border: 1px solid rgb(29, 27, 27);
            width: 250px;
            height: 100px;
            /* overflow:hidden; */
            /* position: absolute; */
        }
    
        .img-cell:nth-child(1){
            background-color: cornflowerblue;
        }
        .img-cell:nth-child(2){
            background-color: rgb(100, 237, 184);
        }
        .img-cell:nth-child(3){
            background-color: rgb(207, 237, 100);
        }
        .img-cell:nth-child(4){
            background-color: rgb(237, 100, 184);
        }
       .img-box li{
            width: 250px;
            height: 100px;
            position: absolute;
            top:0;
            left:0;
            /* 所有的img透明度设为0 隐藏 */
            opacity: 0;
            transition:opacity 2s;
        }
        li.active{
            opacity: 1;
            background: chartreuse;
        }
        .lb-continer>.left{
            display: block;
            background-color: brown;
            position: absolute;
            left: 0;
            top:50%;
            display: none;
        }
        .lb-continer>.right{
            display: block;
            background-color: rgb(42, 165, 93);
            position: absolute;
            right: 0;
            top:50%;
            display: none;
        }
        .click-ul{
            position: absolute;
            bottom: 0;
            left:50%;
            transform: translateX(-50%);
            display: flex;
        }
        .click-ridius{
            width: 10px;
            height: 10px;
            background-color: cornsilk;
            border-radius: 50%;
            
        }
        .click-action{
            background-color: crimson;
        }
        .img-cell img{
            width: 100%;
            height: 100%;
        }
        .li-style{
            width: 10px;
            height: 10px;
            background-color: burlywood;
            border-radius: 50%;
        }
    </style>
</head>
<body>
    <div class="lb-continer">
        <ul class="img-box">
  
            <li class="img-cell active">
                <img src="../图片素材/by.jpeg" alt="">
            </li>
            <li class="img-cell">
                <img src="../图片素材/hb.jpeg" alt="">
            </li>
            <li class="img-cell">
                <img src="../张玮/屏幕快照 2021-03-27 下午12.41.55.png" alt="">
               
            </li>
            <li class="img-cell">
                <img src="../图片素材/td.jpeg" alt="">
            </li>
        </ul>
        <span class="left">1</span>
        <span class="right">2</span>
        <!-- 可点击的小圆圈定位 -->
        <ul class="click-ul">
        </ul>
    </div>
    <script>
        // 最外层的盒子
        var lbContiner=document.querySelector('.lb-continer')
        // 左侧按钮
        var left=document.querySelector('span.left');
        // 右侧按钮
        var right=document.querySelector('span.right');
        // 轮播图的盒子
        var imgBox=document.querySelector('.img-box');
       var imgLi=document.querySelectorAll('.img-box li')
       var clickBox=document.querySelector('.click-ul')
       console.log(imgLi);
        //  创建焦点,有多少img就创建多少个li,添加到焦点的父元素里  
       for(var i=0;i<=imgLi.length-1;i++){
            var li=document.createElement('li');//创建li标签  
            li.classList.add('li-style');//给li添加类型
            // 给每一个小焦点添加自定义的index,记录这是第几个焦点
            li.dataset.index=i;
            // 默认显示第一张img,所以给第一个焦点添加选中
            if(i==0){
                li.classList.add('active');
            }
            // 给每一个焦点添加点击事件,当被点击的时候
            li.onclick=function(){
                console.log(this.dataset);
               changeImg(this.dataset.index-0);//先转换一下数据类型
            }
            clickBox.appendChild(li)
       }
        // 鼠标移入轮播图的外层盒子就显示左右按钮
        lbContiner.addEventListener('mouseover',function(){
            left.style.display='block';
            right.style.display='block';
            // 鼠标移入时清除定时器
            clearInterval(timer)
       
        })
         // 鼠标移出轮播图的外层盒子就隐藏左右按钮
        lbContiner.addEventListener('mouseout',function(){
            left.style.display='none';
            right.style.display='none';  
            // 鼠标移出时开启定时器
            autoPlay()
        })
        right.onclick=function(){
            changeImg(true);
        };
        left.onclick=function(){
            changeImg(false);
        };
        var num=0;
        function changeImg(value){
            console.log('****',value)
            // 通过调用函数的时候给形参传值来判断点击的是left按钮还是right按钮
            // 将之前显示img隐藏
            imgLi[num].classList.remove('active');
            clickBox.children[num].classList.remove('active');
            // 显示下一张
            if(value===true){
                //形参赋值为true的时候点击是right按钮,显示下一张img
                num++
                if (num==imgLi.length) {
                    num=0;
                }
            }else if(value===false){
                // //形参赋值为false的时候点击是left按钮,显示上一张img
                num--
                if (num<0) {
                    num=imgLi.length-1

                }
            }else{
                // 点击焦点是传入所点击焦点的索引
                num=value
            }
           
          
           
            console.log(num,imgLi.length)
            imgLi[num].classList.add('active');
            // 给当前img对应的焦点添加选中样式
            clickBox.children[num].classList.add('active');

        }
        // ---自动轮播
        autoPlay();
        var timer;
        function autoPlay(){
            timer=setInterval(function(){
                // right.click();
                 changeImg(true);
             },2000)
        }
        //-------切换标签页
        changeDocument()
        function changeDocument(){
            // 可以给document添加一个显示隐藏的监听器
            document.addEventListener('visibilitychange',function(){
                // console.log('标签页状态变化')
                // document身上有一个属性,叫做visibilityState属性可以表示当前页面是显示还是隐藏状态 hidden或者visibilitySate
                // console.log(document.visibilityState);
                if(document.visibilityState=='hidden'){
                    //页面隐藏了 清除定时器关闭自动轮播
                    console.log("_____清除定时器");
                    clearInterval(timer)

                }else{
                    //页面显示了 开启定时器自动轮播
                    console.log("_____开启定时器");
                    timer=setInterval(() => {
                        right.click();
                    }, 1000);

                }
            })
        }
      
    </script>
</body>
</html>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值