JS(二)

193 阅读32分钟

七、流程控制

程序 = 数据 + 算法 任何复杂的程序算法都可以通过“顺序”,“分支”,循环三种基本的程序逻辑组合实现

if语句

if语句

含义 如果

if 关键字

它用于解决分支问题

它决定了代码是否执行

if 语句并不会改变代码的执行顺序

if的基本语法:

/* 
if (布尔表达式) {
    some code....
}
var message;
*/
​
if (!message){
  message = "你爹还是你爹";
}
console.log(message); // "你爹还是你爹"if( 2 < 1 ) console.log(1);
//当if结构中(then)只有一条语句时 可以省略大括号
//省略大括号时 将该条语句和if 同行书写

JS引擎在执行到if语句时

首先判断小括号中的布尔表达式

当布尔表达式的结果为 true 时 则执行then (大括号中的内容)

false 时 不执行 then (大括号中的内容)


在实际应用场景中 很多情况 if的小括号中 不放置布尔表达式

小括号中可以放入 任意数据

当if的条件不是布尔表达式时 ECMAScript会自动将其转换成布尔值进行判断

if-else 语句

含义 如果....要不然....

else 关键字

else关键字无法单独使用

需要配合 if 一起出现

语法:

/*
    if(布尔表达式){
            then
    } else {
            then
    }
*/

if-else 是一个标准的分支结构

它决定了代码的执行内容 2选1的情况

当if中的布尔表达式的结果为 true 时 则执行 if语句的then

false 时 则执行 else语句的then


if 决定了代码是要执行

if-else 决定了代码从哪里执行

if-else 如果只有一条语句 可以使用 条件运算符代替

if-else 嵌套

指定是 在 else 中出现 if-else结构

var age = prompt('请输入年龄');
age = parseInt(age);
if (isNaN(age)){
  console.log('年龄不正确');
} else {
    if (age >= 18){
    console.log('纸醉金迷');
    } else {
    console.log('学习');
  }
}
// 需要判断第一个条件(有效数) 第一个条件为true 则不进行后面的判断
// 第一个条件不满足 判断第二个条件(age >= 18)

else if 语句

EMCAScript 为 if-else的嵌套 提供了独立的语法支持 else if

else if 也不能单独使用 必须配合 if 一起使用

var age = prompt('请输入年龄');
age = parseInt(age);
if (isNaN(age)){
  console.log('年龄不正确');
} else if(age >= 18){
  console.log('纸醉金迷');
} else {
  console.log('学习');
}

switch-case 语句

它相较于if-else 更加特殊

它决定了程序从某个入口开始执行

一般情况 指的是某个范围内的特定值

switch结构也用于解决分支问题

所有的switch能完成的功能 都可以使用if-else代替

switch比较擅长解决 枚举类型(可以被列举出的数据)的分支

if-else 可以判断相等 不相等 大于小于 等等情况的分支 适用性更广

switch-case 只能判断 相等 且是严格相等(全等)使用上比较局限

switch-case 代码结构更清晰 效率更高 只能判断相等情况 if-else 适用更多的场景

语法:

中括号 表示的含义是 可有可无

/*
    switch(变量){
    case 常量:
        code..
        [break;]
    case 常量:
        code..
        [break;]
    [default:]
        code..
    }
*/var day = new Date().getDay(); //获得日期中的星期 取值范围0-6
switch(day){
  case 1: // day === 1
    console.log('星期一')
    break;
  case 2: // day === 2
    console.log('星期二')
    break;
  case 3: // day === 3
    console.log('星期三')
    break;
  case 4: // day === 4
    console.log('星期四')
    break;
  case 5: // day === 5
    console.log('星期五')
    break;
    case 5: // day === 6
    console.log('星期六')
    break;
}

在switch结构中 break 用于结束switch结构

如果从某个节点进入程序没有break时 switch将自动向下执行 穿透执行 直到遇到break为止

break 关键字 不是必须添加的 需要看使用需求

default 关键字 不是必须添加的 需要看使用需求

八、循环结构

循环语句

在大部分情况下 三种循环结构都是可以互换的

for语句适合明确循环次数的场景

不能明确循环次数的场景 推荐使用 while

不能明确循环次数且至少需要执行一次的场景 推荐do-while

while语句

是ECMAScript中的 循环结构

while 是一个关键字

基本语法:

/*
while(布尔表达式){
  some code.
} 
*/

当JavaScript引擎指向while语句时 会判断while小括号中的布尔表达式

布尔表达式结果为true 则执行大括号中的语句

执行语句块结束后 继续判断布尔表达式

布尔表达式结果为false 则结束while结构

var i = 1;
while(i < 5){
  console.log('你好世界');
  i++;
}

在创建循环时 需要为循环设置一个出口(循环条件有可能变成false的情况)

循环如果没有出口(永远是true) 这种情况叫做 死循环

死循环 会导致浏览器卡顿 内存溢出


在while 循环的小括号中放置 循环条件(布尔表达式) 也可以放置其他词

当小括号中的值不是布尔值或布尔表达式 自动将其转换成 布尔类型进行判断

do-while语句

do 做

while 语句 在循环结构中是 先判断 后执行 如果循环条件为false 则不会执行

do-while 语句是 先执行 后判断 如果循环条件 为false 至少会执行一次

do-while 在执行结束后判断循环条件

条件为true 则继续执行

条件为false 循环结束

语法:

/*
do{
    语句
}while(布尔表达式)
*/

for语句

语法来自c语言

语法:

/*
for(表达式1;表达式2;表达式3){
    循环体
}
*/
// 表达式1. 循环初始化条件 通常情况下是一个声明表达式
// 表达式2. 布尔表达式(循环执行条件) 
// 表达式3. 赋值语句(用于修改循环条件)

for语句的执行流程:

  • 执行表达式1 进行循环(只执行一次)

  • 执行循环条件 执行表达式2 <-------

    • 结果为true 执行循环体 ↑
    • 结果为false 循环结束 ↑
  • 执行表达式3 改变循环条件 ---------->

for(var i=1,a=0;i<10,a<10;i++,a++){
    console.log(i,a);
}
//当使用 ,设置多个条件时
//最后一个才是循环条件

in

关键字 ==in==

关键字 in 的作用 是用于检测属性是否存在

var o = {
  name: 'zhangsan',
  age: 20,
  sex: 'nan'
};
​
//我们需要判断对象 o  是否拥有sex属性
console.log(typeof o.sex !== 'undefined'); // true
console.log(o.sex !== undefined); // trueconsole.log('sex' in o); // true
console.log('address' in o); // false

for...in...

是一种循环结构

它的作用是用于遍历对象

for-in 会枚举出所有的key

使用 in 关键字 判断 对象中是否拥有改属性

判断结果true 则执行循环体

var o = {
  name: 'zhangsan',
  age: 20,
  sex: 'nan'
};
​
//属性phone 是不可枚举属性
Object.defineProperty(o, 'phone', { value: 13688778888 });
​
console.log(o);
//age: 20
//name: "zhangsan"
//sex: "nan"
//phone: 13688778888 //不可枚举for (var key in o) {
  console.log(key, o[key]);
//age: 20
//name: "zhangsan"
//sex: "nan"
}
​
var arr = [43, 6, 54, 32, , true, null, , 'aaa', , , false];
​
for (var i = 0; i < arr.length; i++) {
  console.log(arr[i]);
}
​
// 数组的本质也是对象
// 数组的索引 其实就是对象的属性名 只不过这个属性名是数字 并且有序
// 数组的length属性是不可枚举属性console.log(arr);
​
for (var key in arr) {
  console.log(arr[key]);
}

Iterator

JavaScript 原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6 又添加了Map和Set。这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,比如数组的成员是Map,Map的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构。 遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。 Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。 Iterator 的遍历过程是这样的。

