Bootstrap

呕血整理JavaScript知识重点(面试复习必备)

ECMAScript

JavaScript 官方名称是 ECMAScript 是一种属于网络的脚本语言,已经被广泛用于Web应用开发,常用来为网页添加各式各样的动态功能,为用户提供更流畅美观的浏览效果。

编辑器

VS code一款微软出品的强大前端编辑器,具有很强的拓展性,常用插件有如下类型:

  1. live server,服务器形式运行代码,ctrl+s保存自动刷新代码;
  2. auto rename tag,标签更改后自动补全;
  3. Bracket Pair Colorizer 2,代码语法高亮;
  4. Prettier - Code formatter,代码格式化,需要进入vscode设置,搜索save关键词,勾选format on save选项;
  5. vue,代码高亮;

代码执行顺序

由于程序由上往下执行,避免程序页面延迟,将js代码放入body的尾部。 script标签按顺序执行,引入模块代码延后执行。

命名规则

数字、字母、下划线、$符号均可以作为变量名,但是要注意不能以数字开头和关键名命名,例如while、class等关键词均不可以使用。

声明关键字

var

  1. var声明的变量没有块级作用域,仅有函数作用域,重复命名会覆盖;
if (1) {
  var a = 6;
  let b = 9;
}
console.log(a); // 6
console.log(b); // b is not defined
  1. 造成可以先使用后申明的现象(变量提升),环境在执行代码之前优先将解析变量并放入堆中,未定义的变量优先定义为undefined,不会发生报错的现象,打开严格模式也不会进行报错。
a = 3;
alert(a);
var a;

// 真实的执行顺序
1.var a;
2.a = 3;
3.alert(a);

let

  1. 先声明后使用会造成TDC暂时性死区(报错);
  2. 存在块级作用域;
  3. 同一作用域下不可以重复声明;

const

  1. 定义后的变量不可改变;
  2. 声明的同时必须赋值;
  3. 当const是对象等引用类型变量时只要地址不变即可;
  4. 拥有块、函数、全局作用域;

无关键词

无关键词定义全局变量会造成全域污染,导致变量冲突难以调试的问题。

object.freeze变量冻结

非严格模式下变量被冻结后再次被使用不会报错,开启严格模式后控制台会进行报错。

"use strict"
const INFO = {
  url: 'https://n.huasenjio.top/',
  port: '8080'
};
Object.freeze(INFO);
INFO.port = '443'; //Cannot assign to read only property
console.log(INFO);

作用域

1.块级作用域

if、while、for语句形成块级作用域,var声明的变量是没有块级作用域。

<script>
  if (1) {
    var a = 9;
    console.log("1是逻辑值为真");
  }
console.log(a); // 输出9
</script>

2.函数作用域

当声明函数时会在函数的内部形成一个函数的作用域,let和const声明的变量有块级作用域和函数作用域,var也不能穿透。所以可以用函数作用域来解决var无块级作用域的缺点。

<script>
  function b() {
  var a = 9;
}
console.log(a); // a is not defined
</script>

传值与传址

1.传值

函数传递参数时,基本数据类型的赋值是直接传递值。

2.传址

引用类型的复合对象的赋值是传递地址

严格模式

  1. 先声明再使用;
  2. 必须使用关键词声明变量;
  3. 匿名函数的this不再指向window对象而是undefined;

运算符

1.数学运算符

let a = 4;
let b = 5
console.log(3 % 2); // 1,取余;
console.log(3 / 2); // 1.5,除法;
console.log(a++); // 5,自加运算符,此时a=4,但这条指令执行完成后a+1;
console.log(--b); // 6,自减运算符,此时a=6,执行这段代码前b+1;

2.比较运算符