(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。 (2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。 (3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。 (4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。

Iterator 主要是提供给 for-of 消费的

for-of

只有部署了 Iterator 接口的数据 才可以使用for-of 进行遍历

const arr = [5432, 23, 32, 54, 32];
​
for (let key in arr) {
  console.log(arr[key]);
}
​
for (let value of arr) {
  console.log(value);
}

for-in 和 for-of 都可以用于数组的遍历 for-in 枚举的是 key for-of 枚举的是 value

const arr = [2, 3, 5, 4];
console.log(arr);
​
const obj = { x: 1, y: 2 };
console.log(obj);
​
for (let value of obj) {
  console.log(value);
}
​
const map = new Map();
map.set('a', 123);
map.set(true, 333);
map.set(123, 456);
map.set([1, 2, 3], 'ok');
​
console.log(map);
for (let [key, value] of map) {
  console.log(key, value);
}
​
const set = new Set([12, 5, 43, 43, 53]);
console.log(set);
​
for (let value of set) {
  console.log(value);
}

原生具备 Iterator 接口的数据结构如下。

Array Map Set String TypedArray 函数的 arguments 对象 NodeList 对象

循环结束语句

break 和 continue 在三种循环结构中都适用

continue在while和do-while中 跳过本次剩余代码 进入下一次循环的条件判断

continue在for语句中 跳过本次剩余代码 执行表达式3

break语句

break 是关键字

break 在 switch 结构中 用于结束switch结构

break 语句也可以应用于循环结构

它在循环中的含义是 结束循环

var i = 1;
while( i < 10){
  if(i == 5){  //此处为循环结束条件
    break;
  }
  console.log(i);
  i++
}   // i = 4//一旦break语句在循环结构中被执行 则立即结束当前循环
//并且break之后在循环中的代码将不执行
//使用break就是在为循环添加结束条件

一般break语句 都会放在if语句中执行

continue语句

continue 关键字

英文含义是 继续

通常放在循环结构中使用

它的作用是跳过本次循环 进入下一次循环

var i = 0;
while (i < 100){
  i++
  if(i % 10 == 7){
    continue;
  }
  console.log(i)
}

循环嵌套

指的是 在一个循环中 出现另外一个循环体

至少两个嵌套

for(var i=1;i<3;i++){
  for(var j=1;j<3;j++){
    console.log(i,j)
  }
}

递归

递归指的是函数在执行过程中的自调用

递归本质上是一种循环结构

构成递归需具备的条件:

  1. 子问题须与原始问题为同样的事,且更简单;

  2. 不能无限制地调用本身,须有个出口,化简为非递归状态处理。

    在数学和计算机科学中,递归指由一种(或多种)简单的基本情况定义的一类对象或方法,并规定其他所有情况都能被还原为其基本情况。

function fn(a){
  console.log(a);// 5 4 3 2 1 0
  if(a!=0){
    fn(a-1);// fn(4) fn(3) fn(2) fn(1) fn(0)
  }
}

为递归设置出口比较麻烦(没有出口会导致溢出)

它是一种效率极低的循环遍历方式

递归遍历的应用场景: 树结构(html,目录,对象)

九、 函数(function)

函数的概念以及作用

  • 函数(function)是一个大型程序中的某部分代码(子程序),由==一个==或==多个==语句块组成。它负责完成某项特定任务,而且相较于其他代码,具备相对的独立性。
  • 一般会有输入参数并有返回值,提供对==过程的封装==和==细节的隐藏==。这些代码通常会被集成为软件库
  • 在面对对象编程语言中,类别或对象的子程序也被称作方法(method)

相较其他代码 函数相对独立

使用函数的意义:

  • 封装过程
  • 隐藏细节

方法(method) 本质是 函数(function)

函数的创建

函数的创建方式

  1. 函数声明
  2. 函数表达式
  3. 函数的构建
  4. 箭头函数

ECMAScript中的函数 是以对象的形式存在的(函数的本质是对象)

函数命名规范

  • 可以使用字母、数字、下划线(_)、美元符($)

  • 标识符不能以数字开头

  • 标识符 不能是关键字 和 保留字

  • 标识符 命名要有明确含义(语义化)

  • 标识符的命名 通常采用 骆驼命名法(驼峰式命名)

    • 小驼峰式命名(变量/函数) 首字母小写 之后的每一个单词首字母大写
    • 大驼峰式命名(类/构造函数) 每一个单词的首字母大写

函数声明

声明式函数

使用关键字 function 加上一个函数名 以及参数列表 函数体 组成

语法:

/*
function functionName(参数值){
  函数值
}
*/

函数不会自动执行

使用函数名调用函数执行

function myFunction(){
  console.log('你好世界');
  console.log('hello world')
}
// 调用函数 使用函数名加小括号
myFunction();

//调用函数可以改变代码的执行顺序
//函数还具有代码的 复用性(重复使用)

函数的表达式(赋值式函数创建)

将一个匿名函数赋值给一个变量或对象的属性

当一个函数被赋值给一个变量时 此时变量名就是函数名

var myFn = function(){
  console.log('函数表达式');
}
myFn();

var o = new Object();
o.fn = function(){
  console.log('函数表达式');
}
O.fn();

函数表达式只能先赋值后使用

函数构建

使用Function构造函数 创建函数

var myFn = new Function('console.log("hello wrold")');
myFn();

匿名函数

指的是创建函数时 未给函数进行命名

使用匿名函数的好处

  • 匿名函数可以有效的隔离作用域 避免作用域之间的互相污染(作用域越小越好)
  • 匿名函数 仅在执行时 临时创建作用域对象 更节约资源

匿名函数 没有名字 不存在声明提前 临时创建的作用域链对象 在函数执行结束后也会立即销毁

匿名函数比实名函数更节约内存


匿名函数的使用方法

  1. 自执行函数(自调用函数 / 一次性函数)
  2. 回调函数(将一个函数作为参数 传给另外一个函数)

自执行函数

(function(){})()
(function(){}())
!function(){}()
~function(){}()
+function(){}()
void function(){}

可以为每一个功能 提供一个自执行函数 可以有效隔离作用域 避免作用域污染

回调函数(callback)

回调函数就是一个被作为参数传递的函数

function show(callback){
  var product = ''
  setTimeout(function(){
    product = '牙膏';
    callback( product + '到货啦')
  },5000)
}
show(function(message){
  console.log(message)
})
​
//------------------------------------------------
​
function isOdd(num1,callback){
  if(num1 % 2){
    callback('是奇数')
  }else{
    callback('是偶数')
  }
}
isOdd(10,function(message){
  console.log(message)
})
​
//------------------------------------------------
​
function fn(callback){
  callback('abc')
}
​
fn(function(msg){
  console.log(msg)
})
​
​
​
//JavaScript 引擎执行代码
//调用函数fn 传入实参匿名函数 这个匿名函数被赋值给了callback 形参
//此时callback就是当前的匿名函数//执行28行 28行调用callback callback在31行 传入'abc'实参 赋值msg
//执行31行 匿名函数
//执行32行 输出 msg 的 'abc' 值
//callback 执行结束
//代码回到29行
//fn函数执行结束

箭头函数

箭头函数就是函数的语法糖

function fn(n) {
  return n;
}
​
console.log(fn(5));//5let fn = n => n;
console.log(typeof fn);//function
console.log(fn(15));//15

箭头函数的标准语法 (形参) => {函数体}

const fn1 = () => {
  console.log('我是箭头函数');
};
​
fn1();

箭头函数最常见的使用场景 —— 回调函数

const arr = [534, 22, 7654, 43, 6, 54, 11];
arr.sort(function (a, b) {
  return a - b;
});

当箭头函数的函数体中 只有一条语句 且该条语句是返回语句时 可以省略大括号 和 return 关键字

arr.sort((a, b) => b - a);
console.log(arr);
​
let result = arr.filter(function (elm) { return elm % 2 });

当箭头函数的参数只有一个时 可以省略小括号

let result = arr.filter(el => el % 2 == 0);
console.log(result);

如果需要定义一个箭头函数 返回一个空对象 {}

  1. 写一个函数体 使用return语句
  2. 给大括号外面包一层圆括号(让里面的大括号被解析成对象 而不是函数体)
const fn2 = () => {
  return {};
};

const fn2 = () => ({});
let result = fn2();
console.log(result);

注意事项

使用箭头函数的注意点

  1. 箭头函数中没有自己的this对象
  2. 不可以将箭头函数作为构造函数使用,如果对箭头函数使用new, js引擎将抛出一个错误,箭头函数没有new
  3. 箭头函数中没有arguments对象,如果需要使用arguments可以用 rest 参数代替。
  4. 箭头函数不能作为Generator 函数,不可以使用yield命令

this 关键字 是一个动态的指针 它会指向函数运行时所在的对象 this 的指向 和函数的定义无关 和函数的调用有关

在箭头函数中没有this对象 在箭头函数中使用this 则默认指向父级作用域中this 在箭头函数中 this的指向是固定的 永远指向父级作用域的this

var a = 10;

const obj = {
  a: 8,
  fn() {
    console.log(this.a);

    setTimeout(function () {
      console.log(this); // window
      console.log(this.a); // 10
    }, 2000);
        setTimeout(() => {
      console.log(this.a); // 8
    }, 3000);

  },// es6 对象函数语法糖 相当于 fn:function(){}
  fn2: () => {
    console.log(this.a);//8
  }
}

// obj.fn();
obj.fn2();//8

在箭头函数中 this的指向是固定的 永远指向父级作用域的this

window.onload = function () {
  let box = document.querySelector('.box');

  box.onclick = function () {
    // let that = this;
    // setTimeout(function () {
    //   console.log(that);
    //   that.style.background = 'red';
    // }, 3000);
    setTimeout(() => {
      this.style.background = 'yellow';
    }, 3000)
  }
}

不可以将箭头函数作为构造函数使用,如果堆箭头函数使用new, js引擎将抛出一个错误

function fn() { }
const f = new fn();
console.log(f);

const fn2 = () => { }
const f2 = new fn2(); // not a constructor
console.log(f2);

箭头函数中没有arguments对象,如果需要使用arguments可以用 rest 参数代替。

function fn() {
  console.log(arguments);
}

fn(543, 11, 756, 342, 54);

const fn2 = (...rest) => {
  // console.log(arguments);  // arguments is not defined

  console.log(rest);
}

fn2(543, 11, 756, 342, 54);

参数的创建

为了函数的使用 更加灵活 函数可以携带参数

参数的创建时在函数创建时进行的

在创建函数时有一个 小括号 加做参数列表

可以在参数列表中 添加函数

参数的命名与变量的命名方式相同

多个参数可以使用英文逗号隔开

创建参数 就先当是为函数创建了局部变量(这个参数仅在函数内有效)

创建函数时 写在参数列表中的参数 叫做 形式参数(形参)

执行函数时 卸载参数列表中的参数 叫做 实际参数(实参)

function my(num1,num2){
  console.log(num1); 				// 2
  console.log(num2); 				// 5 	
  console.lg(num1 + num2) 	// 7
}

myFn(2,5);//2 5 7
//调用函数时 实参列表中的参数 会依次赋值给形参变量

按值传递

调用函数时 写在参数列表中的实参 是在按值传递的(传递值)

参数的传递式依次将实参给形参赋值

形参如果未被赋值 默认值 undefined

function fn(a,b){
  a+=b;
  console.log(a);
}
var a=2;
var b=5;
fn(a,b); //7
// 实参的a b 式两个值
console.log(a);//2

惰性函数

惰性函数 它是一种函数的高阶使用方式

惰性函数的特点

函数本身并不确定自身功能 它的功能由第一次执行决定

function fn(type) {
  if (type === 1) {
    fn = function () {
      console.log(1);
    }
  } else if (type === 2) {
    fn = function () {
      console.log(2);
    }
  }
  return fn(type);
}
//-------------------------------
fn(1);//1
fn(2);//1
fn(3);//1
fn(4);//1
fn(5);//1
//------------------------------
console.log(fn);
//ƒ fn(type) {
//      if (type === 1) {
//        fn = function () {
//         console.log(1);
//        }
//      } else if (type === 2) {
//        fn = function () {
//          console.log(2);
//        }
//      }…
fn(2);//2
console.log(fn); 
//ƒ () {
//          console.log(2);
//       }
fn();//2
fn();//2

案例:

function addEvent(el, type, fn) {
  // 冗余代码 这个判断只需执行1次 判断结束后结果就不会在变 没有必要重复判断
  if (typeof el.addEventListener === 'function') {
    el.addEventListener(type, fn);
  } else {
    el.attachEvent('on' + type, fn);
  }
}

//正确用法
function addEvent(el, type, fn) {
  if (typeof el.addEventListener === 'function') {
    addEvent = function (el, type, fn) {
      el.addEventListener(type, fn);
    }
  } else {
    addEvent = function (el, type, fn) {
      el.attachEvent('on' + type, fn);
    }
  }
  return addEvent(el, type, fn);
}

终极版本:

 var addEvent = (function () {
      if (typeof document.addEventListener === 'function') {
        return function (el, type, fn) {
          el.addEventListener(type, fn);
        }
      } else {
        return function (el, type, fn) {
          el.attachEvent('on' + type, fn);
        }
      }
    })();

arguments

是函数中的一个内置对象

他会存在每一个函数中

它的作用是用于存储函数的实际参数

arguments.callse 式一个静态指针 它指向了当前函数

function fn(a,b,c,d,e,f,g){
  console.log(a,b,c,d,e,f,g);
  console.log(arguments);
}
fn(2,3,4,5,6,7,8)

在函数不设置形参的情况下 可以通过 arguments 获得实参

arguments 为每一个实参 捏造了一个编号(索引)

arguments 是一个有序结构(按照一定的顺序排序)

索引式从0开始计数 0表示的第一个 1表示第二个 .... 以此类推

最大索引式参数的数量 -1

arguments.length

用于表示参数的个数

在函数中 通过arguments 和属性访问器 [ ] 对实参进行访问

语法:

arguments[index]

function fn(){
  console.log(arguments.length);
  console.log(arguments[3])
}
​
//遍历(将所有成员都访问一次)for (var i=0;i<arguments.length;i++){ //i index item
  console.log(arguments[i]);
}

arguments.callee

它是一个指针

它指向当前函数

function fn(){
  arguments.callee();  //相当于fn()
  //Maximum call stack size exceeded 栈(内存)溢出
}

处理不同类型的参数

 // function myFn() {
    //   switch (arguments.length) {  // 实际参数的数量
    //     case 1:
    //       console.log(arguments[0]);
    //       break;
    //     case 2:
    //       console.log(arguments[0] + arguments[1]);
    //       break;
    //     default:
    //       console.log('参数数量错误');
    //   }
    // }
​
    // myFn('你好');
    // myFn('你好', '世界');
    // myFn('你好', '世界', '!');
​
    // ------------------------------------------------------
    function getClass() {
      switch (typeof arguments[0]) {
        case 'string':
          console.log('参数为字符串类型');
          break;
        case 'number':
          console.log('参数为数字类型');
          break;
        case 'boolean':
          console.log('参数为布尔类型');
          break;
        case 'undefined':
          console.log('参数为未定义类型');
          break;
        case 'object':
          console.log('参数为对象或null类型');
          break;
        case 'function':
          console.log('参数为函数类型');
          break;
      }
    }
​
    getClass(123);
    getClass(new Object());
    getClass(alert);

返回值的设置和接收(return)

为给函数提供更灵活的操作 除了参数以外 函数还可以配有返回值

返回值的意义 就是函数的 执行结束

使用函数时 如果函数有返回值 可以使用赋值语句来接受

var num =parseInt('32a'); //将parseInt函数的返回值 赋值给 num 变量

为函数设置返回值 在函数中使用关键字 ==return==

  • 每个函数只有一个返回值
  • 函数的返回值 可以是任意类型的数据
  • 执行return语句后 函数将终止执行

==return== 关键字 不光可以用于返回值的设置 也可以用于结束函数执行

使用return语句结束函数 且没有设置返回值的情况 函数默认返回 undefined

如果函数没有return语句 默认也返回undefined

function myFunction(num){
  return 'abc';
}
myFunction(1); //'abc'

//定义形参后 调用函数时未进行传参 则默认参数为 undefined
function fn2(a){
  console.log(a); 
}
fn2();//undefined

//每个函数 只能有一个返回值
function fn3(){
  var x=5;
  var y=6;
  return x,y;
}
var res=fn3()
console.log(res); //6

//如果一定需要返回多个值 可以使用对象作为返回值
function fn4(){
	var o = new Object();
	o.x=5;
	o.y=6;
  return o;
}
var res=fn4()
console.log(res); //{x:5,y:6}

什么情况下需要函数返回值?

返回值是函数的执行结果

如果我们需要将这个结果进行保存 或 需要 将这个结果放到其他地方使用 就需要为函数设置返回值

function fn5(a,b){
  return a+b
}
fn5(3,5); //8

函数方法

alert()

弹出一个警告框⚠️

isNaN()

判断这个值是否是 NaN 并返回一个布尔值结果

console.log(isNaN(5))
console.log(isNaN('abc'))

typeof()

判断数据类型

语法:typeof value

返回值:string

console.log(typeof 123) //number
console.log(typeof NaN)	//number
console.log(typeof true)	//boolean
console.log(typeof null)	//object
console.log(typeof undefined) //undefined
console.log(typeof 'abc')	//string

console.log(typeof new Object()) //object

console.log(typeof alert) //function

toFixed()

在进行浮点数计算时可以手动控制小数的精度

使用 toFixed() 可以固定小数的位数

语法:num.toFixed(length); // 0 - 100

返回值:string

var num1 = 5
console.log(num1.toFixed(2)) //5.00

prompt()

弹出一个警告框(带有输入框)

用户输入的内容可以使用变量进行保存

用户输入的所有内容都是 字符串类型

var userInput = prompt("请输入");

Data

getDay()

获得日期中的星期 取值范围0-6

方法根据本地时间,返回一个具体日期中一周的第几天,0 表示星期天。对于某个月中的第几天

dateObj.getDay()

Math

Math.random()

生成一个0-1的随机数

这个随机数的范围 理论最小值是0 理论最大值是0.999999999999……

生成一个随机数

var n = parseInt(Math.random()* m + n) n~n+m

console

console.log

控制台日志

console.log("我是你爹")

console.time()

你可以启动一个计时器来跟踪某一个操作的占用时长。每一个计时器必须拥有唯一的名字,页面中最多能同时运行10,000个计时器。当以此计时器名字为参数调用 console.timeEnd()时,浏览器将以毫秒为单位,输出对应计时器所经过的时间。

console.time(timerName);

console.timeEnd()

停止一个通过 console.time() 启动的计时器

console.timeEnd(timerName);

window

window.onload()

窗口.加载

当浏览器窗口 资源加载完毕

执行一个函数

window.onload = function(){
  console.log('ok')
}

document

document.write()

在文档(html文档中的body)中输出

这种输出方式 可以识别标签

document.write("<h1>我是你爹<h1>")

document.getElementById()

通过元素的id获得元素

<input type="button" value="按钮" id="btn"/>
<script>
  var btn = document.getElementByid('btn');
  console.log(btn);
</script>

JSON

JSON格式主要用于前后端的数据交互 在进行数据交互时 通常需要将JSON转换成同等的字符串格式进行发送 前后端进行数据交互 发送的是 JSON字符串格式

JSON字符串有一个硬性要求 内层双引号 外层单引号 内层的属性名 一定要有引号 内层的属性值 字符串类型也一定要有引号

JSON.stringify()

将对象转换成JSON字符串

var o = {username: "zhangsan", age: 20, sex: "nan"}
var obj= JSON.stringify(o);
console.log(obj);//{"username":"zhangsan","age":20,"sex":"nan"} 的字符串

JSON.parse()

将JSON字符串转换成对象

var str = '{"username":"zhangsan","age":20,"sex":"nan"}';
    // 内层如果不是双引号会报错
var obj = JSON.parse(str);
console.log(obj.age);//20
console.log(obj)//返回一个对象

call()

Function.prorotype.call() 语法: fn.call(thisObj,[prop,...propN]); 参数: thisObj(object) 函数中this需要指向的对象 prop(any) 传入到函数中的实际参数 描述: call 会立即调用函数fn 将函数中的this指向修改成 thisObj 如fn有参数则依次将prop作为fn的参数传入

遍历器函数(Generator)

Generator ES2015新增的 遍历器函数

从语法上看 是函数的一种表达形式

它的作用是解决异步的编程问题 让异步的代码 同步化执行

Generator 不会单独使用 它需要配合 co.js 库 一起使用 co.js 库是一个自执行器 它会自动调用Generator的next函数

Generator 函数 是在函数名和function关键字中间添加一个星号

Generator 函数 配合 yield 语句使用

yield 英文含义 生产/产出

yield 会返回一个对象 对象中包含两个属性 value属性表示yield语句后的结果 done属性表示generator的状态

function* fn() {
  console.log(1);
  yield 'aaa';
  console.log(2);
  yield 'bbb';
  console.log(3);
  yield 'ccc';
}
let gen = fn(); // 调用Generator函数会得到一个遍历器对象
console.log(gen);

使用遍历器对象的 next 函数 是将指针移动向下一个状态(yield) 每次调用next方法, 内部的指针会从函数的头部或上一次停下来的地方开始执行,直到遇到下一个yield(或retrun为止) 实际上Generator函数是分段执行的, yield 表示标记暂停的位置,next用于恢复执行

let result;
result = gen.next();//1
console.log(result);//{vule:'aaa',done:false}
result = gen.next();//2
console.log(result);//{vule:'bbb',done:false}
result = gen.next();//3
console.log(result);//{vule:'ccc',done:false}
result = gen.next();
console.log(result);//{vule:undefined,done:true}

// 使用函数调用 加上赋值语句作为循环条件 (循环条件是变量的值)
while (result = gen.next()) {
  if (result.done) break;
  console.log(result.value);
}

async/await

ES2017 新增的关键字 async/await 用于异步操作的优化

async 是Generator的语法糖

async 表示 函数中有异步操作 (async关键字 出现在函数前) await 表示 需要等待异步操作的结果 (await关键字 出现在异步操作前)

async 替换了 Generator 的星号 await 替换了 yield 语句

async / await 是配合 promise 一起使用

 window.onload = function () {
  let btn = document.querySelector('#btn');
	// btn.onclick = function () {
  //   $.get('./interface/hasuser.php', { username: 'root' }, 'json')
  //     .then(val => {
  //       console.log(val)
  //     })
  //     .catch(err => {
  //       console.log(err);
  //     });
  // }

  btn.onclick = async function () {
    let result = await $.get('./interface/hasuser.php', { username: "lisi" }, 'json');
    console.log(result);
  }
}

工厂函数

工厂函数(创建型 设计模式 工厂模式) 工厂函数的作用是用于创建对象 用于隐藏复杂的对象构建细节 在生产对象的过程中 用户不需要知道具体的创建流程 只需要根据需求提供相应的材料即可

工厂生产的产品 是一个完整的产品(成品) 不需要额外进行加工 工厂的特点是可以批量生产

function Phone(screen, mainboard, cpu) {
  let phone = new Object();
  phone.screen = screen;
  phone.mainboard = mainboard;
  phone.cpu = cpu;
  return phone;
}

const phone1 = Phone('三星', '华为', '麒麟9000');
console.log(phone1);

const phone2 = Phone('三星', '华为', '麒麟8000');
console.log(phone2);

// ----------------------------------------------
// document.createElement()  工厂函数

let div = document.createElement('div');
let img = document.createElement('img');

console.log(div);
console.log(img);

十、事件和事件函数

JavaScript是由 事件驱动的

  • 常见事件
  • 鼠标点击
  • 鼠标移动
  • 键盘弹起
  • 键盘按下
  • 窗口打开
  • 窗口关闭
  • .............

事件实质上就是一系列动作

事件处理的特点就是 函数不由用户调用

函数由事件触发执行

元素添加事件

三个必要条件:

  1. 事件源(html元素)
  2. 事件类型(onclick onmousemove onmousedown ....)
  3. 事件处理函数(函数表达式 将一个函数赋值给一个元素的事件属性)
<input type="button" value="按钮" id="btn"/>
<script>
  var btn = document.getElementByid('btn');
  btn.onlick = function(){
    arlert('按钮被点击了');
  }
</script>

html元素

 <script>
 window.onload = function () {
      var btn = document.getElementById('btn');
      var box = document.getElementById('box');
​
      // JS获得的任意html元素 都是以对象的形式存在的
      // console.log(typeof btn); // Object
      // console.log(typeof box);   // Object
​
      // JS访问对象的语法  访问属性 使用 .  操作符
      // box.id
      // console.log(box.id);
      // box.id = 'box2';
      // box.style = "color:yellow";
      // box.title = "hello world";
​
      // class属性例外
      // class 在JS中是 关键字  不能用作命名
      // 访问class时 需要使用的名称是 className
      // box.className = 'yellow';
​
      // JS语法
      // box.style.color = 'red';
      // box.style.backgroundColor = 'yellow';
      // box.style.fontSize = '3em';
​
      // CSS语法
      // box.style = 'color:red;background-color:yellow;font-size:5em;';
    }
</script>
<button id="btn">按钮</button>
  <div id="box">
    Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolore excepturi sint hic quaerat iusto quidem blanditiis
    name eaque. Alias, nemo minus provident repudiandae sapiente iste rem animi ratione iusto vel.
  </div>

鼠标常见事件

事件的名称没有采用驼峰命名 全部小写

在为元素绑定时间是需要添加==on==

onmouseenter / onmouselevae

不支持冒泡事件

//当鼠标在进入时
box.onmouseenter = function(){
 // box.style.background = 'red';
  this.style.background;
}
//当鼠标离开时
box.onmouselevae = function(){
  // box.style.background = 'yellow';
  this.style.background = 'yellow'
}

onmouseover / onmouseout

支持冒泡事件

//当鼠标在元素上时
box.onmouseover = function(){
 // box.style.background = 'red';
  this.style.background;
}
//当鼠标移出时
box.onmouseout = function(){
  // box.style.background = 'yellow';
  this.style.background = 'yellow'
}

onclick / ondbclick

鼠标单机 鼠标双击

//当鼠标单机时
box.onclick = function(){
 // box.style.background = 'red';
  this.style.background;
}
//当鼠标双击时
box.ondbclick = function(){
  // box.style.background = 'yellow';
  this.style.background = 'yellow'
}

oncontextmenu

鼠标右击事件

  box.oncontextmenu = function (ev) {
    console.log(ev.button);
    console.log(ev.type);
  }

键盘常见事件

onkeydown / onkeyup

当键盘==按下==和==弹起==时

键盘上任何按键都会触发 onkeyup

所以获得用户输入值时用 oninput 事件

userinput.onkeydown = function (){
  console.log('keydown',this.value); // this.valuse 相当于 userinput.value
}
​
userinput.onkeyup = function (){
  console.log('keyup',this.value); 
}

表单常见事件

<form action="#" method="get" id="myform">
    <input type="text" id="username">
    <input type="submit">
</form>

onsubmit

表单提交事件

myform.onsubmit = function(){
  alert('表单提交了')
}

onfocus

输入框获得焦点

myform.onfocus = function(){
  //this.style.borderColor = 'red'
  this.value = '你好世界'
}

onblur

输入框失去焦点

myform.onblur = function(){
  this.value += '!'
}

oninput

输入事件

username.oninput = function () {
    console.log('用户输入了', this.value);
}

十一、对象

对象 object

是ECMScript提供的一个 内置 引用数据类型

它的本质 是一组数据(属性)和功能(方法)的集合

对象创建

构造函数 构建对象

var o = new Object();
//对象的属性就是对象的数据
o.name =  '小明'
o.age = 20
//属性方法  函数表达式
//方法就是对象的功能
o.sayHello = function(){
console.log('hello','我是'+o.name)
}
o.sayHello();

JavaScript 中 所有对象都是由构造函数创建的

let o = new Object(); let d = new Date(); let reg = new RegExp();

在面向对象编程语言中 对象是由类创建的 在JavaScript中 构造函数就被看作是类

工厂函数 / 类 / 构造函数 -> 创建对象 命名时都使用 大驼峰命名法

构造函数是用于创建对象的 构造函数需要配合 new 关键字使用 new的作用是在内存中开辟一块存储空间 创建一个对象存入该空间 并返回它的内存地址

一个函数 是构造函数 还是 普通函数 取决于 调用方式 调用时使用new关键字 它就是构造函数 不使用 new就是普通函数

当函数作为构造函数使用时 函数中的this会指向新创建的对象

let o = null;
function Phone() {
  o = this;
  // console.log(this);
}
​
let p1 = new Phone();
console.log(p1);
// console.log(p1 === o);
​
// new Phone(); 
​
let p2 = new Phone();
console.log(p2);
console.log(p1 === p2);
function Bar() {
  // 构造函数中 this 指向新创建的对象
  // 构造函数中 this 关键字上 添加的属性 会作用到所有的实例对象
  // this关键字上的属性 叫做实例的 私有属性(每一个实例对象的属性值都是互相独立的)
  this.a = 'abc';
}
​
let b1 = new Bar();
let b2 = new Bar();
b1.a = 'aaaaaa';
console.log(b1);
console.log(b2);
function Foo a, b, c) {
  // 构造函数 this 中的属性会 作用于每一个实例对象
  this.a = a;
  this.b = b;
  this.c = c;
}
​
let f1 = new Foo('a', 'b', 'c');
console.log(f1);
​
let f2 = new Foo(1, 2, 3);
console.log(f2);
​
let f3 = new Foo(1);
console.log(f3);
// 自定义构造函数 就是自定义数据类型(类)
function Phone(screen, mainboard, cpu) {
  this.screen = screen;
  this.mainboard = mainboard;
  this.cpu = cpu;
​
  // this关键字上 添加的方法 叫做实例的 私有方法(每一个实例都会拥有一个独立的方法)
​
  this.call = function (phoneNumber) {
    console.log(`正在给${phoneNumber}打电话`);
  }
​
  this.play = function (gameName) {
    console.log(`正在打开${gameName}`);
  }
}
​
const phone1 = new Phone('三星', '华为', '麒麟9000');
console.log(phone1);
​
const phone2 = new Phone('三星', '华为', '麒麟8000');
console.log(phone2);
​
// phone1.call(13677778888);
// phone2.call(16436666666);
​
// console.log(phone1.call === phone2.call);
function Phone(screen, mainboard, cpu) {
  this.screen = screen;
  this.mainboard = mainboard;
  this.cpu = cpu;
​
  // this关键字上 添加的方法 叫做实例的 私有方法(每一个实例都会拥有一个独立的方法)
  // 使用私有方法 会造成资源的浪费 占据过多的内存空间
  // 需要让相同的功能 共用一个函数
  // this.call = function (phoneNumber) {
  //   console.log(`正在给${phoneNumber}打电话`);
  // }
​
  // this.play = function (gameName) {
  //   console.log(`正在打开${gameName}`);
  // }
}

字面量创建

{}表示对象

它的本质是构建函数的=='语法糖'==它的本质就是 new Object()

字面量创建对象时 可以直接在大括号中书写 属性和属性值

属性和属性值 是以 键值对的形式书写的

var obj={
//键值对中间使用 英文的冒号隔开
//冒号的左边 属性名
//冒号的右边 属性值
//多组属性名和属性值 使用英文逗号隔开//key(属性名) : value(属性值)
  username: '小花',
  age: 18,
  sex: 'nan',
  //sayName:function(){
  //  console.log(obj.usename)
  //}
    sayName(){// 来自ES6的语法
   console.log(obj.usename)
  }
} //new Object();
//对象的本质 是一组 无序 的键值对

JSON

对象可以使用字面的语法进行创建 字面量的本质就是new Object()的语法糖

字面量的语法 有一个别名 叫做 JSON(JavaScript 对象 简谱)

JSON是一种中立于语言和平台的 轻量级 数据交换格式

{key:value , key:value , key:value}

在JavaScript中 对象的属性名 也是有数据结构的

对象的属性名 默认类型是 字符串

//书写字面量时 如果未给属性名添加引号 则会由JS引擎自动添加
var o={
  'username': '小花',
  'age': 18,
    'sex': 'nan'
}

JSON格式主要用于前后端的数据交互

在进行数据交互时 通常需要将JSON转换成同等的字符串格式进行发送

前后端进行数据交互 发送的是 JSON字符串格式

var objstr='{"username":"zhangsan" , "age":20 , "sex":"nan"}'
//JSON字符串有一个硬性要求
//内层双引号 外层单引号
//内层的属性名 一定要有引号
//内层的属性值 字符串类型也一定要有引号

转换为字符串方法

var o = { "username": "zhangsan", "age": 20, "sex": "nan" };
    // console.log(o + ''); // '[object Object]'
    // console.log(o.toString());  // '[object Object]'

JSON的转换方法

JSON.stringify() 将对象转换成JSON字符串

var str='{"username":"zhangsan" , "age":20 , "sex":"nan"}';
var obj= JSON.stringify(str);
console.log(obj);

JSON.parse()将JSON字符串转换成对象

var str = '{"username":"zhangsan","age":20,"sex":"nan"}';
    // 内层如果不是双引号会报错
    var obj = JSON.parse(str);
    console.log(obj.age);

对象操作

使用点操作符==( . )== 可以访问对象的属性

使用属性访问器 中括号[] 也可以访问对象的属性

var o = { "username": "zhangsan", "age": 20, "sex": "nan" };
console.log(o.age);//20
console.log(o[sex])//sex is not defined
console.log(o['sex'])// 'nan'var a = 'username';
console.log(o.a);// undefined  // o.a 点操作符a识别为对象o的属性 访问的属性a 但是对象并没有属性a
//访问一个对象中不存在的属性 则返回undefinedconsole.log(o[a]);// 'xiaoming' // o[a] 中括号中将a识别成了变量 事实上访问的是o['username']
//成功访问到username属性
​
o['phoneNumber'] = 13688877777; //相当于 o.phoneNumber = 13688877777
console.log(o)
​
o['age'] = 18//为对象中不存在的属性进行赋值操作 就是为对象添加属性
//为对象中已存在的属性进行赋值操作 就是修改对象的属性值//-----------------------------------------------------//删除对象的属性 使用关键字 delete
//使用delete 可以删除属性 但是不能删除最外层的对象
//如果对象的属性 是一个对象 是可以被删除的delete o.age; //detele o['age']
detele o.info;
console.log(o)
​
//不是所有的是属性都可以被删除的
//有些对象的特定属性是无法删除的Object.defineProperty(o,'test',{
  value:123456
});//对象 o 属性名 test 属性值 123456
​
detele o.test //test 属性无法删除console.log(o)
console.log(o.test);

对象的属性操作(CRUD)

  • 增 增加属性 为不存在的属性进行赋值
  • 删 删除属性 使用delete关键字删除属性
  • 改 修改属性 为已存在的属性进行赋值
  • 查 查看属性 使用点操作符 或中括号 访问属性

对象的遍历

const obj = {
  x: 1,
  y: 2,
  z: 3,
  a: 100
};

keys()

Object.keys() 返回对象中所有可以枚举的key 返回值是一个数组

const arr = Object.keys(obj);
​
// console.log(arr);
​
for (var i = 0; i < arr.length; i++) {
  console.log(obj[arr[i]]);
}

values()

Object.values() 返回对象中所有可枚举的key所对应的value值 返回结果是一个数组

const arr2 = Object.values(obj);
console.log(arr2);

对象的方法

assign()

Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象分配到目标对象。它将返回目标对象。 语法: Object.assign(target,...sources); 返回值: target 注意点: 该方法直接修改target 所以不需要接收返回值 不修改source

const obj1 = {
  x: 1,
  y: 2
}
​
const obj2 = {
  z: 3,
  x: 2
}
​
const obj3 = {
  a: 5,
  b: 6,
  x: 7
}
Object.assign(obj1, obj3, obj2);
​
console.log(obj1);
​
--------------------------------------------
const user1 = {
  username: 'xiaoming',
  age: 18
}
​
const user2 = {
  username: 'xiaowang',
  sex: 'nan'
}
​
Object.assign(user2, user1);
console.log(user2);

数组也是对象 数组的属性(key) 是索引值 Object.assign() 方法 在合并属性时 参考的是可以枚举属性的 key 在相同索引时 进行了值的替换 Object.assign 不适合合并数组 Array.prototype.concat() 合并数组

const arr1 = ['a', 'b', 'c'];
const arr2 = ['x', 'y'];
// Object.assign(arr1, arr2);
console.log(arr1.concat(arr2));

应用场景 给函数参数(对象类型) 设置默认值

function myFunction(options) {
  const defaults = {
    width: 0,
    height: 0,
    top: 0,
    left: 0
  };
​
  Object.assign(defaults, options);
  console.log(defaults);
}
​
myFunction({
  top: 200,
  left: 50
});