比较运算符分为两个,“==”和``“===”`,双等号的运算符比较时会将等式两边的数据变成相同的数据类型再比较,这个特点叫隐式转换;三等号运算符称为严格比较,不会转变为相同的数据类型来比较,数据类型不同就返回false。

<script>
  // undefined仅与null和本身相等
  console.log(0 == undefined); //false;
	console.log("" == undefined); //false;
	console.log([] == undefined); //false;
	console.log({} == undefined); //false;
	console.log(undefined == null); //true
</script>

3.三目运算符

表达式 ? 表达式为真时 :表达式为假时,可以嵌套使用。

循环控制

1.if

if (1) {
  // 表达式为真的代码块
} else {
  // 表达式为假的代码块
}

2.for

for (let i = 0; i < 10; i++) {
  // 执行代码块
}

3.for-in

for-in遍历数组时,根据下标遍历,遍历对象时是根据属性名遍历。

let arr = ["森", "酱", "与", "猪"];
let obj = {
  name: "花森",
  sex: "男",
};
// 遍历数组
for (const key in arr) {
  console.log(key); //下标
  console.log(arr[key]);
}

// 遍历对象
for (const key in obj) {
  console.log(key); // name sex
  console.log(obj[key]); // 花森 男
}

4.for-of

for-of是根据数组内的元素进行遍历,可以遍历迭代器对象。

let arr = ["森", "酱", "与", "猪"]

for (const item of arr) {
  console.log(item); // 森 酱...
}

5.forEach

let arr = ["森", "酱", "与", "猪"];
arr.forEach((item, index, arr) => {
  console.log(item); // 元素
  console.log(index); // 下标
  console.log(arr); // 原数组
});

6.while

var a = 0;
while (1) {
  a++;
  if (a == 10) {
    continue; // 跳过a = 10并直接进入a=11循环
  }
  console.log(a);

  if (a == 20) {
    break; // 跳出结束循环
  }
}

7.switch

不设置break会造成穿透效果

let name = '视频';
switch (name) {
    case '产品':
        console.log('huasen');
        break;
    case '视频':
        console.log('zhuqi');
        break;
    default:
        console.log('hs');
}

8.break和continue

  1. break跳出当前循环体;
  2. continue跳过本次循环;
  3. 通过label跳出父级循环;
var a = 0;
while (1) {
  a++;
  if (a == 10) {
    continue; // 跳过a = 10并直接进入a=11循环
  }
  console.log(a);

  if (a == 20) {
    break; // 跳出结束循环
  }
}

this指向

匿名函数中的this指向的是window对象(严格模式下指向undefined),可以通过作用域链的方式获取到外部的this,同样也可以通过ES6箭头函数取到父级作用域的this指针。

<script>
  let huasen = {
    names: "花森",
    sex: "男",
    showSex: function () {
      let that = this;
      function getName() {
        console.log(this.names); //this指向window,输出 undefined。
        console.log(that.names); //that指向huasen,作用域链向上查找。
      }
      getName(); //调用getName是一个函数getName,它没有没有依靠,因为匿名函数中的this是指向window,所以调用者就是window,所以this就指向window。
    },

    showName: () => {
      // 箭头函数默然会指向父级作用域,此处调用showName方法的是huasen,它的父级作用域就是window,this指向window。
      console.log("方法是箭头函数", this);
    },
  };
huasen.showSex(); // 男
huasen.showName();
</script>
Call与Apply

函数a.call(对象b,参数1,参数2...),函数a将它的this指针指向对象b,同时传递参数的形式是一个一个传递;函数a.apply(对象b,[参数1,参数2...]),函数a将它的this指针指向对象b,传递参数的形式是数组,两者更改this指向后不会立刻执行函数,bind的方式绑定this会立即执行该函数。

function User(name, type = "人类") {
  this.name = name;
  this.type = type;
  showType = function () {
    console.log(this.name);
  };
  // 默认会返回一个生成后的实例
  return this;
}
function Pig(name, type = "猪") {
  this.name = name;
  this.type = type;
  showType = function () {
    console.log(this.name);
  };
}
console.log(new User("花森", "人类"));
let zhuqi = {
  name: "李琦",
  sex: "女",
};
// User.call(zhuqi, "猪琦", "猪"); // 通过call将User函数的this指向zhuqi对象,call通过一个一个参数传入,最后返回this再次指向zhuqi,相同属性名会遭到覆盖。
User.apply(zhuqi, ["猪琦", "猪"]); // apply的不同点是通过数组直接传入参数,其他效果一致。
console.log(zhuqi); //{sex: "女", name: "猪琦", type: "猪"}
Bind

bind()是将函数绑定到某个对象,比如 a.bind(hd) 可以理解为将a函数绑定到hd对象上即 hd.a()。绑定后会立即执行函数,bind是赋值函数行为会返回一个新的函数。

function hs(a, b) {
  console.log(this);
  return this.f + a + b;
}

//使用bind会生成新函数
let newFunc = hs.bind({ f: 1 }, 3); // 将函数hs绑定给了对象{f:1}

//1+3+2 参数2赋值给b即 a=3 b=2
console.log(newFunc(2));

数据类型检测

1.typeof

基本数据类型检测,无法辨别数组和对象,合适基本的数据类型辨别,具体可以辨别如下类型:

  1. number/string/boolean;
  2. function;
  3. object;
  4. undefined;
let a = 1;
console.log(typeof a); //number

let b = "1";
console.log(typeof b); //string

//未赋值或不存在的变量返回undefined
var huasen;
console.log(typeof huasen);

function run() {}
console.log(typeof run); //function

let c = [1, 2, 3];
console.log(typeof c); //object

let d = { name: "n.huasenjio.top" };
console.log(typeof d); //object

2.instanceof

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上,就是它是否存于某一个继承的链,如果是数组,那么他的祖先就必定存在Array构造函数,如此进行精准判断数据类型;

let hs = [];
let huasen = {};
console.log(hs instanceof Array); //true
console.log(huasen instanceof Array); //false

let c = [1, 2, 3];
console.log(c instanceof Array); //true

let d = { name: "n.huasenjio.top" };
console.log(d instanceof Object); //true

function User() {}
let hd = new User();
console.log(hd instanceof User); //true

字面量与对象

当我们声明一个数组let a =[]时,同样可以使用a.push()方法,据我的了解,只有对象才可以调用方法而Array构造函数的prototype原型上面拥有这个push方法,所以推论内部将[]转换成为对象。

let hd = "huasen"; // 字面量形式
let h = new String("hs"); // 对象的形式
console.log(huasen.length); //6个字符
console.log(h.length); //2个字符

Number

Number用于表示整数和浮点数,数字是 Number实例化的对象,可以使用对象原型上提供的丰富方法。

1.数字变量的命名

let huasen = 3; //字面量形式
let h = new Number(3); //对象的形式
console.log(h+3); //6

2.判断书否是整数

console.log(Number.isInteger(1.2));

3.NaN无效数值

console.log(Number("huasen")); //声明传参传递数字无效产生NaN
console.log(2 / 'huasen'); //无效的数值计算会产生NaN
Number.isNaN(变量) // 判断是否是NaN
Object.is(对象1,对象2) // 判断两个对象是否相等

4.类型转换

使用Number构造函数基本上可以转换所有类型

console.log(Number('huasen')); //NaN
console.log(Number(true)); //1
console.log(Number(false));	//0
console.log(Number('9')); //9
console.log(Number([])); //0
console.log(Number([5]));	//5
console.log(Number([5, 2])); //NaN
console.log(Number({})); //NaN

5.常用方法

1)字符串转数字parseInt:

提取的字符串并去除空白数字字串转变为整数

console.log(parseInt('1' * 1));	//1 隐式转换
console.log(parseInt('  99huasen'));	//99
console.log(parseInt('18.55'));	//18 小数点被忽略

2)字符串转为浮点数parseFloat:

console.log(parseFloat('  99huasen'));	//99
console.log(parseFloat('18.55'));	//18.55且不会忽略小数点

3)浮点数四舍五入toFixed:

console.log(1.55667.toFixed(2)); //1.56

String

字符串类型是使用非常多的数据类型

1.字符串变量的声明

let hs = new String('huasen');
// 获取字符串长度
console.log(hs.length);
// 获取字符串
console.log(hs.toString());

2.转义符号

有些字符有双层含义,需要使用 \ 转义符号进行含义转换,下例中引号为字符串边界符,如果输出引号时需要使用转义符号。

let content = '花森 \'huasenjio.top\''; // 内部两个'是需要转义
console.log(content); // 花森 'huasenjio,top'

常见转义符号表:

符号说明
\t制表符
\n换行
\斜杠符号(转义)
单引号
"双引号R

3.连接运算符

let year = 2019,
name = '花森导航';
console.log(name + '成立于' + year + '年');

4.模板字面量

使用 ` …` 符号包裹的字符串中可以写入引入变量与表达式,变量使用${}放入,并且可以换行而不产生错误。

let url = 'huasenjio.top';
console.log(`花森导航网址是${url}`); //花森导航网址是huasenjio.top

5.标签模板

通过tag方法将字符串和变量分离到两个数组中

let lesson = 'css';
let web = '花森';
tag `访问${web}学习${lesson}前端知识`;

function tag(strings, ...values) {
    console.log(strings); //["访问", "学习", "前端知识"]
    console.log(values); // ["花森", "css"]
}

6.常用方法

1)使用length属性可以获取字符串长度:

console.log("huasenjio.top".length)

2)字母大小写转换(toLowerCase和toLowerCase):

console.log('HUASENJIO.top'.toLowerCase()); //huasenjio.top 转小写
console.log('huasenjio.top'.toLowerCase()); //HUASENJIO.TOP 转小写

3)移除字符空白trim:

let str = '   huasenjio.top  ';
console.log(str.length);
console.log(str.trim().length);

console.log(name.trimLeft()); // 删除左空白
console.log(name.trimRight()); // 删除又空白

4)抓取单个字符chatAt:

console.log('huasenjio'.charAt(3)) // s 通过下标(0开始)获取到对于的字符

5)数组形式获取字符:

console.log('huasenjio'[3]) // 3 数字索引获取字符串

7.字符串的截取

slice
  • 截掉
let hs = "12345678";
console.log(hs.slice(3)); //45678 slice(截掉的个数)
  • 截取
// 参数为正数情况
let hs = "12345678";
console.log(hs.slice(3, 6)); //456 [开始下标,结束下标)

// 参数为负数情况
console.log(hs.slice(-2)); //78 末尾取两个
console.log(hs.slice(1, -2)); //23456 [开始下标,-2(倒数第二个开始)) 第二个参数是负数说明从尾部数
substring
  • 截掉
let hs = "12345678";
console.log(hs.substring(3));  //45678 substring(截掉的个数)
  • 截取
let hs = "12345678";
console.log(hs.substring(3, 6)); //456 [开始下标,结束下标)
console.log(hs.substring(3, 0)); //123 参数中选最小值作为开始下标进行截取
console.log(hs.substring(3, -9)); //123 负数转为0再按最小值作为开始下标进行截取
substr
  • 截掉
let hs = "12345678";
console.log(hs.substr(3));  //45678 substr(截掉的个数)
  • 截取
let hs = "12345678";
console.log(hs.substr(3, 4)); //4567 [开始下标,截取个数]

console.log(hs.substr(-3, 2)); //78 [-3,个数]

8.查找字符串

indexOf检索

从开始(下标为零)进行查找,若查找到返回第一个目标的下标,查找不到返回-1,可以指定开始查找的位置。

console.log("12345678".indexOf("3")); //1
console.log("12345678".indexOf("5", 3)); //4 从第3个字符向后搜索

search检索

search() 方法用于检索字符串中指定的子字符串,也可以使用正则表达式搜索进行搜索,返回子串的开始下标。

let str = "huasenjio.top";
console.log(str.search("top"));
console.log(str.search(/top/i));

9.字符串替换

replace

replace方法用于字符串的替换操作,默认替换第一个匹配的字符串,如果想要通过全局替换需要配合正则表达式使用。

let name = "1122331122";
web = name.replace("22", "**");
console.log(web);

// 配合正则完成替换
let str = "2023/02/12";
console.log(str.replace(/\//g, "-"));

10.重复生成repeat

通过已有的字符串生成重复的字符串

function star(num = 3) {
	return '*'.repeat(num);
}
console.log(star());

11.字符串分割split

通过某一个符号对字符串进行分割并返回分割后的数组

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

12.类型转换

let hs = 99;
console.log(hs.toString()); //99 String类型

let arr = ["1", "2", "3"];
console.log(arr.toString()); //1,2,3 String类型

let a = {
  name: "huasen",
};
console.log(a.toString()); //[object Object] String类型

Math

math数学对象,提供了很多数学相关的计算方法。

1.最大值最小值

console.log(Math.min(1, 2, 3)); // 1
console.log(Math.max(1, 2, 3)); // 3

2.向上取整ceil

console.log(Math.ceil(1.111)); // 2

3.向下整数floor

console.log(Math.floor(1.555)); // 1

4.四舍五入处理round

console.log(Math.round(1.5)); //2

5.生成随机数random

random 方法用于返回 >=0 且 <1 的随机数(包括0但不包括1)

const number = Math.floor(Math.random() * 5); // [0,5)
console.log(number);

const number = Math.floor(Math.random() * (5+1)); // [0,5]
console.log(number);

const number = Math.floor(Math.random() * (5 - 2)) + 2; // [2,5)
console.log(number);

const number = Math.floor(Math.random() * (5 - 2+1)) + 2; // [2,5]
console.log(number);

X+Math.floor(Math.random()*(Y-X)) // [X,Y)
X+Math.floor(Math.random()*(Y-X+1)) // [X,Y]

Date

处理日期的方法,通过 Date 类型提供的丰富功能可以非常方便的操作。

1.时间戳

定义为从格林威治时间1970年01月01日00时00分00秒起至现在的总秒数

2.获取当前日期

let now = new Date();
console.log(now); // Mon Oct 19 2020 21:15:23 GMT+0800 (中国标准时间)
console.log(typeof now); //object
console.log(now * 1); //获取时间戳

//直接使用函数获取当前时间
console.log(Date());
console.log(typeof Date()); //string

//获取当前时间戳单位毫秒
console.log(Date.now());

3.时间格式的封装

function dateFormat(date, format = "YYYY-MM-DD HH:mm:ss") {
  const config = {
    YYYY: date.getFullYear(),
    MM: date.getMonth() + 1,
    DD: date.getDate(),
    HH: date.getHours(),
    mm: date.getMinutes(),
    ss: date.getSeconds(),
  };
  for (const key in config) {
    format = format.replace(key, config[key]);
  }
  return format;
}
console.log(dateFormat(new Date(), "YYYY年MM月DD日HH时mm分ss秒"));

Boolean

布尔类型包括 truefalse 两个值

1.声明定义

console.log(new Boolean(true)); //true 对象形式
let hs =true; // true 字面量形式

2.隐式转换

逻辑运算符"=="进行两个是比较是否相等时,不同的数据类型会造成隐式转换后再比较,属于js中比较难的部分,具体情况如下:

  1. 数组和布尔值比较,[] == true;//false,空数组转为""再转为0,逻辑值true直接转为1;
  2. 数组符串比较,[1,2,3] == '1,2,3' // true,[1,2,3]转换成字符串"1,2,3"最后跟字符串比较;
  3. 字符串和数字进行比较,'1' == 1 // true,字符串转为数字后与数字1比较;
  4. 字符串和布尔值进行比较,'1' == true; // true,字符串转为数字1,布尔值转为数字1,比较相等;
  5. 布尔值和数字比较,true == 1;/ true,布尔值转为数字1再比较;

隐式转换图

有趣的事情是[] == false和![] == false的结果都是true,第一个[]数组转为""再转成0,false直接转为0,比较相等;第二个同理,!符号优先级高所以先执行,所以式子就变成(![]) == false,估计就能理解了!另外有几个比较的特殊undefined == null // trueNaN == XXX //NaN和任意类型都不等(包括自己)

3.显示转换

使用 !! 转换布尔类型

let hs = "";
console.log(!!hs); //false
hs = 0;
console.log(!!hs); //false
hs = null;
console.log(!!hs); //false
hs = new Date("2020-2-22 10:33");
console.log(!!hs); //true
hs = [];
console.log(!!hs); //false
hs = {};
console.log(!!hs); //false

Array

数组是多个变量值的集合,数组是Array 对象的实例,Array原型上系统预设很多方式可以供我们调用。

1.数组的声明

console.log(new Array(1, '花森', 'hs')); //[1, "花森", "hs"]
const array = ["花森", "huasen"];
const array = [["花森","hs"], ["猪琦","zhuqi"]];
console.log(array[1][0]); // 猪琦

let hs = Array.of(3);
console.log(hs); // [3]
hs = new Array(3); // 创建长度为3的空数组
console.log(hs);

hs = Array.of(1, 2, 3);
console.log(hs); //[1, 2, 3]

2.类型检测isArray

console.log(Array.isArray([1, "花森", "hs"])); //true
console.log(Array.isArray(9)); //false

3.类型转换

数组2字符串

大部分数据类型都可以使用.toString() 函数转换为字符串

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

console.log(String([1, 2, 3])); //1,2,3 构造函数

console.log([1, 2, 3].join("-"));//1-2-3 join()
伪数组2数组

使用Array.from可将类数组转换为数组,类数组指包含 length 属性或可迭代的对象,伪数组不具备数组中的某一个方法,一般使用时都要转为数组再进行使用。

let str = '花森酱';
console.log(Array.from(str)); //["花", "森", "酱"]

let user = {
  0: '花森酱',
  '1': 18,
  length: 2
};
console.log(Array.from(user)); //["花森酱", 18]

let divs = document.querySelectorAll("div");
let hs = [...divs]
字符串2数组

使用join,将数组中的元素连接成字符串。

let price = "99,78,68";
console.log(price.split(",")); //["99", "78", "68"]

4.数组合并拆分

展开语法

使用展开语法来合并数组相比 concat 要更简单,使用... 可将数组展开为多个值。

et a = [1, 2, 3];
let b = ['a', '花森', ...a];
console.log(b); //["a", "花森", 1, 2, 3]
concat

concat方法用于连接两个或多个数组,返回是一个数组,元素是值类型的是复制操作,如果元素是引用类型还是指向同一对象,属于浅拷贝的操作。

let arr = ["hs", "花森"];
let hs = [1, 2];
let cms = [3, 4];
console.log(arr.concat(hs, cms)); //["hs", "花森", 1, 2, 3, 4]
copyWithin

使用 copyWithin 从数组中复制一部分到同数组中的另外位置,语法说明array.copyWithin(target, start, end),target复制到的索引地址位置,start元素复制的开始索引,end结束的索引。

const arr = [1, 2, 3, 4,5];
console.log(arr.copyWithin(2, 0, 2)); //[1, 2, 1, 2, 5]

5.解构赋值

解构是一种更简洁的赋值特性,可以理解为分解一个数据的结构。

//数组使用
let [name, url] = ['花森', 'hs'];
console.log(name);

// 解构函数返回值
function huasen() {
	return ['hs', '花森'];
}
let [a, b] = huasen();
console.log(a); //hs

// 函数参数解构
function hs([a, b], c) {
  console.log(a, b);
  console.log(c);
}
hs(["花森", "hs"], "猪琦");

// 解构数组
let [a, ...b] = ['花森', 'hs', '猪琦'];
console.log(b);  // ['hs', '猪琦'];

// 解构字符串
"use strict";
const [...a] = "huasenjio";
console.log(a); //Array(9)

// 解构单个变量
let [,url]=['花森','huasenjio'];
console.log(url);//huasenjio

6.操作元素

// 数组追回元素
let arr = [1, "花森", "hs"];
arr[arr.length] = "huasenjio";
console.log(arr); //[1, "花森", "hs", "huasenjio"]

// push批量压入元素,改变数组长度,返回值是元素的数量。
let arr = [1, "花森", "hs"];
arr.push("huasenjio","zhuqi");
console.log(arr); //[1, "花森", "hs", "huasenjio","zhuqi"]

// pop尾弹一个元素,改变数组长度,返回被弹出的元素。
let arr = [1, "花森", "hs"];
arr.pop();
console.log(arr); //[1, "花森"]

// shift头弹一个元素,改变数组长度,返回头弹出的元素。
let arr = [1, "花森", "hs"];
console.log(arr.shift());
console.log(arr); //["花森","hs"]

// unshift头插一个元素,改变数组长度,返回值是元素的数量。
let arr = [1, "花森", "hs"];
console.log(arr.unshift("爱"));
console.log(arr); //["花森","hs"]

// fill填充数组,返回填充完成的数组。
console.log(new Array(4).fill("hs")); //["hs", "hs", "hs", "hs"]

7.数组截取

slice截取

使用 slice 方法从数组中截取部分元素组合成新数组(并不会改变原数组),不传第二个参数时截取到数组的最后元素。不设置参数默认截取整个数组。

let arr = [0, 1, 2, 3, 4, 5, 6];
console.log(arr.slice(1, 3)); // [1,2] [开始下标,结束下标]

let str = "0123456";
console.log(str.slice(1, 3)); // 12 [开始下标,结束下标)
splice截掉

使用 splice 方法可以添加、删除、替换数组中的元素,会对原数组进行改变,返回值为删除的元素。

// [开始下标,删除数量]
let arr = [0, 1, 2, 3, 4, 5, 6];
console.log(arr.splice(1, 3)); //返回删除的元素 [1, 2, 3] 
console.log(arr); //删除数据后的原数组 [0, 4, 5, 6]

// 通过修改length删除最后一个元素
let arr = ["1", "2""3""4""5"];
arr.length = arr.length - 1;
console.log(arr);

// [开始下标,删除参数,删除位置的新元素]
let arr = [0, 1, 2, 3, 4, 5, 6];
console.log(arr.splice(1, 3, 8)); //返回删除的元素 [1, 2, 3]
console.log(arr); //删除数据后的原数组 [0, 4, 5, 6]

// 两个交换位置小案例
function move(array, before, to) {
  if (before < 0 || to >= array.length) {
    console.error("指定位置错误");
    return;
  }
  const newArray = [...array];
  const elem = newArray.splice(before, 1);
  newArray.splice(to, 0, ...elem);
  return newArray;
}
const array = [1, 2, 3, 4];
console.table(move(array, 0, 3));

8.数组清空

// 更改指针法
let user = [{ name: "hs" }, { name: "花森" }];
let cms = user;
user = [];
console.log(user); // []
console.log(cms); // [{...},{...}]

// 修改长度法
let user = [{ name: "hs" }, { name: "花森" }];
user.length = 0;
console.log(user);

// splice方法删除所有数组元素
let user = [{ name: "hs" }, { name: "花森" }];
user.splice(0, user.length);
console.log(user);

9.元素查找

indexOf

使用 indexOf 从前向后查找元素出现的位置,如果找不到返回 -1,indexOf使用严格模式去匹配,找到则返回第一次出现的下标。

let arr = [7, 3, 2, 8, 2, 6];
console.log(arr.indexOf(2)); // 2 从前往后查找第一个出现2的下标

// 指定索引向后进行查找
let arr = [7, 3, 2, 8, 2, 6];
console.log(arr.indexOf(2, 3)); //4 从第二个元素开始向后查找
lastIndexOf

使用 lastIndexOf 从后向前查找元素出现的位置,具体使用参考indexOf方法使用。

let arr = [7, 3, 2, 8, 2, 6];
console.log(arr.lastIndexOf(2)); // 4 从后查找2出现的位置
includes

使用 includes 查找字符串返回值是布尔类型更方便判断

let arr = [7, 3, 2, 6];
console.log(arr.includes(8)); //true
find

find 方法找到后会把值返回出来,找不到则返回值为undefined,返回第一次查找到的词就停止查找。

let arr = [
  { name: "猪琦", sex: "女" },
  { name: "花森", sex: "男" },
  { name: "皮卡丘", sex: "动物" },
];

let find = arr.find(function (item) {
  return item.name == "花森";
});

console.log(find); // {name: "花森", sex: "男"}

10.数组顺序

reverse

对数组进行反转输出

let arr = [1, 4, 2, 9];
console.log(arr.reverse()); //[9, 2, 4, 1]
sort

sort方法每次使用两个值进行比较,Array.sort((a,b) => a-b),返回负数a排在b的前,从小到大排序;返回正数则b排在a的前面,返回0时不动。

// 默认从小到大排序
let arr = [1, 4, 2, 9];
console.log(arr.sort()); //[1, 2, 4, 9]

// 按某值进行排序
console.log(arr.sort(function (v1, v2) {
	return v2 - v1;
})); //[9, 4, 2, 1]

11.数组拓展函数

every一假则假

every 用于递归的检测元素,要所有元素操作都要返回真结果才为真,遇到一个不满足条件就立即退出,不再继续遍历下去,最终返回布尔值。

const user = [
  { name: "李四", js: 89 },
  { name: "马六", js: 55 },
  { name: "张三", js: 78 },
];
const resust = user.every((item, index, arr) => {
  console.log(item); // 元素
  console.log(index); // 下标
  console.log(arr); // 原数组
  item.js >= 60;
});
console.log(resust);
some一真则真

使用 some 函数可以递归的检测元素,如果有一个返回true。

const user = [
  { name: "李四", js: 89 },
  { name: "马六", js: 55 },
  { name: "张三", js: 78 },
];
const resust = user.some((item, index, arr) => {
  console.log(item); // 元素
  console.log(index); // 下标
  console.log(arr); // 原数组
  item.js >= 60;
});
console.log(resust);
filter

按一定条件筛选元素并组成新数组返回

const user = [
  { name: "李四", js: 89 },
  { name: "马六", js: 55 },
  { name: "张三", js: 78 },
];
const resust = user.filter((item, index, arr) => {
  console.log(item); // 元素
  console.log(index); // 下标
  console.log(arr); // 原数组
  return item.js > 60; // 过滤取到js成绩大于60的元素并返回一个新的数组
});
console.log(resust);
map

将遍历数组,遍历过程中执行代码,最后返回一个元素添加到新数组中并返回。

const user = [
  { name: "李四", js: 89 },
  { name: "马六", js: 55 },
  { name: "张三", js: 78 },
];
const resust = user.map((item, index, arr) => {
  console.log(item); // 元素
  console.log(index); // 下标
  console.log(arr); // 原数组
  item.js > 60;
  return item.js + "分数";
});
console.log(resust);
reduce

使用 reducereduceRight 函数可以迭代数组的所有元素,reduce 从前开始 reduceRight 从后面开始,存在如下参数。

参数说明
prev上次调用回调函数返回的结果
cur当前的元素值
index当前的索引
array原数组
const user = [
  { name: "李四", js: 89 },
  { name: "马六", js: 55 },
  { name: "张三", js: 78 },
];
const resust = user.reduce((pre, cur, index, arr) => {
  console.log(cur); // 当前元素
  console.log(index); // 下标
  console.log(pre); // 上一个遍历的元素
  return "返回值"; // 返回值将作为pre参数
}, 0); // 0指定pre的初始值

//计算所有js成绩的和
const user = [
  { name: "李四", js: 89 },
  { name: "马六", js: 55 },
  { name: "张三", js: 78 },
];
let t = 0;
const resust = user.reduce((pre, cur, index, arr) => {
  console.log(cur); // 当前元素
  console.log(index); // 下标
  console.log("pre", pre); // 上一个遍历的元素

  return (pre += cur.js); // 返回值将作为pre参数
}, 89);

迭代器

数组中可以使用多种迭代器方法

keys

通过迭代对象获取索引,可通过next()一步一步向下拿到全部索引并执行操作。

const hs = ["花森", "huasen""猪琦"];
const keys = hs.keys(); // 获得迭代器对象
console.log(keys.next()); // {value: 0, done: false}
console.log(keys.next()); // {value: 1, done: false}

// 需要把上面两行console.log去掉,因为上面两行迭代器就已经遍历到最后了,所以影响输出效果。
for (const key of keys) {
  console.log(key);
}

values

通过迭代对象获取值,可通过next()一步一步向下拿到全部索引并执行操作。

entries

返回数组所有键值对的迭代器,可以使用解构加for-of循环获取数据。

const arr = ["a", "b", "c", "花森"];
for (const [key, value] of arr.entries()) {
  console.log(key, value);
}

Symbol

Symbol 的值是唯一的数据类型,防止使用属性名冲突而产生,比如向第三方对象中添加属性就有可能产生变量名冲突,而且Symbol不可以添加属性。

1.变量声明

let hs = Symbol();
let edu = Symbol("传入描述Symbol的字符串,有利于分辨Symbol!");
console.log(hs); // symbol
console.log(edu); // Symbol(传入描述Symbol的字符串,有利于分辨Symbol!)
console.log(edu.description) // 获取描述
console.log(hs == edu); //false

2.查找Symbol.for

根据描述获取到唯一Symbol变量,如果不存则新建一个Symbol对象,使用Symbol会被系统登记,而使用Symbol不会被系统登记。

let hs = Symbol.for("传入描述Symbol的字符串,有利于分辨Symbol!");
let edu = Symbol.for("传入描述Symbol的字符串,有利于分辨Symbol!");
console.log(hs == edu); // true

3.登记记录Symbol.keyFor

返回Symbol登记的描述,如果没有找到则返回undefined

let hs = Symbol.for("花森");
console.log(Symbol.keyFor(hs)); //花森

4.操作要点

Symbol可以保证对象属性的唯一,Symbol的声明和访问使用[]的形式进行操作,不能使用.,因为点是操作字符串属性的写法。

let symbol = Symbol("花森");
let obj = {
  [symbol]: "huasenjio"
};
console.log(obj[symbol]); //huasenjio

5.实例操作

覆盖耦合

通过Symbol作为键值,每一个Symbol均是唯一,所以存入数据不会因为key值相同造成覆盖的可能。

class Cache {
  static data = {};
  static set(name, value) {
    this.data[name] = value;
  }
  static get(name) {
    return this.data[name];
  }
  }

  let user = {
    name: "花森",
    key: Symbol("343434"),
  };

  let cart = {
    name: "猪琦",
    key: Symbol("121212"),
  };
  Cache.set(user.key, user);
  Cache.set(cart.key, cart);
  console.log(Cache.get(user.key));
  console.log(Cache.data);
遍历属性

Symbol 不能使用 for/in和for/of 遍历操作,遍历时Symbol属性默认被忽略,但是可以通过Object.getOwnPropertySymbols,遍历到对象中的所有Symbol属性。

let symbol = Symbol("导航");
let user = {
  name: "花森",
  [symbol]: "huasenjio",
};

for (const key of Object.getOwnPropertySymbols(user)) {
  console.log(key);
}
let symbol = Symbol("导航");
let user = {
  name: "花森",
  [symbol]: "huasenjio",
};

for (const key of Object.getOwnPropertySymbols(user)) {
  console.log(key);
}
变量保护

使用symbol作为属性名,起到变量保护的作用,无法被遍历访问到,可以通过对外函数返回的方式去访问。

const site = Symbol("网站名称");
class User {
  constructor(name) {
    this[site] = "森酱";
    this[name] = name;
  }
  getName() {
    return `${this[site]}-${this.name}`;
  }
}
const hs = new User("hs");
console.log(hs.getName());
console.log(hs.site); // 无法访问到森酱
for (const key in hs) {
  console.log(key);
}

Set

无论是基本类型还是对象引用,都不能存入重复的元素,只可以保存值而没有键名;严格类型检测入字符串不等于数值型数字;元素唯一。

1.变量声明

使用数组作为初始化的数据,如果存在多个相同值则仅会保存第一个值,其余的全部忽略。

let hs = new Set(["花森", "hs", "1", 1]); 
console.log(hs.values()); //{"花森", "hs", "1" ,1}

2.常用操作

add添加元素
let hs = new Set();

hs.add('huasen');
size获取数量
let hs = new Set(['huasen', '花森']);
console.log(hs.size); //2
has检测元素
let hs = new Set(['huasen', '花森']);
console.log(hs.has("花森")); // true
delete删除
console.log(hs.delete("hdcms")); //true
clear清空元素
hs.clear();

3.数组转换

const set = new Set(["hs", "花森"]);
console.log([...set]) // 点语法
console.log(Array.from(set)); // array.from

4.数组去重

console.log([...new Set("hhssjjoo")].join(""));//hsjo

5.遍历数据

let arr = [7, 6, 2, 8, 2, 6];
let set = new Set(arr);
//使用forEach遍历
set.forEach((item,key) => console.log(item,key)); // item 和 value一致

//使用for-of
for (const iterator of set) {
	console.log(iterator);
}

6.交集

通过数组的过滤函数filter和has查询,

let hs = new Set(["1", "8"]);
let zhuqi = new Set(["2", "8"]);
let newSet = new Set([...hs].filter((item) => zhuqi.has(item))); // 返回zhuqi数据中也有的数
console.log(newSet); //{"8"}

7.差集

let hs = new Set(["1", "8"]);
let zhuqi = new Set(["2", "8"]);
let newSet = new Set([...hs].filter((item) => !zhuqi.has(item))); // 返回zhuqi数据中没有的数
console.log(newSet); 

8.交集

let hs = new Set(["1", "8"]);
let zhuqi = new Set(["2", "8"]);
let newSet = new Set([...hs,...zhuqi]); // 返回zhuqi数据中没有的数
console.log(newSet); 

9.WeakSet

WeakSet结构同样不会存储重复的值,储存的元素必须是对象类型,垃圾回收机制不考虑WeakSet,当一个变量被引用时,内存中会有一个引用计数器会进行加一,但对于weakset引用,计数器不会加一,所以weakset不管是否在使用变量都均会被删除,是属于弱引用的范畴,weakset没有keys(),values(),entries()和size等方法,且不能被遍历!

声明
new WeakSet(["hs", "zhuqi"]); //Invalid value used in weak set

new WeakSet("hdcms"); //Invalid value used in weak set 

new WeakSet([{ name: "huasen" }]); //正确
基本操作
const hs = new WeakSet();
const arr = ["hdcms"];

//添加操作
hd.add(arr);
console.log(hs.has(arr));

//删除操作
hd.delete(arr);

//检索判断
console.log(hs.has(arr));

Map

Map是一组键值对的结构,用于解决以往不能用对象做为键的问题,具有极快的查找速度,函数、对象、基本数据类型均可以作为键和值。其中键是对象则保存的是内存地址,如果值相同但内存地址不同也会被视为两个键值对。

1.声明定义

可以接受数组作为参数,该数组的成员是一个表示键值对的数组。

let m = new Map([
  ["h", "花"],
  ["s", "森"],
]);

console.log(m.get("h")); //花

2.基本操作

let m = new Map([
  ["h", "花"],
  ["s", "森"],
]);
// 获取键值对的数量
console.log(m.size);

// 读取元素
console.log(m.get("h")); //花

// 删除元素
console.log(m.delete("h")); //花

// 清空map
console.log(map.clear());

3.遍历数据

可以使用keys/values 函数遍历键与值

let hs = new Map([["h", "花"], ["s", "森"]]);
for (const key of hs.keys()) {
  console.log(key);
}
for (const value of hs.values()) {
  console.log(value);
}

4.数组转换

  1. […map];
  2. Array.from(map);

5.WeabMap

详细请参考WeabSet的用法

函数进阶

函数是将复用的代码块封装起来的模块,js中的函数也是对象,可以通过构造函数Function创建实例,全域定义的函数是属于window对象中,容易造成覆盖的问题,使用let/const定义的参数时不会压入window对象中。标准声明的函数优先级更高,解析器会优先执行提取放在代码树的顶端,所以同意作用域下函数声明的位置顺序不限制。

1.声明定义

// 构造函数的方式
let hs = new Function("title", "console.log(title)");
hd('花森酱');

// 标准语法的方式
function hs(num) {
	return ++num;
}
console.log(hs(3));

// es6的箭头函数
let huasen = () => {
  
}

// 简写形式
user = {
  setName() {
    
  }
}

2.匿名函数

匿名函数需要赋值给某一个变量,而且一定需要以;结尾,匿名函数的执行者是window对象即内部的this是指向window对象,严格模式下this指向undefined而不是window对象。

let hs = function () {
  console.log(this); // window对象
};
hs();

[].map(()=>{})

3.立即执行函数

立即执行函数指函数定义时立即执行,可以定义私有作用域防止污染全局作用域。

"use strict";
(function () {
    var web = 'hs';
})();
console.log(web); //web is not defined

4.函数参数

形参实参

调用函数时传入的变量叫做实参,定义函数参数是的变量叫做形参。当形参的数量大于实参时,没有被赋值的形参就被定义为undefined;实参的数量大于形参时,多余的实参将被忽略并且不会报错。

// n1,n2 为形参
function sum(n1, n2) {
	return n1+n2;
}

// 参数 2,3 为实参
console.log(sum(2, 3)); //5
默认参数

通常为形参设置默认值

// 无序考虑兼容
function avg(total, year) {
  year = year || 1; // 赋值
  return Math.round(total / year);
}
console.log(avg(2000, 3));

//es6写法需要考虑兼容性
function avg(total, year = 1) {
  return Math.round(total / year);
}
console.log(avg(2000, 3));
回调函数

函数调用时可以传递函数参数,一般用于回调函数,执行调用的函数a时,默认执行传入的函数,这个场景叫做回调函数。

// 箭头函数演示
[].filter(()=> {})
// 普通函数
[].filter(function(){})
argument

arguments 是函数获得到所有参数集合,获得传入参数的集合。

// 普通参数
let hs = function () {
  console.log(arguments); // 函数参数
};
hs(1, 2, 3, 4);

// 对参数进行求和
function sum() {
  return [...arguments].reduce((total, num) => {
    return (total += num);
  }, 0);
}
console.log(sum(2, 3, 4, 2, 6)); //17

5.递归调用

递归指函数内部调用自身的方式,主要用于数量不确定的循环操作,必须有要结束循环的条件否则会陷入死循环,一般慎用递归调用,容易造成内存泄露的风险。

function factorial(sum) {
  if (sum <= 1) {
    return sum;
  } else {
    return sum * factorial(--sum);
  }
}
console.log(factorial(4));

6.标签函数

function hd(str, ...values) {
  console.log(str); //["站点", "-", "", raw: Array(3)]
  console.log(values); //["花森", "huasenjio.top]
}
let name = "花森",
    url = "huasenjio.top";
hd`站点${name}-${url}`;

作用域

作用域链只向上查找,找到全局window即终止。当在每一个对象中引用了一个变量是,如果挡墙作用域不存在就往上级作用域查找,[].toString(),可以调用输出字符串,细心的小伙伴可能发现,数组的原型根本没有toString方法,但是Object上面存在toString方法。所以查找toString变量的顺序是先去找数组的原型,再去找在Object上的原型,直到window全局,如果还没有找到则报错。

console.log(name); // 直接这样输出并不会报错因为window上默认存在name属性且为空
console.log(n); // n is not defined 因为window上面没有n属性

对象

面向对象程序设计成为OOP,对象是属性和方法的集合体,内部的复杂逻辑代码被隐藏,仅仅暴露少量代码给外界,更改对象内部的复杂逻辑不会对外部造成影响和抽象,继承是通过代码复用减少冗余,根据不同形态的对象产生不同的结果称之为多态。

1.声明定义

let obj = {
  name: 'huasen',
  get:function() {
  	return this.name;
  }
}
console.log(obj.get()); //huasen

2.属性管理

基本操作
let user = {
  name: "huasen",
  ["my-title"]: "花森导航",
};
console.log(user.name); // 使用点语法
console.log(user["name"]); // 使用数组形式
console.log(user["my-title"]); // 如果属性名不是合法变量就必须使用括号的形式

// 添加属性
user.a = "A" // 使用点语法
user["b"] = "B" // 使用数组形式

// 删除属性
delete user.name;
检测属性

1)hasOwnProperty检测对象自身是否包含指定的属性,不检测原型链上继承的属性。

let obj = { name: '花森'};
console.log(obj.hasOwnProperty('name')); //true

2)使用 in 可以在原型对象上检测。

let obj = {name: "花森"};
let hs = {
  web: "huasenjio"
};

//设置hs为obj的新原型
Object.setPrototypeOf(obj, hs);
console.log(obj);

console.log("web" in obj); //true 说明web属性在obj的原型链上
console.log(obj.hasOwnProperty("web")); //false
assign属性合并

使用Object.assign静态方法进行从一个或多个对象复制属性,并将属性合并,但是是浅拷贝。

"use strict";
let hs = { a: 1, b: 2 };
hd = Object.assign(hs, { f: 1 }, { m: 9 });
console.log(hs); //{a: 1, b: 2, f: 1, m: 9}
动态属性

对象属性可以通过表达式计算定义,动态设置属性或者执行方法时很美妙。

let id = 0;
const user = {
  [`id-${id++}`]: id,
  [`id-${id++}`]: id,
  [`id-${id++}`]: id
};
console.log(user);

3.引用特性

对象和函数一样是引用数据类型,赋值操作相当于复制并赋予地址,其实还是引用同一块内存地址。

let user = {
  name: "huasen",
  ["tit-le"]: "花森导航",
};
let per = user;
console.log(per);
per.name = "猪琦";
console.log(user); // user中也被修改了

// 函数参数同样也是赋值地址
(function () {
  arguments[0]["tit-le"] = "笔录";
})(user);

4.对象转换

对象直接参与计算时,系统根据计算的场景在string、number、default之间转换。如果场景需要字符串类型,则对象执行toString()后执行valueOf获取到字符串,如果需要字符串型,先执行valueOf获得数值后执行toString获得字符串。

let hs = {
  name: "花森",
  num: 1,
  valueOf: function () {
    console.log("valueOf");
    return this.num;
  },
  toString: function () {
    console.log("toString");
    return this.name;
  },
};
console.log(hs + 3); //valueOf 4
console.log(`${hs}导航`); //toString 花森导航

5.解构赋值

解构是一种更简洁的赋值特性,可以理解为分解一个数据的结构,用法与数组的解构相似,建议解构使用var/let/const声明,否则严格模式下会报错。

// 对象的解构
let info = {name:'花森',url:'huasenjio'};
let {name:n,url:u} = info // 重新定义变量名为n u
console.log(n); 

// 如果属性名与变量相同可以省略属性定义
let {name:n,url:u} = {name:'花森',url:'huasenjio'};

// 函数返回值解构到变量
function hs() {
  return {
    name: '花森',
    url: 'huasenjio'
  };
}
let {name: n,url: u} = hs();
console.log(n);

// 函数传参
"use strict";
function hd({ name, age }) {
  console.log(name, age); //花森 18
}
hd({ name: "花森", age: 18 });

6.默认值

let [name, site = '花森'] = ['hs'];
console.log(site); //花森

7.遍历对象

keys/values/entries
const hs = {
  name: "花森",
  age: 10
};
console.log(Object.keys(hs)); //["name", "age"] 获取对象属性名组成的迭代器
console.log(Object.values(hs)); //["花森", 10] 获取对象属性组成的迭代器
console.table(Object.entries(hs)); //[["name","花森"],["age",10]] 两个迭代器
for/of遍历迭代器

For-of是不可以直接遍历对象,它是用于遍历迭代对象。

const hd = {
  name: "后盾人",
  age: 10
};
for (const key of Object.keys(hd)) {
  console.log(key);
}

8.对象的拷贝

浅拷贝
// for-in的方式遍历对象进行浅拷贝
let obj = {name: "花森"};

let hs = {};
for (const key in obj) {
  hs[key] = obj[key];
}
hs.name = "huasen";
console.log(hs);
console.log(obj);

// Object.assign进行简单的浅拷贝但同名属性将会被覆盖
Object.assign(hs, obj);
hs.name = "huasen";
console.log(hs);
console.log(obj);

// 展开语法
let hs = { ...obj };
深拷贝

浅拷贝不会将深层的数据复制,深拷贝完全就是复制出一个新的对象,两个对象完全独立。

let obj = {
  name: "花森",
  user: {
    name: "hs",
  },
  data: [],
};

function copy(object) {
  let obj = object instanceof Array ? [] : {}; // 判断是数组或者对象进行声明变量
  // 解构获得键值对
  for (const [k, v] of Object.entries(object)) {
    obj[k] = typeof v == "object" ? copy(v) : v; // 如果当前属性是引用类型则递归调用,基础数据类型则直接赋值。
  }
  return obj; // 返还对象
}

let huasen = copy(obj);
huasen.data.push("zhuqi");
console.log(JSON.stringify(huasen, null, 2));
console.log(JSON.stringify(obj, null, 2));

9.构造函数

工厂模式

普通函数中返还一个相同结构的对象,修改工厂模式的方法会影响所有的同类对象,且声明不需要new关键词。

function stu(name) {
  return {
    name,
    show() {
      console.log(this.name); // this代表函数调用者
    }
  };
}
const lisi = stu("李四");
lisi.show();
const hs = stu("huasen");
hs.show();
构造函数

构造函数的函数名首字母需要大写的命名规范,this指向当前创建的对象,系统会自动返回this关键词,但也可以收到return返回,手动返回必须是对象,不然setter方法会屏蔽且需要new关键词生成对象。

function Student(name) {
  this.name = name;
  this.show = function() {
    console.log(this.name);
  };
  // return this; // 系统会自动返回
}
const lisi = new Student("李四");
lisi.show();
const xj = new Student("王五");
wangwu.show();

10.属性特征

查看特征

使用 Object.getOwnPropertyDescriptor查看对象属性的描述

let obj = {
  name: "花森",
  user: {
    name: "hs",
  },
  data: [],
};
console.log(
  JSON.stringify(Object.getOwnPropertyDescriptor(obj, "name"), null, 2)
);
// {
//   "value": "花森",
//   "writable": true,
//   "enumerable": true,
//   "configurable": true
特性说明默认值
configurable能否使用delete 能否需改属性特性 或能否修改访问器属性true
enumerable对象属性是否可通过for-in循环或Object.keys() 读取true
writable对象属性是否可修改true
value对象属性的默认值undefined
设置属性

使用Object.defineProperty 方法修改属性特性

// 禁止遍历修改删除
"use strict";
const user = {
  name: "花森",
  age: 18
};
Object.defineProperty(user, "name", {
  value: "花森",
  writable: false,
  enumerable: false,
  configurable: false
});

// 一次性修改多个属性
Object.defineProperty(user, {
  name: {value: "花森", writable: false},
  age: {value: 18,enumerable: false}
});
禁止添加

Object.preventExtensions 禁止向对象添加属性,Object.isExtensible(user)可以判断是否可以向属性中添加属性。

"use strict";
const user = {
  name: "花森"
};
Object.preventExtensions(user);
user.age = 18; //Error
封闭对象

Object.seal()方法封闭一个对象,阻止添加新属性并将所有现有属性标记为configurable: false,可以通过isSealed检测是否发生封闭。

冻结对象

Object.freeza 冻结的对象不允许添加、删除、修改、writable、configurable都会被标记为false

"use strict";
const user = {
  name: "花森"
};
Object.freeze(user);
user.name = "花森"; //Error

12.属性访问器

getter方法用于获得属性值,setter方法用于设置属性,这是JS提供的存取器特性即使用函数来管理属性。用于避免错误的赋值,需要动态检测值的改变。属性只能在访问器和普通属性选择一个,不能共同存在。

getter/setter
"use strict";
const user = {
  data: { name: "花森", age: null },
  set age(value) {
    if (typeof value != "number" || value > 100 || value < 10) {
      throw new Error("年龄格式错误");
    }
    this.data.age = value;
  },
  get age() {
    return `年龄: ${this.data.age}`;
  },
};
user.age = 18; // 不能随便赋值
console.log(user.age);
内部私有属性
"use strict";
const user = {
  get name() {
    return this._name;
  },
  set name(value) {
    if (value.length <= 3) {
      throw new Error("用户名不能小于三位");
    }
    this._name = value;
  },
};
user.name = "花森酱酱";
console.log(user.name);
访问器描述符

使用 defineProperty 定义私有属性

// 函数写法
function User(name, age) {
  let data = { name, age };
  Object.defineProperties(this, {
    name: {
      get() {
        return data.name;
      },
      set(value) {
        if (value.trim() == "") throw new Error("无效的用户名");
        data.name = value;
      }
    },
    age: {
      get() {
        return data.name;
      },
      set(value) {
        if (value.trim() == "") throw new Error("无效的用户名");
        data.name = value;
      }
    }
  });
}

// class语法糖写法
"use strict";
const DATA = Symbol();
class User {
  constructor(name, age) {
    this[DATA] = { name, age };
  }
  get name() {
    return this[DATA].name;
  }
  set name(value) {
    if (value.trim() == "") throw new Error("无效的用户名");
    this[DATA].name = value;
  }
  get age() {
    return this[DATA].name;
  }
  set age(value) {
    if (value.trim() == "") throw new Error("无效的用户名");
    this[DATA].name = value;
  }
}

// 测试代码
let hs = new User("花森", 18);
console.log(hs.name);
hs.name = "huasen";
console.log(hs.name);
console.log(hs);

13.代理拦截

代理(拦截器)是整个对象的访问控制,setter/getter 是对单个对象属性的控制,而代理是对整个对象的控制。

  1. 读写属性时代码更简洁;
  2. 对象的多个属性控制统一交给代理完成;
  3. 严格模式下set必须返回布尔值;
let user = {
  data: { name: "花森", sex: 18 },
  title: "函数代理",
};

("use strict");
// 为对象建立代理
const proxy = new Proxy(user, {
  get(obj, property) {
    console.log("getter方法执行了");
    return obj[property];
  },
  set(obj, property, value) {
    console.log("setter方法执行了");
    obj[property] = value;
    return true;
  },
});
// 使用代理修改属性
proxy.age = 10;
// 使用代理获得属性
console.log(proxy.name);

14.JSON

json是广泛运用前后端数据交换的格式,具有轻量级且易于阅读和编写的特点。

  1. json是数据格式替换xml的最佳方式
  2. 前后端交互数据的主要格式;
  3. json标准中要求双引号包裹属性;
let lessons = [
  {
    "title": '媒体查询',
    "category": 'css',
    "click": 199
  },
  {
    "title": 'FLEX',
    "category": 'css',
    "click": 12
  },
  {
    "title": 'MYSQL',
    "category": 'mysql',
    "click": 89
  }
];

console.log(lessons[0].title);

序列化

序列化是将 json 转换为字符串,一般用来向其他语言传输使用。

let lessons = [
  {
    "title": '媒体查询',
    "category": 'css',
    "click": 199
  },
  {
    "title": 'FLEX',
    "category": 'css',
    "click": 12
  },
  {
    "title": 'MYSQL',
    "category": 'mysql',
    "click": 89
  }
];
// 使用JSON字符串序列化
console.log(JSON.stringify(lessons)); // 值 属性 tab数
反序列化

使用 JSON.parse 将字符串 json 解析成对象

console.log(JSON.parse(jsonStr));

事件

文档、浏览器、标签元素等元素在特定状态下触发的行为即为事件,JS为不同的事件定义的类型,事件目标是指产生时间的对象,事件具有冒泡捕获的特性,一个行为可能会造成多个事件的触发,处理事件的一段代码称为处理程序。

1.事件绑定

html中绑定

可以在html元素上设置事件处理程序,浏览器解析后会绑定到DOM属性中。

<button onclick="alert(`huasen`)">huasen</button>
dom绑定

可以将事件处理程序绑定到DOM属性中,使用setAttribute方法设置事件处理程序无效,属性名称区分大小写。

<div id="app">huasen</div>
<script>
  const app = document.querySelector('#app')
  app.onclick = function () {
    this.style.color = 'red'
  }
</script>
事件监听

建议使用新的事件监听绑定方式,transtionend / DOMContentLoaded 等事件类型只能使用事件监听addEventListener 处理,同一个事件类型设置多个事件处理程序则会按顺序先后执行,同样可以给为添加元素添加事件。具有以下参数方法:

1)参数一,事件类型;

2)参数二,事件处理程序;

3)参数三,制定的选项,once仅执行一次,capture:true/false捕获阶段,passive:true永远不调用preventDefault()阻值默认行为;

方法说明
addEventListener添加事件处理程序
removeEventListener移除事件处理程序

2.事件对象

执行事件处理程序时,会产生当前事件相关信息的对象,即为事件对事。系统会自动做为参数传递给事件处理程序,事件对象常用属性如下:

属性说明
type事件类型
target事件目标对象 冒泡的父级通过该属性可以找到在哪个元素上执行了事件
currentTarget当前执行事件的对象
timeStamp事件发生时间
x相对窗口的X坐标
y相对窗口的Y坐标
clientX相对窗口的X坐标
clientY相对窗口的Y坐标
screenX相对计算机屏幕的X坐标
screenY相对计算机屏幕的Y坐标
pageX相对于文档的X坐标
pageY相对于文档的Y坐标
offsetX相对于事件对象的X坐标
offsetY相对于事件对象的Y坐标
layerX相对于父级定位的X坐标
layerY相对于父级定位的Y坐标
path冒泡的路径
altKey是否按了alt键
shiftKey是否按了shift键
metaKey是否按了媒体键
window.pageXOffset文档参考窗口水平滚动的距离
window.pageYOffset文档参考窗口垂直滚动的距离

3.冒泡捕获

冒泡行为

标签元素是嵌套的,在一个元素上触发的事件,同时也会向上执行父级元素的事件处理程序,一直到HTML标签元素。大部分事件都有冒泡特性,但像focus事件则不会发生冒泡。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>测试</title>
    <style>
      #app {
        background: #34495e;
        width: 300px;
        padding: 30px;
      }
      #app h2 {
        background-color: #f1c40f;
        margin-right: -100px;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <h2>花森酱</h2>
    </div>
    <script>
      const app = document.querySelector("#app");
      const h2 = document.querySelector("h2");
      app.addEventListener("click", (event) => {
        console.log(`执行事件的节点:${event.currentTarget.nodeName}`);
        console.log(`触发节点:${event.target.nodeName}`);
      });
      h2.addEventListener("click", () => {
        console.log(`执行事件的节点:${event.currentTarget.nodeName}`);
        console.log(`触发节点:${event.target.nodeName}`);
      });
    </script>
  </body>
</html>
阻止冒泡

冒泡过程中的任何事件处理程序中,都可以执行 event.stopPropagation() 方法阻止继续进行冒泡传递,仅会阻止当前代码段的程序,但可以通过event.stopImmediatePropagation()阻止相同事件的冒泡行为。

事件捕获

事件执行顺序为 捕获 > 事件目标 > 冒泡阶段执行,在向下传递到目标对象的过程即为事件捕获,通过设置第三个参数为true或{ capture: true } 在捕获阶段执行事件处理程序。

<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>测试</title>
    <style>
      #app {
        background: #34495e;
        width: 300px;
        padding: 30px;
      }
      #app h2 {
        background-color: #f1c40f;
        margin-right: -100px;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <h2>
        huasenjio
        <span>花森</span>
      </h2>
    </div>
    <script>
      const app = document.querySelector("#app");
      const h2 = document.querySelector("h2");
      const span = document.querySelector("span");
      app.addEventListener(
        "click",
        (event) => {
          console.log("底部  事件");
        },
        { capture: true }
      );
      h2.addEventListener("click", (event) => {
        console.log("中间  事件");
      });
      span.addEventListener("click", (event) => {
        console.log("上面  事件");
      });
    </script>
  </body>
</html>

事件委托

借助冒泡思路,我们可以不为子元素设置事件,而将事件设置在父级。然后通过父级事件对象的event.target查找子元素,并对他做出处理,此过程叫做事件委托。

<ul>
  <li data-action="hidden">花森导航</li>
  <li data-action="color" data-color="red">笔录</li>
  </ul>
  <script>
    class HS {
      constructor(el) {
        el.addEventListener("click", (e) => {
          const action = e.target.dataset.action;
          this[action](e);
        });
      }
      hidden() {
        event.target.hidden = true;
      }
      color() {
        event.target.style.color = event.target.dataset.color;
      }
    }
  new HS(document.querySelector("ul"));
</script>

4.默认行为

JS会有些对象会设置默认事件处理程序,比如A链接在点击时会进行跳转。一般默认处理程序会在用户定义的处理程序后执行,所以我们可以在我们定义的事件处理程序员取消默认事件处理程序的执行。使用onclick绑定的时间处理程序,return false就可以阻止默认行为,推荐event.preventDefault()进行阻止默认行为。

<a href="http://n.huasenjio.top">花森笔录</a>
<script>
  document.querySelector('a').addEventListener('click', () => {
    event.preventDefault()
    alert(event.target.innerText)
  })
</script>

5.窗口文档

事件名说明
window.onload文档解析及外部资源加载后
DOMContentLoaded文档解析后不需要外部资源加载 只能使用addEventListener设置
window.beforeunload文档刷新或关闭时
window.unload文档卸载时
scroll页面滚动时

6.鼠标事件

针对鼠标操作的行为有多种事件类型,鼠标事件会触发在Z-INDEX最高的那个元素,仅能在顶层元素触发事件,被盖住的元素是无法触发点击事件。

事件名说明
click鼠标单击事件 同时顺序触发 mousedown/mouseup/click
dblclick鼠标双击事件
contextmenu点击右键后显示的所在环境的菜单
mousedown鼠标按下
mouseup鼠标抬起时
mousemove鼠标移动时
mouseover鼠标移动时
mouseout鼠标从元素上离开时
mouseup鼠标抬起时
mouseenter鼠标移入时触发 不产生冒泡行为
mosueleave鼠标移出时触发 不产生冒泡行为
oncopy复制内容时触发
scroll元素滚动时 可以为元素设置overflow:auto;产生滚动条来测试
事件属性
属性说明
which执行mousedown/mouseup时 显示所按的键 1左键 2中键 3右键
clientX相对窗口X坐标
clientY相对窗口Y坐标
pageX相对于文档的X坐标
pageY相对于文档的Y坐标
offsetX目标元素内部的X坐标
offsetY目标元素内部的Y坐标
altKey是否按了alt键
ctrlKey是否按了ctlr键
shiftKey是否按了shift键
metaKey是否按了媒体键
relatedTargetmouseover事件时从哪个元素来的 mouseout事件时指要移动到的元素 当无来源(在自身上移动)或移动到窗口外时值为null

7.键盘事件

针对键盘输入操作的行为有多种事件类型

事件名说明
Keydown键盘按下时 一直按键不松开时keydown事件会重复触发
keyup按键抬起时
事件属性
属性说明
keyCode返回键盘的ASCII字符数字
code按键码
key按键的字符含义表示 大小写不同
altKey是否按了alt键
ctrlKey是否按了ctlr键
shiftKey是否按了shift键
metaKey是否按了媒体键

8.表单事件

下面是可以用在表单上的事件类型

事件类型说明
focus获取焦点事件
blur失去焦点事件
element.focus()让元素强制获取焦点
element.blur()让元素失去焦点
change文本框在内容发生改变并失去焦点时触发 select/checkbox/radio选项改变时触发事件
input内容改变时触发 包括粘贴内容或语音输入内容都会触发事件
submit提交表单

原型和继承

每一个构造函数都存在prototype原型对象,通过new实例的对象会继承原型对象的方法属性,实例对象通过__proto__属性指向构造函数的prototype原型对象,所有的函数的原型默认是Object的实例对象,因为Object构造函数的原型上具有toString/toValues/isPrototypeOf 方法,所以每个对象都可以调用。当实例上不存在属性或者方法则将到原型上查找,原型对象包含constructor属性指向构造函数,对象包含__proto__ 指向他的原型对象。

原型继承图

1.Object.getPrototypeOf

用于获取一个对象的原型

console.log(Object.getPrototypeOf(a));

2.Object.setPrototypeOf

可以使用 __proto__Object.setPrototypeOf 设置对象的原型

function Person() {
  this.getName = function() {
    return this.name;
  };
}
function User(name, age) {
  this.name = name;
  this.age = age;
}
let lisi = new User("李四", 12);
Object.setPrototypeOf(lisi, new Person()); //将Person的一个实例对象作为lisi的原型
console.log(lisi.getName()); //李四

3.原型检测

instanceof

instanceof 检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上

function A() {}
function B() {}
function C() {}

const c = new C();
B.prototype = c;
const b = new B();
A.prototype = b;
const a = new A();

console.dir(a instanceof A); //true
console.dir(a instanceof B); //true
console.dir(a instanceof C); //true
console.dir(b instanceof C); //true
console.dir(c instanceof B); //false
isPrototypeOf

使用isPrototypeOf检测一个对象是否是另一个对象的原型链中

const a = {};
const b = {};
const c = {};

Object.setPrototypeOf(a, b);
Object.setPrototypeOf(b, c);

console.log(b.isPrototypeOf(a)); //true
console.log(c.isPrototypeOf(a)); //true
console.log(c.isPrototypeOf(b)); //true

4.属性遍历

in

使用in 检测原型链上是否存在属性,使用 hasOwnProperty 只检测当前对象是否存在属性。

let a = { url: "huasen" };
let b = { name: "花森" };
Object.setPrototypeOf(a, b); // 将a设置为b的原型
console.log("name" in a); // true
console.log(a.hasOwnProperty("name")); // false
console.log(a.hasOwnProperty("url")); // false
for-in
let hs = { name: "花森" };
// 以hs为原型创建huasen对象
let huasen = Object.create(hs, {
  url: {
    value: "huasenjio",
    enumerable: true,
  },
});
console.log(huasen);
for (const key in huasen) {
  console.log(key);
}

5.借用原型

使用 callapply 可以借用其他原型方法完成功能

let hs = {
  data: [1, 2, 3, 4, 5],
};
// 借用Math上面的max方法
console.log(Math.max.apply(null, hs.data));

let zhuqi = {
  lessons: { js: 100, php: 78, node: 78, linux: 125 },
};
// Object.values()被返回可枚举属性值的对象
console.log(Math.max.apply(zhuqi, Object.values(zhuqi.lessons)));

6._proto_

实例化对象上存在一个__proto__记录原型的信息,可以通过对象访问到原型的属性和方法,严格意义上讲__proto__ 不是对象属性,可以理解为protortype的一个getter/setter实现,是一个非标准的定义,内部使用getter/setter 控制输入值,所以只允许对象或者null的赋值,建议使用建议使用 Object.setPrototypeOfObject.getProttoeypOf替代__proto__ 的使用。

构造函数图

lisi.__proto__ = new Person();

7.继承与多态

实现继承
<script>
  function Person() {}
  Person.prototype.getName = function () {
    console.log("parent method");
  };

  function User() {}

  // 方式一,全部实例对象实现继承,Object.create(Person.prototype),建立一个a空对象且空对象的原型对象是Person的原型对象,User.prototype = a.__proto__ = Person.prototype。
  User.prototype = Object.create(Person.prototype);
  User.prototype.constructor = User;
  // 原型对象的方法设置放在建立原型链之后,避免造成覆盖。
  User.prototype.showUser = function () {
    console.log("User的方法");
  };

  console.log(new User());

  // 方式二,对单个对象实现继承,设置lisi对象原型,等同于lisi.__proto__ = Person.prototype,同样会造成原型对象的constructor构造函数丢失。
  // let lisi = new User();
  // Object.setPrototypeOf(lisi, Person.prototype);
  // console.dir(lisi);
  // console.dir(new User());
</script>
继承

对象能使用它上游原型链上存在的方法和状态

// Array原型对象上不存在valueOf方法,但是因为Array继承Object对象,即[]的__proto__指向的是Array的原型对象,而Array的__proto__指向的是Object的原型对象,因为Object的原型对象上存在valueOf方法,所以[]可以使用。
console.log([]);
console.log([1, 2, 3, 4].valueOf()); //  (4) [1, 2, 3, 4]
方法重写

子类的定义与父类相同方法名的方法就可以重写父类的方法,思想就是不让子类顺着原型链网上找,因为它在自己的原型上查找到方法后就不会再向上查找,这一点与nodejs找依赖库的思想一致。

function Person() {}
Person.prototype.getName = function() {
  console.log("parent method");
};

function User(name) {}
User.prototype = Object.create(Person.prototype);
User.prototype.constructor = User;

User.prototype.getName = function() {
  //调用父级同名方法
  Person.prototype.getName.call(this);
  console.log("child method");
};
let hd = new User();
hd.getName();
多态

根据多种不同的形态产生不同的结果,下而会根据不同形态的对象得到了不同的结果。例如动物是猫和狗的父类,动物有会叫的方法,猫和狗实现动物叫的方法时表现形式不同。这就是多态。

function User() {}
User.prototype.show = function() {
  console.log(this.description());
};

function Admin() {}
Admin.prototype = Object.create(User.prototype);
Admin.prototype.description = function() {
  return "管理员在此";
};

function Member() {}
Member.prototype = Object.create(User.prototype);
Member.prototype.description = function() {
  return "我是会员";
};

function Enterprise() {}
Enterprise.prototype = Object.create(User.prototype);
Enterprise.prototype.description = function() {
  return "企业帐户";
};

for (const obj of [new Admin(), new Member(), new Enterprise()]) {
  obj.show();
}

8.继承进阶

构造函数
function User(name) {
  this.name = name;
  console.log(this); // Admin
}
User.prototype.getUserName = function () {
  return this.name;
};

function Admin(name) {
  User.call(this, name); // 将User构造函数绑定给Admin
}
Admin.prototype = Object.create(User.prototype);

let hs = new Admin("花森");
console.log(hs.getUserName()); //花森
Mixin多继承

JS不能实现多继承,如果要使用多个类的方法时可以使用mixin混合模式来完成。mixin类是一个包含许多其他类方法的对象,经过Object.assign对象合并的方式为原型添加方法。

<script>
  function extend(sub, sup) {
    sub.prototype = Object.create(sup.prototype);
    sub.prototype.constructor = sub;
  }
  function User(name, age) {
    this.name = name;
    this.age = age;
  }
  User.prototype.show = function () {
    console.log(this.name, this.age);
  };
  const Credit = {
    total() {
      console.log("统计积分");
    },
  };
  const Request = {
    ajax() {
      console.log("请求后台");
    },
  };

  function Admin(...args) {
    User.apply(this, args);
  }
  extend(Admin, User);
  Object.assign(Admin.prototype, Request, Credit);
  let hs = new Admin("花森", 19);
  console.dir(hs);
  hs.show();
  hs.total(); //统计积分
  hs.ajax(); //请求后台
</script>

class

为了和其他语言的形态一致,JS提供了class关键词用于模拟传统的class,只是原型继承的语法糖形式,class为了让类的声明和继承更加简洁清晰,class默认使用严格模式执行。

1.声明定义

<script>
  // 构造函数构造对象
  function User(name) {
    //实例对象属性
    this.name = name;
  }
  //静态属性
  User.type = "用户";
  //静态方法
  User.showTypea = function() {
    // 静态方法调用的对象是构造函数 所以静态方法中仅可以使用静态属性
    return this.type;
  };
  // 原型添加函数(所有实例对象可以访问)
  User.prototype.showName = function() {
    return this.name;
  };
  console.dir(User);
  console.dir(new User("森哥哥"));

  // 类的方式构建对象 (extends继承对于构造函数是 Admin.__proto__ == User, 对于实例对象是 实例对象.__proto__ == User.prototype.)
  class Admin extends User {
    //静态属性
    static type = "管理员";
  constructor(name) {
    super(); // 指调用父类的构造函数
    //实例对象属性
    this.name = name;
  }
  //静态方法
  static showType() {
    // 静态方法调用的对象是构造函数 所以静态方法中仅可以使用静态属性
    return this.type;
  }
  // 原型添加函数(所有实例对象可以访问)
  showName() {
    return this.name;
  }
  }
  console.dir(Admin);
  console.dir(new Admin("猪琦琦"));
</script>

2.静态访问

静态属性

静态属性即为类设置属性,无需实例化即可调用,针对的是构造器设置。

// es5构造函数构造对象
function User(name) {}
//静态属性
User.type = "用户";

// class中使用static关键词
class Admin extends User {
  //静态属性
  static type = "管理员";
}
静态方法
// es5形式
function User(name) {}
User.showTypea = function() {
  // 静态方法调用的对象是构造函数 所以静态方法中仅可以使用静态属性
  return this.type;
};

// class静态方法
static showType() {
  // 静态方法调用的对象是构造函数 所以静态方法中仅可以使用静态属性
  return this.type;
}

3.访问器

访问器可以对对象的属性进行访问控制,访问器可以管控属性,有效的防止属性随意修改,加上get/set修饰,操作是不需要添加函数括号。

class User {
  constructor(name) {
    this.data = { name };
  }
  get name() {
    return this.data.name;
  }
  set name(value) {
    if (value.trim() == "") throw new Error("invalid params");
    this.data.name = value;
  }
}
let hs = new User("花森");
hs.name = "huasen";
console.log(hs.name);
public

public 指不受保护的属性,在类的内部与外部都可以访问到。

class User {
  url = "huasenjio";
  constructor(name) {
    this.name = name;
  }
}
let hs = new User("花森");
console.log(hs.name, hs.url);
protected

protected是受保护的属性修释,不允许外部直接操作,但可以继承后在类内部访问。

// 属性定义为以 _ 开始,来告诉使用者这是一个私有属性,请不要在外部使用,自启提示作用。
class Article {
  _host = "http://huasenjio.top/";
  set host(url) {
    if (!/^https:\/\//i.test(url)) {
      throw new Error("网址错误");
    }
    this._host = url;
  }
  lists() {
    return `${this._host}/article`;
  }
}
private

private 指私有属性,只在当前类可以访问到,并且不允许继承使用,为属性或者方法名前家#则是声明私有属性,是有属性只能在声明的类中使用。

class User {
  #host = "http://huasenjio.top/";
  constructor(name) {
      this.name = name;
      this.#check(name);
    }
  #check = () => {
  if (this.name.length <= 5) {
    throw new Error("用户名长度不能小于五位");
  }
  return true;
  };
  }
  class Pig extends User {
    constructor(name) {
      super(name);
      this.name = name;
    }
  }
  let pig = new Pig("猪琦猪猪猪户");
  // console.log(pig.#host); // '#host' must be declared in an enclosing class
  // console.log(new User().#host); // '#host' must be declared in an enclosing class

4.super

所有继承中 this 始终为调用对象,super 是用来查找当前对象的原型。在constructor中super指调用父类引用,必须先调用super()后再进行this赋值。

<script>
  class User {
      constructor(name) {
        this.name = name;
      }
    }
  class Pig extends User {
    constructor(name, type) {
      // super会调用父类的constructor构造器,因为父类的name属性是会被Pig继承,所以实例子类时需要将父类需要的参数通过super传递。
      super(name);
      this.type = type;
    }
  }
  let pig = new Pig("猪琦", "猪");
  console.log(pig);
</script>


// 实现原理
function Parent(name) {
  this.name = name;
}
function User(...args) {
  Parent.apply(this, args);
}
User.prototype = Object.create(User.prototype)
User.prototype.constructor = User;
const hs = new User("花森");
console.log(hs.name);

任务管理

JavaScript 语言的一大特点就是单线程,同一个时间只能处理一个任务。为了协调事件、用户交互、脚本、UI 渲染和网络处理等行为,防止主线程的不阻塞,JavaScript 处理任务是在等待任务、执行任务 、休眠等待新任务中不断循环中,也称这种机制为事件循环。

console.log("1.花森是同步代码");
setTimeout(function () {
  console.log("6.定时器是宏任务代码");
  Promise.resolve().then(() => {
    console.log("7.下次循环才会执行当前微任务");
  });
}, 0);
// 激活微任务
Promise.resolve()
  .then(function () {
  console.log("3.promise1微任务");
})
  .then(function () {
  console.log("4.promise2微任务");
})
  .then(() => {
  console.log("5.单次循环内微任务执行完成后才可以执行宏任务");
});
console.log("2.huasenjio时同步代码");

模块化开发

项目变大时需要把不同的业务分割成多个文件,即模块的思想,模块是比对象与函数更大的单元,使用模块组织程序便于维护与扩展且模块默认运行在严格模式下。使用模块化开发有以下优点:

  1. 模块化是一个独立的文件,可以包含函数或者类库;
  2. 使用模块化可以解决全局变量冲突;
  3. 模块可以隐藏内部实现,只对外保留开发接口;
  4. 模块化可以避免全局变量造成打代码不可控;
  5. 模块可以服用提高编码的效率;

1.管理引擎

过去JS不支持模块时我们使用AMD/CMD(浏览器端使用)、CommonJS(Node.js)、UMD(都支持)等形式定义模块。AMD代表性的是 require.js,CMD 代表是淘宝的 seaJS 框架。

2.基本使用

// html文件中导入模块需要定义属性 type="module"
<script type="module">
  import { hs } from "./a.js"; // 浏览器中使用必须添加路径
</script>

// 新建文件a.js内容如下
export let hs = {
  name: "花森"
};

3.作用域

模块都有独立的顶级作用域,不同模块之间不可以访问。

<script type="module">
  let hs = "huasenjio";
</script>

<script type="module">
  alert(hs); // Error
</script>

4.预解析

模块在导入时只执行一次解析,之后的导入不会再执行模块代码,而使用第一次解析结果,并共享数据,可以在首次导入时完成一些初始化工作。

5.导入导出

ES6使用基于文件的模块即一个文件一个模块:

  1. 使用export导出方法属性;
  2. 使用import导入模块接口;
  3. 使用 * 导入全部模块接口;
  4. 导出的状态和方法都是地址,模块内修改会影响导入的变量;
导出模块

定义文件hd.js导出内容如下

export const site = "花森导航";
export const func = function() {
  return "is a module function";
};
export class User {
  show() {
    console.log("user.show");
  }
}

// 别名导出
export { site, func as action, User as user };
导入模块
// 具名导入(不忽略大小写)
<script type="module">
  import { User, site, func } from "./hd.js";
  console.log(site);
  console.log(User);
</script>

// 批量导入
<script type="module">
  import * as api from "./hd.js";
  console.log(api.site);
  console.log(api.User);
</script>

// 别名导入
<script type="module">
  import { User as user, func as action, site as name } from "./hd.js";
  let func = "houdunren";
  console.log(name);
  console.log(user);
  console.log(action);
</script>

6.默认导出

当文件中导出的内容模块只有一个,也就是说仅需导入一个内容,这时可以使用默认导入,使用default定义默认导出的接口,导入时不需要使用{}且名字任意。

// hd.js文件中暴露导出
default class {
  static show() {
    console.log("User.method");
  }
}

// html中引入
<script type="module">
  import User from "./hd.js";
  User.show();
</script>

7.动态导入

<script>
  if (true) {
    let hd = import("./hd.js").then(module => {
      console.log(module.site);
    });
  }
</script>

8.指令总结

表达式说明
export function show(){}导出函数
export const name=‘后盾人’导出变量
export class User{}导出类
export default show默认导出
const name = ‘后盾人’ export {name}导出已经存在变量
export {name as hd_name}别名导出
import defaultVar from ‘houdunren.js’导入默认导出
import {name,show} from ‘a.j’导入命名导出
Import {name as hdName,show} from ‘houdunren.js’别名导入
Import * as api from ‘houdunren.js’导入全部接口

Promise

JavaScript 中存在很多异步操作,Promise 将异步操作队列化,按照期望的顺序执行,返回符合预期的结果,可以通过链式调用多个 Promise 达到我们的目的,使用promise还可以避免回调地域的现象,当一个Promise建立时执行内部的同步代码,resolve或者reject后开启微任务(then内部代码),并载入微任务队列。

1.基本理解

我在打游戏的时候(主进程),突然想吃可乐鸡腿,于是你让猪琦(Promise)开了一个做可乐鸡腿的任务(异步任务),因为不影响你打游戏,所以它叫异步任务。如果猪琦同意接受(resolve)则猪琦将去开始去烹饪,做好叫我吃可乐鸡腿(then中result回调函数);如果猪琦烹饪时可乐鸡腿发生意外(错误)或者拒绝任务(reject)并将我打了一顿,让我跪键盘(then中error回调函数),如果你没有跪键盘或者其他举措补偿(没设置error回调),则统一按分手(catch)处理。不管怎样最后都会和好(finally),开心的过日子。

new Promise((resolve, reject) => {
    resolve("成功处理");
    reject("拒绝处理");
  	console.log("同步代码"); // 优先执行
  })
    .then(
    (res) => {
      console.log("成功", res); // resolve则会执行此方法体
    },
    (err) => {
      console.log("拒绝", err); // 语法错误与reject则执行此方法体
    }
  )
    .catch((error) => {
    console.log("发生语法错误"); // 未设置reject回调函数时交给catch统一处理
  });
状态说明

Promise包含pending、fulfilled、rejected三种状态,Promise是队列状态,状态可一直向后传递,每一个Promise都可以改变状态,Promise可以链式传递一个传一个。

Promise队列图

  1. pending 初始等待状态,可由new Promise()获得该状态;
  2. resolve 已经解决, promise 状态设置为fulfilled ,可由Promise.resolve()获得该状态;
  3. reject 拒绝处理, promise 状态设置为rejected,可由Promise.reject()获得该状态;
  4. promise 是生产者,通过 resolvereject 函数告知异步任务的状态,一旦状态改变将不可更改;

2.异步加载

function load(file, resolve, reject) {
  const script = document.createElement("script");
  script.src = file;
  script.onload = resolve;
  script.onerror = reject;
  document.body.appendChild(script);
}
load(
  "./js/hs.js",
  script => {
    console.log(`${script.path[0].src} 加载成功`);
    hs();
  },
  error => {
    console.log(`${error.srcElement.src} 加载失败`);
  }
);

3.then

promise 需要提供一个then方法访问promise 结果,then 用于定义当 promise 状态发生改变时的处理,即promise处理异步操作,then 用于结果处理输出。

  1. then方法必须返回Promise,手动返回或者系统自动返回;
  2. 执行resolve时跳入then方法中的第一个回调参数;
  3. 执行reject时跳入then方法中的第二个回调参数;
<script>
  new Promise((resolve, reject) => {
    resolve("成功");
  })
    .then((res) => {
    console.log("1" + res);
    return new Promise((resolve, reject) => {
      resolve("成功");
    });
  })
    .then((res) => {
    console.log("2" + res);
    // 如果返回的是未处理的Promise则阻塞等待处理
    return new Promise((resolve, reject) => {
      reject("失败");
    });
  })
    .then(null, (err) => {
    console.log("3" + err);
    return "then链式调用默认执行resolve回调方法并将return指赋值给res";
  })
    .then((res) => {
    console.log("4" + res);
  })
    .catch((error) => {
    console.log(error);
  });
</script>

4.catch

catch用于失败状态的处理函数,等同于 then(null,reject){},建议使用catch统一处理错误。如果不存在reject回调函数则会跳入catch方法,整一个Promise链上的错误都可以被catch捕获。

new Promise((resolve, reject) => {
  resolve("成功");
  })
    .then((res) => {
    console.log("1" + res);
    abc; // 这个错误一直下沉,直到找到reject回调方法,如果then链上没有同意由catch捕获。
    return new Promise((resolve, reject) => {
      resolve("成功");
    });
  })
    .then((res) => {
    console.log("2" + res);
    // 如果返回的是未处理的Promise则阻塞等待处理
    return new Promise((resolve, reject) => {
      reject("失败");
    });
  })
    .then(null, (err) => {
    console.log("3" + err);
    return "then链式调用默认执行resolve回调方法并将return指赋值给res";
  })
    .then((res) => {
    console.log("4" + res);
  })
    .catch((error) => {
    console.log(error);
  });

5.事件处理

unhandledrejection事件用于捕获到未处理的Promise错误,下面的 then 产生了错误,但没有catch 处理,这时就会触发事件。

window.addEventListener("unhandledrejection", function(event) {
  console.log(event.promise); // 产生错误的promise对象
  console.log(event.reason); // Promise的reason
});

new Promise((resolve, reject) => {
  resolve("success");
}).then(msg => {
  throw new Error("fail");
});

6.finally

无论状态是resolvereject 都会执行此动作

const promise = new Promise((resolve, reject) => {
  reject("hs");
})
.then(msg => {
  console.log("resolve");
})
.catch(msg => {
  console.log("reject");
})
.finally(() => {
  console.log("resolve/reject状态都会执行");
});

7.拓展接口

resolve

使用 Promise.resolve() 方法可以快速的返回一个状态为fulfilled的promise对象

Promise.resolve("花森").then(value => {
  console.log(value); // 花森
});
reject

使用 Promise.reject() 方法可以快速的返回一个状态为rejected的promise对象

Promise.reject("花森").then(null,err => {
  console.log(err); // 花森
});
all

使用Promise.all 方法可以同时执行多个并行异步操作,等待多个promise完成任务后返回一个有序的数组,需要注意一下几点:

  1. 任何一个Promise执行失败都会调用catch方法;
  2. 一次发送多个异步操作;
  3. 参数必须是可迭代对象例如Array和Set;
const hs = new Promise((resolve, reject) => {
  setTimeout(() => {
      resolve("第一个Promise");
    }, 1000);
  });
  const zhuqi = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("第二个Promise");
    }, 1000);
  });
  const h = Promise.all([hs, zhuqi])
  .then((results) => {
    console.log(results);
  })
  .catch((msg) => {
    console.log(msg);
  });
  setTimeout(() => {
    console.log(h);
  }, 1000);
allSettled

allSettled 用于处理多个promise ,只关注执行完成,不关注是否全部执行成功,allSettled 状态只会是fulfilled

const p1 = new Promise((resolve, reject) => {
  resolve("resolved");
});
const p2 = new Promise((resolve, reject) => {
  reject("rejected");
});
Promise.allSettled([p1, p2])
.then(msg => {
  console.log(msg);
})
race

使用Promise.race() 处理容错异步,队列中Promise优先执行则优先返回,具有一下的几点特性:

  1. 最快返回的promise为准;
  2. 如果传入参数不是Promise则内部自动转为Promise;
  3. 无论内部的Promise返回的转态是reject还是resolve,race都会返回一个fulfilled状态promise,如果传入的Promise都存在语法错误则会返回一个Pending状态的Promise;
const hs = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject("第一个Promise");
  }, 1000);
});
const zhuqi = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject("第二个Promise");
  }, 3000);
});
const h = Promise.race([hs, zhuqi])
.then((results) => {
  console.log("成功" + results);
})
.catch((msg) => {
  console.log("错误" + msg);
});
setTimeout(() => {
  console.log(h); // Promise {<fulfilled>: undefined}
}, 4000);

8.async/await

使用 async/await 是promise 的语法糖,可以让编写 promise 更清晰易懂。

async

函数前加上async关键词,函数将返回promise就可以像使用标准Promise一样使用了。

async function hs() {
  return "huasenjio";
}
console.log(hs());
hs().then((value) => {
  console.log(value);
});
await

使用await关键词后面的Promise执行完成则继续向下执行,否则阻塞等待,内部await一旦有一个出现reject或者语法错误,则直接跳入err的回调函数中,具有一下特性:

  1. await后面跟Promise,若不是则直接返回后面值;
  2. await必须放在async定义的函数中使用;
  3. await用于代替then的链式调用;
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("promise处理");
  }, 1000);
});
const huasen = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject("p拒绝"); // 一旦出现拒绝又不进行处理,直接跳入err回调函数中,下面的代码将不再执行。
  }, 1000);
});
async function hs() {
  let result = await promise;
  console.log("需要等待await完成");
  let res = await huasen;

  return "执行完成"; // 如果不存在语法错误且不拒绝,则返回值跳入resolve回调函数中的res参数中。
}
hs().then(
  (res) => {
    console.log("成功", res);
  },
  (err) => {
    console.log("失败", err);
  }
);

DOM

浏览器解析HTML文件时会生成一个DOM对象,JS可以调用控制页面元素,但需要注意的是操控是浏览器以及渲染了页面内容,否则无法读取到节点对象。

1.节点对象

html中的每一个标签对应着js中的一个DOM节点对象,包含有属性方法,可以调用,以下是对象的基本知识:

  1. 包括有12种类型节点对象;
  2. 常用节点对象为document、标签元素节点、文本节点、注释节点
  3. 节点均继承node对象,所以拥有相同的属性和方法;
  4. document节点是DOM的根节点;
类型
元素节点1(body对象)
属性节点2(标签属性)
文本节点3(标签内文字)
注释节点8(标签内注释)
文档节点9(document对象)
文档类型节点10(<!DOCTYPE html>
<body id="hs">
  <!-- 花森 -->
  huasenjio
</body>
<script>
  console.log(document.nodeType) //9 document对象
  console.log(document.childNodes.item(0).nodeType) //10
  console.log(document.body.nodeType) //1
  console.log(document.body.attributes[0].nodeType) //2
  console.log(document.body.childNodes[1].nodeType) //8
</script>

2.DOM原型链

浏览器渲染过程中会将文档内容生成为不同的对象,不同的节点类型有专门的构造参数创建对象,使用console.dir可以查看详细属性方法,节点也是对象所以觉有JS对象的特征,有以下节点类型:

原型说明
Object根对象
EventTarget提供事件支持
Node提供parentNode等节点操作方法
Element提供getElementsByTagName、querySelector样式选择器等方法
HTMLElement所有元素的基础类 提供className、nodeName等方法
HTMLHeadingElementHead标题元素类
<div id="hs">花森酱</div>
<input type="text" name="title" id="title" />
  <script>
  let hs = document.getElementById("id");
function prototype(el) {
  let proto = Object.getPrototypeOf(el); // 获取对象原型
  console.log(proto);
  Object.getPrototypeOf(proto) ? prototype(proto) : ""; // 递归获取
}
prototype(hs);
</script>
对象合并
<div id="hs">huasenjio</div>
<script>
  let hs = document.getElementById('hs')
  Object.assign(hs, {
    color: 'red',
    change() {
      this.innerHTML = '花森酱'
      this.style.color = this.color
    },
    onclick() {
      this.change()
    },
  })
</script>
样式合并
<div id="hs">huasenjio</div>
<script>
  let hs = document.getElementById('hs')
  Object.assign(hs.style, {
    color: 'red',
  })
</script>

3.页面文档

document是window对象的属性,是由HTMLDocument类实现的实例,继承node类则可以用node的相关方法,document文档包含着页面唯一的元素标签。

html

系统提供了简单的方式来获取html元素

console.log(document.documentElement)
文档信息

使用title获取和设置文档标题

//获取文档标题
console.log(document.title)

//设置文档标签
document.title = '花森酱测试文件'

//获取当前URL
console.log(document.URL)

//获取域名
document.domain

//获取来源地址
console.log(document.referrer)
body

可通过document.body获取到页面

4.节点属性

nodeType

不同类型的节点拥有不同的属性,nodeType指以数值返回节点类型,递归获取元素某节点下的全部标签元素。

nodeType说明
1元素节点
2属性节点
3文本节点
8注释节点
9document对象
<div id="hs">
  <ul>
  <li>
  <h2><strong>花森</strong></h2>
    </li>
</ul>
</div>
<script>
    function all(el) {
    let items = [];
    [...el.childNodes].map((node) => {
      if (node.nodeType == 1) {
        items.push(node, ...all(node));
      }
    });
    return items;
  }
console.log(all(document.body));
</script>
nodeName

nodeName指定节点的名称,获取值为大写形式,注意空行也是一个文本节点。

nodeTypenodeName
1元素名称如DIV
2属性名称
3#text
8#comment

5.节点集合

Nodelist与HTMLCollection都是包含多个节点标签的集合

  1. getElementsBy…等方法返回的是HTMLCollection;
  2. querySelectorAll样式选择器返回的是 NodeList;
  3. NodeList和NodeList集合是动态的;
length

Nodelist与HTMLCollection包含length属性则记录节点元素的数量

转换数组

有时使用数组方法来操作节点集合,这就需要将节点集合转化为数组类型,有以下几种方式可以实现。

// Array.from()
console.log(Array.from(elements))

// 点语法
[...elements]

6.常用元素

系统针对特定标签提供了快速选择的方式

方法说明
document.documentElement文档节点即html标签节点
document.bodybody标签节点
document.headhead标签节点
document.links超链接集合
document.anchors所有锚点集合
document.formsform表单集合
document.images图片集合

7.节点关系

节点是根据HTML内容产生的,所以也存在父子、兄弟、祖先、后代等节点关系,下例中的代码就会产生这种多重关系,目前文本节点也会匹配上关系。

  1. h1与ul是兄弟关系;
  2. span与li是父子关系;
  3. ul与span是后代关系;
  4. span与ul是祖先关系;
<h1>花森</h1>
<ul>
  <li>
    <span>huasen</span>
    <strong>猪琦</strong>
  </li>
</ul>
节点属性说明
childNodes获取所有子节点
parentNode获取父节点
firstChild子节点中第一个
lastChild子节点中最后一个
nextSibling下一个兄弟节点
previousSibling上一个兄弟节点

8.元素关系

使用childNodes等获取的节点包括文本与注释

节点属性说明
parentElement获取父元素
children获取所有子元素
childElementCount子标签元素的数量
firstElementChild第一个子标签
lastElementChild最后一个子标签
previousElementSibling上一个兄弟标签
nextElementSibling下一个兄弟标签

9.选择器

系统提供了丰富的选择节点(NODE)的操作方法

getElementById

使用ID选择是非常方便的选择具有ID值的节点元素,此方法仅存在与document对象上。

<div id="hs">huasen</div>
<div id="app"></div>
<script>
  function getByElementIds(ids) {
  return ids.map((id) => document.getElementById(id));
}
let nodes = getByElementIds(["hs", "app"]);
console.dir(nodes);
</script>
getElementByClassName

getElementsByClassName用于按class 样式属性值获取元素集合

const nodes = document.getElementsByClassName('hs')
getElementByName

使用getElementByName获取设置了name属性的元素,返回NodeList节点列表对象,顺序即元素在文档中的顺序。

<div name="hs">花森</div>
<script>
  const div = document.getElementsByName("hs");
console.dir(div);
</script>
getElementByTagName

使用getElementsByTagName用于按标签名获取元素

const divs = document.getElementsByTagName('div')
通配符

可以使用通配符*获取所有元素

const nodes = document.getElementsByTagName('*')
querySelectorAll

在DOM操作中也可以使用这种方式查找元素,使用querySelectorAll根据CSS选择器获取Nodelist节点列表。获取的NodeList节点列表是静态的就是添加或者删除元素后list不会发生变化。

<div id="app">
  <div>花森/div>
  <div>猪琦</div>
</div>
<script>
  const app = document.body.querySelectorAll("#app")[0];
const nodes = app.querySelectorAll("div");
console.log(nodes); //2
</script>
querySelector

querySelector使用CSS选择器获取一个元素

const nodes = app.querySelectorAll("div");
matches

用于检测元素是否是指定的样式选择器匹配

<div id="app" class="app">
  <div>花森/div>
  <div>猪琦</div>
</div>
<script>
  const app = document.body.querySelectorAll("#app")[0];
console.log(app.matches(".app")) // true
</script>
closest

根据样式查找某元素最近的祖先元素

// 这个例子是查找li元素最近的祖先并且符合`.comment`样式
<div class="comment">
  <ul class="comment">
    <li>huasenjio</li>
</ul>
</div>

<script>
    const li = document.getElementsByTagName("li")[0];
const node = li.closest(`.comment`);
console.log(node);
</script>

10.动态与静态

通过 getElementsByTagname 等getElementsBy… 函数获取的Nodelist与HTMLCollection集合是动态,即有元素添加或移动操作集合将实时反映最新状态。

  1. 使用getElement…返回的都是动态的集合;
  2. 使用querySelectorAll返回的是静态集合;
<h1>花森导航</h1>
<h1>huasenjio</h1>
<button id="add">添加元素</button>

<script>
  let elements = document.getElementsByTagName("h1");
	//let elements = document.querySelectorAll("h1");
console.log(elements);
let button = document.querySelector("#add");
button.addEventListener("click", () => {
  document
    .querySelector("body")
    .insertAdjacentHTML("beforeend", "<h1>hs</h1>");
  console.log(elements);
});
</script>

11.元素特征

标准的属性(src|className|herf)可以使用DOM属性的方式进行操作,但是对于非标准的属性则不可以,可以理解为元素的属性分两个地方保存,DOM属性记录标准属性,特征中记录标准和定制属性。简而言之就是对奇怪的属性进行操作,有以下几种方法:

方法说明
getAttribute获取属性
setAttribute设置属性
removeAttribute删除属性
hasAttribute属性检测
attributes

元素提供了attributes 属性可以只读的获取元素的属性

<div class="hs" data-content="花森">hs.com</div>
<script>
  let hs = document.querySelector(".hs");
console.dir(hs.attributes["class"].nodeValue); //hs
console.dir(hs.attributes["data-content"].nodeValue); //后盾人
</script>
hasAttribute

用于检测对象是否存在某个属性

console.log(hdcms.hasAttribute('class')) //false
自定义属性

虽然可以随意定义特征并使用getAttribute等方法管理,建议使用以data-为前缀的自定义特征处理,针对这种定义方式JS也提供了接口方便操作。

  1. 元素中以data-作为前缀的属性会添加按到属性map集合中;
  2. 使用元素的dataset可以获取属性集合中的属性;
  3. 改变dataset的值也会影响元素;
<div class="hs" data-title-color="red">花森酱</div>
<script>
  let hs = document.querySelector(".hs");
	console.log(hs.dataset);
	hs.innerHTML = `
	<span style="color:${hs.dataset.titleColor}">${hs.innerHTML}</span>`;
</script>

12.创建节点

创建节点的就是构建出DOM对象

createTextNode

创建文本对象并添加到元素中

<div id="app"></div>
<script>
  let app = document.querySelector('#app')
  let text = document.createTextNode('花森')
  app.append(text)
</script>
createElement

使用createElement方法可以标签节点对象

<div id="app"></div>
<script>
  let app = document.querySelector('#app')
  let span = document.createElement('span')
  span.innerHTML = '花森酱'
  app.append(span)
</script>
createDocumentFragment

使用createDocumentFragment创建虚拟节点容器具有一下特点:

  1. 创建的节点的parentNode为Null;
  2. 使用createDocumentFragment创建的节点来暂存文档节点
  3. createDocumentFragment创建的节点添加到其他节点上时;
  4. 不直接操作DOM所以性能更好;
  5. 排序/移动等大量DOM操作时建议使用createDocumentFragment
cloneNode&importNode

使用cloneNode和document.importNode用于复制节点对象操作

  1. cloneNode是节点方法;
  2. cloneNode参数为true时递归赋值子节点即深拷贝;
  3. importNode是document对象方法;
<div id="app">huasen</div>
<script>
  let app = document.querySelector('#app')
  let newApp = app.cloneNode(true)
  document.body.appendChild(newApp)
</script>

13.节点内容

innerHTML

inneHTML用于向标签中添加html内容,同时出发浏览器的解析器重绘DOM树。

<div id="app"></div>
<script>
  let app = document.querySelector("#app");
	console.log(app.innerHTML);

	app.innerHTML = "<h1>花森</h1>";
</script>
outerHTML

outerHTML与innerHTML的区别是包含父标签,不会将原来的内容删除掉依然在DOM树节点上。

<div id="app">
  <div class="hs" data="hs">花森</div>
  <div class="zhuqi">猪琦</div>
</div>
<script>
  let app = document.querySelector('#app')
  console.log(app.outerHTML)

  app.outerHTML = '<h1>酱</h1>'
</script>
textContent与innerText

textContent与innerText是访问或添加文本内容到元素中

<div id="app">
  <div class="hs" data="hs">花森</div>
  <div class="zhuqi">猪琦</div>
</div>
<script>
  let app = document.querySelector('#app')
  console.log(app.outerHTML)

  app.textContent = '<h1>酱</h1>'
</script>
insertAdjacentText

将文本插入到元素指定位置,不会对文本中的标签进行解析,即不会将<h1>皮卡丘</h1>以HTML标签形式去渲染。

选项说明
beforebegin元素本身前面
afterend元素本身后面
afterbegin元素内部前面
beforeend元素内部后面
<div id="app">
  <div class="hs">花森</div>
  <div class="zq">猪琦</div>
</div>
<script>
  let app = document.querySelector(".hs");
  app.insertAdjacentText("afterend", "<h1>皮卡丘</h1>");
</script>

14.节点管理

节点元素的管理,包括添加、删除、替换等操作,具体有以下参数位置:

推荐方法
方法说明
append节点尾部添加新节点或字符串
prepend节点开始添加新节点或字符串
before节点前面添加新节点或字符串
after节点后面添加新节点或字符串
replaceWith将节点替换为新节点或字符串
<div id="app">花森酱</div>
<script>
  let app = document.querySelector("#app");
	app.append("huasenjio.top");
</script>
inserAdjacentHTML

html文本插入到元素指定位置,浏览器会对文本进行标签解析,具有包括以下参数位置:

选项说明
beforebegin元素本身前面
afterend元素本身后面
afterbegin元素内部前面
beforeend元素内部后面
<div id="app">花森酱</div>
<script>
  let app = document.querySelector("#app");
	app.insertAdjacentHTML("afterbegin",'<h1>后盾人</h1>');
</script>
inserAdjacentElement

nsertAdjacentElement() 方法将指定元素插入到元素的指定位置

  1. 第一个参数是位置;
  2. 第二个参数为新元素节点;
<div id="app">花森酱</div>
<script>
  let app = document.querySelector("#app");
  let span = document.createElement('span')
	app.insertAdjacentHTML("afterbegin",span);
</script>
古老管理手法

下面列表过去使用的操作节点的方法

方法说明
appendChild添加节点
insertBefore用于插入元素到另一个元素的前面
removeChild删除节点
replaceChild进行节点的替换操作

15.表单控制

表单查找

JS为表单的操作提供了单独的集合控制

  • 使用document.forms获取表单集合;
  • 使用form的name属性获取指定form元素;
  • 根据表单项的name属性使用form.elements.title获取表单项;
  • 直接写成form.name形式;
  • 针对radio/checkbox获取的表单项是一个集合;
<form action="" name="hs">
  <input type="text" name="title" />
</form>
<script>
    const form = document.forms.hs;
console.log(form.title.form === form); //true
</script>

16.样式管理

通过DOM修改样式可以通过更改元素的class属性或通过style对象设置行样式来完成

className

使用JS的className可以批量设置样式,style中设置对应的类名,达到修改样式的目的。

let app = document.getElementById('app')
app.className = 'hs'
classList

通过使用 classList属性,添加移除操作操作,实现样式和dom的绑定。

方法说明
node.classList.add添加类名
node.classList.remove删除类名
node.classList.toggle切换类名
node.classList.contains类名检测
let app = document.getElementById('app')
app.classList.add('hs')
console.log(app.classList.contains('hs')) //false
单个样式属性设置

使用节点的style对象来设置行样式,单词采用驼峰命名法。

let app = document.getElementById('app')
app.style.backgroundColor = 'red'
app.style.color = 'yellow'
批量设置样式
// 方式一
let app = document.getElementById('app')
app.style.cssText = `background-color:red;color:yellow`

// 方式二
app.setAttribute('style', `background-color:red;color:yellow;`)

17.样式获取

style

可以使用DOM对象的style属性读取行样式,需要注意的是style对象仅可以获取内联样式属性。

	<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>测试</title>
    <style>
      div {
        color: yellow;
      }
    </style>
  </head>
  <body>
    <div id="app" style="background-color: red; margin: 20px">后盾人</div>
    <script>
      let app = document.getElementById("app");
      console.log(app.style.backgroundColor);
      console.log(app.style.margin);
      console.log(app.style.marginTop);
      console.log(app.style.color);
    </script>
  </body>
getComputedStyle

使用window.getComputedStyle可获取所有应用在元素上的样式属性,第一个参数为元素,第二个参数为伪类,此方式获得的是经过计算后的样式属性,可能与真实的设置值有所不同。

let fontSize = window.getComputedStyle(app).fontSize

空间坐标

1.基础理解

首先参考画布分为视口(窗口)与文档的含义

  1. 文档尺寸一般大于视口尺寸;
  2. F12打开控制台会造成尺寸相应变小;
  3. 视口的尺寸不包括浏览器的菜单和状态栏等;
  4. 视口坐标的操作需要考虑滚动条的位置;

视口文档图

2.视口文档

获取浏览器视口宽度的集中方法

方法说明注意
window.innerWidth视口宽度包括滚动条(不常用)
window.innerHeight视口高度包括滚动条(不常用)
document.documentElement.clientWidth视口宽度
document.documentElement.clientHeight视口高度

3.定位方向

方法说明备注
element.getBoundingClientRect()返回元素相对于视口的坐标信息窗口坐标
element.getClientRects行级元素每行尺寸位置组成的数组
element.offsetParent拥有定位属性的父级对于隐藏元素/body/html值为null
element.offsetWidth元素宽度尺寸(内边距+边框+宽)
element.offsetHeight元素高度尺寸(内边距+边框+高)
element.offsetLeft相对于祖先元素的X轴坐标
element.offsetTop相对于祖先元素的Y轴坐标
element.clientWidth内容宽度(width+内边距)(行级元素为0)
element.clientHeight内容宽度(width+内边距)(行级元素为0)
element.clientLeft内容距离外部的距离
element.clientTop内容距离顶部的距离
element.scrollWidth元素宽度 内容+内边距+内容溢出的尺寸
element.scrollHeight元素高度 内容+内边距+内容溢出的尺寸
element.scrollLeft水平滚动条左侧已经滚动的宽度
element.scrollTop垂直滚动条顶部已经滚动的高度

4.坐标判断

获取相对于视口的xy坐标上的元素,如果坐标定在视口外则返回值为null。

方法说明
element.elementsFromPoint返回指定坐标点所在的元素集合
element.elementFromPoint返回指定坐标点最顶级的元素

5.滚动控制

方法说明参数说明
window.pageXOffset文档相对窗口水平滚动的像素距离
window.pageYOffset文档相对窗口垂直滚动的像素距离
element.scrollLeft()元素X轴滚动位置
element.scrollTop()元素Y轴滚动位置
element.scrollBy()按偏移量进行滚动内容参数为对象,{top:垂直偏移量,left:水平偏移量,behavior:‘滚动方式’}
element.scroll() 或 element.scrollTo()滚动到指定的具体位置参数为对象,{top:X轴文档位置,left:Y轴文档位置,behavior:‘滚动方式’}
element.scrollLeft获取和设置元素X轴滚动位置设置X轴文档位置
element.scrollTop获取和设置元素Y轴滚动位置设置Y轴文档位置
element.scrollIntoView(bool)定位到顶部或底部参数为true元素定位到顶部
document.documentElement.scroll({ top: 30, behavior: 'smooth' })

网络请求

浏览器天生具发送HTTP请求的能力

1.XMLHttpRequest

使用XMLHttpRequest发送请求数据

// 通过XMLHttpRequst封装Ajax网络请求
// 实例:HuaSenAjax.get(url,options,data),HuaSenAjax.post(url,options,data),new HuaSenAjax("GET",url,options,data),默认请求GET且异步请求,函数返回一个Promise对象。
class HuaAjaxTools {
  // 定义返回类型为JSON类型
  options = {
    responseType: "json",
  };
  // 构造函数,默认get请求,默认不传参数,参数列表options用于覆盖。
  constructor(method = "GET", url, options, data = null, async = true) {
    this.method = method;
    this.url = url;
    this.data = this.formatData(data);
    this.async = async;
    Object.assign(this.options, options); // 合并覆盖参数
  }
  // 格式化处理参数,处理将POST请求数据序列化。
  formatData(data) {
    if (typeof data != "object" || data == null) data = {};
    let form = new FormData(); // FormData类型其实是在XMLHttpRequest2级定义,它是为序列化表以及创建与表单格式相同的数据(当然是用于XHR传输)提供便利。
    for (const [name, value] of Object.entries(data)) {
      form.append(name, value); //添加至序列化列表中
    }
    return form;
  }
  // 处理连接参数的静态方法
  static handleUrl(url = null, data = null) {
    if (typeof url != "string" || url == null) url = "";
    if (typeof data != "object" || data == null) data = {};
    if (Object.keys(data).length !== 0) {
      url = url.replace(/\/$/, "") + "?";
      let params = "";
      for (const [name, value] of Object.entries(data)) {
        params += "&" + name + "=" + encodeURIComponent(value);
      }
      params = params.replace(/^&/, ""); // 利用正则去除参数中的第一个&符号
      url = url + params;
    }
    return url;
  }
  // 声明get静态方法
  static get(url, options, data) {
    try {
      url = this.handleUrl(url, data);
      return new this("GET", url, options).xhr();
    } catch (e) {
      return Promise.reject("get方法内部出错" + e);
    }
  }
  // 声明post静态方法
  static post(url, options, data) {
    try {
      return new this("POST", url, options, data).xhr();
    } catch (e) {
      return Promise.reject("post方法内部出错" + e);
    }
  }
  static jsonp(url, data) {
    return new Promise((resolve, reject) => {
      try {
        // window对象绑定一个回调函数
        const body = document.body;
        const script = document.createElement("script");
        window[data.cb] = function (res) {
          resolve(res); // 处理promise激活微任务
        };
        url = this.handleUrl(url, data); // 处理URL
        script.src = url;
        body.appendChild(script); // body标签后添加script标签程序自动加载内容
        body.removeChild(script); // 获得参数后移除添加的script标签
      } catch (e) {
        body.removeChild(script);
        reject("jsonp中发生错误", e);
        
      }
    });
  }
  // 异步发送
  xhr() {
    return new Promise((resolve, reject) => {
      const xhr = new XMLHttpRequest(); // 声明网络请求对象
      xhr.open(this.method, this.url, this.async); // 设置网络请求配置
      xhr.responseType = this.options.responseType; // 请求数据类型
      xhr.send(this.data); //发送参数
      xhr.onload = function () {
        // 请求成功后的网络执行的函数
        if (xhr.status == 200) {
          //状态码
          resolve(xhr.response);
        } else {
          reject({ status: xhr.status, error: xhr.statusText });
        }
      };
      xhr.onerror = function (error) {
        reject(error);
      };
    });
  }
}
export { getByElementIds, HuaAjaxTools };

// 使用实例
import { HuaAjaxTools } from "../utils.js";

HuaAjaxTools.get(
  "http://api.tianapi.com/txapi/everyday/index",
  { responseType: "json" },
  {
    key: "2cab6669e9d6766c2990eccfa3253ee5",
  }
).then(
  (result) => {
    console.log(result);
  },
  (reject) => {
    console.log(reject);
  }
);

HuaAjaxTools.post(
  "http://api.tianapi.com/txapi/everyday/index",
  {
    responseType: "json",
    headers: {
      "Content-Type": "application/json;charset=utf-8",
    },
  },
  {
    key: "2cab6669e9d6766c2990eccfa3253ee5",
  }
).then(
  (result) => {
    console.log(result);
  },
  (reject) => {
    console.log(reject);
  }
);

HuaAjaxTools.jsonp("https://www.baidu.com/su", {
  wd: "花森", // 参数关键值
  cb: "handleSuggestion", // 回调函数
}).then(
  (success) => {
    console.log(success);
  },
  (error) => {
    console.log(error);
  }
);

2.fetch

FETCH是JS升级后提供的更简便的网络请求的操作方法,内部使用Promise完成请求,使用response.json()接收JSON类型数据,使用response.text()接收text类型数据。

get
fetch(
  "http://wthrcdn.etouch.cn/weather_mini?city=%E9%87%91%E5%B7%9E%E5%8C%BA"
)
  .then((res) => {
  return res.json();
})
  .then((data) => {
  console.log(data);
});
post

发送的JSON类型需要设置请求头为 application/json;charset=utf-8

fetch(`链接`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json;charset=utf-8',
  },
  body: JSON.stringify({ name: '花森' }),
})
  .then((response) => {
    return response.json()
  })
  .then((data) => {
    console.log(data)
  })

正则表达式

正则表达式是用于匹配字符串中字符组合的模式,正则表达式是在宿主环境下运行,不是一门单独的语言,几乎主流语言(js/php/node.js)等都存在正则表达式语法。

1.声明正则

// 字面量
let hs = "huasenjio";
console.log(/u/.test(hs));//true "//"字面量形式写法但不可以在其中使用变量
console.log(/hs/.test(hs)); //false 变量形式被当做字符型去匹配
console.log(eval(`/${hs}/`).test(hs)); //true 通过eval函数实现解析变量

// 对象形式
let hs = "huasenjio.top";
let zhuqi = ".top";
let reg = new RegExp(zhuqi); // 匹配规则
console.log(reg.test(hs)); //true 



2.选择符

| 这个符号带表选择修释符,选择符左右两侧仅能匹配一个。

let hs = "森酱生气了";
console.log(/(森酱|猪琦)生气了/.test(hs));

3.字符转义

转义用于改变字符的含义,例如/在是正则符号的边界,如果想要输入网址匹配,则需要通过\/转移成字符串的含义。

const url = "https://";
console.log(/https:\/\//.test(url)); //true

4.字符边界

使用字符边界符用于控制匹配内容的开始与结束约定

边界符说明
^匹配字符串的开始
$匹配字符串的结束 忽略换行符
let hs = "n.huasenjio.top";
console.log(/^n\./.test(hs)); //n.打头的字符串会被匹配

5.元子字符

元字符是正则表达式中的最小元素,仅代表单一一个字符。

元字符说明示例
\d匹配任意一个数字[0-9]
\D与除了数字以外的任何一个字符匹配[^0-9]
\w与任意一个英文字母数字或下划线匹配[a-zA-Z_]
\W除了字母数字或下划线外与任何字符匹配[^a-zA-Z_]
\s任意一个空白字符匹配 如空格 制表符\t 换行符\n[\n\f\r\t\v]
\S除了空白符外任意一个字符匹配[^\n\f\r\t\v]
.匹配除换行符外的任意字符
let hd = "huasenjio 2010";
console.log(hd.match(/\d/g)); // ["2", "0", "1", "0"]
console.log(hd.match(/\d/)); // ["2"]
console.log(hd.match(/\d+/)); // ["2010"]

6.模式修饰

正则表达式在执行时会按他们的默认执行方式进行匹配

修饰符说明
i不区分大小写字母的匹配
g全局搜索所有匹配内容
m视为多行
s视为单行忽略换行符 使用. 可以匹配所有字符
yregexp.lastIndex 开始匹配 匹配不成功就不再继续
u正确处理四个字符的 UTF-16 编码
// 全域g,忽略大小写i。
let hd = "huasenJIO 2010";
console.log(hd.match(/Jio \d+/gi)); // ["JIO 2010"]

// u配合属性别名,L表示字母,P表示标点符号,\p{sc=Han}表示中国汉字。
console.log(hd.match(/\p{L}+ \d+/gu)); // ["huasenJIO 2010"]

// RegExp对象lastIndex 属性可以返回或者设置正则表达式开始匹配的位置,必须配合g模式使用,匹配完成时lastIndex会被重置为0且对exec有效。
let hs = `花森导航网址`;
let reg = /\p{sc=Han}/gu;
reg.lastIndex = 2; // 正则遍历的脚步
while ((res = reg.exec(hs))) {
  console.log(reg.lastIndex);
  console.log(res[0]);
}

// y
let hs = "udunren";
let reg = /u/y;
console.log(reg.exec(hs));
console.log(reg.lastIndex); //1
console.log(reg.exec(hs)); //null
console.log(reg.lastIndex); //0

7.原子表

在一组字符中匹配某个元字符就要将其加入[]

原子表说明
[]只匹配其中的一个原子
[^]只匹配"除了"其中字符的任意一个原子
[0-9]匹配0-9任何一个数字(需要升序)
[a-z]匹配小写a-z任何一个字母(注意顺序)
[A-Z]匹配大写A-Z任何一个字母(注意顺序)
let hs= "huasenJIO 2010";
console.log(hs.match(/[hua]/g)); // ["h", "u", "a"] 匹配到h、u、a中任意的单个字符

8.原子组

多个元子当成一个整体匹配,可以通过元子组完成,用括号()进行包裹。

const hs = `<h1>huasenjio</h1>`;
console.log(/<(h1)>.+<\/\1>/.test(hs)); //true
使用分组

$n 指在替换时使用匹配的分组数据,下面是匹配h标题标签替换成p段落标签,并引用第二个分组的数据放入。

 let hs = `