create()

Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的proto。 创建一个新对象, 使用一个现有对象作为新对象的原型 创建一个新对象,继承现有对象的属性

const obj = {
  name: 'xiaoming',
  sex: 'nan',
  age: 20,
  sayName: function () {
    return this.name;
  }
}
​
obj.age = 18;
​
const obj2 = Object.create(obj);
obj2.name = 'xiaowang'
console.log(obj2);
console.log(obj2.name);
​
console.log(obj2.sayName());

defineProperty()

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。 语法: Object.defineProperty(obj,prop,descriptor); 参数: obj(object) 需要定义属性或修改属性的对象 prop(string) 属性名 descriptor(object) 需要定义的属性的描述信息

const o = {
  username: 'xiaoming'
}
​
Object.defineProperty(o, 'age', {
  value: 18,
  enumerable: true, // enumerable 设置 属性是否可被枚举 默认值 false
  writable: true, // writable 设置 属性是否可以被赋值符改变 默认值 false
  configurable: true // configurable 设置当前属性是否可以被删除 默认值 false
});
​
console.log(o);
console.log(o.age);
​
for (let key in o) {
  console.log(key, o[key]);
}
​
o.age = 15;
console.log(o.age);
​
delete o.age;
console.log(o);
​
// -------------------------------------------const obj = {
  1: '你好',
  2: '世界',
  3: '!',
  // length: 3
}
​
Object.defineProperty(obj, 'length', { value: 3 });
​
for (let key in obj) {
  console.log(key, obj[key]);
}
​
for (let i = 1; i <= obj.length; i++) {
  console.log(obj[i]);
}

defineProperty 提供了 getter 和 setter 两个函数 用于 属性赋值操作 和 读取操作 的 拦截

在读取对象属性时 会触发 getter 函数 函数名叫做 get 在设置对象属性时 会触发 setter 函数 函数名叫做 set

let o = {
  x: 1
};
​
let a = 5;
Object.defineProperty(o, 'x', {
  get() {
    // console.log('我被触发了');
    return 'ok';
  },
  set() {
    // console.log('set触发了');
    a = 8;
  }
});
​
console.log(o.x); // 读取属性x时 会触发 拦截函数 get 并读取get函数的返回值
o.x = 123;  // 执行赋值操作 会触发 拦截函数 set
console.log(a);

试题:

let values = ['animal', 'dog', 'husky'];
let index = 0;
​
// 数据劫持
// 设置window.a的拦截函数 拦截a的值
// 每次读取a时 触发拦截函数 返回一个结果
Object.defineProperty(this, 'a', {
  get() {
    return values[index++]
  }
});
​
if (a === 'animal' && a === 'dog' && a === 'husky') {
  console.log('哈哈 哈士奇');
}

is()

Object.is() 方法判断两个值是否为同一个值

let a = 1;
let b = 1;
console.log(Object.is(a, b));
​
let c = null;
let d;
// console.log(c == d);
console.log(Object.is(c, d));
​
let e = NaN;
let f = NaN;
console.log(NaN == NaN);
console.log(NaN === NaN);// false
console.log(Object.is(e, f)); // true
const o1 = {
  a: 1,
  b: 2,
  c: NaN
}

const o2 = {
  a: 1,
  b: 2,
  c: NaN
}

十二、数据结构

所谓数据结构 指的是讲数据使用特定的关系或进行存储

  • 树 栈 堆 链表 哈希表 字典 散列表 图 ......

数组

是一种常见的数据结构

数组指的是计算机中使用一组连续的内存来保存数据(有序)

将多个元素使用一个变量名进行存储

数组中的元素 是有序排列 每一个数组的元素都拥有一个对应的索引(index)

索引值(index) 是从0开始计数 0表示的是数组中第一个元素 1表示第二个元素 以此类推

数组索引的最小值就是0 最大值是数组的成员数量-1


数据类型

基本数据类型 Undefined Null Boolean Number String BigInt

引用数据类型 Object Function Array

数组创建

所有新创建的数组 都会默认拥有一个length属性

length表示的是数组中元素的数量

length是一个 ==不可枚举== 且 ==不可删除的属性==

使用数组构建函数进行创建

var arr = new Array();
delete arr.length // length是一个无法删除的属性
console.log(arr);// [] 空数组
console.log(arr.length)// 0 // 创建数组时 向数组的构建函数中添加的实参 将依次被存放在新数组中
var arr2 = new Array('西瓜','芒果','菠萝');
console.log(arr2);//['西瓜','芒果','菠萝']var arr3 = new Array(12, 66, 66, 22, 11);
console.log(arr3);//[12, 66, 66, 22, 11]
console.log(arr3.length);// 5// 使用数组的构建函数创建数组
// 如果只有一个参数 且参数是number 表示声明数组的长度
// number 必须是一个大于等于0的 整数
var arr4 = new Array(3.14);//Invalid array length 无效的数组长度
console.log(arr4);
console.log(arr4.length);

使用数组字面量进行创建

[] 表示数组

var arr = []; // []是new Array(0)的'语法糖'
console.log(arr);
​
var arr2 = [3]; // 字面量语法中 写的值 会直接成为数组的成员 并不会影响长度
// length 属性允许被赋值符修改
arr2.length = 15;
console.log(arr2.length);//15
console.log(arr2); //[3,……]
​
var arr3 = ['a', 'b', 'c', 'd'];
// 使用 [index] 访问数组中的元素
// 数组的本质也是对象 只不过数组的属性名是索引值 且 它是有序的
console.log(arr3[1]); //'b'
// 通过索引和赋值操作 可以修改数组中的元素值
arr3[0] = 'z';
console.log(arr3);//['z', 'b', 'c', 'd']
​
arr3[9] = 'q';
console.log(arr3);//['z', 'b', 'c', 'd', ……,'q']
// 数组的索引和数组的元素个数无关 和 最大索引有关
console.log(arr3.length);
​
// 如果数组的索引没有存放对应的元素 则默认为 undefined
console.log(arr3[5]); // undefined
​
// 访问数组中不存在的索引内容 返回 undefined
console.log(arr3[15]); // undefined
​
// -----------------------------------
// 在数组的结尾依次添加元素
arr3[arr3.length] = '你';
arr3[arr3.length] = '好';
arr3[arr3.length] = '世';
arr3[arr3.length] = '界';
console.log(arr3);//['你','好','世','界']
​
// --------------------------------------
// arr3 清空(删除里面所有元素)
arr3.length = 0;  // 正确arr3 = [];  // 错误 使用了一个新数组 替换老数组
// []  的函数是 new Array(0);

数组的遍历

将数组所有成员都访问一次

正序遍历(索引值为升序)

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

倒序遍历(索引值为降序)

for (var i = arr.length - 1; i >= 0; i--) {
    console.log(arr[i]);
}

数组API

Array.prototype 数组的原型

数组的原型 是一个对象

在这个对象中保存了数组实例的公有属性和公有方法

原型中所有的属性和方法 都可以在实例对象中访问到

实例对象

使用构造函数创建对象 就叫做创建这个构造函数的实例

var o = new Object();  //o 是 Object 的实例
var arr = new Array(); //arr 是 Array 的实例
var num = new Number(123);  //num 是 Number 的实例console.log(Array.prototype);

push()

Array.prototype.push()

描述: 方法push 将在数组的结尾处依次添加元素

语法: arr.push(element,[element2,...,elementN]);

参数: element(any) 需要添加到数组尾部的元素

返回值: length(number) 返回添加元素后数组的新长度

注意点: 方法push会直接修改原有数组

var arr = [1, 2, 3, 4, 5];
var len = arr.push(6, 7);  // 将6 和 7 依次添加到数组的结尾
console.log(arr);
console.log(len);

pop()

Array.prototype.pop()

描述: 方法pop 将删除数组中的最后一个元素

语法: arr.pop();

返回值: lastElement 被删除的最后一个元素

注意点: 如果数组arr中没有元素 则返回undefined 该方法直接修改原数组

var arr = [1, 2, 3, 4];
var result = arr.pop();
console.log(arr);
console.log(result);

join()

Array.prototype.join()

描述: join方法将数组中所有的元素转换成字符串连接起来

语法: arr.join([separator]);

参数: separator(string) 分离器 默认值(',')

返回值: 新字符串

注意点: 该方法不修改原有数组

join将数组中所有成员转换成字符串类型 使用的是 toString 函数

null 和 undefined 是没有toString函数的

ECMAScript 为了调用时不发生错误 忽略了 null 和 undefined

var arr = [1, 2, 3, 4, '你好', 'ok', true, null, false, NaN, undefined, , , 123];
var str = arr.join('');
console.log(str);

concat()

Array.prototype.concat()

描述: concat方法用于 合并一个或多个数组

语法: arr.concat(value,[value2...,valueN]);

参数: value(any) 数组或值 所有的value会依次被添加到新数组中

返回值: array 新数组

注意点: 该方法不修改原有数组

var arr = [1, 2, 3];
var arr2 = arr.concat(4, 5, 6);
console.log(arr2);  // [1,2,3,4,5,6]
console.log(arr);  // [1,2,3]
​
var arr3 = [1, 2, 3];
// concat的参数中 如果有数组 会自动展开最外面一层中括号
var arr4 = arr3.concat(4, [5, [6, 7]]);
console.log(arr4); // [1,2,3,4,5,[6,7]]
​
var arr5 = [1, 2, 3];
arr5.push([4, 5, 6]); // push不会自动展开数组
console.log(arr5);//[1,2,3,[4,5,6]]
​
// concat创建的是一个数组的 浅拷贝
var arr6 = [1, [2, 3]];
var arr7 = arr6.concat(4, 5);
arr6[1][0] = 0;
console.log(arr7[1][0]);//0

reverse()

Array.prototype.reverse()

描述: reverse方法将数组中所有元素进行颠倒排列

语法: arr.reverse()

返回值: 原数组

注意点: 直接修改原数组

var arr = [1, 2, 3, 4, 5];
arr.reverse();
console.log(arr);

sort()

Array.prototype.sort()

描述: 通过特点的规则对数组进行排序

语法: arr.sort([compareFunction]);

参数: conmpareFunction(function) 可选参数

比较函数 用来指定某种排列顺序的函数。如果省略,元素则按照字符串的unicode进行排序

回调函数参数:

firstElement 第一个用于比较的元素

secondElement 第二个用于比较的元素

返回值: 原数组

注意点: 直接修改原数组

默认情况下 数组将所有元素转换成字符串后 按照 Unicode 编码 进行排序

arr.sort();
console.log(arr);
​
var arr2 = ['caa', 'abc', 'aaa', 'a', 'bbc', 'bab', 'cba', 'acc'];
console.log(arr2);

升序排列

var arr = [2, 5, 4, 21, 221, 354, 123, 21, 114, 33, 121, 341];
    arr.sort(function (a, b) {
    return a - b;
});

降序排列

var arr = [2, 5, 4, 21, 221, 354, 123, 21, 114, 33, 121, 341];
arr.sort(function (a, b) {
	return b - a;
});

如果指明了 compareFunction ,那么数组会按照调用该函数的返回值排序。即 a 和 b 是两个将要被比较的元素:

如果 compareFunction(a, b) 小于 0 ,那么 a 会被排列到 b 之前;

如果 compareFunction(a, b) 等于 0 , a 和 b 的相对位置不变。备注: ECMAScript 标准并不保证这一行为,而且也不是所有浏览器都会遵守(例如 Mozilla 在 2003 年之前的版本);

如果 compareFunction(a, b) 大于 0 , b 会被排列到 a 之前。

compareFunction(a, b) 必须总是对相同的输入返回相同的比较结果,否则排序的结果将是不确定的。

当返回值小于0时 a和b交换位置

当返回值大于等于0时 a和b不交换位置


返回一个小于0的数 就可以实现 reverse 的效果

var arr = [2, 5, 4, 21, 221, 354, 123, 21, 114, 33, 121, 341];
arr.sort(function () {
	return -1;
});
console.log(arr);

随机打乱数组(随机排序)

var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
arr.sort(function () {
  return Math.random() - 0.5;
});
console.log(arr);
var data = [{
      username: 'zhangsan',
      age: 25
    }, {
      username: 'lisi',
      age: 15
    }, {
      username: 'wangwu',
      age: 18
    }, {
      username: 'zhaoliu',
      age: 23
    }, {
      username: 'fengqi',
      age: 11
    }, {
      username: 'lilei',
      age: 17
    }, {
      username: 'hanmeimei',
      age: 27
}];

data.sort(function (a, b) {
  var val1 = a.age;
  var val2 = b.age;
  return val1 - val2;
  			or
  return val2 - val1;
});
console.log(data);

根据数组中的对象的某一个key 进行排序( 阿里巴巴 面试真题 )

function sortBy(prop, type) {
	type = type || 'desc'; // type表示排序规则 desc表示降序 order表示升序排列
  return function (a, b) {
    var val1 = a[prop];
    var val2 = b[prop];
    return type === 'desc' ? val2 - val1 : val1 - val2;
  }
}
data.sort(sortBy('age', 'order'));

shift()

Array.prototype.shift()

描述: shift方法将数组中第一个元素移出数组 将数组中剩余元素向前移动一位

该方法会将数组的长度-1

语法: arr.shift();

返回值: firstElement 被删除的第一个元素 如果数组为空则返回 undefined

注意点: 直接修改原数组

慎用(性能差)

这个方法的时间复杂度过高 O(n)

var arr = [1, 2, 3, 4, 5];
var result = arr.shift();
console.log(arr);
console.log(result);

// 如果数组长度大于100 不推荐使用此方法
// 将索引为99的元素 赋值给索引0 将长度-1
// [5,2,3,4]

unshift()

Array.prototype.unshift() 描述: unshift在数组头部依次添加元素 添加一个或多个元素 将数组中原有的元素依次向后移动 语法: arr.unshift(value[,valueN]); 参数: value(any) 需要添加到数组头部的元素 返回值: length(number) 返回添加后数组的新长度 注意点: 直接修改原数组 不推荐使用 时间复杂度过高 O(n)

var arr = [1, 2, 3];
arr.unshift('ok', 0);
console.log(arr);

// 时间复杂度 O(1)
arr[arr.length] = arr[0];
arr[0] = 'ok';
console.log(arr);

slice()

Array.prototype.slice();

描述: slice将在数组中选择一个范围 获得这个返回中的内容 组成一个新数组

语法: arr.slice([start[,end]]);

参数: start(number) 开始索引

end(number) 结束索引

返回值: 一个新数组 包含了start到end这个范围内的内容

该方法不修改原有数组

新数组中的元素包含start索引所指元素 不包含end索引所指元素

如果没有参数 则从数组的开始获取到数组的结尾

slice获得的内容是一个数组的浅拷贝

如果没有end参数 则表示从start开始获取到数组的结尾

slice的参数允许是负数 如果是负数则从后往前计算元素 -1表示最后一个 -2表示倒数第二个

var arr = [1, 2, 3, 4, 5, 6, 7, 8];
var arr2 = arr.slice(2, 6);
console.log(arr2);// 3 4 5 6
var arr3 = arr.slice();
console.log(arr3);// 1 2 3 4 5 6 7 8
console.log(arr === arr3);// false
var arr4 = arr.slice(2);
console.log(arr4);// 3 4 5 6 7 8
var arr5 = arr.slice(2, -2);
console.log(arr5);// 3 4 5 6
var arr6 = arr.slice(-5);
console.log(arr6);// 4 5 6 7 8

var arr = [2, 3, [5, 4, 5, 2], 10, 2, 4, 5, 4]
var arr2 = arr.slice();
arr[1] = 5;
arr[2][1] = 3;
console.log(arr)
0: 2
1: 5
2: (4) [5, 3, 5, 2]
3: 10
4: 2
5: 4
6: 5
7: 4
console.log(arr2)
0: 2
1: 3
2: (4) [5, 3, 5, 2]
3: 10
4: 2
5: 4
6: 5
7: 4

toString()

Array.prototype.toString() 描述: toString方法会将数组中所有的元素 都进行toString操作 使用逗号连接成一个新的字符串 语法: arr.toString() 返回值: string 字符串 此方法的调用结果 和join不传参数时 结果相同

该方法不修改原有数组

var arr = [1, 2, 4, true, null, undefined, 'abc', NaN, [1, 2, 3]];
var str = arr.toString();
console.log(str);  // '1,2,4,true,,,abc,NaN,1,2,3'

splice()

Array.prototype.splice() 描述: 用于在数组中进行元素的 删除 插入 替换 语法: arr.splice(start[,deleteCount[,value,...valueN]]); 参数: start(number) 开始索引 deleteCount(number) 删除元素的个数 value(any) 需要添加到start索引位置的元素 返回值: 新数组 包含了所有被删除的元素 如果没有元素被删除 则返回一个空数组

var arr = [1, 2, 3, 4, 5, 6, 7];
var arr2 = arr.splice(2, 2);  // 删除
console.log(arr);// 1,2,5,6,7
console.log(arr2);// 3,4
​
var arr3 = arr.splice(2, 2, 5, 5);  // 删除 插入 -》 替换
console.log(arr); //
console.log(arr3);
​
var arr4 = arr.splice(3, 0, 'ok', 4);
console.log(arr);
console.log(arr4);

indexOf()

Array.prototype.indexOf() 语法:arr.indexOf(searchElement,[fromIndex=0]); 参数:searchElement 需要在数组中查找的元素 fromIndex(number) 开始查找的索引值 默认0 描述: 在数组中查找指定的元素 如果有 则返回第一个被找到的元素的索引 如果没有 返回-1 返回值: index 或 -1

var arr = [45, 654, 2, 342, 5, 12, 2];
var index = arr.indexOf(2, 3);
console.log(index);

原理分析(代码重构)

Array.prototype.indexOf2 = function (searchElement, fromIndex) {
fromIndex = fromIndex || 0; // 如果没有传该参数 默认为0
​
for (var i = fromIndex; i < this.length; i++) {
  if (this[i] === searchElement) {
    return i;
  }
}
return -1;
}
​
var arr = [54, 23, 654, 4, 3, 53, 23];
var index = arr.indexOf2(23, 2);
console.log(index);

foreach()

Array.prototype.forEach() 语法: arr.forEach(callback); 回调参数: currentValue 当前的元素 [index] 当前元素的索引 [array] 当前数组 描述: forEach 将遍历数组 为数组的每一个元素 执行一个回调函数 返回值: undefined

var arr = [1, 2, true, null, undefined, NaN, 'abc', , , '65'];

arr.forEach(function (currentValue, index, array) {
  console.log(currentValue, index, array);
});

arr.forEach(function (val) {
  console.log(val);
});

原理分析(代码重构)

Array.prototype.forEach2 = function (callback) {
  for (var i = 0; i < this.length; i++) {
    if (i in this) { // 判断索引在数组中是否存在  
    // 如果索引存在 就将 当前元素,索引,当前数组 传给回调函数作为参数
      callback(this[i], i, this);
    }
  }
}
arr.forEach2(function (val, i) { //形参 val ,i 接收的就是上面来的 this[i],i
  console.log(val, i)
});

filter()

Array.prototype.filter() 语法: arr.filter(callback); 参数: callback(function) 回调参数: currentValue [index] [array] 返回值: 新数组 包含了所有通过测试的元素 描述: filter方法 用于在数组中进行 检索匹配 检测数组中的每一个元素 是否符合给定条件(布尔表达式) 将所有通过测试的元素 组成一个新数组(将结果为true的元素组成一个新数组)

//----------------传统方法---------------------------------
var arr = [54, 65, 7, 65, 23, 5, 46, 5, 234, 76, 3];
//传统办法 找所有奇数
var result = [];
for (var i = 0; i < arr.length; i++) {
  if (arr[i] % 2) {
    result.push(arr[i]);
  }
}

arr.forEach(function (val) {
  if (val % 2) {
    result.push(val);
  }
})
console.log(result);
//---------------------------------------------------------

var arr2 = arr.filter(function (val) {
  return val % 2 == 0;
});

console.log(arr2);
//----------------------------------------------------------

  var data = [{
      username: 'zhangsan',
      age: 25
    }, {
      username: 'lisi',
      age: 15
    }, {
      username: 'wangwu',
      age: 18
    }, {
      username: 'zhaoliu',
      age: 23
    }, {
      username: 'fengqi',
      age: 11
    }, {
      username: 'lilei',
      age: 17
    }, {
      username: 'hanmeimei',
      age: 27
    }];
var result = data.filter(function (elm) {
  return elm.age < 18;
});

console.log(result);

原理分析(代码重构)

 var data = [{
      username: 'zhangsan',
      age: 25
    }, {
      username: 'lisi',
      age: 15
    }, {
      username: 'wangwu',
      age: 18
    }, {
      username: 'zhaoliu',
      age: 23
    }, {
      username: 'fengqi',
      age: 11
    }, {
      username: 'lilei',
      age: 17
    }, {
      username: 'hanmeimei',
      age: 27
    }];
 Array.prototype.filter2 = function (callback) {
    var result = [];
    for (var i = 0; i < this.length; i++) {
    if (i in this) {
      if (callback(this[i], i, this)) {
        result.push(this[i]);
      }
    }
  }

  return result;
}

var res = data.filter2(function (elm) {
  return elm.age >= 18;
});

console.log(res);

map()

Array.prototype.map() 描述: map方法将创建一个新数组,其内容由每一个回调的返回值组成 语法: arr.map(callback); 参数: callback(function) 回调参数: currentValue [index] [array] 返回值: 新数组

map函数 将遍历数组 为每一个数组的元素 执行一次回调函数 将所有回调函数的结果(返回值) 组成一个新数组

var arr = [1, 2, 3, 4, 5];

var result = [];
arr.forEach(function (val) {
  result.push(val * 10);
});
console.log(result);

var arr2 = arr.map(function (val) {
  return val * 10;
});

console.log(arr2);
//---------------------------------
  var data = [{
      username: 'zhangsan',
      age: 25
    }, {
      username: 'lisi',
      age: 15
    }, {
      username: 'wangwu',
      age: 18
    }, {
      username: 'zhaoliu',
      age: 23
    }, {
      username: 'fengqi',
      age: 11
    }, {
      username: 'lilei',
      age: 17
    }, {
      username: 'hanmeimei',
      age: 27
    }];
var result = data.map(function (elm) {
  return elm.username;
});

console.log(result);

// 链式调用
// 当返回值是数组时 可以在函数结尾继续调用数组的其他函数
var result = data.filter(function (elm) {
  return elm.age >= 18;
}).map(function (elm) {
  return elm.username;
});

console.log(result);

源码重构

Array.prototype.map2 = function (callback) {   
  var result = [];  
  for (var i = 0; i < this.length; i++) {
    if (i in this) {
      result.push(callback(this[i], i, this));
    }
  }
  return result;
}

var arr = [2, 2, 3543, 12, 23, 1];
var res = arr.map2(function (elm) {
  return elm * 2;
});

console.log(res);

reduce()

Array.prototype.reduce() 语法: arr.reduce(callback,[object]); 参数: callback(function) 回调函数 object 传入到回调中的对象 回调参数: prev 上一个 next 下一个 如果参数中传入了 object object 传入的对象 current 当前的元素 描述: reduce 用于数组的 归并操作

// 应用场景1  求和
var arr = [1, 2, 33, 643, 2, 3];

 
console.log(reuslt);

arr.reduce(function (prev, next) {
  // 当参数中只有一个回调函数时
  // 参数 prev 表示数组的第一个元素
  // 参数 next 表示数组中的下一个元素

  // reduce会对数组进行遍历
  // 从第二个遍历开始
  // prev 则为上一次回调函数的返回值

  // 最后一次回调函数的返回值 就会成为reduce的返回值
  console.log(prev, next);

  return 100;
});
// -------------------------------------
// 应用场景2  数据统计
var arr2 = [54, 3, 4, 36, 654, 65, 123, 64, 74];
var result = arr2.reduce(function (obj, current) {
  console.log(obj, current);
  current % 2 ? obj.odd++ : obj.even++;
  return obj;
}, { odd: 0, even: 0 });
console.log(result);

原理分析

 var data = [{
  username: 'zhangsan',
  age: 25
}, {
  username: 'lisi',
  age: 15
}, {
  username: 'wangwu',
  age: 18
}, {
  username: 'zhaoliu',
  age: 23
}, {
  username: 'fengqi',
  age: 11
}, {
  username: 'lilei',
  age: 17
}, {
  username: 'hanmeimei',
  age: 27
}];
Array.prototype.reduce2 = function (callback, object) {
      var reuslt;
      if (typeof object === 'object' && object != null) { // 判断必须要传入一个对象
        for (var i = 0; i < this.length; i++) {
          if (i in this) {
            reuslt = callback(object, this[i]);
          }
        }
        return reuslt;
      }
    }


var res = data.reduce2(function (obj, current) {
  current.age >= 18 ? obj.gte18++ : obj.lt18++;

  return obj;
}, { lt18: 0, gte18: 0 });

console.log(res);

every()

Array.prototype.every() 语法: arr.every(callback) 参数: callback(function) 回调参数: currentValue [index] [array] 返回值: 布尔值 描述: every 为数组的每一个元素执行一个回调函数 回调函数中 返回一个布尔表达式 如果所有布尔表达式的结果 为true 则every的结果为true 任意一个结果为false 则every的结果为false

rr.every(function (val) {
  return val % 2 == 0;
});

console.log(result);
// ----------------------------
var data = [{
  username: 'zhangsan',
  age: 25
}, {
  username: 'lisi',
  age: 25
}, {
  username: 'wangwu',
  age: 18
}, {
  username: 'zhaoliu',
  age: 23
}, {
  username: 'fengqi',
  age: 19
}, {
  username: 'lilei',
  age: 27
}, {
  username: 'hanmeimei',
  age: 27
}];

var reuslt = data.every(function (elm) {
  return elm.age >= 18;
});

console.log(reuslt);

源码重构

Array.prototype.every2 = function (callback) {

  var temp; // 临时存放结果  布尔值结果

  for (var i = 0; i < this.length; i++) {
    if (i in this) {
      temp = callback(this[i], i, this); // 调用回调 接收返回值 返回值是布尔值

      if (!temp) return false; // 判断布尔值如果是false  则直接返回false
    }
  }
  return true; // 循环结束 说明没有false  则直接返回ture
}
var arr2 = [2, 6, 4, 56];

var res = arr2.every2(function (elm) {
  return elm % 2 == 0;
});

console.log(res);

some()

Array.prototype.some() 语法: arr.some(callback) 参数: callback(function) 回调参数: currentValue [index] [array] 返回值: 布尔值 描述: some 为数组的每一个元素执行一个回调函数 回调函数中 返回一个布尔表达式 如果有布尔表达式的结果 为true 则some的结果为true 如果没有布尔表达式的结果为true 则some的结果为false

var data = [{
  username: 'zhangsan',
  age: 25
}, {
  username: 'lisi',
  age: 25
}, {
  username: 'wangwu',
  age: 27
}, {
  username: 'zhaoliu',
  age: 23
}, {
  username: 'fengqi',
  age: 19
}, {
  username: 'lilei',
  age: 27
}, {
  username: 'hanmeimei',
  age: 27
}];

var res = data.some(function (elm) {
  return elm.age < 18;
});

console.log(res);

源码重构

Array.prototype.some2 = function (callback) {
  var temp;

  for (var i = 0; i < this.length; i++) {
    if (i in this) {
      temp = callback(this[i], i, this);

      if (temp) return true;
    }
  }
  return false;
}

var arr2 = [2, 6, 4, 56];

var res = arr2.some2(function (elm) {
  return elm % 2 == 0;
});

console.log(res);

find()

Array.prototype.find() find() 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined。 语法: arr.find(callback); 参数: callback(function) 回调参数: currentValue [index] [array] 返回值: 返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined。

var arr = [312, 543, 60, 32, 654, 1, 25, 43, 32];

// 找一个5的倍数
var reuslt = arr.find(function (el) {
  return el % 5 == 0;
});
console.log(reuslt);

源码重构

Array.prototype.find2 = function (callback) {
  for (var i = 0; i < this.length; i++) {
    if (i in this) {
      if (callback(this[i], i, this)) {
        return this[i];
      }
    }
  }
}
var arr2 = [65, 3, 34, 43, 5, 80];
var res = arr2.find2(function (elm) {
  return elm % 8 == 0;
});

console.log(res);

findIndex()

Array.prototype.findIndex() findIndex()方法返回数组中满足提供的测试函数的第一个元素的索引。若没有找到对应元素则返回 undefined。 语法: arr.findIndex(callback); 参数: callback(function) 回调参数: currentValue [index] [array] 返回值: 返回数组中满足提供的测试函数的第一个元素的值的索引。否则返回 undefined。

var arr = [312, 543, 60, 32, 654, 1, 25, 43, 32];

// 找一个5的倍数
var reuslt = arr.findIndex(function (el) {
  return el % 5 == 0;
});
console.log(reuslt);

源码重构

Array.prototype.findIndex2 = function (callback) {
  for (var i = 0; i < this.length; i++) {
    if (i in this) {
      if (callback(this[i], i, this)) {
        return i;
      }
    }
  }
}

二维数组

二维数组指的是数组中的每一个元素都是数组

数组嵌套

var arr = [  ['北京', '上海', '辽宁'],
  ['大连', '沈阳', '杭州'],
  ['宁波', '南京', '长沙']
];

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

数组拷贝

浅拷贝

浅拷贝的意思应该就是,原对象(就是被拷贝的),目标对象是在堆中保存在两个不同地址的,但是当他们的属性中有引用类型或者函数之类的,就会指向同一个地址,所以修改其中一个对象的属性会改变另一个的对象的属性