<h1>花森酱</h1>
<span>huasen</span>
<h2>Jio</h2>
`;
// 第一个分组(h[1-6]),第二个分组([\s\S]*)。
let reg = /<(h[1-6])>([\s\S]*)<\/\1>/gi;
console.log(hs.replace(reg, `<p>$2</p>`));

9.重复匹配

基本使用

如果要重复匹配一些内容时我们要使用重复匹配修饰符

符号说明
*重复取零次或更多次(0或n)
+重复一次或更多次(1或n)
?重复零次或一次(0或1)
{n}重复n次
{n,}重复n次或更多次
{n,m}重复n到m次
let hd = "hssss";
console.log(hd.match(/hs+/i)); //hssss
禁止贪婪

正则表达式在进行重复匹配时,默认是贪婪匹配模式,当后面还存在符合的字符就一直贪婪匹配下去,这时就可以使用禁止贪婪,可以通过?进行修饰。

使用说明
*?重复任意次 但尽可能少重复
+?重复1次或更多次 但尽可能少重复
??重复0次或1次 但尽可能少重复
{n,m}?重复n到m次 但尽可能少重复
{n,}?重复n次以上 但尽可能少重复
// 满足表达式即可不贪婪下去
let str = "1234";
console.log(str.match(/\d+/)); //1234
console.log(str.match(/\d+?/)); //1
console.log(str.match(/\d{2,3}?/)); //12
console.log(str.match(/\d{2,}?/)); //12

<h1>huasen</h1>
<h2>花森</h2>
<h3></h3>
<h1></h1>
<script>
  let body = document.body.innerHTML;
	let reg = /<(h[1-6])>[\s\S]*?<\/\1>/gi; // 禁止贪婪 ["<h1>huasen</h1>","<h2>花森</h2>","<h3></h3>","<h1></h1>"]
	let reg = /<(h[1-6])>[\s\S]*<\/\1>/gi; // 开启贪婪 ["<h1>huasen</h1>↵ <h2>花森</h2>↵ <h3></h3>↵ <h1></h1>"]
	console.table(body.match(reg));
</script>

10.字符串方法

search

search() 方法用于检索字符串中指定的子字符串,也可以使用正则表达式搜索,返回值为索引位置。

let str = "huasenjio.top";
console.log(str.search("top")); // 10下标匹配到
console.log(str.search(/\.top/i)); // 9
match

直接使用字符串搜索

let str = "huasenjio.top";
console.log(str.match("top")); // 返回匹配的参数
console.log(str.match(/\.top/i)); // 返回匹配的参数
split

通过正则匹配到的符号进行字符串分割

let str = "2023/02-12";
console.log(str.split(/-|\//));
replace

replace 方法不仅可以执行基本字符替换,也可以进行正则替换,下面替换日期连接符。

let str = "2023/02/12";
console.log(str.replace(/\//g, "-")); //2023-02-12

11.正则方法

test

使用正则判定字符串是否存在特定符号,检测输入的邮箱是否合法。

<body>
  <input type="text" name="email" />
</body>

<script>
  let email = document.querySelector(`[name="email"]`);
  email.addEventListener("keyup", e => {
    console.log(/^\w+@\w+\.\w+$/.test(e.target.value));
  });
</script>
exec

不使用 g 修饰符时与 match 方法使用相似,使用 g 修饰符后可以循环调用直到全部匹配完。

<div class="content">花森导航中的作者花森不断分享种花技巧</div>
<script>
  let content = document.querySelector(".content");
  let reg = /(?<tag>花)/g;
  let num = 0;
  while ((result = reg.exec(content.innerHTML))) {
    num++;
  }
  console.log(`花共出现${num}次`);
</script>

11.断言匹配

断言虽然写在扩号中但它不是组,所以不会在匹配结果中保存,可以将断言理解为正则中的条件。

(?=exp)

零宽先行断言 ?=exp 匹配后面为 exp 的内容

(?<=exp)

零宽后行断言 ?<=exp 匹配前面为 exp 的内容

(?!exp)

零宽负向先行断言 后面不能出现 exp 指定的内容

(?<!exp)

零宽负向后行断言 前面不能出现exp指定的内容s

window对象

window 是客户端浏览器对象模型的基类,window对象也称BOM是JavaScript的全局对象,每一个标签页就是一个独立的窗口独立的BOM对象,通过BOM对象我们可以操作当前窗口,进行进一步操作!

1.对象分类

  1. Window,客户端js的顶层对象,当body和frameset标签出现时window对象就会自动创建;
  2. navigator,包含客户端浏览器的信息;
  3. screen,客户端屏幕信息;
  4. history,浏览器窗口访问过的URL信息;
  5. location,包含当前网页文档的URL信息;
  6. document,整个页面文档标签信息;

2.系统交互

window定义了3个人机交互的方法

alert("你好");
console.log(confirm("你的电脑将被攻击")); // true
console.log(prompt("花森导航的网址为:")); // 输入值

3.打开窗口

使用 window 对象的 open() 方法可以打开一个新窗口并返回一个window对象

//window.open (URL, name, features, replace)
let a = window.open("http://n.huasenjio.top/", "zhuzhu");
console.log(a); // 返回window对象
  1. URL跳转网页的链接;
  2. name新窗口的名称,注意不是网站标题;
  3. feature声明了新窗口要显示的标准浏览器的特征;
  4. replace确定打开的网页在浏览器记录中添加一条访问记录还是替换掉原来网页中的条目;
特征说明
fullscreen = yes | no | 1 | 0是否使用全屏模式显示浏览器(默认no)
height = pixels窗口文档显示区的高度
left = pixels窗口的 x 坐标
location = yes | no | 1 | 0是否显示地址字段(默认是 yes)
menubar = yes | no | 1 | 0是否显示菜单栏(默认是 yes)
resizable = yes | no | 1 | 0窗口是否可调节尺寸(默认是 yes)
scrollbars = yes | no | 1 | 0是否显示滚动条(默认是 yes)
status = yes | no | 1 | 0是否添加状态栏(默认是 yes)
toolbar = yes | no | 1 | 0是否显示浏览器的工具栏(默认是 yes)
top = pixels窗口的 y 坐标
width = pixels窗口的文档显示区的宽度

新创建的window对象有一个opener属性指向原始的网页对象

win = window.open(); //打开新的空白窗口
win.document.write("<h1>这是新打开的窗口</h1>"); //在新窗口中输出提示信息
win.focus(); //让原窗口获取焦点
win.opener.document.write("<h1>这是原来窗口</h1>"); //在原窗口中输出提示信息
console.log(win.opener == window); //检测window.opener属性值

4.关闭窗口

关闭当前窗口,使用 window.closed 属性可以检测当前窗口是否关闭。

window.close();

5.定时器

方法说明
setInterval()按照执行的周期(单位为毫秒)调用函数或计算表达式
setTimeout()在指定的毫秒数后调用函数或计算表达式
clearInterval()取消由 setInterval() 方法生成的定时器
clearTimeout()取消由 setTimeout() 方法生成的定时器

6.框架集合

每一个frame标签都是一个window对象,使用frame可以访问每一个window对象,frames是一个数据集合,存储者所有的window对象,下标从0开始,访问顺序从左到右从上到下,通过parent.frames[0]访问对应的frame框架的window对象。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<frameset rows="50%,50%" cols="*" frameborder="yes" border="1" framespacing="0">
  <frameset rows="*" cols="50%,*,*" framespacing="0" frameborder="yes" border="1">
      <frame src="http://n.huasenjio.top/" name="left" id="left" />
      <frame src="middle.html" name="middle" id="middle">
      <frame src="right.html"name="right" id="right">
  </frameset>
  <frame src="http://huasenjio.top/" name="bottom" id="bottom">
</frameset>
<body>
  
</body>
</html>

7.窗口大小位置

方法 moveTo() 可以将窗口的左上角移动到指定的坐标,方法 moveBy() 可以将窗口上移、下移、左移、右移指定数量的像素,方法 resizeTo() 和 resizeBy() 可以按照绝对数量和相对数量调整窗口的大小。

调整窗口位置

moveTo() 和 moveBy()

窗体大小

resizeTo() 和 resizeBy()

滚动条

scrollTo() 和 scrollBy()

navigator对象

navigator 对象存储了与浏览器相关的基本信息,例如名称、版本、系统信息,通过window.navigator 可以引用该对象,读取客户端基本信息。

1.浏览器检测方法

特征检测法

特征检测法就是根据浏览器是否支持特定的功能来决定相应操作的非精确判断方式,但却是最安全的检测方法,仅仅在意浏览器的执行能力,那么使用特征检测法就完全可以满足需要。

// 检测当前浏览器是否支持 document.getElementsByName 特性,不支持则使用document.getElementsByTagName 特性,进行兼容处理。
if (document.getElementsByName) {  //如果存在则使用该方法获取a元素
    var a = document.getElementsByName ("a");
} else if (document.getElementsByTagName) {  //如果存在则使用该方法获取a元素
    var a = document.getElementsByTagName ("a");
}
字符串检测法

客户端浏览器每次发送 HTTP 请求时,请求头中有一个user-agent(用户代理)属性,使用用户代理字符串检测浏览器类型,可以通过navigator.userAgent获取客户端信息。

var s = window.navigator.userAgent;
//简写方法
var s = navigator.userAgent;
console.log(s);
//返回类似信息:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36

2.检测版本号

获得客户端信息后通过正则表达式筛选对应信息即可

location对象

location 对象存储了当前文档位置(URL)相关的信息,例如网页的地址,访问历史信息,可以通过window.location进行访问。location中定义有一下8个属性:

1.对象属性

属性说明
href声明了当前显示文档的完整 URL(设置可以利用网页跳转)
protocol声明了 URL 的协议部分(例如:“http:”)
host声明了当前 URL 中的主机名和端口部分(例如:“huasenjio.top:80”)
hostname声明了当前 URL 中的主机名
port声明了当前 URL 的端口部分
pathname声明了当前 URL的路径部分
search声明了当前 URL 的查询部分(例如:“?id=123&name=location”)
hash声明了当前 URL 中锚部分(“#top”指定在文档中锚记的名称)

2.对象方法

reload

可以重新装载当前文档

replace

可以装载一个新文档而无须为它创建一个新的历史记录,即不能通过单击“返回”按钮返回当前的文档,window.locationdocument.location不同,前者是location对象,后者是一个只读字符串,当服务器发生重定向,document.location 包含的是已经装载的 URL,而 location.href 包含的则是原始请求文档的 URL。

history对象

history 对象存储了库互动浏览器的浏览历史,通过window.history可以访问对象,取到最近访问且有限的条目URL信息。HTML5 之前,为了保护客户端浏览信息的安全和隐私,history 对象禁止 JavaScript 脚本直接操作这些访问信息。HTML5 新增了一个 History API,该 API 允许用户通过 JavaScript 管理浏览器的历史记录,实现无刷新更改浏览器地址栏的地址。

1.基本操作

window.history.back();1// 历史记录中后退,等效于在浏览器的工具栏上单击“返回”按钮。
window.history.forward();1 // 历史记录中前进,等效于浏览器中单击“前进”按钮。
window.history.go(1);  // 移动到指定的历史记录点
window.history.length; // 访问记录长度
window.history.state; // 当前标签的访问记录

2.添加修改

添加和修改历史记录条目,HTML5 新增 history.pushState() 和 history.replaceState() 方法,允许用户逐条添加和修改历史记录条目。

pushState

pushState可以修改referer值,调用该方法后创建的 XMLHttpRequest 对象会在 HTTP 请求头中使用referer值,referrer 的值则是创建 XMLHttpRequest 对象时所处的窗口的 URL。 pushState() 方法类似于设置 window.location=’#foo’,它们都会在当前文档内创建和激活新的历史记录条目,pushState() 方法永远不会触发 hashchange 事件。

var stateObj = {foo : "bar"};
history.pushState (stateObj, "标题", "bar.html"); // 当前标签URL变为XXX/bar.html
replaceState

history.replaceState() 与 history.pushState() 用法相同,pushState() 是在 history 栈中添加一个新的条目,replaceState() 是替换当前的记录值。

3.popstate事件

每当激活的历史记录发生变化时,都会触发 popstate 事件。如果被激活的历史记录条目是由 pushState() 创建,或者是被 replaceState() 方法替换的,popstate 事件的状态属性将包含历史记录的状态对象。

screen对象

screen 对象存储了客户端屏幕信息,可以用来探测客户端硬件配置,实现根据显示器屏幕大小选择使用图像的大小,根据颜色深度选择使用 16 色图像或 8 色图像。

function center(url) {
  //窗口居中处理函数
  var w = screen.availWidth / 2; //获取客户端屏幕宽度的一半
  var h = screen.availHeight / 2; //获取客户端屏幕高度的一半
  var t = (screen.availHeight - h) / 2; //计算居中显示时顶部坐标
  var l = (screen.availWidth - w) / 2; //计算居中显示时左侧坐标
  var p = "top=" + t + ",left=" + l + ",width=" + w + ",height=" + h; //设计坐标参数字符串
  var win = window.open(url, "url", p); //打开指定的窗口,并传递参数
  win.focus(); //获取窗口焦点
}
console.log(screen);
center("http://huasenjio.top"); //调用该函数

document对象

浏览器加载文档会自动构建文档对象模型(DOM),文档中每个元素都映射到一个数据集合。

document对象图

1.动态生成文档内容

使用 document 对象的 write() 和 writeln() 方法可以动态生成文档内容

write()

生成内容追加在文档树的末尾

document.write ('Hello,World');
writeln()

writeln() 方法与 write() 方法完全相同,只不过在输出参数之后附加一个换行符。

XMLHttpRequest

XMLHttpRequest(XHR)对象用于与服务器交互,通过 XMLHttpRequest 可以在不刷新页面的情况下请求特定 URL,更新页面的局部内容。

1.定义声明

构造函数用于初始化一个 XMLHttpRequest 实例对象

let xhr = new XMLHttpRequest();

2.属性类型

XMLHttpRequest对象属性说明
onreadystatechange根据readyState属性发生变化调用方法
readyState发送请求进程状态分为(0|1|2|3|4完成)
response服务器返回的数据类型
responseText返回文本数据
responseType定义服务器响应类型
responseURL返回经过序列化(serialized)的响应 URL
responseXML返回XML格式数据
status代表服务器响应的状态码
statesText包含完整的响应状态文本(例如,"200 OK")
unload上传进度
timeout请求的最大请求时间(毫秒)超出自动终止
withCreadtials布尔值指定跨域 Access-Control 请求是否应当带有授权信息

3.常用方法

XMLHttpRequest.方法说明
abort()立即终止请求
getAllResponseHeaders()字符串的形式返回所有用 CRLF 分隔的响应头
getResponseHeader()返回包含指定响应头的字符串
open()初始化一个请求
overrideMimeType覆写由服务器返回的 MIME 类型
send()发送请求
setRequestHeader()设置 HTTP 请求头的值(open之后且send()之前设置)

4.事件监听

addEventListener监听说明
abort当 request 被停止时触发
error当 request 遭遇错误时触发
load请求成功完成时触发
timeout预设时间内没有接收到响应时触发

5.简单示例

let xhr = new XMLHttpRequest();
// 开启请求任务 readyState=0
xhr.open(
  "GET",
  "http://wthrcdn.etouch.cn/weather_mini?city=%E9%87%91%E5%B7%9E%E5%8C%BA"
);
xhr.send(); //发送请求 readyState=1

// 监听readyState变化来等待服务器响应
xhr.onreadystatechange = function () {
  if (xhr.readyState == 4 && xhr.status == 200) {
    console.log("获取数据:" + xhr.responseText); // readyState=4
  } else {
    console.log("readyState:" + xhr.readyState); // readyState=2  readyState=3
  }
};
// 请求成功回调
xhr.addEventListener("load", () => {
  console.log("请求成功会触发事件!");
});

6.XMLHttpRequest2新特性

旧版本只支持文本格式传输,无法用于读取上传二进制文件,接收和传送时无法查看进度信息,不能跨域请求,第二版本优化功能具有一下特点:

  1. 设置HTTP请求的时限;
  2. 可以使用FormData对象管理表单数据;
  3. 上传文件;
  4. 可以请求不同域名下的数据(跨域请求);
  5. 获取服务器端的二进制数据;
  6. 可以获得数据传输的进度信息;
formData

ajax操作往往用来传递表单数据,所以HTML5新增了一个FormData对象,可以模拟表单数据。

// 实例化对象
var formData = new FormData(); 
// 添加表单项
formData.append('username', '张三');
formData.append('id', 123456);
// 发送格式
xhr.send(formData);
上传文件

新版XMLHttpRequest对象可以上传文件,假定files是一个"选择文件"的表单元素(input[type="file"]),我们同样使用formData格式进行传输。

var formData = new FormData();

for (var i = 0; i < files.length;i++) {

  formData.append('files[]', files[i]);

}

xhr.send(formData);
接收二进制数据

服务器取回二进制数据是使用新增的responseType属性,如果服务器返回文本数据则属性值为”text“,不要用IE6这种古董浏览器还可以支持其他格式数据,例如设置传回属性是json字符串格式。

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://wthrcdn.etouch.cn/weather_mini?city=%E9%87%91%E5%B7%9E%E5%8C%BA');
xhr.responseType: "json",
xhr.send();
xhr.onreadystatechange = function () {
  if (xhr.readyState == 4 && xhr.status == 200) {
    console.log(xhr.response); // readyState=4
  } else {
    console.log("readyState:" + xhr.readyState); // readyState=2  readyState=3
  }
};
进度信息

新版本的XMLHttpRequest对象在传输数据时可以通过监听progress事件用来返回进行信息,它分成上传下载情况,下载的progress事件属于XMLHttpRequest对象,上传的progress事件属于XMLHttpRequest.upload对象。

xhr.onprogress = 方法;
xhr.upload.onprogress = 方法;

// 实例
xhr.onprogress = function (event) {
  console.log(event);
  if (event.lengthComputable) {
    var percentComplete = event.loaded / event.total;
  }
};

HTTP请求头

HTTP协议报文头部由方法、URL、HTTP版本、HTTP首部字段等部分构成,携带报文主题大小,使用语言,认证信息详细信息。

1.首部字段类型

  1. 通用首部字段;
  2. 请求首部字段;
  3. 响应首部字段;
  4. 实体首部字段;

2.通用首部字段

通用首部字段说明
Cache-Control控制缓存行为(no-cache|max-age|public)
Connection连接状态管理(close|Keep-Alive|timeout max=500)
DateHTTP/1.1协议使用RFC1123中规定的日期时间的格式
Pragma客户端要求中间服务器不返回缓存资源(Pragma:no-cache)
Trailer简要记录主体报文记录首部字段()
Trailer-Encoding规定了传输报文主体时使用的编码方式
Upgrade用于检测HTTP协议及其他协议是否可以使用更高的版本进行通信(TLS/1.0)
Via追踪客户端与服务端之间的请求和响应报文的传输路径
Warning错误通知(110响应过期|112断开连接|)

3.请求首部字段

首部字段名说明
Accept*用于代理可处理的媒体类型
Accept-Charset通知服务器用户代理支持的字符集及字符集的相对优先级顺序
Accept-Language告知服务器用户代理能够处理的自然语言集以及自然语言集的相对优先级
Accept-Encoding*通知服务器用户代理支持的内容编码及内容编码的优先级顺序
Authorization告知服务器用户代理的认证信息
Expect期待服务器的特定行为
From告知服务器使用代理的用户的电子邮件地址
Host请求的资源所处的互联网主机名和端口号
If-Mach条件请求(比较实体标记(ETAG)相同才可以请求)
If-Modified-Since用于确认代理或客户端拥有的本地资源的有效性(304缓存)
If-None-Match相反比较实体标记
Max-Forwads字段以十进制整数形式指定可经过的服务器最大数目
Proxy-Authorization客户端接收到从代理服务器发送过来的认证质询(返回时提交必要信息)
Range客户端发送带有该首部字段的请求可以指定服务器资源的范围
User-Agent创建请求的浏览器和用户代理名称等信息传达给服务器
If-Range资源未更新是发送实体Byte的范围请求
If-UNmodified-Since相反的比较更新时间
Referer告知服务器请求的原始资源的URI
TE首部字段会告知服务器客户端能够处理响应的传输编码方式以及相对优先级

4.响应首部字段

响应首部字段说明
Accept-Ranges是否接受字节范围请求
Age推算资源创建经过时间
Etag告知客户端实体标识(可将资源以字符串形式做唯一性标识)
Location命令客户端重定向至指定URL
Retry-After首部字段告知客户端应该在多久之后再次发送请求
Server首部字段告知客户端当前服务器上安装的 HTTP 服务器应用程序的信息
Vary源服务器会向代理服务器传达关于本地缓存使用方法的命令
WWW-Authenticate首部字段用于 HTTP 访问认证

5.实体首部字段

实体首部字段说明
Allow首部字段用于通知客户端能够支持指定资源的所有 HTTP 方法
Content-Encoding告知客户端服务器对实体的主体部分选用的内容编码方式
Content-Language告知客户端实体主体使用的自然语言
Content-LengthContent-Length 表明了实体主体部分的大小
Content-Location首部字段给出与报文主体部分相对应的 URI
Content-MD5MD5 算法生成的值目的是检查报文主体在传输过程中是否保持完
Content-Range告知客户端作为响应返回的实体
Content-Type首部字段说明了实体主体内对象的媒体类型
Expires资源失效的日期告知客户端
Last-Modified首部字段指明资源最终修改的时间

6.服务Cookie的首部字段

属于响应首部字段,用于服务端开始管理客户端状态的交互的载体。

属性说明
NAME=VALUE赋予Cookie的名称和值
expires=DATECookie有效期(不指明则默认为浏览器关闭时)
path=PATH服务器上的文件目录作为Cookie的适用对象
domain=域名作为Cookie适用对象的域名
Secure仅在HTTPS安全通信时才会发生Cookie
HttpOnly限制Cookie不能被JavaScript脚本访问

声明

一切资源均来源于各大免费论坛社区,只提供互联网数据收录查询服务且不会存在储存用户数据和贩卖数据。资源仅供大家学习交流,请勿用于商业用途,造成损失及法律责任与本人无关。如有侵犯您的权益,请联系小生,吾将积极配合!

寄语

前段时间在金蝶软件园实习结束,回来就开发了一款网络导航应用,因为由于搜索引擎的竞价很严重,所以获取到优质内容已经不再容易了,这也是我开发这个网站的初衷,网站无广告,推荐的网站和工具都是很良心,涉及生活的方方面面,生活上的娱乐追剧,学习上的奋笔直书,它都是你的好帮手,我相信你给它几分钟的时间浏览,你就会喜欢上它了。尽管我只是一个大三的学生,但服务器运维费均是我自掏腰包,凭着一份爱好在支撑!vue项目第一次进会慢一些,同样也想听到大家的宝贵意见,不出意外的话我会将它开源,希望大家能够支持鼓励!

花酱与猪 - 自定义网络资源导航

PC端
手机端

iPad端

联系

🐧企鹅:184820911

📕邮箱:[email protected]

;