数组浅拷贝(只拷贝了数组的第一层数据)

var arr = [1, 2, 3, 4, [5, 6, 7]];
var arr2 = [];
for (var i = 0; i < arr.length; i++) {
	arr2[i] = arr[i]; // 赋值语句 当赋值的数据类型是基本数据类型 直接获得值 如果是引用类型 获得地址
}

console.log(arr);
console.log(arr2);
console.log(arr === arr2); //false
arr2[4][0] = 0; 
console.log(arr);//[1,2,3,4,[0,6,7]]
console.log(arr[4] === arr2[4]);//true

深拷贝

var arr2 = JSON.parse(JSON.stringify(arr)); // '[1,2,3,4,[5,6,7]]'
console.log(arr2);

是一种数据结构(以特定的结构保存数据)

可以将栈看作是一个容器 这个容器只有一个入口和出口

这个入口和出口 在栈的顶部

它式一种 LIFO 的数据结构 Last In First out(后进先出)

Set

ES2015 提供新的数据结构 Set 。它是类似于数组的数据结构(有序) Set的成员都是唯一的 (没有重复的值) Set本身 是一个构造函数 可以实例化

const set = new Set([23, 6, 5, 537, 23, 54]);
console.log(set);//[23,6,5,537,54]

可以使用Array.from 将Set转换成数组

const arr = Array.from(set);
console.log(arr);//[23,6,5,537,54]

Set的应用场景是去除重复项

const arr = [23, 6, 5, 537, 23, 54, 6];
//传统的去重
const arr2 = [];
for (let i = 0; i < arr.length; i++) {
  if (arr2.indexOf(arr[i]) == -1) {
    arr2.push(arr[i]);
  }
}
console.log(arr2);//[23, 6, 5, 537, 54]

const arr3 = [...new Set(arr)];
console.log(arr3);//[23, 6, 5, 537, 54]

字符串去重

const str = 'aaacccccdeeeeffffff';
const s = new Set(str);
console.log([...s].join(''));//acdef

add()

Set.prototype.add() 向set中添加元素 元素将自动添加到set结构的结尾处 如果该元素已存在则忽略 add函数返回 当前set对象 支持链式调用

const set=new Set()
set.add(2);
set.add(5);
set.add(5);
set.add(6);

set.add(2).add(5).add(6).add(9);
console.log(set);//[2,5,6,9]

size()

Set.prototype.size set没有length属性 使用size查看成员数量

console.log(set.size);

delete()

Set.prototype.delete() 删除set中指定的元素 返回一个布尔值(是否删除成功)

const set=new Set();
set.add(2).add(5).add(6).add(9);
let res = set.delete(7);
console.log(set);//[2,5,6,9]
console.log(res);//false

has()

Set.prototype.has() 判断某个元素在set中是否存在 返回值 布尔值

const set=new Set();
set.add(2).add(5).add(6).add(9);
console.log(set.has(3));//false
console.log(set.has(6));//true

clean()

Set.prototype.clear(); 清空set 删除所有元素

const set=new Set();
set.add(2).add(5).add(6).add(9);
set.clear();
console.log(set);//返回一个空对象
console.log(set instanceof Set);//true

Map

数据结构Map Map是一种类似于对象的 哈希结构(key和value对应) Map 提供的是 value 和 value 的对应 任意的数据类型 都可以作为Map结构的key

let box1 = document.querySelector('#box1');
let box2 = document.querySelector('#box2');

const map = new Map();

遍历MapIterator:

console.log(map.keys())//键名
console.log(map.values());//键值
console.log(map.entries());//键名:键值

map.forEach((val, key) => {
	console.log(val, key);
  //hello <div id="box1"></div>
	//world <div id="box2"></div>
})

试题:

  let box1 = document.querySelector('#box1');
  let box2 = document.querySelector('#box2');

  const obj = {};

  //obj[] 表示需要访问属性
  //对象的属性名 有两种数据类型 分别是 String 和 Symbol
  //所以 当前对象的属性名 是 String 类型

  //obj[box1]  box1本质是元素节点 节点就是对象
  //JS引擎调用了对象的toString方法 将 box1 转换成了 String

  obj['[object HTMLDivElement]'] = 'hello';
  obj['[object HTMLDivElement]'] = 'world';

  obj[box1] = 'hello';
  obj[box2] = 'world';

  console.log(box1.toString());  // '[object HTMLDivElement]'
  console.log(box2.toString());  // '[object HTMLDivElement]'

  console.log(obj);//world

set()

Map.prototype.set(key,value);

map.set(box1, 'hello');
map.set(box2, 'world');

get()

Map.prototype.get(key);

console.log(map.get(box2));//hello
console.log(map.get(box1));//world

delete()

Map.prototype.delete(key);

map.delete(box1);
console.log(map)//Map(1) 就只有一个了

clear()

Map.prototype.clear();

map.clear();
console.log(map)//Map(0) 

十三、BOM与DOM

BOM

BOM 浏览器对象模型 没有相关标准 部分功能被广泛支持 浏览器厂商 会根据自己的需求 去定义不同的功能 BOM

window

window 表示的是一个窗口 它指的是浏览器中的标签页 每打开一个标签页 就是新建了一个window对象 window 是一个对象 它是BOM的根(root)对象 它是级别最高的对象 window 在浏览器环境中 是全局对象

console.log(window);

var 声明的所有全局变量 都是window的子属性 (var 声明的全局变量会和window绑定) function 声明的全局函数 也是 window 的子属性(function 声明的全局函数 会和window绑定)

var abc = '你好';
console.log(abc);//'你好'
console.log(window.abc);//'你好'

function fn() {
  console.log('javascript');
}

window.fn();//javascript
function fn2() {
  // 函数中声明了变量a 变量a在当前fn2的函数作用域中有效
  // 变量b 没有声明 直接进行了赋值操作

  // 当你没有声明变量b时 进行赋值操作 浏览器会认为 b 是window的子属性
  // 应为window的子属性可以省略 window.
  // 浏览器将b 理解成 window.b 进行赋值

  var a = b = 5;
}

fn2();
console.log(b); // 访问window.b // 5
function fn3() {
  var msg = '你好世界';

  window.msg = msg; // 将函数作用域中的变量赋值给全局对象的属性
}

fn3();
console.log(msg); //'你好世界'

全局属性

属性名含义
length返回窗口中的框架数量(HTML5标准中被删除)
innerHeight返回窗口的文档显示区的高度
innerWidth返回窗口的文档显示区的宽度
name设置或返回窗口名称
pageXOffset设置或返回当前页面相对于窗口显示区左上角的x位置
pageYOffset设置或返回当前页面相对于窗口显示区左上角的y位置
outerHeight返回窗口的外部高度
outerWidth返回窗口的外部宽度
 var name = 50;  // 和全局属性name 重名了  全局属性 name 表示窗口名称 是一个字符串类型的值
name += 80;  // 拼接
console.log(name);// 5080
console.log(length); // 全局属性 返回框架集数量 已弃用

返回文档显示区域的大小
console.log(innerHeight);
console.log(innerWidth);

浏览器大小
console.log(outerHeight);
console.log(outerWidth);

全局方法

方法名含义
alert()显示带有一段消息和一个确认按钮的警告框
confirm()显示带有一段消息以及确认和取消按钮的对话框。
prompt()显示可提示用户输入的对话框
close()关闭浏览器窗口
open()打开一个新的浏览器窗口或查找一个已命名的窗口
blur()把键盘焦点从顶层窗口移开
focus()把键盘焦点给予一个窗口
print()打印当前窗口的内容
assign()加载新的文档
reload()重新加载当前文档
replace()用新的文档替换当前文档

confirm() 弹出一个带有 确定和取消 窗口 返回一个布尔值

close() 关闭页面

print() 打印

<button id="cls">关闭</button>
<button id="prt">打印</button>
<script>
    window.onload = function () {
      var cls = document.getElementById('cls');
      var prt = document.getElementById('prt');
      
   //------------------------------------------------
     cls.onclick = function () {
    // confirm 弹出一个带有 确定和取消 窗口 返回一个布尔值
    // console.log(confirm('你确定吗?'));
        var bool = confirm('确定要关闭页面吗?');
        if (bool) {
 			 		close();  // 关闭页面
				}
     }
      
  //----------------------------------- 
     prt.onclick = function () {
     	print(); // 打印
  	 }
      
 //------------------------------------  
      var count = 0;
  		window.onblur = function () {
    		count++;
    		console.log('您已经离开页面焦点' + count + '次');
   	 		if (count > 5) {
  				alert('考试结束');
				}
      }
	}
</script>

open 打开新的窗口open(网址," ",位置)

var config = "left=200,top=300,width=500,height=300";
//打开窗口

var openurl = "https://www.baidu.com";

var newWin = open(openurl, 'popwin', config);

定时器

多用于网页动态时钟、制作倒计时效果

周期性时钟

  • 以一定的间隔执行代码,循环往复

一次性时钟

  • 在一个设定的时间间隔之后来执行代码,而不是在函数被调用后立即执行

一次性定时器

一次性计时器 setTimeout() 语法: setTimeout(callback,delay); 参数: callback(function) 回调函数 delay(number) 延迟时间 以毫秒为单位 返回值: id(number) 计时器编号

setTimeout 在设定时间到达后 执行一次回调函数 只执行一次

setTimeout(function () {
  console.log('你好');
}, 3000);

关闭一次性计时器 clearTimeout(id)

var timer = setTimeout(function () {
  console.log('你好世界')
}, 3000);

window.onload = function () {
  var btn = document.getElementById('btn');

  btn.onclick = function () {
    clearTimeout(timer);
  }

}

周期性定时器

周期性计时器 setInterval() 语法: setInterval(callback,delay); 参数: callback(function) 回调函数 delay(number) 间隔执行时间 单位为毫秒 返回值: id(number)

周期性计时器 在开启后每间隔一定的时间执行一次回调函数

关闭周期性计时器 clearInterval(id)

var count = 1;
var timer = setInterval(function () {
if (count > 10) {
    clearInterval(timer); // 关闭计时器
    return;
  }
    console.log('你好世界' + count);
  count++;
}, 1000);
​
console.log('ok');

navigator

var browser = {
      userAgent: function () {
        var ua = navigator.userAgent;
        var ualower = navigator.userAgent.toLocaleLowerCase();
        return {
          trident: ua.indexOf('Trident') > -1, // IE内核
          presto: ua.indexOf('Presto') > -1, // opera内核
          webKit: ua.indexOf('AppleWebKit') > -1, //苹果、谷歌内核
          gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') == -1, // 火狐内核
          mobile: !!ua.match(/AppleWebKit.*Mobile.*/) || !!ua.match(/AppleWebKit/), // 是否为移动终端
          ios: !!ua.match(/(i[^;]+;( U;)? CPU.+Mac OS X/), // IOS终端
          android: ua.indexOf('Android') > -1 || ua.indexOf('Mac') > -1, // 安卓终端
          iPhone: ua.indexOf('iPhone') > -1 || ua.indexOf('Mac') > -1, // 是否为iphone或QQHD浏览器
          iPad: ua.indexOf('iPad') > -1, // 是否为iPad
          webApp: ua.indexOf('Safari') == -1, // 是否web应用程序,没有头部与底部
          QQbrw: ua.indexOf('MQQBrowser') > -1, // QQ浏览器(手机上的)
          weiXin: ua.indexOf('MicroMessenger') > -1, // 微信
          QQ: ualower.match(/\sQQ/i) == " qq", // QQ App内置浏览器(需要配合使用)
          weiBo: ualower.match(/WeiBo/i) == "weibo", // 微博
          ucLowEnd: ua.indexOf('UCWEB7.') > -1, //
          ucSpecial: ua.indexOf('rv:1.2.3.4') > -1,
          webview: !(ua.match(/Chrome/([\d.]+)/) || ua.match(/CriOS/([\d.]+)/)) && ua.match(/(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/),
          ucweb: function () {
            try {
              return parseFloat(ua.match(/ucweb\d+.\d+/gi).toString().match(/\d+.\d+/).toString()) >= 8.2
            } catch (e) {
              if (ua.indexOf('UC') > -1) {
                return true;
              }
              return false;
            }
          }(),
          Symbian: ua.indexOf('Symbian') > -1,
          ucSB: ua.indexOf('Firofox/1.') > -1
        };
      }()
    };
    console.log(browser.userAgent);

location

location 对象 属于BOM 是全局对象 window.location

用于保存或设置 URL(统一资源定位器) 相关信息

URL的组成 协议 主机名 端口 路径 查询 锚点

协议 http:// https:// 主机名 www.baidu.com 127.0.0.1 端口 :80 :443 路径 /news/index.html 查询 ?username=zhangsan&age=25&sex=nan 锚点 #info

www.baidu.com:443/news/index.…

console.log(location);
​
console.log(location.href);  // 用于获得或设置完整url信息
console.log(location.host); //  获得或设置 主机名和端口号
console.log(location.hostname); // 主机名
console.log(location.port); // 端口号
console.log(location.protocol); // 协议
console.log(location.pathname); // 路径
console.log(location.hash); // 锚点 #开始的部分
console.log(location.search); // 查询 ?开始的部分
window.onload = function () {
    var box = document.getElementById('box'); 
  box.onclick = function () {
    location = 'https://www.baidu.com';
    // location.href = 'https://www.baidu.com';
  }
}

history

window.history的方法:

方法名含义
back()加载history列表中的前一个URL
forward()加载history列表中的下一个URL
go()加载history列表中的某一个具体页面(-1上一个页面 1下一个页面 0刷新)

search 表单提交提取数据

var data = location.search.slice(1).split('&');
console.log(data);
​
// data.forEach(function (val) {
//   var item = val.split('=');
//   // console.log(item);
//   switch (item[0]) {
//     case 'username':
//       console.log('用户名是:' + item[1]);
//       break;
//     case 'password':
//       console.log('密码是:' + item[1]);
//       break;
//   }
// });
​
var result = data.reduce(function (obj, curr) {
  var item = curr.split('=');
  obj[item[0]] = item[1];
  return obj;
}, {});
​
console.log(result);

SPA(Single Page Application) 单页面应用

window.onhashchange = function () {
  var main = document.getElementById('main');
​
  switch (location.hash) {
    case '#/index':
      main.innerHTML = '首页';
      break;
    case '#/music':
      main.innerHTML = '音乐';
      break;
    case '#/user':
      main.innerHTML = '用户';
      break;
    case '#/settings':
      main.innerHTML = '设置';
      break;
  }
}
 <ul>
    <li><a href="#/index">首页</a></li>
    <li><a href="#/music">音乐</a></li>
    <li><a href="#/user">用户</a></li>
    <li><a href="#/settings">设置</a></li>
</ul>
<div id="main">
    首页
</div>

DOM

DOM是W3C(万维网联盟)的标准,是中立于平台和语言的接口,它允许程序和脚本动态的访问和更新文档的内容、结构和样式。 W3C DOM标准被分为3个不同的部分:

  • 核心DOM 针对任何结构化文档的标准模型
  • XML DOM 针对XML文档的标准模型
  • HTML DOM针对HTML文档的标准模型

节点

在HTML文档中

document 根节点 doctype 元素 属性 文本 注释

都是节点

节点组成了 文档树 html文档是一种树结构

childNodes

node.childNodes // 获得一个元素节点的所有子节点集合 获得到的元素 是一个 类数组对象(ArrayLike / TypedArray) 类数组对象 特点是 所有的元素都是有序排列 并有索引和length属性

console.log(list.childNodes);

parentNode

node.parentNode 获得父级节点

通过元素节点的 parentNode 可以向上访问父级节点 最高级别的节点是 document

最多可以访问到 document

console.log(list.parentNode.parentNode.parentNode);

parentElement

node.parentElememnt 获得父元素

通过元素节点的 parentElement 可以向上访问父级元素 最高级别的元素是 html 最多可以访问到 html

console.log(list.parentElement.parentElement);

firstChild

node.firstChild 第一个子节点

console.log(list.firstChild);

lastChild

node.lastChild 最后一个子节点

console.log(list.lastChild);

firstElementChild

node.firstElementChild 第一个子元素

console.log(list.firstElementChild);

lastElementChild

node.lastElementChild 最后一个子元素

console.log(list.lastElementChild);

children

node.children 获得子元素集合

类数组对象

console.log(list.children);
  var arr = Array.from(list.children);

  arr.forEach(function (elm, i) {
    // if (i % 2) {
    //   elm.style = 'color:red';
    // } else {
    //   elm.style = 'color:blue';
    // }

    elm.style = i % 2 ? 'color:red' : 'color:blue';
  });

previousSibling

node.previousSibling 上一个兄弟节点

nextSibling

node.nextSibling 下一个兄弟节点

previousElementSibling

node.previousElementSibling 上一个兄弟元素

nextElementSibling

node.nextElementSibling 下一个兄弟元素

textContent

 window.onload = function () {
  var box = document.getElementById('box');  
  //元素节点 textContent 用于获取或设置 元素的 文本内容
  //textContent 不识别标签
  element.textContent
​
  console.log(box.textContent);
​
  box.textContent = '<h1>你好世界</h1>';
  box.innerHTML = '<h1>你好世界</h1>';
}
<div id="box">
    <span style="color:red">Lorem ipsum dolor sit amet consectetur adipisicing elit.</span> Vitae rem illo distinctio
    aperiam aut labore
    voluptas,
    officia eveniet? Iusto aperiam necessitatibus dolorem quos cumque non perferendis sequi facilis. Neque, repellendus.
</div>

类数组对象转换成数组(重点)

  1. Array.from(ArrayLike) // 来自ES6 推荐方法
  2. [].slice.call(ArrayLike) // 兼容低版本ie
  3. [...ArrayLike] // 来自ES6 展开运算符
  var arr = Array.from(list.childNodes);
  console.log(arr);
​
  var arr2 = [].slice.call(list.childNodes);
  console.log(arr2);
​
  var arr3 = [...list.childNodes];
  console.log(arr3);

节点属性

所有的节点 都有 节点名(nodeName) 节点类型(nodeType) 节点值(nodeValue)

节点 string类型 number类型 string或null


元素节点(html标签) 元素名(大写) 1 null

属性节点(标签属性) 属性名 2 属性值(string)

文本节点(文本内容) #text 3 文本值(string)

注释节点(注释内容) #comment 8 注释内容(stirng)

文档节点(document) #document 9 null

文档声明(doctype) html 10 null

window.onload = function () {
  var list = document.getElementById('list');
  console.log(list.nodeName);  // 'UL'
  console.log(list.nodeType);   // 1
  console.log(list.nodeValue); // null
​
  console.log(list.firstElementChild.nodeName); //  'LI'
  console.log(list.firstElementChild.nodeType);  // 1
  console.log(list.firstElementChild.nodeValue);  // null
​
  console.log(list.attributes['title']);  // 属性节点  title属性
  console.log(list.attributes['title'].nodeName);  // 'title'
  console.log(list.attributes['title'].nodeType);  //  2
  console.log(list.attributes['title'].nodeValue); // '我是list的title属性'
​
  console.log(list.firstChild.nodeName);  // '#text'
  console.log(list.firstChild.nodeType);  // 3
  console.log(list.firstChild.nodeValue); // '\n    '
​
  console.log(list.childNodes[5]); // 注释节点
  console.log(list.childNodes[5].nodeName);   // '#comment'
  console.log(list.childNodes[5].nodeType);   // 8
  console.log(list.childNodes[5].nodeValue);  // 注释内容
​
  console.log(document);
  console.log(document.nodeName);  // '#document'
  console.log(document.nodeType);  // 9
  console.log(document.nodeValue); // null
  
  console.log(document.doctype);
  console.log(document.doctype.nodeName);  // 'html'
  console.log(document.doctype.nodeType);  // 10
  console.log(document.doctype.nodeValue); // null
  }
<body>
  <ul id="list" title="我是list的title属性">
    <li>item 1</li>
    <li>
      item 2
      <span>我是span</span>
    </li>
    <!-- 我是注释 -->
    <li>item 3</li>
    <li>item 4</li>
    <li>item 5</li>
  </ul>
</body>

元素节点属性

btn.onclick = function () {
  if (box.hasAttribute('class')) {
    box.f('class');
  } else {
    box.setAttribute('class', 'red');
  }
}

attributes

获得元素节点的属性集合 属性集合 是一个 ==类数组== 对象 element.attributes

console.log(box.attributes);
console.log(box.attributes[0]);  // 通过索引获得属性节点
console.log(box.attributes['title']); // 通过属性名获得属性节点

getAttribute

获得元素的属性值

element.getAttribute(attributeName)

console.log(box.getAttribute('id'));
console.log(box.getAttribute('title'));

setAttribute

设置或修改属性值 当属性已存在时为修改属性值 属性不存在时为设置属性值

element.setAttribute(attributeName,attributeValue);

box.setAttribute('class', 'red');
box.setAttribute('title', 'div元素');

removeAttribute

删除属性

element.removeAttribute(attributeName);

box.removeAttribute('title');

hasAttribute

判断属性是否存在 返回布尔值

element.hasAttribute(attributeName);

console.log(box.hasAttribute('title'));

类名操作

HTML5 标准 为类名操作提供了一套比较遍历的API classList

语法: element.classList

btn.onclick = function () {
    box.classList.add('show'); // 添加类名
    box.classList.remove('c3');  // 删除类名
    box.classList.replace('c3', 'c7'); // 替换类名
    box.classList.toggle('c9'); // 切换类名
  }

元素选择

  <input type="checkbox" name="check">
  <input type="checkbox" name="check">
  <input type="checkbox" name="check">
  <input type="checkbox" name="check">
  <input type="checkbox" name="check">
  <input type="checkbox" name="check">
    <ul id="list">
    <li>item 1</li>
    <li class="item">item 2</li>
    <li>item 3</li>
    <li class="item">item 4</li>
    <li class="item">item 5</li>
    <li class="item">item 6</li>
    <li>item 7</li>
    <li>item 8</li>
    <li>item 9</li>
    <li>item 10</li>
  </ul>
​
  <ul>
    <li>1111</li>
    <li>1111</li>
    <li>1111</li>
    <li>1111</li>
    <li>1111</li>
    <li>1111</li>
    <li>1111</li>
  </ul>

getElementsByTagName

节点.通过标签名选择元素 选择结果是一个类数组对象 哪怕只有一个元素被选中 也是类数组对象 node.getElementsByTagName()

var list = document.getElementById('list'); // 查找整个DOM树
var res = list.getElementsByTagName('li');  // 仅在list中查找 缩小了查找范围 
console.log(res);

getElementsByName

通过name属性查找元素 结果是类数组对象 表单元素有name属性 所以这个方法通常用来选择表单元素 document.getElementsByName()

var result = document.getElementsByName('check');
console.log(result);

getElementsByClassName

通过类名获取元素 结果是类数组对象 node.getElementsByClassName()

var result = document.getElementsByClassName('item');
console.log(result);

getElementById

通过元素的id获得元素 结果是类数组对象

node.getElementById()

<input type="button" value="按钮" id="btn"/>
<script>
  var btn = document.getElementByid('btn');
  console.log(btn);
</script>

querySelector

querySelector 获得与css选择器匹配的第一个元素

node.querySelector()

var firstcheck = document.querySelector('[type="checkbox"]');
console.log(firstcheck);

querySelectorAll

querySelectorAll 获得与css选择器匹配的所有元素

node.querySelectorAll() 返回类数组对象

var check = document.querySelectorAll('[type="checkbox"]');
console.log(check);
​
var li = document.querySelectorAll('#list>li:not(.item)');
console.log(li);

特殊选取

console.log(document.documentElement); // 选择HTML
console.log(document.body); // 选择BODY
console.log(document.head); // 选择HEAD

元素

创建元素

document.createElement(tagName);

var box = document.createElement('div');
box.textContent = '你好世界';
box.setAttribute('id', 'box');
console.log(box);

添加元素

父节点.追加子节点(子节点)

在指定父节点的结尾处添加一个子节点

parentNode.appendChild(childNode);

document.body.appendChild(box); //在body加入名为 box 的 div

移动元素

var list1 = document.querySelector('.list1');
var li = document.querySelector('.list1>li:nth-child(3)');
​
var result = list1.removeChild(li);
var list2 = document.querySelector('.list2');
​
console.log(result);
​
list2.appendChild(result);
​
//--------------------------------
//如果需要移动元素 可以直接
parentNode.appendChild(child);
list2.appendChild(li);

插入元素

在指定父节点中 一个以存在的子节点之前插入一个新节点

parnetNode.insertBefore(newChild,existingChild)

var title = document.querySelector('#title');
document.body.insertBefore(box, title);

删除子节点

在父节点中 删除子节点

返回被删除的节点

parentNode.removeChild(childNode);

删除元素

element.remove() 删除元素 没有返回值

替换元素

在指定的父节点中 使用一个新节点替换一个旧节点

parentNode.replaceChild(newChild,oldChild);

var li = document.createElement('li');
li.textContent = '我是新元素';
​
var list1 = document.querySelector('.list1');
list1.replaceChild(li, list1.lastElementChild);

HTML DOM

HTML DOM 的思想是 将HTML元素 对象化 将每一个标签元素 看作是一个对象

标准DOM 适用于 元素创建 修改 删除 查询 节点操作 属性操作 HTMLDOM 适用于 属性的访问和修改

对象化 就是把标签看成对象 对象.属性 的语法 就是由 HTML DOM 提供的

console.log(box.title);
console.log(box.className);
console.log(box.id);
// box.title = '你好世界';  // box.setAttribute('title','你好世界');
​
  //-----------------------------------
  var img = document.createElement('img');
  img.setAttribute('src', '1.jpg');
  console.log(img);
​
//不推荐使用 因为有些是没有的
  var img2 = new Image();
  img2.src = '2.jpg';
  console.log(img2);
​
  var div = new Div();
  console.log(div); //没有
​
  var span = new Span();
  console.log(span); //没有
​
  var option = new Option();
  console.log(option);

css属性

offsetWidth / offsetHeight

获得元素的盒模型大小 盒模型 box-sizing content-box 标准盒模型 大小 = content + padding + border border-box 怪异盒模型 大小 = 宽高

console.log(outer.offsetWidth, outer.offsetHeight);
console.log(inner.offsetWidth, inner.offsetHeight);

offsetTop / offsetLeft

获得元素的 定位值 默认根据body的位置进行计算 如果在它的祖先级元素中 存在定位元素 则根据最近的一个定位元素的位置进行计算

console.log(inner.offsetTop, inner.offsetLeft);

访问元素行内样式

访问元素的行内样式 element.style 是一个对象

element.style.cssText 获得行内样式文本

element.style.属性 只能获得和设置 元素的行内样式 = box.sytle['属性']

window.onload = function () {
  var box = document.querySelector('.box');  
  
  console.log(box.style.cssText);
  console.log(box.style.height);
  console.log(box.style.border);
  console.log(box.style.color);
​
  box.style.color = 'blue';
​
  box.style.backgroundColor = 'red'; // 多个单词写驼峰命名法 
  box.style['background-color'] = 'yellow';
}
​
<div class="box" style="width:100px;height:100px;border:1px solid black;"></div>

getComputedStyle

获得计算后样式(获得的就是最后生效的样式) getComputedStyle(elm)

ie9以下浏览器 elm.currentStyle

window.onload = function () {
  var box = document.querySelector('.box');
 
  console.log(getComputedStyle(box)['color']);
  console.log(getComputedStyle(box)['width']);
    
  console.log(box.currentStyle['width']);
​
  //兼容函数
  function getStyle(elm, style) {
    if (typeof getComputedStyle === 'function') {
      return getComputedStyle(elm)[style];
    } else {
      return elm.currentStyle[style];
    }
  }
​
  console.log(getStyle(box, 'width'));
}

滚动事件

window.onload = function () {
  var main = document.querySelector('.main');
​
  var span = document.createElement('span');
​
  main.appendChild(span); // 放入元素
​
  span.style.position = 'fixed';
  span.style.top = 0;
  span.style.left = 0;
  span.style.backgroundColor = '#0A74DA';
  span.style.height = '3px';
  span.style.zIndex = 100;
​
  // 窗口滚动条滚动事件
  // window.onscroll
​
  // 改变窗口大小事件
  // window.onresize
​
  window.onscroll = window.onresize = function () {
    // 获得 滚动条距离页面顶部的距离(html元素的scrollTop)
    // console.log(document.documentElement.scrollTop);
var top = document.documentElement.scrollTop; // 滚动条滚动的距离
​
var height = document.body.offsetHeight - innerHeight; // 总共能滚动的距离
​
if (height > 0 && top) {
  span.style.width = top / height * 100 + '%';
} else {
  span.style.width = 0;
}
  }
}

onscroll

窗口滚动条滚动事件 window.onscroll

onresize

改变窗口大小事件 window.onresize

文件碎片

文档碎片 (将创建型的DOM操作 存入一个临时容器 将这个容器一次性放入页面) 优化DOM性能( DOM性能非常差 -> 原则是 CSS能实现的 不写JS )

document.createDocumentFragment()

window.onload = function () {
  var list = document.getElementById('list');
​
  // for (var i = 1; i <= 100; i++) {
  //   var li = document.createElement('li');
  //   li.textContent = 'item ' + i;
  //   list.appendChild(li);
  // } 
  
  var f = document.createDocumentFragment();
  for (var i = 1; i <= 100; i++) {
    var li = document.createElement('li');
    li.textContent = 'item ' + i;
    f.appendChild(li);
  }
  list.appendChild(f);
}

DOM事件

DOM 0级事件绑定

DOM0级事件 事件绑定 将一个事件处理函数 赋值给一个事件处理属性

window.onload = function () {
  var btn = document.getElementById('btn');
​
  // DOM0级事件处理 只能为元素添加一个事件处理函数
​
  btn.onclick = function () {
    console.log('ok');
  }
​
  btn.onclick = function () {
    console.log('abc');
  }
​
  function fn() {
    console.log('实名函数');
  }
​
  btn.onclick = fn;
​
  // --------------------------------------------
​
  btn.onclick = function () {
    console.log('ok');
​
    btn.onclick = null; // 解绑事件 将事件处理属性赋值为null
  }
}

DOM 2级事件监听

DOM2级事件处理(事件监听) node.addEventListener(eventType,callback,[,bool=false]); 参数: eventType(string) 事件类型(事件名) callback(function) 事件监听函数 bool 事件捕获(true)/事件冒泡(false)

DOM2允许给一个元素 添加多个事件处理函数 多个事件处理函数 会依次添加到 事件池(队列) 先进先出 当事件被触发时 将依次从事件池中取出事件处理函数 执行

window.onload = function () {
  var btn = document.querySelector('#btn');
​
  btn.addEventListener('click', function () {
    alert(1);
  });
​
  btn.addEventListener('click', function () {
    alert(3);
  });
​
  btn.addEventListener('click', function () {
    alert(2);
  });
}

HTML5标准 新增了一个事件 用于替代 window.onload 事件 DOMContentLoaded 当 文档结构加载完毕后执行 这个事件必须使用DOM2级 addEventListener 进行添加

window.onload = function () { //页面资源加载完毕
  console.log('window.onload');
}
​
document.addEventListener('DOMContentLoaded', function () { //文档结构加载完毕
  // console.log('ok');
  var btn = document.querySelector('#btn');
  btn.addEventListener('click', function () {
    alert('btn click');
  })
});

移除事件

node.removeEventListener(eventType,callback);

document.addEventListener('DOMContentLoaded', function () {
  var btn = document.querySelector('#btn');
​
  function fn() {
    console.log('ok');
    btn.removeEventListener('click', fn);
  }
​
  btn.addEventListener('click', fn);
});

IE事件处理

ie 事件处理 attachEvent(eventType,callback);

ie 移除事件 detachEvent(eventType,callback);

ie的事件名需要带on

btn.attachEvent('onclick', function () {
  alert(123);
    btn.detachEvent('onclick', arguments.callee);
});

在IE9以下的浏览器

没有addEVentListener

低版本的IE时间 没有事件捕获流程 默认冒泡

兼容性处理

事件处理

 function addEvent(el, type, fn) {
      if (typeof el.addEventListener === 'function') {
        el.addEventListener(type, fn);
      } else {
        el.attachEvent('on' + type, fn);
      }
    }

删除事件

function removeEvent(el, type, fn) {
  if (typeof el.removeEventListener === 'function') {
    el.removeEventListener(type, fn);
  } else {
    el.datachEvent('on' + type, fn);
  }
}

案例:

window.onload = function () {
  var btn = document.getElementById('btn');
​
  addEvent(btn, 'click', function () {
    alert('你好');
​
    removeEvent(btn, 'click', arguments.callee);
  });
}

事件处理周期(事件流)

事件处理周期(事件流) 事件在DOM中传播的过程

在标准DOM中 事件处理周期分为三个阶段

  1. 事件捕获 从最不具体的事物到最具体的事物 指事件沿DOM树向下传播
  2. 目标触发 运行目标元素的事件 监听(绑定)函数
  3. 事件冒泡 从最具体的事物到最不具体的事物 指事件沿DOM树向上传播

所有事件都会经历这三个阶段 但是表现只有两个阶段( 捕获/触发 | 触发/冒泡 )

DOM0 只能实现 ( 触发 / 冒泡 ) 事件的表现行为是沿DOM树向上传播

<body>
  <div id="box">
    <button id="btn">按钮</button>
  </div>
</body>
  var box = document.querySelector('#box');
  var btn = document.querySelector('#btn');
​
  box.onclick = function () {
    console.log(2);
  }
​
  btn.onclick = function () {
    console.log(1);
  }
​
  document.body.onclick = function () {
    console.log(0);
  }
​
  document.documentElement.onclick = function () {
    console.log(3);
  }
//1 2 0 3  冒泡 

DOM2 默认情况事件冒泡

第三个参数传递为true 则切换至事件捕获

  btn.addEventListener('click', function () {
    console.log(0);
  }, true);
​
  box.addEventListener('click', function () {
    console.log(1);
  }, true);
​
  document.body.addEventListener('click', function () {
    console.log(2);
  }, true);
​
}

事件对象

事件对象 当事件处理函数被触发时 JS引擎会自动创建出一个对应的事件对象 自动传入到事件处理函数中 事件处理函数的第一个参数 就是事件对象

在标准浏览器中 事件对象 属于DOM规范 但是 在低版本IE中 事件对象 属于BOM规范 它是全局对象

事件对象中常用属性 type 表示事件类型 target 表示事件源 指向当前事件触发的元素 target和this的指向是不同的 事件处理函数中的this指向当时 添加事件的元素 target指的是触发事件的元素

  btn.onclick = function (ev) {
    ev = ev || event; // 兼容ie浏览器
    console.log(ev.type);
    console.log(ev.target);
    console.log(this);
    console.log(ev.target === this);
  }
​
  box.onclick = function (ev) {
    // console.log(1);
    console.log(ev.target);
    console.log(this);
  }

DOM标准方法 阻止冒泡 / 捕获

ev.stopPropagation();

ie阻止冒泡

ev.cancelBubble = true;

  btn.onclick = function (ev) {
    ev = ev || event;
​
    console.log(1);
    // ev.stopPropagation(); // DOM标准方法  阻止冒泡 / 捕获
​
    if (ev.stopPropagation) {
      ev.stopPropagation();
    } else {
      ev.cancelBubble = true; // ie阻止冒泡
    }
​
  }
​
  box.onclick = function () {
    console.log(2);
  }
​
//-------------------------------------------------------
​
 box.addEventListener('click', function (ev) {
    console.log(1);
    ev.stopPropagation();//阻止捕获
  }, true);
​
  btn.addEventListener('click', function (ev) {
    console.log(2);
    // ev.stopPropagation();//阻止冒泡
  }, true);
}

事件委托(事件委派)

将事件处理函数 委托给 目标元素的 父级或祖先级元素 通过事件冒泡的特性为元素添加事件

事件委托的好处:

  1. 可以给未来元素添加事件
  2. 可以减少DOM元素查找(元素选择)
  3. 可以减少元素的事件添加次数
var box = document.querySelector('#box');
      var btn = document.querySelector('#btn');
    
      // btn.onclick = function () {
      //   var newBtn = document.createElement('button');
      //   newBtn.textContent = '按钮';
      //   box.appendChild(newBtn);
      // }
​
      // var btns = document.querySelectorAll('button');
      // for (var i = 0; i < btns.length; i++) {
      //   btns[i].onclick = function () {
      //     var newBtn = document.createElement('button');
      //     newBtn.textContent = '按钮';
      //     box.appendChild(newBtn);
      //   }
      // }
​
            box.onclick = function (ev) {
        if (ev.target.nodeName === 'BUTTON') {
          var newBtn = document.createElement('button');
          newBtn.textContent = '按钮';
          box.appendChild(newBtn);
        }
}

案例:

<body>
  <div class="box">
    <h1 class="title">你好世界</h1>
    <p class="text">
      Lorem ipsum dolor sit, amet consectetur adipisicing elit. Voluptas, explicabo excepturi incidunt perspiciatis
      dolorum iusto, animi deserunt at, ab assumenda ullam laborum? Placeat accusamus excepturi nobis, illum
      perspiciatis optio rem?
    </p>
    <span class="text2">我是span元素</span>
    <div class="content">我是div元素</div>
    <button class="btn">按钮</button>
  </div>
</body>
var box = document.querySelector('.box');
​
      box.addEventListener('click', function (ev) {
        switch (ev.target.className) {
          case 'title':
            console.log('h1元素被点击了');
            break;
          case 'text':
            console.log('你点击了p元素');
            break;
          case 'text2':
            console.log('你点击了span元素');
            break;
          case 'content':
            console.log('你点击了div元素');
            break;
          case 'btn':
            console.log('按钮被点击了');
            break;
        }
        
      });

鼠标事件属性

    console.log(ev.button);   // 表示鼠标按键编号 0表示鼠标左键 2表示右键
    console.log(ev.type);
​
    console.log(ev.clientX, ev.clientY); // 根据浏览器可视区域左上角计算坐标
    console.log(ev.pageX, ev.pageY); // 根据页面左上角 计算坐标
    console.log(ev.layerX, ev.layerY); // 同上
    console.log(ev.offsetX, ev.offsetY); // 根据当前元素的左上角 计算标准
    console.log(ev.screenX, ev.screenY); // 根据屏幕左上角 计算坐标
  // 鼠标右键事件
  box.oncontextmenu = function (ev) {
    console.log(ev.button);
    console.log(ev.type);
  }

滚轮事件

mousewheel chrome/ie DOMMouseScroll FireFox

当浏览器不识别事件时 会自动忽略

//兼容chrome/ie
document.addEventListener('mousewheel', function (ev) {
  // ev.wheelDelta  用于判断滚动方向
  // 这个值大于0  表示向上滚动
  // 这个值小于0  表示向下滚动
  console.log(ev.wheelDelta);
});
​
//兼容 FireFox
document.addEventListener('DOMMouseScroll', function (ev) {
  // ev.detail  用于判断滚动方向
  // 这个值小于0  表示向上滚动
  // 这个值大于0  表示向下滚动
  console.log(ev.detail);
});

滚动兼容

function mouseScroll(ev) {
      ev = ev || event;
      var dir = true; // true表示向上滚动  false表示向下滚动
      if (ev.wheelDelta) { // 对象.属性 查找对象的属性 如果属性不存在 返回undefined
        dir = ev.wheelDelta > 0 ? true : false;
      } else {
        dir = ev.detail < 0 ? true : false;
      }
      console.log(dir);
}

键盘事件

keydown 需要判断用户按了什么键 keyup 需要获得输入内容(更推荐input)

键盘事件属性 ev.keyCode / ev.which 用于判断按键编号(askii编码)

数字 48-57 字母 65-90

enter 13 空格 32 ctrl 17

document.onkeydown = function (ev) {
  // console.log(ev.keyCode, ev.which);
  if (ev.which === 13) {
    console.log('你好世界');
  } else if (ev.which === 32) {
    console.log('hello world');
  }
}

事件默认行为

页面跳转是a元素的默认行为 默认行为是可以被阻止的

DOM标准方法 ev.preventDefault();

低版本ie ev.retrunValue = false;

  // link.onclick = function (ev) {
  //   alert('你好');
  //   ev.preventDefault();
  //   // return false; // 可以用 但是不推荐 应为不是标准方法
  // }
​
  // ---------------------------------
document.oncontextmenu = function (ev) {
    ev.preventDefault();
    console.log('右键点击了');
​
    document.body.innerHTML = '<div style="border:1px solid #ccc;width:150px;min-height:300px;position:absolute;top:' + ev.pageY + 'px;left:' + ev.pageX + 'px"><a>你好</a></div>';
  }
}

this

关键字

他是一个动态指针 它会指向某一个对象

在事件处理函数中 this 指向当前的事件源

this 关键字出现在函数中 指向函数的调用者(谁调用指向谁)

它的指向是由执行决定的 而不是定义决定的

function fn4() {
      console.log(this);
    }
fn4();  // window
​
var o = {
  x: 1
};o.fn = fn4;
​
// console.log(o);
// console.log(o.fn === fn4);
​
o.fn();  // o

this 关键字 在全局环境下 指向window window的所以子属性和方法 都可以省略 'window.' 进行访问

console.log(this);
window.alert('你好世界');
alert('哈哈哈');
console.log(window.alert === alert);

改变this指向的三个方法

call(无数个参数)

  • 第一个参数:改变this指向
  • 第二个参数:实参
  • 使用之后会自动执行该函数
function fn(a,b,c){
        console.log(this,a+b+c); // this指向window
    }
    fn();
    fn.call(document,1,2,3);//call改变之后this指向document  
    //输出 #document 6   1,2,3是实参 结果相加为6

apply(两个参数)

  • 第一个参数:改变this指向
  • 第二个参数:数组(里面为实参)
  • 使用时候会自动执行函数
jsfunction fn(a,b,c){
        console.log(this,a+b+c); 
    }
    fn();
    fn.apply(document,[1,2,3]); 

bind(无数个参数)

  • 第一个参数:改变this指向
  • 第二个参数之后:实参
  • 返回值为一个新的函数
  • 使用的时候需要手动调用下返回 的新函数(不会自动执行)
function fn(a,b,c){
    console.log(this,a+b+c); //window
}
let ff = fn.bind('小明',1,2,3); //手动调用一下

call、apply与bind区别:前两个可以自动执行,bind不会自动执行,需要手动调用

call、bind与apply区别:前两个都有无数个参数,apply只有两个参数,而且第二个参数为数组

异步操作和同步操作

JavaScript 语言的执行环境 是 浏览器内核中的 JavaScript引擎 JavaScript引擎 是一个单线程 执行环境 线程 指的是计算机可以同时执行的任务数 多个语句 需要逐个执行(这是单线程环境的特点) 如果上一个语句执行没有结束 下一个语句就不会执行 线程阻塞 I/O(Input/Output)是最常见的阻塞方式 事实上所有的I/O 都会造成阻塞 BIOS(Basic Input Output System)

JS有几种方式可以绕过阻塞 JS引擎采用了 一种叫做 事件轮询(Event Loop)的机制 来处理I/O阻塞的情况 具体的来说就是将会造成阻塞的操作 改变成 异步操作 并绕开执行

JS引擎为异步操作 提供了 任务队列(一种数据结构 特点 先进先出) 所有异步操作 都会进入任务队列等待, 当主线程将同步代码执行结束后 才开始执行 队列中的任务

同步操作(sync) 依次执行 异步操作(async) 绕过执行

console.log(1);
    setTimeout(function () {
      console.log(2);
    }, 30000);
    console.log(3);
// 1 3 2 
//----------------------------
  for (var i = 0; i < 5; i++) {
      setTimeout(function () {
        console.log(i);
      }, 0);
    }
for (var i = 0; i < 5; i++) {
  (function (i) { // 函数作用域 0 1 2 3 4  此处产生了5个函数作用域
    setTimeout(function () {
      console.log(i); //访问变量 有一个原则 就近原则 访问的是父级作用域的i
    }, 0);
  })(i);
}
//------------------------------
    window.onload = function () {
      var btn = document.getElementById('btn');
      // 所有的事件也是异步操作
      btn.onclick = function () {
        alert('你好世界');
      }
      console.log('哈哈哈哈');
    }