JavaScript ES6

目录

  • 变量声明
      • 弱类型
      • 变量提升
      • 变量作用域
        • var
        • let
        • const
        • 重复定义
  • 严格模式
  • 比较运算符
        • 赋值运算符
        • 算术运算符
        • 复合运算符
        • 一元运算符
        • 比较运算符
        • 逻辑运算符
  • 流程控制
        • if
        • if/else
        • 三元表达式
        • switch
        • while
        • do/while
        • for
        • break/continue
        • label
        • for/in
        • for/of
  • 基本类型
        • 类型检测
      • String
        • 转义符号
        • 类型转换
      • Boolean
        • 声明定义
        • 隐式转换
        • 显式转换
        • 实例操作
      • Number
        • 声明定义
        • 基本函数
        • NaN
        • 类型转换
        • Math
      • Date
        • 声明日期
        • 对象方法
        • moment.js
  • 数组
      • 声明数组
        • 创建数组
        • Array.of
        • 类型检测
      • 类型转换
        • 字符串
        • Array.from
      • 展开语法
        • 数组合并
        • 函数参数
        • 节点转换
      • 解构赋值
        • 基本使用
        • 严格模式
      • 简洁定义
        • 默认值
        • 函数参数
      • 管理元素
        • 基本使用
        • 扩展语法
        • push
        • pop
        • shift
        • unshift
        • fill
        • slice
        • splice
        • 清空数组
      • 合并拆分
        • join
        • split
        • concat
        • copyWithin
      • 查找元素
        • indexOf
        • lastIndexOf
        • includes
        • find
        • find原理
      • 数组排序
          • everse
        • sort
      • 循环遍历
        • for
        • forEach
        • for/in
        • for/of
        • keys
        • values
        • entries
      • 扩展方法
        • every
        • some
        • filter
        • map
        • reduce
  • 函数
      • 基础知识
        • 声明定义
        • 匿名函数
        • 立即执行
        • 函数提升
        • 形参实参
        • 默认参数
        • 函数参数
        • arguments
        • 箭头函数
        • 递归调用
        • 回调函数
        • 展开语法
        • 标签函数
      • this
        • 函数调用
        • 方法调用
        • 箭头函数
      • apply/call/bind
        • 原理分析
        • apply/call
        • bind
  • 对象
      • 基础知识
        • OOP
        • 基本声明
        • 操作属性
        • 对象方法
        • 引用特性
        • this
        • 展开语法
      • 对象转换
        • 基础知识
        • Symbol.toPrimitive
        • valueOf/toString
        • 解构赋值
        • 严格模式
        • 简洁定义
        • 嵌套解构
        • 默认值
        • 函数参数
      • 属性管理
        • 添加属性
        • 删除属性
        • 检测属性
        • assign
        • 计算属性
        • 传值操作
      • 遍历对象
        • 获取内容
        • for/in
        • for/of
      • 对象拷贝
        • 浅拷贝
        • 深拷贝
        • 构建函数
        • 工厂函数
        • 构造函数
        • 严格模式
        • 内置构造
        • 对象函数
        • 抽象特性
        • 问题分析
        • 抽象封装
      • 属性特征
        • 查看特征
        • 设置特征
        • 禁止添加
        • 封闭对象
        • 冻结对象
        • 属性访问器
        • 访问器描述符
        • 闭包访问器
      • 代理拦截
        • 代理函数
        • 截取字符
        • 双向绑定
        • 表单验证
      • JSON
        • 声明定义
        • 序列化
        • 反序列化

变量声明

弱类型

在JS中变量类型由所引用的值决定

	var web = "web";
	console.log(typeof web); //string
	web = 99;
	console.log(typeof web); //number
	web = true;
	console.log(typeof web); //boolean
	web = {};
	console.log(typeof web); //object

使用分号表示一段指令的结束,当没有输入分号时如果有换行符JS会自动添加分号,减少错误的发生。
但推荐每个指令都以分号结束
在使用构建工具时,不使用分号结束可能会造成异常

变量提升

变量提升示例
下面是 if(false) 中定义的var也会发生变量提升,注释掉if 结果会不同

var web = "js";
function hd() {
  if (false) {
    var web = "javascript";
  }
  console.log(web);
}
hd();

解析器会先解析代码,然后把声明的变量的声明提升到最前,这就叫做变量提升。

下面代码在解析过程中发现while不能做为变量名,没有到执行环节就出错了。

var web = 'houdunren';
console.log(web);
let while = 'hdcms'; //Uncaught SyntaxError: Unexpected token 'while'

使用 var 声明代码会被提升到前面,赋值还在原位置

console.log(a); //undefined
var a = 1;
console.log(a);  //1

//以上代码解析器执行过程如下
var a;
console.log(a); //1
a = 1;
console.log(a); //1

变量作用域

var

使用 var 声明的变量存在于最近的函数或全局作用域中,没有块级作用域的机制。

没有块作用域很容易污染全局,下面函数中的变量污染了全局环境

function run() {
  web = "web";
}
run();
console.log(web); //web

没有块作用作用域时var也会污染全局

for (var i = 0; i < 10; i++) {
  console.log(i);
}
console.log(i);

下例中体验到 var 没有块作用域概念, do/while 定义的变量可以在块外部访问到

var num = 0;

function show() {
  var step = 10;
  do {
    var res = 0;
    console.log(num = step++);
    res = num;
  } while (step < 20);
  console.log(`结果是${res}`);
}
show();

var 全局声明的变量也存在于 window对象中

var hd = "houdunren";
console.log(window.hd); //houdunren

以往没有块任用时使用立即执行函数模拟块作用域

(function() {
  var $ = this.$ = {};
  $.web = "web";
}.bind(window)());
console.log($.web);

有了块作用域后实现就变得简单多了

{
  let $ = (window.$ = {});
  $.web = "web";
}
console.log($.web);

let

与 var 声明的区别是 let/const 拥有块作用域,下面代码演示了块外部是无法访问到let声明的变量。

建议将let在代码块前声明
用逗号分隔定义多个
let存在块作用域特性,变量只在块域中有效

if (true) {
    let web = 'whs',url = 'icebound.com';
    console.log(web); //whs
}
console.log(web); //web is not defined

块内部是可以访问到上层作用域的变量

if (true) {
  let user = "whs";
  (function() {
    if (true) {
      console.log(`这是块内访问:${user}`);
    }
  })();
}
console.log(user);

每一层都是独立作用域,里层作用域可以声明外层作用域同名变量,但不会改变外层变量

function run() {
  hd = "icebound";
  if (true) {
    let hd = "whs";
    console.log(hd); //whs
  }
  console.log(hd); //icebound
}
run();

const

使用 const 用来声明常量,这与其他语言差别不大,比如可以用来声明后台接口的URI地址。

  • 常量名建议全部大写
  • 只能声明一次变量
  • 声明时必须同时赋值
  • 不允许再次全新赋值
  • 可以修改引用类型变量的值
  • 拥有块、函数、全局作用域
  • 常量不允许全新赋值举例
	try {
	  const URL = "https://www.baidu.com";
	  URL = "https://www.icebound.com"; //产生错误
	} catch (error) {
	  throw new Error(error);
	}

改变常量的引用类型值,可以使用Object的冻结方法冻结

	const INFO = {
      url: "https://www.icebound.com",
      port: "8080",
    };
    INFO.port = "443";
    console.log(INFO);//{url: "https://www.icebound.com", port: "443"}



    const HOST = {
      url: "https://www.icebound.com",
      port: "8080",
    };
    Object.freeze(HOST)
    HOST.port = "443";
    console.log(HOST);//{url: "https://www.icebound.com", port: "8080"}

下面演示了在不同作用域中可以重名定义常量

	const NAME = '后盾人';
	
	function show() {
	  const NAME = '向军大叔';
	  return NAME;
	}
	console.log(show());
	console.log(NAME);

重复定义

使用 var 可能造成不小心定义了同名变量

//优惠价
var price = 90;
//商品价格
var price = 100;
console.log(`商品优惠价格是:${price}`);

使用let 可以避免上面的问题,因为let声明后的变量不允许在同一作用域中重新声明

let web = 'houdunren.com';
let web = '后盾人'; //Identifier 'web' has already been declared

不同作用域可以重新声明

let web = 'houdunren.com';
if (true) {
	let web = '后盾人'; //Identifier 'web' has already been declared
}

严格模式

严格模式可以让我们及早发现错误,使代码更安全规范,推荐在代码中一直保持严格模式运行。

主流框架都采用严格模式,严格模式也是未来JS标准,所以建议代码使用严格模式开发

#基本差异
变量必须使用关键词声明,未声明的变量不允许赋值

"use strict";
url = 'icebound.com'; //url is not defined

强制声明防止污染全局

"use strict";
function run() {
  web = "icebound";
}
run();
console.log(web); //houdunren

关键词不允许做变量使用

"use strict";
var public = 'icebound.com';

变量参数不允许重复定义

"use strict";
//不允许参数重名
function hd(name, name) {} 

单独为函数设置严格模式

function strict(){  
  "use strict";  
  return "严格模式";  
}  
function notStrict() {  
  return "正常模式";  
}  

为了在多文件合并时,防止全局设置严格模式对其他没使用严格模式文件的影响,将脚本放在一个执行函数中。

(function () {
  "use strict";
  url = 'houdunren.com';
})();

比较运算符

赋值运算符

使用 = 进行变量赋值

let url = 'icebuond.com';

算术运算符

包括以下几种算术运算符。

运算符 说明

示例 符号
* 乘法
/ 除法
+ 加法
- 减法
% 取余数
let a = 5,b = 3;
console.log(a * b); //15
console.log(a % b); //2

复合运算符

可以使用 =、/=、+=、-=、%= 简写算术运算。即 n=2 等同于 n=n*2。

let n = 2;
n *= 2;
console.log(n);

对变量加减相应数值。

let n = 2;
n += 3;
console.log(n); //0
n -= 5;
console.log(n); //5

n+=3 是 n=n+3 的简写形式

一元运算符

前置操作
前置操作会在表达式最先执行。

let n = 1;
++n
console.log(n);
--n
console.log(n);

++n 就是 n=n+1 的简写形式。

使用后置操作符,++n 会在最先执行,所以f的结果是33。

let n = 2;
let f = 30 + ++n;
console.log(f);

后置操作
后置操作会在表达式最后执行。

let n = 1;
n++
console.log(n);

使用后置操作符,n++ 会在最后执行,所以f的结果是32。

let n = 2;
let f = 30 + n++;
console.log(f);

参与数学计算

let a = 1;
b = a++ + 2;
console.log(b); //3

比较运算符

运算符 说明
> 大于
< 小于
>= 大于或等于
<= 小于等于
== 强制类型转换比较
=== 不强制类型转换比较

下面来体验不同类型的比较结果

let a = 1,b = 2,c = '1';

console.log(a < b); //true
console.log(a == b); //false
console.log(a == c); //true
console.log(a === c); //false
console.log(a == true); //true
console.log(a === true); //false

==的类型装换规则

value1 value2 转换后统一类型 结果
boolean number number 仅成立true==1,false ==0
boolean string number 仅成立true==“1”,false ==“0”
boolean object string 永为假
number string number ---------
number object string 永为假
string object string 等同于 string==object.toString()仅且string="[object Object]"时成立

这样会导致一种 字符串“3”不等于真且不等于假的结果,所以不建议使用==和boolean之间做比较

逻辑运算符

逻辑与
使用 && 符号表示逻辑与,指符号两端都为 true 时表达式结果为true。

let a = true,b = true;
if (a && b) {
    console.log('表达式成立');
}

逻辑或
使用 || 符号表示逻辑或,指符号左右两端有一方为true,表达式即成立。

let a = true,b = false;
if (a || b) {
    console.log('表达式成立');
}

逻辑非
使用 ! 符号表示逻辑非,即原来是true转变为false,反之亦然。

let a = true,b = false;
if (a && !b) {
    console.log('表达式成立');
}

优先级
下列中因为 && 的优先级高所以结果是 true。

console.log(true || false && false);

可以使用 () 来提高优先级

console.log((true || false) && false);

流程控制

if

当条件为真时执行表达式代码块。

let state = true;
if (true) {
    console.log('表达式成立');
}

如果只有一条代码块,可以不用写 {}

let state = true;
if (true)
    console.log('表达式成立');
console.log('一直都显示的内容');

if(value)中参数value会自动调用Boolean的构造方法,js允许对象都放在if判断中并给出结果,但Boolean构造方法和value==true是有区别的

 function test(a) {
      if (a) {
        console.log("if(a) yse");
      } else {
        console.log("if(a) no");
      }
      console.log(Boolean(a));
      if (a == true) {
        console.log("if(a==true) yes");
      } else {
        console.log("if(a==true) no");
      }
      if (a == false) {
        console.log("if(a==false) yes");
      } else {
        console.log("if(a==false) no");
      }
    }
    test(2);
    // if(a) yse
    // true
    // if(a==true) no
    // if(a==false) yes

    test("abcd");
    // if(a) yse
    // true
    // if(a==true) no
    // if(a==false) no

if/else

下面是使用多条件判断密码强度的示例

<body>
  <input type="password" name="title" />
  <span></span>
</body>
<script>
  let input = document.querySelector("[name='title']");
  input.addEventListener("keyup", function() {
    let length = this.value.length;
    let msg;
    if (length > 10) {
      msg = "密码已经无敌了";
    } else if (length > 6) {
      msg = "密码安全性中级";
    } else {
      msg = "这密码,要完的节奏";
    }
    document.querySelector("span").innerHTML = msg;
  });
</script>

三元表达式

是针对 if 判断的简写形式。

let n = true ?'yes' : 'no';
console.log(n); //yes

let f = true ? (1 == true ? 'yes' : 'no') : 3;
console.log(f); // yes

switch

可以将 switch 理解为 if 的另一种结构清晰的写法。

如果表达式等于 case 中的值,将执行此 case 代码段
break 关键字会终止 switch 的执行
没有任何 case匹配时将执行default 代码块
如果case执行后缺少break则接着执行后面的语句

let name = '视频';
switch (name) {
    case '产品':
        console.log('hdcms.com');
        break;
    case '视频':
        console.log('houdunren.com');
        break;
    default:
        console.log('houdunwang.com')
}

case 合用示例

let error = 'warning';
switch (error) {
  case 'notice':
  case 'warning':
      console.log('警告或提示信息');
      break;
  case 'error':
      console.log('错误信息');
}switchcase 都可以使用表达式

function message(age) {
  switch (true) {
    case age < 15:
      console.log("儿童");
      break;
    case age < 25:
      console.log("青少年");
      break;
    case age < 40:
      console.log("青年");
      break;
    case age < 60:
      console.log("中年");
      break;
    case age < 100:
      console.log("老年");
      break;
    default:
      console.log("年龄输出错误");
  }
}
message(10);

下面例子缺少break 后,会接着执行后面的switch代码。

switch (1) {
  case 1:
    console.log(1);
  case 2:
    console.log(2);
  default:
    console.log("default");
}

while

循环执行语句,需要设置跳出循环的条件否则会陷入死循环状态。下面是循环输出表格的示例。

let row = 5;
document.write(``);while(row--!=0){
  document.write(``);}
document.write(`
${row}
`
);

do/while

function hd(row = 5) {
  let start = 0;
  do {
    let n = 0;
    do {
      document.write("*");
    } while (++n <= start);
    document.write("
"
); } while (++start <= row); } hd();

for

可以在循环前初始化初始计算变量。下面是使用for 打印倒三角的示例

for (let i = 10; i > 0; i--) {
    for (let n = 0; n < i; n++) {
        document.write('*');
    }
    document.write("
"
); }

for的三个参数可以都省略或取几个

let i = 1;
for (; i < 10; ) {
  console.log(i++);
}

break/continue

break用于退出当前循环,continue用于退出当前循环返回循环起始继续执行。

获取所有偶数,所有奇数使用 continue 跳过

for (let i = 1; i <= 10; i++) {
  if (i % 2) continue;
  console.log(i);
}

获取三个奇数,超过时使用 break退出循环

let count = 0,num = 3;
for (let i = 1; i <= 10; i++) {
  if (i % 2) {
    console.log(i);
    if (++count == num) break;
  }
}

label

标签(label) 为程序定义位置,可以使用continue/break跳到该位置。

下面取i+n 大于15时退出循环

houdunren: for (let i = 1; i <= 10; i++) {
  hdcms: for (let n = 1; n <= 10; n++) {
    if (n % 2 != 0) {
      continue hdcms;
    }
    console.log(i, n);
    if (i + n > 15) {
      break houdunren;
    }
  }
}

for/in

用于遍历对象的所有属性,for/in主要用于遍历对象,不建议用来遍历数组。

遍历数组操作

let hd = [
  { title: "第一章 走进JAVASCRIPT黑洞", lesson: 3 },
  { title: "ubuntu19.10 配置好用的编程工作站", lesson: 5 },
  { title: "媒体查询响应式布局", lesson: 8 }
];
document.write(`
  
`);for(let key in hd){
  document.write(`
  
  
  `);}
document.write("
标题课程数
${hd[key].title} ${hd[key].lesson}
"
);

遍历对象操作

let info = {
  name: "后盾人",
  url: "houdunren.com"
};
for (const key in info) {
  if (info.hasOwnProperty(key)) {
    console.log(info[key]);
  }
}

遍历window对象的所有属性

for (name in window) {
  console.log(window[name]);
}

for/of

用来遍历 Arrays(数组), Strings(字符串), Maps(映射), Sets(集合)等可迭代的数据结构。

与 for/in 不同的是 for/of 每次循环取其中的值而不是索引。

后面在讲到遍历器 章节后大家会对for/of有更深的体会

let arr = [1, 2, 3];
for (const iterator of arr) {
    console.log(iterator);
}

遍历字符串

let str = 'icebound';
for (const iterator of str) {
    console.log(iterator);
}

使用迭代特性遍历数组

const hd = ["whs", "icebound"];

for (const [key, value] of hd.entries()) {
  console.log(key, value); //这样就可以遍历了
}

使用for/of 也可以用来遍历DOM元素

<body>
  <ul>
    <li></li>
    <li></li>
  </ul>
</body>
<script>
  let lis = document.querySelectorAll("li");
  for (const li of lis) {
    li.addEventListener("click", function() {
      this.style.backgroundColor = "red";
    });
  }
</script>

基本类型

类型检测

JS提供了非常丰富的数据类型,开发者要学会使用最适合的数据类型处理业务 。

typeof
typeof 用于返回以下原始类型

  • 基本类型:number/string/boolean
  • function
  • object
  • undefined
    可以使用typeof用于判断数据的类型
let a = 1;
console.log(typeof a); //number

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

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

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

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

let d = { name: "icebound.com" };
console.log(typeof d); //object

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

也可以理解为是否为某个对象的实例,typeof不能区分数组,但instanceof则可以。

后面章节会详细介绍原型链

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

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

let d = { name: "icebound.com" };
console.log(d instanceof Object); //true

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

值类型与对象
下面是使用字面量与对象方法创建字符串,返回的是不同类型。

let hd = "icebound";
let cms = new String("whs"); 
console.log(typeof hd, typeof cms); //string object

只有对象才有方法使用,但在JS中也可以使用值类型调用方法,因为它会在执行时将值类型转为对象。

let hd = "icebound";
let cms = new String("whs");
console.log(hd.length); 
console.log(cms.length); 

String

字符串类型是使用非常多的数据类型,也是相对简单的数据类型。

声明定义
使用对象形式创建字符串

let hd = new String('icebound');
// 获取字符串
console.log(hd.toString());

字符串使用单、双引号包裹,单、双引号使用结果没有区别。

let content = whs";
console.log(content);

转义符号

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

let content = '后盾人 \'houduren.com\'';
console.log(content);

常用转义符号列表如下

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

连接运算符
使用 + 可以连接多个内容组合成字符串,经常用于组合输出内容使用。

模板字面量
使用 ` 符号包裹的字符串中可以写入引入变量与表达式

let url = 'whs';
console.log(`作者是是${url}`); //后盾人网址是houdunren.com

支持换行操作不会产生错误

let url = 'houdunren.com';
document.write(`后盾人网址是${url}
大家可以在网站上学习到很多技术知识`);

使用表达式

function show(title) {
	return `ES6`;
}
console.log(`JavaScript可使用模板字面量的版本是${show()}`)

模板字面量支持嵌套使用

let lessons = [
	{title: '媒体查询响应式布局'},{title: 'FLEX 弹性盒模型'},{title: 'GRID 栅格系统'}
];

function template() {
  return `
    ${lessons.map((item)=>` <li>${item.title} `).join('')} </ul>`; } document.body.innerHTML = template();

标签模板
标签模板是提取出普通字符串与变量,交由标签函数处理

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

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

下面例子将标题中有后盾人的使用标签模板加上链接

let lessons = [
  { title: "后盾人媒体查询响应式布局", author: "后盾人向军" },
  { title: "FLEX 弹性盒模型", author: "后盾人" },
  { title: "GRID 栅格系统后盾人教程", author: "古老师" }
];

function links(strings, ...vars) {
  return strings
    .map((str, key) => {
      return (
        str +
        (vars[key]
          ? vars[key].replace(
              "后盾人",
              `后盾人`
            )
          : "")
      ); 
    })
    .join("");
}

function template() {
  return `
    ${lessons .map(item => links`<li>${item.author}:${item.title}`) .join("")} </ul>`; } document.body.innerHTML += template();

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

console.log("icebound".length)

大小写转换
将字符转换成大写格式

console.log('icebound'.toUpperCase()); //ICEBOUND

转字符为小写格式

console.log('ICEBOUND'.toLowerCase()); //icebound

移除空白
使用trim删除字符串左右的空白字符

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

使用trimLeft删除左边空白,使用trimRight删除右边空白

let name = " whs ";
console.log(name);
console.log(name.trimLeft());
console.log(name.trimRight()); 

trim方法不会改变原字符串

let name = " whs ";
console.log("|"+name+"|");//| whs |
console.log("|"+name.trim()+"|");//|whs|
console.log("|"+name+"|"); //| whs |

获取单字符
根据从0开始的位置获取字符

console.log('houdunren'.charAt(3))

使用数字索引获取字符串

console.log('houdunren'[3])

截取字符串
使用 slice、substr、substring 函数都可以截取字符串。
slice、substring 第二个参数为截取的结束位置
substr 第二个参数指定获取字符数量

let hd = 'icebound';
console.log(hd.slice(3)); //bound
console.log(hd.substr(3)); //bound
console.log(hd.substring(3)); //bound

console.log(hd.slice(3, 6)); //bou
console.log(hd.substring(3, 6)); //bou
console.log(hd.substring(3, 0)); //ice 较小的做为起始位置
console.log(hd.substr(3, 6)); //bound

console.log(hd.slice(3, -1)); //boun 第二个为负数表示从后面算的字符
console.log(hd.slice(-2));//nd 从末尾取
console.log(hd.substring(3, -9)); //ice 负数转为0
console.log(hd.substr(-3, 2)); //un 从后面第三个开始取两个

查找字符串
从开始获取字符串位置,检测不到时返回 -1

console.log('icebound'.indexOf('c')); //1
console.log('icebound'.indexOf('d', 3)); //7 从第3个字符向后搜索

从结尾来搜索字符串位置

console.log('icebound.com'.lastIndexOf('o')); //10
console.log('icebound.com'.lastIndexOf('o', 7)); //4 从第7个字符向前搜索

search() 方法用于检索字符串中指定的子字符串,也可以使用正则表达式搜索

let str = "icebound.com";
console.log(str.search("com"));
console.log(str.search(/\.com/i));

includes 字符串中是否包含指定的值,第二个参数指查找开始位置

console.log('icebound.com'.includes('o')); //true
console.log('icebound.com'.includes('0', 7)); //true

startsWith 是否是指定位置开始,第二个参数为查找的开始位置。

console.log('icebound.com'.startsWith('o')); //true
console.log('icebound.com'.startsWith('o', 1)); //true

endsWith 是否是指定位置结束,第二个参数为查找的结束位置。

console.log('icebound.com'.endsWith('com')); //true
console.log('icebound.com'.endsWith('o', 2)); //true

下面是查找关键词的示例

const words = ["js", "css"];
const title = "js es6";
const status = words.some(word => {
  return title.includes(word);
});
console.log(status);

替换字符串
replace 方法用于字符串的替换操作

let name = "icebound";
web = name.replace("ice", "whs");
console.log(web);

默认只替换一次,如果全局替换需要使用正则

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

使用字符串替换来生成关键词链接

const word = ["js", "css"];
const string = "学习js与css知识";
const title = word.reduce((pre, word) => {
  return pre.replace(word, `${word}">${word}`);
}, string);
document.body.innerHTML += title;

使用正则表达式完成替换

let res = "icebound.com".replace(/u/g, str => {
  return "@";
});
console.log(res);

重复生成
下例是根据参数重复生成星号

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

下面是模糊后三位电话号码

let phone = "98765432101";
console.log(phone.slice(0, -3) + "*".repeat(3));

类型转换

将字符串转换为数组

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

隐式类型转换会根据类型自动转换类型

let age = 99 + '';
console.log(typeof age ); //string

使用 String 构造函数可以显示转换字符串类型

let age = 99;
console.log(typeof String(age));

js中大部分类型都是对象,可以使用类方法 toString转化为字符串

let age = 99;
console.log(typeof age.toString()); //string


let arr = ['js', 'css'];
console.log(typeof arr.toString()); //string

Boolean

布尔类型包括 true 与 false 两个值,开发中使用较多的数据类型。

声明定义

使用对象形式创建布尔类型

console.log(new Boolean(true)); //true
console.log(new Boolean(false)); //false

但建议使用字面量创建布尔类型

let hd =true;

隐式转换

基本上所有类型都可以隐式转换为 Boolean类型。

数据类型 true false
String 非空字符串 空字符串
Number 非0的数值 0 、NaN
Array 数组不参与比较时 参与比较的空数组
Object 所有对象
undefined undefined
null null
NaN NaN

当与boolean类型比较时,会将两边类型统一为数字1或0。

如果使用Boolean与数值比较时,会进行隐式类型转换 true转为1,false 转为0。

console.log(3 == true); //false
console.log(0 == false); //true

下面是一个典型的例子,字符串在与Boolean比较时,两边都为转换为数值类型后再进行比较。

console.log(Number("icebound")); //NaN
console.log(Boolean("icebound")); //true
console.log("icebound" == true); //false
console.log("1" == true); //true

数组的表现与字符串原理一样,会先转换为数值

console.log(Number([])); //0
console.log(Number([3])); //3
console.log(Number([1, 2, 3])); //NaN
console.log([] == false); //true
console.log([1] == true); //true
console.log([1, 2, 3] == true); //false

引用类型的Boolean值为真,如对象和数组

if ([]) console.log("true");
if ({}) console.log("true");

显式转换

使用 !! 转换布尔类型

let ice = '';
console.log(!!ice ); //false
ice = 0;
console.log(!!ice ); //false
ice = null;
console.log(!!ice ); //false
ice = new Date("2020-2-22 10:33");
console.log(!!ice ); //true

使用 Boolean 函数可以显式转换为布尔类型

let whs = '';
console.log(Boolean(whs )); //false
whs = 0;
console.log(Boolean(whs )); //false
whs = null;
console.log(Boolean(whs )); //false
whs = new Date("2020-2-22 10:33");
console.log(Boolean(hdwhs ); //true

实例操作

下面使用Boolean类型判断用户的输入,并给出不同的反馈。

while (true) {
  let n = prompt("请输入后盾人成立年份").trim();
  if (!n) continue;
  alert(n == 2010 ? "回答正确" : "答案错误!看看官网了解下");
  break;
}

Number

声明定义

使用对象方式声明

let hd = new Number(3);
console.log(hd+3); //6

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

let num = 99;
console.log(typeof num);

基本函数

判断是否为整数

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

指定返回的小数位数可以四舍五入

console.log((16.556).toFixed(2)); // 16.56

NaN

表示无效的数值,下例计算将产生NaN结果。

console.log(Number("icebound")); //NaN

console.log(2 / 'icebound'); //NaN

NaN不能使用 == 比较,使用以下代码来判断结果是否正确

var res = 2 / 'icebound';
if (Number.isNaN(res)) {
	console.log('Error');
}

也可以使用 Object.is 方法判断两个值是否完全相同

var res = 2 / 'icebound';
console.log(Object.is(res, NaN));

类型转换

Number

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

console.log(Number('houdunren')); //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

parseInt

提取字符串开始去除空白后的数字转为整数。

console.log(parseInt('  99houdunren'));	//99
console.log(parseInt('18.55'));	//18

parseFloat

转换字符串为浮点数,忽略字符串前面空白字符。

console.log(parseFloat('  99houdunren'));	//99
console.log(parseFloat('18.55'));	//18.55

比如从表单获取的数字是字符串类型需要类型转换才可以计算,下面使用乘法进行隐式类型转换。

<input type="text" name="num" value="66">
<script>
  let num = document.querySelector("[name='num']").value;
  console.log(num + 5); //665

  console.log(num * 1 + 5); //71
</script>

舍入操作
使用 toFixed 可对数值舍入操作,参数指定保存的小数位

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

浮点精度
大部分编程语言在浮点数计算时都会有精度误差问题,下面来看JS中的表现形式

let hd = 0.1 + 0.2
console.log(hd)// 结果:0.30000000000000004

这是因为计算机以二进制处理数值类型,上面的0.1与0.2转为二进制后是无穷的

console.log((0.1).toString(2)) //0.0001100110011001100110011001100110011001100110011001101
console.log((0.2).toString(2)) //0.001100110011001100110011001100110011001100110011001101

处理方式

一种方式使用toFixed 方法进行小数截取

console.log((0.1 + 0.2).toFixed(2)) //0.3
console.log(1.0 - 0.9) //0.09999999999999998
console.log((1.0 - 0.9).toFixed(2)) //0.10

将小数转为整数进行计算后,再转为小数也可以解决精度问题

Number.prototype.add = function (num) {
	//取两个数值中小数位最大的
  let n1 = this.toString().split('.')[1].length
  let n2 = num.toString().split('.')[1].length
  
  //得到10的N次幂
  let m = Math.pow(10, Math.max(n1, n2))

  return (this * m + num * m) / m
}
console.log((0.1).add(0.2))

推荐做法

市面上已经存在很多针对数学计算的库 mathjs 、decimal.js 等,我们就不需要自己构建了。下面来演示使用 decimal.js 进行浮点计算。

<script src="https://cdn.bootcss.com/decimal.js/10.2.0/decimal.min.js"></script>

<script>
	console.log(Decimal.add(0.1, 0.2).valueOf())
</script>

Math

Math 对象提供了众多方法用来进行数学计算,下面我们介绍常用的方法,更多方法使用请查看 MDN官网 了解。

取极限值
使用 min 与 max 可以取得最小与最大值。

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

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

使用apply 来从数组中取值

console.log(Math.max.apply(Math, [1, 2, 3]));

舍入处理
取最接近的向上整数

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

得到最接近的向下整数

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

四舍五入处理

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

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

返回0~5的随机数,不包括5

const number = Math.floor(Math.random() * 5);
console.log(number);

返回0~5的随机数,包括5

const number = Math.floor(Math.random() * (5+1));
console.log(number);

下面取2~5的随机数(不包括5)公式为:min+Math.floor(Math.random()*(Max-min))

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

下面取2~5的随机数(包括5)公式为:min+Math.floor(Math.random()*(Max-min+1))

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

下面是随机点名的示例

let stus = ['小明', '张三', '王五', '爱情'];
let pos = Math.floor(Math.random() * stus.length);
console.log(stus[pos]);

随机取第二到第三间的学生,即1~2的值

let stus = ['小明', '张三', '王五', '爱情'];
let pos = Math.floor(Math.random() * (3-1)) + 1;
console.log(stus[pos]);

Date

网站中处理日期时间是很常用的功能,通过 Date 类型提供的丰富功能可以非常方便的操作。

声明日期

获取当前日期时间

let now = new Date();
console.log(now);
console.log(typeof date); //object
console.log(now * 1); //获取时间戳


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

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

计算脚本执行时间

const start = Date.now();
for (let i = 0; i < 2000000; i++) {}
const end = Date.now();
console.log(end - start);

当然也可以使用控制台测试

console.time("testFor");
for (let i = 0; i < 20000000; i++) {}
console.timeEnd("testFor");

根据指定的日期与时间定义日期对象

let now = new Date('2028-02-22 03:25:02');
console.log(now);

now = new Date(2028, 4, 5, 1, 22, 16);
console.log(now);

使用展示运算符处理更方便

let info = [2020, 2, 20, 10, 15, 32];
let date = new Date(...info);
console.dir(date);

#类型转换
将日期转为数值类型就是转为时间戳单位是毫秒

let hd = new Date("2020-2-22 10:33:12");
console.log(hd * 1);

console.log(Number(hd));

console.log(hd.valueOf())

console.log(date.getTime());

有时后台提供的日期为时间戳格式,下面是将时间戳转换为标准日期的方法

const param = [1990, 2, 22, 13, 22, 19];
const date = new Date(...param);
const timestamp = date.getTime();
console.log(timestamp);
console.log(new Date(timestamp));

对象方法

格式化输出日期

let time = new Date();
console.log(
  `${time.getFullYear()}-${time.getMonth()}-${time.getDate()} ${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}`
);

封装函数用于复用

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日"));

下面是系统提供的日期时间方法,更多方法请查看 MDN官网

方法 描述
Date() 返回当日的日期和时间。
getDate() 从 Date 对象返回一个月中的某一天 (1 ~ 31)。
getDay() 从 Date 对象返回一周中的某一天 (0 ~ 6)。
getMonth() 从 Date 对象返回月份 (0 ~ 11)。
getFullYear() 从 Date 对象以四位数字返回年份。
getYear() 请使用 getFullYear() 方法代替。
getHours() 返回 Date 对象的小时 (0 ~ 23)。
getMinutes() 返回 Date 对象的分钟 (0 ~ 59)。
getSeconds() 返回 Date 对象的秒数 (0 ~ 59)。
getMilliseconds() 返回 Date 对象的毫秒(0 ~ 999)。
getTime() 返回 1970 年 1 月 1 日至今的毫秒数。
getTimezoneOffset() 返回本地时间与格林威治标准时间 (GMT) 的分钟差。
getUTCDate() 根据世界时从 Date 对象返回月中的一天 (1 ~ 31)。
getUTCDay() 根据世界时从 Date 对象返回周中的一天 (0 ~ 6)。
getUTCMonth() 根据世界时从 Date 对象返回月份 (0 ~ 11)。
getUTCFullYear() 根据世界时从 Date 对象返回四位数的年份。
getUTCHours() 根据世界时返回 Date 对象的小时 (0 ~ 23)。
getUTCMinutes() 根据世界时返回 Date 对象的分钟 (0 ~ 59)。
getUTCSeconds() 根据世界时返回 Date 对象的秒钟 (0 ~ 59)。
getUTCMilliseconds() 根据世界时返回 Date 对象的毫秒(0 ~ 999)。
parse() 返回1970年1月1日午夜到指定日期(字符串)的毫秒数。
setDate() 设置 Date 对象中月的某一天 (1 ~ 31)。
setMonth() 设置 Date 对象中月份 (0 ~ 11)。
setFullYear() 设置 Date 对象中的年份(四位数字)。
setYear() 请使用 setFullYear() 方法代替。
setHours() 设置 Date 对象中的小时 (0 ~ 23)。
setMinutes() 设置 Date 对象中的分钟 (0 ~ 59)。
setSeconds() 设置 Date 对象中的秒钟 (0 ~ 59)。
setMilliseconds() 设置 Date 对象中的毫秒 (0 ~ 999)。
setTime() 以毫秒设置 Date 对象。
setUTCDate() 根据世界时设置 Date 对象中月份的一天 (1 ~ 31)。
setUTCMonth() 根据世界时设置 Date 对象中的月份 (0 ~ 11)。
setUTCFullYear() 根据世界时设置 Date 对象中的年份(四位数字)。
setUTCHours() 根据世界时设置 Date 对象中的小时 (0 ~ 23)。
setUTCMinutes() 根据世界时设置 Date 对象中的分钟 (0 ~ 59)。
setUTCSeconds() 根据世界时设置 Date 对象中的秒钟 (0 ~ 59)。
setUTCMilliseconds() 根据世界时设置 Date 对象中的毫秒 (0 ~ 999)。
toSource() 返回该对象的源代码。
toString() 把 Date 对象转换为字符串。
toTimeString() 把 Date 对象的时间部分转换为字符串。
toDateString() 把 Date 对象的日期部分转换为字符串。
toGMTString() 请使用 toUTCString() 方法代替。
toUTCString() 根据世界时,把 Date 对象转换为字符串。
toLocaleString() 根据本地时间格式,把 Date 对象转换为字符串。
toLocaleTimeString() 根据本地时间格式,把 Date 对象的时间部分转换为字符串。
toLocaleDateString() 根据本地时间格式,把 Date 对象的日期部分转换为字符串。
UTC() 根据世界时返回 1970 年 1 月 1 日 到指定日期的毫秒数。
valueOf() 返回 Date 对象的原始值。

moment.js

Moment.js是一个轻量级的JavaScript时间库,它方便了日常开发中对时间的操作,提高了开发效率。

更多使用方法请访问中文官网 http://momentjs.cn 或 英文官网 https://momentjs.com

<script src="https://cdn.bootcss.com/moment.js/2.24.0/moment.min.js"></script>

获取当前时间

console.log(moment().format("YYYY-MM-DD HH:mm:ss"));

数组

声明数组

数组是多个变量值的集合,数组是Array 对象的实例,所以可以像对象一样调用方法。

创建数组

使用对象方式创建数组

console.log(new Array(1, 'icebound', 'whs')); //[1, "icebound", "whs"]

使用字面量创建是推荐的简单作法

const array = ["whs", "icebound"];

多维数组定义

const array = [["whs"], ["icebound"]];
console.log(array[1][0]);

数组是引用类型可以使用const声明并修改它的值

const array = ["whs", "icebound"];
array.push("wang");
console.log(array);

使用原型的 length属性可以获取数组元素数量

let info = ["icebound", "whs"];
console.log(info.length); //2

数组可以设置任何值,下面是使用索引添加数组

let info = ["icebound"];
info[1] = "whs";

下面直接设置3号数组,会将1/2索引的数组定义为空值

let info = ["icebound"];
info[3] = "whs";
console.log(info.length); //4

声明多个空元素的数组

let info = new Array(3);
console.log(info.length);
console.log(info);

Array.of

使用Array.of 与 new Array 不同是设置一个参数时不会创建空元素数组

let info = Array.of(3);
console.log(info); //[3]

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

类型检测

检测变量是否为数组类型

console.log(Array.isArray([1, "icebound", "whs"])); //true
console.log(Array.isArray(9)); //false

类型转换

可以将数组转换为字符串也可以将其他类型转换为数组。

字符串

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

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

也可以使用函数 String 转换为字符串。

console.log(String([1, 2, 3]));

或使用join连接为字符串

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

Array.from

使用Array.from可将类数组转换为数组,类数组指包含 length 属性或可迭代的对象。

第一个参数为要转换的数据,第二个参数为类似于map 函数的回调方法
let str = ‘whs’;
console.log(Array.from(str)); //[“w”, “h”, “s”]

为对象设置length属性后也可以转换为数组,但要下标为数值或数值字符串

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

DOM元素转换为数组后来使用数组函数,第二个参数类似于map 函数的方法,可对数组元素执行函数处理。

<body>
    <button message="icebound">button</button>
    <button message="whs">button</button>
</body>

<script>
    let btns = document.querySelectorAll('button');
    console.log(btns); //包含length属性
    Array.from(btns, (item) => {
        item.style.background = 'red';
    });
</script>

使用展开语法将 NodeList 转换为数组操作

<style>
    .hide {
      display: none;
    }
</style>

<body>
  <div>whs</div>
  <div>icebound</div>
</body>

<script>
  let divs = document.querySelectorAll("div");
  [...divs].map(function(div) {
    div.addEventListener("click", function() {
      this.classList.toggle("hide");
    });
  });
</script>

展开语法

数组合并

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

let a = [1, 2, 3];
let b = ['a', 'icebound', ...a];
console.log(b); //["a", "icebound", 1, 2, 3]

函数参数

使用展示语法可以替代 arguments 来接收任意数量的参数

function info(...args) {
  console.log(args);
}
info(1, 2, 3, "icebound"); //[1, 2, 3, "icebound"]

也可以用于接收部分参数

function info(site, ...args) {
  console.log(site, args); //icebound (3) [1, 2, 3]
}
info("icebound", 1, 2, 3);

节点转换

可以将DOM节点转为数组,下面例子不可以使用 filter 因为是节点列表

<body>
    <button message="icebound">button</button>
    <button message="whs">button</button>
</body>

<script>
    let btns = document.querySelectorAll('button');
    btns.map((item) => {
        console.log(item); //TypeError: btns.filter is not a function
    })
</script>

使用展开语法后就可以使用数据方法

<body>
  <div>whs</div>
  <div>icebound</div>
</body>

<script>
  let divs = document.querySelectorAll("div");
  [...divs].map(function(div) {
    div.addEventListener("click", function() {
      this.classList.toggle("hide");
    });
  });
</script>

解构赋值

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

建设使用 var/let/const 声明

基本使用

下面是基本使用语法

//数组使用
let [name, url] = ['icebound', 'icebound.com'];
console.log(name);

解构赋值数组

function info() {
	return ['icebound', 'whs'];
}
let [a, b] = info();
console.log(a); //icebound

剩余解构指用一个变量来接收剩余参数

let [a, ...b] = ['icebound', 'icebound', 'whs'];
console.log(b);

如果变量已经初始化过,就要使用() 定义赋值表达式,严格模式会报错所以不建议使用。

let web = "icebound";
[web, url] = ["whs.com", "icebound.com"];
console.log(web);

字符串解构

"use strict";
const [...a] = "icebound";
console.log(a); //Array(13)

严格模式

非严格模式可以不使用声明指令,严格模式下必须使用声明。所以建议使用 let 等声明。

"use strict";

[web, url] = ["whs.com", "icebound.com"];
console.log(web);

简洁定义

只赋值部分变量

let [,url]=['icebound','icebound.com'];
console.log(url);//icebound.com

使用展开语法获取多个值

let [name, ...arr] = ['icebound', 'whs', 'icebound.com'];
console.log(name, arr); //icebound (2) ["whs", "icebound.com"]

默认值

为变量设置默认值

let [name, site = 'whs'] = ['icebound'];
console.log(site); //whs

函数参数

数组参数的使用

function info([a, b]) {
	console.log(a, b);
}
info(['icebound', 'whs']);

管理元素

基本使用

使用从0开始的索引来改变数组

let arr = [1, "icebound", "whs"];
arr[1] = 'icebound教程';
console.log(arr); //[1, "icebound教程", "whs"]

向数组追加元素

let arr = [1, "icebound", "whs"];
arr[arr.length] = 'icebound.com';
console.log(arr); //[1, "icebound", "whs", "icebound.com"]

扩展语法

push

压入元素,直接改变元数组,返回值为数组元素数量

let arr = ["icebound", "whs"];
console.log(arr.push('css', 'js')); //4
console.log(arr); //["icebound", "whs", "向军css", "js"]

使用展示语法批量添加元素

let arr = ["icebound", "whs"];
let info = ["icebound"];
info.push(...arr);
console.log(info); //["icebound", "icebound", "whs"]

根据区间创建新数组

function rangeArray(begin, end) {
  const array = [];
  for (let i = begin; i <= end; i++) {
    array.push(i);
  }
  return array;
}
console.log(rangeArray(1, 6));

pop

从末尾弹出元素,直接改变元数组,返回值为弹出的元素

let arr = ["icebound", "whs"];
console.log(arr.pop()); //whs
console.log(arr); //["icebound"]

shift

从数组前面取出一个元素

let arr = ["icebound", "whs"];
console.log(arr.shift()); //icebound
console.log(arr); //["whs"]

unshift

从数组前面添加元素

let arr = ["icebound", "whs"];
console.log(arr.unshift('向军大叔', 'icebound')); //4
console.log(arr); //["向军大叔", "icebound", "icebound", "whs"]

fill

使用fill 填充数组元素

console.dir(Array(4).fill("icebound")); //["icebound", "icebound", "icebound", "icebound"]

指定填充位置

console.log([1, 2, 3, 4].fill("icebound", 1, 2)); //[1, "icebound", 3, 4]

slice

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

let arr = [0, 1, 2, 3, 4, 5, 6];
console.log(arr.slice(1, 3)); // [1,2]

不设置参数是为获取所有元素

let arr = [0, 1, 2, 3, 4, 5, 6];
console.log(arr.slice()); //[0, 1, 2, 3, 4, 5, 6]

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 = ["icebound", "whs"];
arr.length = arr.length - 1;
console.log(arr);

通过指定第三个参数来设置在删除位置添加的元素

let arr = [0, 1, 2, 3, 4, 5, 6];
console.log(arr.splice(1, 3, 'whs', 'icebound')); //[1, 2, 3]
console.log(arr); //[0, "whs", "icebound", 4, 5, 6]

向末尾添加元素

let arr = [0, 1, 2, 3, 4, 5, 6];
console.log(arr.splice(arr.length, 0, 'whs', 'icebound')); //[]
console.log(arr); // [0, 1, 2, 3, 4, 5, 6, "whs", "icebound"]

向数组前添加元素

let arr = [0, 1, 2, 3, 4, 5, 6];
console.log(arr.splice(0, 0, 'whs', 'icebound')); //[]
console.log(arr); //["whs", "icebound", 0, 1, 2, 3, 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));

清空数组

将数组值修改为[]可以清空数组,如果有多个引用时数组在内存中存在被其他变量引用。

let user = [{ name: "whs" }, { name: "icebound" }];
let cms = user;
user = [];
console.log(user);
console.log(cms);

将数组length设置为0也可以清空数组

let user = [{ name: "whs" }, { name: "icebound" }];
user.length = 0;
console.log(user);

使用splice方法删除所有数组元素

let user = [{ name: "whs" }, { name: "icebound" }];
user.splice(0, user.length);
console.log(user);

使用pop/shift删除所有元素,来清空数组

let user = [{ name: "whs" }, { name: "icebound" }];
while (user.pop()) {}
console.log(user);

合并拆分

join

使用join连接成字符串

let arr = [1, "icebound", "whs"];
console.log(arr.join('-')); //1-icebound-whs 使用join可以指定转换的连接方式

split

split 方法用于将字符串分割成数组,类似join方法的反函数。

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

concat

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

let array = ["whs", "icebound"];
let info = [1, 2];
let cms = [3, 4];
console.log(array.concat(info, cms)); //["whs", "icebound", 1, 2, 3, 4]

也可以使用扩展语法实现连接

console.log([...array, ...info, ...cms]);

copyWithin

使用 copyWithin 从数组中复制一部分到同数组中的另外位置。

语法说明

array.copyWithin(target, start, end)

参数说明

参数 描述
target 必需。复制到指定目标索引位置。
start 可选。元素复制的起始位置。
end 可选。停止复制的索引位置 (默认为 array.length)。如果为负值,表示倒数。
const arr = [1, 2, 3, 4];
console.log(arr.copyWithin(2, 0, 2)); //[1, 2, 1, 2]

查找元素

数组包含多种查找的函数,需要把这些函数掌握清楚,然后根据不同场景选择合适的函数。

indexOf

使用 indexOf 从前向后查找元素出现的位置,如果找不到返回 -1。

let arr = [7, 3, 2, 8, 2, 6];
console.log(arr.indexOf(2)); // 2 从前面查找2出现的位置

如下面代码一下,使用 indexOf 查找字符串将找不到,因为indexOf 类似于===是严格类型约束。

let arr = [7, 3, 2, '8', 2, 6];
console.log(arr.indexOf(8)); // -1

第二个参数用于指定查找开始位置

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

lastIndexOf

使用 lastIndexOf 从后向前查找元素出现的位置,如果找不到返回 -1。

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

第二个参数用于指定查找开始位置

let arr = [7, 3, 2, 8, 2, 6];
//从第五个元素向前查找
console.log(arr.lastIndexOf(2, 5));

//从最后一个字符向前查找
console.log(arr.lastIndexOf(2, -2));

includes

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

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

我们来实现一个自已经的includes函数,来加深对includes方法的了解

function includes(array, item) {
  for (const value of array)
    if (item === value) return true;
  return false;
}

console.log(includes([1, 2, 3, 4], 3)); //true

find

find 方法找到后会把值返回出来

如果找不到返回值为undefined
返回第一次找到的值,不继续查找

let arr = ["whs", "icebound", "whs"];

let find = arr.find(function(item) {
  return item == "whs";
});

console.log(find); //whs

使用includes等不能查找引用类型,因为它们的内存地址是不相等的

const user = [{ name: "李四" }, { name: "张三" }, { name: "icebound" }];
const find = user.includes({ name: "icebound" });
console.log(find);

find 可以方便的查找引用类型

const user = [{ name: "李四" }, { name: "张三" }, { name: "icebound" }];
const find = user.find(user => (user.name = "icebound"));
console.log(find);

#findIndex
findIndex 与 find 的区别是返回索引值,参数也是 : 当前值,索引,操作数组。

查找不到时返回 -1

let arr = [7, 3, 2, '8', 2, 6];

console.log(arr.findIndex(function (v) {
	return v == 8;
})); //3

find原理

下面使用自定义函数

let arr = [1, 2, 3, 4, 5];
function find(array, callback) {
  for (const value of array) {
    if (callback(value) === true) return value;
  }
  return undefined;
}
let res = find(arr, function(item) {
  return item == 23;
});
console.log(res);

下面添加原型方法实现

Array.prototype.findValue = function(callback) {
  for (const value of this) {
    if (callback(value) === true) return value;
  }
  return undefined;
};

let re = arr.findValue(function(item) {
  return item == 2;
});
console.log(re);

数组排序

everse

反转数组顺序

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]

使用排序函数从大到小排序,参数一与参数二比较,返回正数为降序负数为升序

let arr = [1, 4, 2, 9];

console.log(arr.sort(function (v1, v2) {
	return v2 - v1;
})); //[9, 4, 2, 1]

下面是按课程点击数由高到低排序

let lessons = [
  { title: "js", click: 78 },
  { title: "css", click: 12 },
  { title: "java", click: 99 }
];

let sortLessons = lessons.sort((v1, v2) => v2.click - v1.click);
console.log(sortLessons);

循环遍历

参见js中数组循环遍历

for

根据数组长度结合for 循环来遍历数组

let lessons = [
	{title: '媒体查询响应式布局',category: 'css'},
  {title: 'FLEX 弹性盒模型',category: 'css'},
	{title: 'MYSQL多表查询随意操作',category: 'mysql'}
];

for (let i = 0; i < lessons.length; i++) {
  lessons[i] = `icebound: ${lessons[i].title}`;
}
console.log(lessons);

forEach

forEach使函数作用在每个数组元素上,但是没有返回值。

下面例子是截取标签的五个字符。

let lessons = [
	{title: '媒体查询响应式布局',category: 'css'},
  {title: 'FLEX 弹性盒模型',category: 'css'},
	{title: 'MYSQL多表查询随意操作',category: 'mysql'}
];

lessons.forEach((item, index, array) => {
    item.title = item.title.substr(0, 5);
});
console.log(lessons);

for/in

遍历时的 key 值为数组的索引

let lessons = [
	{title: '媒体查询响应式布局',category: 'css'},
  {title: 'FLEX 弹性盒模型',category: 'css'},
	{title: 'MYSQL多表查询随意操作',category: 'mysql'}
];

for (const key in lessons) {
    console.log(`标题: ${lessons[key].title}`);
}

for/of

与 for/in 不同的是 for/of 每次循环取其中的值而不是索引。

let lessons = [
	{title: '媒体查询响应式布局',category: 'css'},
  {title: 'FLEX 弹性盒模型',category: 'css'},
	{title: 'MYSQL多表查询随意操作',category: 'mysql'}
];

for (const item of lessons) {
  console.log(`
    标题: ${item.title}
    栏目: ${item.category == "css" ? "前端" : "数据库"}
  `);
}

使用数组的迭代对象遍历获取索引与值(有关迭代器知识后面章节会讲到)

const info = ['icebound', 'whs'];
const iterator = info.entries();
console.log(iterator.next()); //value:{0:0,1:'icebound'}
console.log(iterator.next()); //value:{0:1,1:'whs'}

这样就可以使用解构特性与 for/of 遍历并获取索引与值了

const info = ["whs", "icebound"];

for (const [key, value] of info.entries()) {
  console.log(key, value); //这样就可以遍历了
}

取数组中的最大值

function arrayMax(array) {
  let max = array[0];
  for (const elem of array) {
    max = max > elem ? max : elem;
  }
  return max;
}
console.log(arrayMax([1, 3, 2, 9]));

keys

通过迭代对象获取索引

const info = ["icebound", "whs"];
const keys = info.keys();
console.log(keys.next());
console.log(keys.next());

获取数组所有键

"use strict";
const arr = ["a", "b", "c", "icebound"];

for (const key of arr.keys()) {
  console.log(key);
}

使用while遍历

let arr = ["whs", "icebound"];
while (({ value, done } = values.keys()) && done === false) {
	console.log(value);
}

values

通过迭代对象获取值

const info = ["icebound", "whs"];
const values = info.values();
console.log(values.next());
console.log(values.next());
console.log(values.next());

获取数组的所有值

"use strict";
const arr = ["a", "b", "c", "icebound"];

for (const value of arr.values()) {
  console.log(value);
}

entries

返回数组所有键值对,下面使用解构语法循环

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

解构获取内容

const info = ["icebound", "whs"];
const iterator = info.entries();

let {done,value: [k, v]} = iterator.next();

console.log(v);

扩展方法

every

every 用于递归的检测元素,要所有元素操作都要返回真结果才为真。

查看班级中同学的JS成绩是否都及格

const user = [
  { name: "李四", js: 89 },
  { name: "马六", js: 55 },
  { name: "张三", js: 78 }
];
const resust = user.every(user => user.js >= 60);
console.log(resust);

标题的关键词检查

let words = ['后盾', '北京', '培训'];
let title = 'icebound不断分享技术教程';

let state = words.every(function (item, index, array) {
  return title.indexOf(item) >= 0;
});

if (state == false) console.log('标题必须包含所有关键词');

some

使用 some 函数可以递归的检测元素,如果有一个返回true,表达式结果就是真。第一个参数为元素,第二个参数为索引,第三个参数为原数组。

下面是使用 some 检测规则关键词的示例,如果匹配到一个词就提示违规。

le

t words = ['后盾', '北京', '武汉'];
let title = 'icebound不断分享技术教程'

let state = words.some(function (item, index, array) {
	return title.indexOf(item) >= 0;
});

if (state) console.log('标题含有违规关键词');

filter

使用 filter 可以过滤数据中元素,下面是获取所有在CSS栏目的课程。

let lessons = [
  {title: '媒体查询响应式布局',category: 'css'},
  {title: 'FLEX 弹性盒模型',category: 'css'},
  {title: 'MYSQL多表查询随意操作',category: 'mysql'}
];

let cssLessons = lessons.filter(function (item, index, array) {
  if (item.category.toLowerCase() == 'css') {
    return true;
  }
});

console.log(cssLessons);

我们来写一个过滤元素的方法来加深些技术

function except(array, excepts) {
  const newArray = [];
  for (const elem of array)
    if (!excepts.includes(elem)) newArray.push(elem);
  return newArray;
}

const array = [1, 2, 3, 4];
console.log(except(array, [2, 3])); //[1,4]

map

使用 map 映射可以在数组的所有元素上应用函数,用于映射出新的值。

获取数组所有标题组合的新数组

let lessons = [
  {title: '媒体查询响应式布局',category: 'css'},
  {title: 'FLEX 弹性盒模型',category: 'css'},
  {title: 'MYSQL多表查询随意操作',category: 'mysql'}
];

console.log(lessons.map(item => item.title));

为所有标题添加上 icebound

let lessons = [
  {title: '媒体查询响应式布局',category: 'css'},
  {title: 'FLEX 弹性盒模型',category: 'css'},
  {title: 'MYSQL多表查询随意操作',category: 'mysql'}
];

lessons = lessons.map(function (item, index, array) {
    item.title = `[icebound] ${item['title']}`;
    return item;
});
console.log(lessons);

reduce

使用 reduce 与 reduceRight 函数可以迭代数组的所有元素,reduce 从前开始 reduceRight 从后面开始。下面通过函数计算课程点击数的和。

第一个参数是执行函数,第二个参数为初始值

传入第二个参数时将所有元素循环一遍
不传第二个参数时从第二个元素开始循环
函数参数说明如下

参数 说明
prev 上次调用回调函数返回的结果
cur 当前的元素值
index 当前的索引
array 原数组

统计元素出现的次数

function countArrayELem(array, elem) {
  return array.reduce((total, cur) => (total += cur == elem ? 1 : 0), 0);
}

let numbers = [1, 2, 3, 1, 5];
console.log(countArrayELem(numbers, 1)); //2

取数组中的最大值

function arrayMax(array) {
  return array.reduce(
  	(max, elem) => (max > elem ? max : elem), array[0]
  );
}

console.log(arrayMax([1, 3, 2, 9]));

取价格最高的财产

let cart = [
  { name: "iphone", price: 12000 },
  { name: "house", price: 2500000 },
  { name: "car", price: 36000 }
];

function maxPrice(array) {
  return array.reduce(
    (item, elem) => (item.price > elem.price ? item: elem),
    array[0]
  );
}
console.log(maxPrice(cart));

计算最贵的财产

let cart = [
  { name: "iphone", price: 12000 },
  { name: "house", price: 2500000 },
  { name: "car", price: 36000 }
];

const total = cart.reduce(
	(total, item) => total += item.price, 0
);
console.log(total); //40600

获取价格超过10万的财产

let goods = [
   { name: "iphone", price: 12000 },
  { name: "house", price: 2500000 },
  { name: "car", price: 36000 }
];

function getNameByPrice(array, price) {
  return array.reduce((item, elem) => {
    if (elem.price > price) {
      item.push(elem);
    }
    return item;
  }, []).map(elem => elem.name);
}
console.table(getNameByPrice(item, 100000));

使用 reduce 实现数组去重

let arr = [1, 2, 6, 2, 1];
let filterArr = arr.reduce((pre, cur, index, array) => {
  if (pre.includes(cur) === false) {
      pre = [...pre, cur];
  }
  return pre;
}, [])
console.log(filterArr); // [1,2,6]

函数

基础知识

声明定义

在JS中函数也是对象函数是Function类的创建的实例,下面的例子可以方便理解函数是对象。

let test = new Function("title", "console.log(title)");
test('whs');

标准语法是使用函数声明来定义函数

function test(num) {
	return ++num;
}
console.log(test(3));

对象字面量属性函数简写

let user = {
  name: null,
  getName: function (name) {
  	return this.name;
  },
  //简写形式
  setName(value) {
  	this.name = value;
  }
}
user.setName('whs');
console.log(user.getName()); // whs

全局函数会声明在window对象中,这不正确建议使用后面章节的模块处理

console.log(window.screenX); //2200

当我们定义了 screenX 函数后就覆盖了window.screenX方法

function screenX() {
  return "whs";
}
console.log(screenX()); //whs

使用let/const时不会压入window

let test = function() {
  console.log("whs");
};
window.test(); //window.test is not a function

匿名函数

函数是对象所以可以通过赋值来指向到函数对象的指针,当然指针也可以传递给其他变量,注意后面要以;结束。下面使用函数表达式将 匿名函数 赋值给变量

let test = function(num) {
  return ++num;
};

console.log(test instanceof Object); //true

let cms = test;
console.log(cms(3));

标准声明的函数优先级更高,解析器会优先提取函数并放在代码树顶端,所以标准声明函数位置不限制,所以下面的代码可以正常执行。

console.log(test(3));
function test(num) {
	return ++num;
};

标准声明优先级高于赋值声明

console.log(test(3)); //4

function test(num) {
  return ++num;
}

var test = function() {
  return "test";
};

程序中使用匿名函数的情况非常普遍

function sum(...args) {
  return args.reduce((a, b) => a + b);
}
console.log(sum(1, 2, 3));

立即执行

立即执行函数指函数定义时立即执行

可以用来定义私有作用域防止污染全局作用域

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

使用 let/const 有块作用域特性,所以使用以下方式也可以产生私有作用域

{
	let web = 'houdunren';
}
console.log(web);

函数提升

函数也会提升到前面,优先级行于var变量提高

console.log(test()); //whs
function test() {
	return 'whs';
}

变量函数定义不会被提升

console.log(test()); //whs

function test() {
	return 'whs';
}
console.log(test()); //whs
var test = function () {
	return 'icebound';
}
console.log(test()); //icebound
console.log(test()); //err
 var test = function () {
    return "icebound";
  };

形参实参

形参是在函数声明时设置的参数,实参指在调用函数时传递的值。

形参数量大于实参时,没有传参的形参值为 undefined
实参数量大于形参时,多于的实参将忽略并不会报错

// n1,n2 为形参
function sum(n1, n2) {
	return n1+n2;
}
// 参数 2,3 为实参
console.log(sum(2, 3)); //5

当没传递参数时值为undefined

function sum(n1, n2) {
  return n1 + n2;
}
console.log(sum(2)); //NaN

默认参数

下面通过计算年平均销售额来体验以往默认参数的处理方式

/

/total:总价 year:年数
function avg(total, year) {
  year = year || 1;
  return Math.round(total / year);
}
console.log(avg(2000, 3));

使用新版本默认参数方式如下

function avg(total, year = 1) {
  return Math.round(total / year);
}
console.log(avg(2000, 3));

下面通过排序来体验新版默认参数的处理方式,下例中当不传递 type 参数时使用默认值 asc。

function sortArray(arr, type = 'asc') {
	return arr.sort((a, b) => type == 'asc' ? a - b : b - a);
}
console.log(sortArray([1, 3, 2, 6], 'desc'));

默认参数要放在最后面

//total:价格,discount:折扣,dis:折后折
function sum(total, discount = 0, dis = 0) {
  return total * (1 - discount) * (1 - dis);
}
console.log(sum(2000, undefined, 0.3));

函数参数

函数可以做为参数传递,这也是大多数语言都支持的语法规则。

<body>
    <button>订阅</button>
</body>
<script>
    document.querySelector('button').addEventListener('click', function () {
        alert('感谢订阅');
    })
</script>

函数可以做为参数传递

function filterFun(item) {
	return item <= 3;
}
let test = [1, 2, 3, 4, 5].filter(filterFun);
console.log(test); //[1,2,3]

arguments

arguments 是函数获得到所有参数集合,下面是使用 arguments 求和的例子

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

更建议使用展示语法

function sum(...args) {
 return args.reduce((a, b) => a + b);
}
console.log(sum(2, 3, 4, 2, 6)); //17

箭头函数

箭头函数是函数声明的简写形式,在使用递归调用、构造函数、事件处理器时不建议使用箭头函数。

无参数时使用空扩号即可

let sum = () => {
	return 1 + 3;
}
console.log(sum()); //4

函数体为单一表达式时不需要 return 返回处理,系统会自动返回表达式计算结果。

let sum = () => 1 + 3;
console.log(sum()); //4

多参数传递与普通声明函数一样使用逗号分隔

let test = [1, 8, 3, 5].filter((item, index) => {
	return item <= 3;
});
console.log(test);

只有一个参数时可以省略括号

let test = [1, 8, 3, 5].filter(item => item <= 3);
console.log(test);

有关箭头函数的作用域知识会在后面章节讨论

递归调用

递归指函数内部调用自身的方式。

主要用于数量不确定的循环操作
要有退出时机否则会陷入死循环
下面通过阶乘来体验递归调用

function factorial(num = 3) {
	return num == 1 ? num : num * factorial(--num);
}
console.log(factorial(5)); //120

累加计算方法

function sum(...num) {
	return num.length == 0 ? 0 : num.pop() + sum(...num);
}
console.log(sum(1, 2, 3, 4, 5, 7, 9)); //31

使用递归修改课程点击数

let lessons = [
  {
    title: "媒体查询响应式布局",
    click: 89
  },
  {
    title: "FLEX 弹性盒模型",
    click: 45
  },
  {
    title: "GRID 栅格系统",
    click: 19
  },
  {
    title: "盒子模型详解",
    click: 29
  }
];
function change(lessons, num, i = 0) {
  if (i == lessons.length) {
    return lessons;
  }
  lessons[i].click += num;
  return change(lessons, num, ++i);
}
console.table(change(lessons, 100));

回调函数

在某个时刻被其他函数调用的函数称为回调函数,比如处理键盘、鼠标事件的函数。

<button id='test'>button</button>
<script>
     document.getElementById('test').addEventListener('click', () => alert('通过回调函数调用'));
</script>

使用回调函数递增计算

let test = ([1, 2, 3]).map(item => item + 10);
console.log(test)

展开语法

展示语法或称点语法体现的就是收/放特性,做为值时是放,做为接收变量时是收。

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

使用展示语法可以替代 arguments 来接收任意数量的参数

function test(...args) {
  console.log(args);
}
test(1, 2, 3, "whs"); //[1, 2, 3, "whs"]

也可以用于接收部分参数

function test(site, ...args) {
  console.log(site, args); //whs (3) [1, 2, 3]
}
test("whs", 1, 2, 3);

使用 … 可以接受传入的多个参数合并为数组,下面是使用点语法进行求合计算。

function sum(...params) {
	console.log(params);
	return params.reduce((pre, cur) => pre + cur);
}
console.log(sum(1, 3, 2, 4));

多个参数时…参数必须放后面,下面计算购物车商品折扣

function sum(discount = 0, ...prices) {
  let total = prices.reduce((pre, cur) => pre + cur);
  return total * (1 - discount);
}
console.log(sum(0.1, 100, 300, 299));

标签函数

使用函数来解析标签字符串,第一个参数是字符串值的数组,其余的参数为标签变量。

function test(str, ...values) {
  console.log(str); //["站点", "-", "", raw: Array(3)]
  console.log(values); //["whs", "icebound"]
}
let name = 'whs',url = 'icebound';
test `站点${name}-${url}`;

this

调用函数时 this 会隐式传递给函数指函数调用时的关联对象,也称之为函数的上下文。

函数调用

全局环境下this就是window对象的引用

<script>
  console.log(this == window); //true
</script>

使用严格模式时在全局函数内this为undefined

var test = 'whs';
function get() {
  "use strict"
  return this.test;
}
console.log(get());
//严格模式将产生错误 Cannot read property 'name' of undefined

方法调用

函数为对象的方法时this 指向该对象

可以使用多种方式创建对象,下面是使用构造函数创建对象

构造函数

函数当被 new 时即为构造函数,一般构造函数中包含属性与方法。函数中的上下文指向到实例对象。

构造函数主要用来生成对象,里面的this默认就是指当前对象

function User() {
  this.name = "whs";
  this.say = function() {
    console.log(this); //User {name: "whs", say: ƒ}
    return this.name;
  };
}
let test = new User();
console.log(test.say()); //whs

对象字面量

下例中的hd函数不属于对象方法所以指向window
show属于对象方法执向 obj对象

let obj = {
  site: "whs",
  show() {
    console.log(this.site); //whs
    console.log(`this in show method: ${this}`); //this in show method: [object Object]
    function test() {
      console.log(typeof this.site); //undefined
      console.log(`this in test function: ${this}`); //this in test function: [object Window]
    }
    test();
  }
};
obj.show();

在方法中使用函数时有些函数可以改变this如forEach,当然也可以使用后面介绍的apply/call/bind

let Lesson = {
  site: "whs",
  lists: ["js", "css", "mysql"],
  show() {
    return this.lists.map(function(title) {
      return `${this.site}-${title}`;
    }, this);
  }
};
console.log(Lesson.show());

也可以在父作用域中定义引用this的变量

let Lesson = {
    site: "whs",
    lists: ["js", "css", "mysql"],
    show() {
      const self = this;
      return this.lists.map(function(title) {
        return `${self.site}-${title}`;
      });
    }
  };
  console.log(Lesson.show());

箭头函数

箭头函数没有this, 也可以理解为箭头函数中的this 会继承定义函数时的上下文,可以理解为和外层函数指向同一个this。

如果想使用函数定义时的上下文中的this,那就使用箭头函数
下例中的匿名函数的执行环境为全局所以 this 指向 window。

var name = 'hdcms';
var obj = {
  name: 'whs',
  getName: function () {
    return function () {
    	return this.name;
    }
  }
}
console.log(obj.getName()()); //返回window.name的值hdcms

以往解决办法会匿名函数调用处理定义变量,然后在匿名函数中使用。

var name = 'hdcms';
var obj = {
  name: 'whs',
  getName: function () {
    var self = this;
		return () => {
    	return this.name;
    }
  }
}
console.log(obj.getName()()); //返回window.name的值hdcms

使用箭头函数后 this 为定义该函数的上下文,也可以理解为定义时父作用域中的this

var name = 'hdcms';
var obj = {
  name: 'whs',
  getName: function () {
    return () => {
    	return this.name;
    }
  }
}
console.log(obj.getName()()); //whs

事件中使用箭头函数结果不是我们想要的

事件函数可理解为对象onclick设置值,所以函数声明时this为当前对象
但使用箭头函数时this为声明函数上下文
下面体验使用普通事件函数时this指向元素对象

使用普通函数时this为当前DOM对象

<body>
  <button desc="hdcms">button</button>
</body>
<script>
  let Dom = {
    site: "whs",
    bind() {
      const button = document.querySelector("button");
      button.addEventListener("click", function() {
        alert(this.getAttribute("desc"));
      });
    }
  };
  Dom.bind();
</script>

下面是使用箭头函数时this指向上下文对象

<body>
  <button desc="hdcms">button</button>
</body>
<script>
  let Dom = {
    site: "whs",
    bind() {
      const button = document.querySelector("button");
      button.addEventListener("click", event => {
        alert(this.site + event.target.innerHTML);
      });
    }
  };
  Dom.bind();
</script>

使用handleEvent绑定事件处理器时,this指向当前对象而不是DOM元素。

<body>
  <button desc="hdcms">button</button>
</body>
<script>
  let Dom = {
    site: "whs",
    handleEvent: function(event) {
      console.log(this);
    },
    bind() {
      const button = document.querySelector("button");
      button.addEventListener("click", this);
    }
  };
  Dom.bind();
</script>

apply/call/bind

改变this指针,也可以理解为对象借用方法,就现像生活中向邻居借东西一样的事情。

原理分析

构造函数中的this默认是一个空对象,然后构造函数处理后把这个空对象变得有值。

function User(name) {
  this.name = name;
}
let test = new User("whs");

可以改变构造函数中的空对象,即让构造函数this指向到另一个对象。

function User(name) {
  this.name = name;
}

let hdcms = {};
User.call(hdcms, "HDCMS");
console.log(hdcms.name); //HDCMS

apply/call

call与apply 用于显示的设置函数的上下文,两个方法作用一样都是将对象绑定到this,只是在传递参数上有所不同。

apply 用数组传参
call 需要分别传参
与 bind 不同 call/apply 会立即执行函数
语法使用介绍

function show(title) {
    alert(`${title+this.name}`);
}
let lisi = {
    name: '李四'
};
let wangwu = {
    name: '王五'
};
show.call(lisi, 'whs');
show.apply(wangwu, ['HDCMS']);

使用 call 设置函数上下文

<body>
    <button message="whs">button</button>
    <button message="hdcms">button</button>
</body>
<script>
    function show() {
        alert(this.getAttribute('message'));
    }
    let bts = document.getElementsByTagName('button');
    for (let i = 0; i < bts.length; i++) {
        bts[i].addEventListener('click', () => show.call(bts[i]));
    }
</script>

找数组中的数值最大值

let arr = [1, 3, 2, 8];
console.log(Math.max(arr)); //NaN
console.log(Math.max.apply(Math, arr)); //8
 console.log(Math.max(...arr)); //8

实现构造函数属性继承

"use strict";
function Request() {
  this.get = function(params = {}) {
    //组合请求参数
    let option = Object.keys(params)
      .map(i => i + "=" + params[i])
      .join("&");

    return `获取数据 API:${this.url}?${option}`;
  };
}
//文章控制器
function Article() {
  this.url = "article/index";
  Request.apply(this, []);
}
let test = new Article();
console.log(
  test.get({
    row: 10,
    start: 3
  })
);
//课程控制器
function Lesson() {
  this.url = "lesson/index";
  Request.call(this);
}
let js = new Lesson();
console.log(
  js.get({
    row: 20
  })
);

制作显示隐藏面板

<style>
    * {
        padding: 0;
        margin: 0;
    }

    body {
        display: flex;
        justify-content: center;
        align-items: center;
        width: 100vw;
        height: 100vh;
    }

    dl {
        width: 400px;
        display: flex;
        flex-direction: column;
    }

    dt {
        background: #e67e22;
        border-bottom: solid 2px #333;
        height: 50px;
        display: flex;
        justify-content: center;
        align-items: center;
        cursor: pointer;
    }

    dd {
        height: 200px;
        background: #bdc3c7;
        font-size: 5em;
        text-align: center;
        line-height: 200px;
    }
</style>

<body>
    <dl>
        <dt>whs</dt>
        <dd>1</dd>
        <dt>hdcms</dt>
        <dd hidden="hidden">2</dd>
    </dl>
</body>
<script>
  function panel(i) {
    let dds = document.querySelectorAll("dd");
    dds.forEach(item => item.setAttribute("hidden", "hidden"));
    dds[i].removeAttribute("hidden");
  }
  document.querySelectorAll("dt").forEach((dt, i) => {
    dt.addEventListener("click", () => panel.call(null, i));
  });
</script>

bind

bind()是将函数绑定到某个对象,比如 a.bind(test) 可以理解为将a函数绑定到hd对象上即 test.a()。

与 call/apply 不同bind不会立即执行
bind 是复制函数形为会返回新函数
bind是复制函数行为

let a = function() {};
let b = a;
console.log(a === b); //true
//bind是新复制函数
let c = a.bind();
console.log(a == c); //false

绑定参数注意事项

function test(a, b) {
  return this.f + a + b;
}


//使用bind会生成新函数
let newFunc = test.bind({ f: 1 }, 3);

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

在事件中使用bind

<body>
  <button>whs</button>
</body>
<script>
  document.querySelector("button").addEventListener(
    "click",
    function(event) {
      console.log(event.target.innerHTML + this.url);
    }.bind({ url: "icebound" })
  );
</script>

动态改变元素背景颜色,当然下面的例子也可以使用箭头函数处理

<style>
  * {
    padding: 0;
    margin: 0;
  }

  body {
    width: 100vw;
    height: 100vh;
    font-size: 3em;
    padding: 30px;
    transition: 2s;
    display: flex;
    justify-content: center;
    align-items: center;
    background: #34495e;
    color: #34495e;
  }
</style>
<body>
  icebound
</body>
<script>
  function Color(elem) {
    this.elem = elem;
    this.colors = ["#74b9ff", "#ffeaa7", "#fab1a0", "#fd79a8"];
    this.run = function() {
      setInterval(
        function() {
          let pos = Math.floor(Math.random() * this.colors.length);
          this.elem.style.background = this.colors[pos];
        }.bind(this),
        1000
      );
    };
  }
  let obj = new Color(document.body);
  obj.run();
</script>

对象

传统的函数编程会有错中复杂的依赖很容易创造意大利式面条代码。

面向过程编程

let name = "王洪硕";
let grade = [
  { lesson: "js", score: 99 },
  { lesson: "mysql", score: 85 }
];
function average(grade, name) {
  const total = grade.reduce((t, a) => t + a.score, 0);
  return name + ":" + total / grade.length + "分";
}
console.log(average(grade, name));

面向对象编程

下面使用对象编程的代码结构清晰,也减少了函数的参数传递,也不用担心函数名的覆盖

let user = {
  name: "whs",
  grade: [
    { lesson: "js", score: 99 },
    { lesson: "mysql", score: 85 }
  ],
  average() {
    const total = this.grade.reduce((t, a) => t + a.score, 0);
    return this.name + ":" + total / grade.length + "分";
  }
};
console.log(user.average());

基础知识

OOP

对象是属性和方法的集合即封装
将复杂功能隐藏在内部,只开放给外部少量方法,更改对象内部的复杂逻辑不会对外部调用造成影响即抽象
继承是通过代码复用减少冗余代码
根据不同形态的对象产生不同结果即多态

基本声明

使用字面量形式声明对象是最简单的方式

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

属性与方法简写

let name = "whs";
let obj = {
  name,
  get() {
    return this.name;
  }
};
console.log(obj.get()); //whs

其实字面量形式在系统内部也是使用构造函数 new Object创建的,后面会详细介绍构造函数。

let demo = {};
let houdunren = new Object();
console.log(demo, houdunren);
console.log(demo.constructor);
console.log(houdunren.constructor);

操作属性

使用点语法获取

let user = {
  name: "王洪硕"
};
console.log(user.name);

使用[] 获取

console.log(user["name"]);

可以看出使用.操作属性更简洁,[]主要用于通过变量定义属性的场景

let user = {
  name: "王洪硕"
};
let property = "name";
console.log(user[property]);

如果属性名不是合法变量名就必须使用扩号的形式了

let user = {};
user["my-age"] = 28;
console.log(user["my-age"]);

对象和方法的属性可以动态的添加或删除。

const demo = {
  name: "whs"
};
demo.age = "10";
demo.show = function() {
  return `${this.name}已经${this.age}岁了`;
};
console.log(demo.show());
console.log(demo);

delete demo.show;
delete demo.age;

console.log(demo);
console.log(demo.age); //undefined

对象方法

定义在对象中的函数我们称为方法,下面定义了学生对象,并提供了计算平均成绩的方法

let lisi = {
  name: "李四",
  age: 22,
  grade: {
    math: 99,
    english: 67
  },
  //平均成绩
  avgGrade: function() {
    let total = 0;
    for (const key in this.grade) {
      total += this.grade[key];
    }
    return total / this.propertyCount("grade");
  },
  //获取属性数量
  propertyCount: function(property) {
    let count = 0;
    for (const key in this[property]) count++;
    return count;
  }
};
console.log(lisi.avgGrade());

一个学生需要手动创建一个对象,这显然不实际的,下面的构造函数就可以解决这个问题

引用特性

对象和函数、数组一样是引用类型,即复制只会复制引用地址。

let demo = { name: "whs" };
let cms = demo;
cms.name = "hdcms";
console.log(demo.name); //hdcms

对象做为函数参数使用时也不会产生完全赋值,内外共用一个对象

let user = { age: 22 };
function demo(user) {
  user.age += 10;
}
demo(user);
console.log(user.age); //32

对多的比较是对内存地址的比较所以使用 == 或 === 一样

let demo = {};
let xj = demo;
let cms = {};
console.log(demo == xj); //true
console.log(demo === xj); //true
console.log(demo === cms); //false

this

this 指当前对象的引用,始终建议在代码内部使用this 而不要使用对象名,不同对象的this只指向当前对象。

下例是不使用 this 时发生的错误场景

删除了xj 变量,但在函数体内还在使用xj变量造成错误
使用 this 后始终指向到引用地址,就不会有这个问题

let xj = {
  name: "王洪硕",
  show() {
    return xj.name;
  }
};
let demo = xj;
xj = null;
console.log(demo.show()); //Error

改用this 后一切正常

let xj = {
  name: "王洪硕",
  show() {
    return this.name;
  }
};
let demo = xj;
xj = null;
console.log(demo.show()); //Error

展开语法

使用…可以展示对象的结构,下面是实现对象合并的示例

let demo = { name: "whs", web: "houdurnen.com" };
let info = { ...demo, site: "hdcms" };
console.log(info);

下面是函数参数合并的示例

function upload(params) {
  let config = {
    type: "*.jpeg,*.png",
    size: 10000
  };
  params = { ...config, ...params };
  console.log(params);
}
upload({ size: 999 });

对象转换

基础知识

对象直接参与计算时,系统会根据计算的场景在 string/number/default 间转换。

如果声明需要字符串类型,调用顺序为 toString > valueOf
如果场景需要数值类型,调用顺序为 valueOf > toString
声明不确定时使用 default ,大部分对象的 default 会当数值使用
下面的数值对象会在数学运算时转换为 number

let houdunren = new Number(1);
console.log(houdunren + 3); //4

如果参数字符串运长时会转换为 string

let houdunren = new Number(1);
console.log(houdunren + "3"); //13

下面当不确定转换声明时使用 default ,大部分default转换使用 number 转换。

let houdunren = new Number(1);
console.log(houdunren == "1"); //true

Symbol.toPrimitive

内部自定义Symbol.toPrimitive方法用来处理所有的转换场景

let demo = {
  num: 1,
  [Symbol.toPrimitive]: function() {
    return this.num;
  }
};
console.log(demo + 3); //4

valueOf/toString

可以自定义valueOf 与 toString 方法用来转换,转换并不限制返回类型。

let demo = {
  name: "whs",
  num: 1,
  valueOf: function() {
    console.log("valueOf");
    return this.num;
  },
  toString: function() {
    console.log("toString");
    return this.name;
  }
};
console.log(demo + 3); //valueOf 4
console.log(`${demo}王洪硕`); //toString 后盾人向军

解构赋值

解构是一种更简洁的赋值特性,可以理解为分解一个数据的结构,在数组章节已经介绍过。

建设使用 var/let/const 声明
#基本使用
下面是基本使用语法

//对象使用
let info = {name:'whs',url:'icebound'};
let {name:n,url:u} = info
console.log(n); // whs

//如果属性名与变量相同可以省略属性定义
let {name,url} = {name:'whs',url:'icebound'};
console.log(name); // whs

函数返回值直接解构到变量

function demo() {
  return {
    name: 'whs',
    url: 'icebound'
  };
}
let {name: n,url: u} = demo();
console.log(n);

函数传参

"use strict";
function demo({ name, age }) {
  console.log(name, age); //王洪硕 18
}
demo({ name: "王洪硕", age: 18 });

系统函数解构练习,这没有什么意义只是加深解构印象

const {random} =Math;
console.log(random());

严格模式

非严格模式可以不使用声明指令,严格模式下必须使用声明。所以建议使用 let 等声明。

// "use strict";
({name,url} = {name:'whs',url:'icebound'});
console.log(name, url);

还是建议使用let等赋值声明

"use strict";
let { name, url } = { name: "whs", url: "icebound" };
console.log(name, url);

简洁定义

如果属性名与赋值的变量名相同可以更简洁

let web = { name: "whs",url: "icebound" };
let { name, url } = web;
console.log(name); //whs

只赋值部分变量

let [,url]=['whs','icebound'];
console.log(url);//icebound

let {name}= {name:'whs',url:'icebound'};
console.log(name); //whs

可以直接使用变量赋值对象属性

let name = "whs",url = "icebound";
//标准写法如下
let demo = { name: name, url: url };
console.log(demo);  //{name: "whs", url: "icebound"}

//如果属性和值变量同名可以写成以下简写形式
let opt = { name, url };
console.log(opt); //{name: "whs", url: "icebound"}

嵌套解构

可以操作多层复杂数据结构

const demo = {
  name:'whs',
  lessons:{
    title:'JS'
  }
}
const {name,lessons:{title}}  = demo;
console.log(name,title); //whs JS

默认值

为变量设置默认值

let [name, site = 'hdcms'] = ['whs'];
console.log(site); //hdcms

let {name,url,user='王洪硕'}= {name:'whs',url:'icebound'};
console.log(name,user);//王洪硕

使用默认值特性可以方便的对参数预设

function createElement(options) {
  let {
    width = '200px',
    height = '100px',
    backgroundColor = 'red'
  } = options;
  
  const h2 = document.createElement('h2');
  h2.style.width = width;
  h2.style.height = height;
  h2.style.backgroundColor = backgroundColor;
  document.body.appendChild(h2);
}
createElement({
	backgroundColor: 'green'
});

函数参数

数组参数的使用

function demo([a, b]) {
	console.log(a, b);
}
demo(['whs', 'hdcms']);

对象参数使用方法

function demo({name,url,user='王洪硕'}) {
	console.log(name,url,user);
}
demo({name:'whs','url':'icebound'}); //whs icebound 王洪硕

对象解构传参

function user(name, { sex, age } = {}) {
  console.log(name, sex, age); //王洪硕 男 18
}
user("王洪硕", { sex: "男", age: 18 });

属性管理

添加属性

可以为对象添加属性

let obj = {name: "whs"};
obj.site = "icebound";
console.log(obj);

删除属性

使用delete 可以删除属性(后面介绍的属性特性章节可以保护属性不被删除)

let obj = { name: "whs" };
delete obj.name;
console.log(obj.name); //undefined

检测属性

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

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

下面通过数组查看

let arr = ["whs"];
console.log(arr);
console.log(arr.hasOwnProperty("length")); //true
console.log(arr.hasOwnProperty("concat")); //false
console.log("concat" in arr); //true

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

let obj = {name: "whs"};
let demo = {
  web: "icebound"
};

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

console.log("web" in obj); //true
console.log(obj.hasOwnProperty("web")); //false

assign

以往我们使用类似jQuery.extend 等方法设置属性,现在可以使用 Object.assign 静态方法

从一个或多个对象复制属性

"use strict";
let demo = { a: 1, b: 2 };
demo = Object.assign(demo, { f: 1 }, { m: 9 });
console.log(demo); //{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);

使用计算属性为文章定义键名

const lessons = [
  {
    title: "媒体查询响应式布局",
    category: "css"
  },
  {
    title: "FLEX 弹性盒模型",
    category: "css"
  },
  {
    title: "MYSQL多表查询随意操作",
    category: "mysql"
  }
];
let lessonObj = lessons.reduce((obj, cur, index) => {
  obj[`${cur["category"]}-${index}`] = cur;
  return obj;
}, {});
console.log(lessonObj); //{css-0: {…}, css-1: {…}, mysql-2: {…}}
console.log(lessonObj["css-0"]); //{title: "媒体查询响应式布局", category: "css"}

传值操作

对象是引用类型赋值是传址操作,后面会介绍对象的深、浅拷贝操作

let user = {
	name: 'whs'
};
let demo = {
	stu: user
};
demo.stu.name = 'hdcms';
console.log(user.name);//hdcms

遍历对象

获取内容

使用系统提供的API可以方便获取对象属性与值

const demo = {
  name: "whs",
  age: 10
};
console.log(Object.keys(demo)); //["name", "age"]
console.log(Object.values(demo)); //["whs", 10]
console.table(Object.entries(demo)); //[["name","whs"],["age",10]]

for/in

使用for/in遍历对象属性

const demo = {
  name: "whs",
  age: 10
};
for (let key in demo) {
  console.log(key, demo[key]);
}

for/of

for/of用于遍历迭代对象,不能直接操作对象。但Object对象的keys/方法返回的是迭代对象。

const demo = {
  name: "whs",
  age: 10
};
for (const key of Object.keys(demo)) {
  console.log(key);
}

获取所有对象属性

const demo = {
  name: "whs",
  age: 10
};
for (const key of Object.values(demo)) {
  console.log(key);
}

同时获取属性名与值

for (const array of Object.entries(demo)) {
  console.log(array);
}

使用扩展语法同时获取属性名与值

for (const [key, value] of Object.entries(demo)) {
  console.log(key, value);
}

添加元素DOM练习

let lessons = [
  { name: "js", click: 23 },
  { name: "node", click: 192 }
];
let ul = document.createElement("ul");
for (const val of lessons) {
  let li = document.createElement("li");
  li.innerHTML = `课程:${val.name},点击数:${val.click}`;
  ul.appendChild(li);
}
document.body.appendChild(ul);

对象拷贝

对象赋值时复制的内存地址,所以一个对象的改变直接影响另一个

let obj = {
  name: 'whs',
  user: {
  	name: 'hdcms'
  }
}
let a = obj;
let b = obj;
a.name = 'lisi';
console.log(b.name); //lisi

浅拷贝

使用for/in执行对象拷贝

let obj = {name: "whs"};

let demo = {};
for (const key in obj) {
  demo[key] = obj[key];
}

demo.name = "hdcms";
console.log(demo);
console.log(obj);

Object.assign 函数可简单的实现浅拷贝,它是将两个对象的属性叠加后面对象属性会覆盖前面对象同名属性。

let user = {
	name: 'whs'
};
let demo = {
	stu: Object.assign({}, user)
};
demo.stu.name = 'hdcms';
console.log(user.name);//whs

使用展示语法也可以实现浅拷贝

let obj = {
  name: "whs"
};
let demo = { ...obj };
demo.name = "hdcms";
console.log(demo);
console.log(obj);

深拷贝

浅拷贝不会将深层的数据复制

let obj = {
    name: 'whs',
    user: {
        name: 'hdcms'
    }
}
let a = obj;
let b = obj;

function copy(object) {
    let obj = {}
    for (const key in object) {
        obj[key] = object[key];
    }
    return obj;
}
let newObj = copy(obj);
newObj.name = 'hdcms';
newObj.user.name = 'icebound';
console.log(newObj);
console.log(obj);

是完全的复制一个对象,两个对象是完全独立的对象

let obj = {
  name: "whs",
  user: {
    name: "hdcms"
  },
  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 demo = copy(obj);
demo.data.push("王洪硕");
console.log(JSON.stringify(demo, null, 2));
console.log(JSON.stringify(obj, null, 2));

构建函数

对象可以通过内置或自定义的构造函数创建。

工厂函数

在函数中返回对象的函数称为工厂函数,工厂函数有以下优点

减少重复创建相同类型对象的代码
修改工厂函数的方法影响所有同类对象
使用字面量创建对象需要复制属性与方法结构

const xj = {
  name: "王洪硕",
  show() {
    console.log(this.name);
  }
};
const demo = {
  name: "whs",
  show() {
    console.log(this.name);
  }
};

使用工厂函数可以简化这个过程

function stu(name) {
  return {
    name,
    show() {
      console.log(this.name);
    }
  };
}
const lisi = stu("李四");
lisi.show();
const xj = stu("王洪硕");
xj.show();

构造函数

和工厂函数相似构造函数也用于创建对象,它的上下文为新的对象实例。

构造函数名每个单词首字母大写即Pascal 命名规范
this指当前创建的对象
不需要返回this系统会自动完成
需要使用new关键词生成对象

function Student(name) {
  this.name = name;
  this.show = function() {
    console.log(this.name);
  };
  //不需要返回,系统会自动返回
  // return this;
}
const lisi = new Student("李四");
lisi.show();
const whs = new Student("王洪硕");
whs.show();

如果构造函数返回对象,实例化后的对象将是此对象

function ArrayObject(...values) {
  const arr = new Array();
  arr.push.apply(arr, values);
  arr.string = function(sym = "|") {
    return this.join(sym);
  };
  return arr;
}
const array = new ArrayObject(1, 2, 3);
console.log(array);
console.log(array.string("-"));

严格模式

在严格模式下方法中的this值为undefined,这是为了防止无意的修改window对象

"use strict";
function User() {
  this.show = function() {
    console.log(this);
  };
}
let demo = new User();
demo.show(); //User

let xj = demo.show;
xj(); //undefined

内置构造

JS中大部分数据类型都是通过构造函数创建的。

const num = new Number(99);
console.log(num.valueOf());

const string = new String("whs");
console.log(string.valueOf());

const boolean = new Boolean(true);
console.log(boolean.valueOf());

const date = new Date();
console.log(date.valueOf() * 1);

const regexp = new RegExp("\\d+");
console.log(regexp.test(99));

let demo = new Object();
demo.name = "whs";
console.log(demo);

字面量创建的对象,内部也是调用了Object构造函数

const demo = {
  name: "whs"
};
console.log(demo.constructor); //ƒ Object() { [native code] }


//下面是使用构造函数创建对象
const hdcms = new Object();
hdcms.title = "开源内容管理系统";
console.log(hdcms);

对象函数

在JS中函数也是一个对象

function demo(name) {}

console.log(demo.toString());
console.log(demo.length);

函数是由系统内置的 Function 构造函数创建的

function demo(name) {}

console.log(demo.constructor);

下面是使用内置构造函数创建的函数

const User = new Function(`name`,`
  this.name = name;
  this.show = function() {
    return this.name;
  };
`
);

const lisi = new User("李四");
console.log(lisi.show());

抽象特性

将复杂功能隐藏在内部,只开放给外部少量方法,更改对象内部的复杂逻辑不会对外部调用造成影响即抽象。

下面的手机就是抽象的好例子,只开放几个按钮给用户,复杂的工作封装在手机内部,程序也应该如此。

img

问题分析

下例将对象属性封装到构造函数内部

function User(name, age) {
  this.name = name;
  this.age = age;
  this.info = function() {
    return this.age > 50 ? "中年人" : "年轻人";
  };
  this.about = function() {
    return `${this.name}${this.info()}`;
  };
}
let lisi = new User("李四", 22);
console.log(lisi.about());

抽象封装

上例中的方法和属性仍然可以在外部访问到,比如 info方法只是在内部使用,不需要被外部访问到这会破坏程序的内部逻辑。

下面使用闭包特性将对象进行抽象处理

function User(name, age) {
  let data = { name, age };
  let info = function() {
    return data.age > 50 ? "中年人" : "年轻人";
  };
  this.message = function() {
    return `${data.name}${info()}`;
  };
}
let lisi = new User("whs", 22);
console.log(lisi.message());

属性特征

JS中可以对属性的访问特性进行控制。

查看特征

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

"use strict";
const user = {
  name: "王洪硕",
  age: 18
};
let desc = Object.getOwnPropertyDescriptor(user, "name"`);
console.log(JSON.stringify(desc, null, 2));

使用 Object.getOwnPropertyDescriptors查看对象所有属性的描述

"use strict";
const user = {
  name: "王洪硕",
  age: 18
};
let desc = Object.getOwnPropertyDescriptors(user);
console.log(JSON.stringify(desc, null, 2));

属性包括以下四种特性

特性 说明 默认值
configurable 能否使用delete、能否需改属性特性、或能否修改访问器属性 true
enumerable 对象属性是否可通过for-in循环,或Object.keys() 读取 true
writable 对象属性是否可修改 true
value 对象属性的默认值 undefined

设置特征

使用Object.defineProperty 方法修改属性特性,通过下面的设置属性name将不能被遍历、删除、修改。

"use strict";
const user = {
  name: "王洪硕"
};
Object.defineProperty(user, "name", {
  value: "whs",
  writable: false,
  enumerable: false,
  configurable: false
});

通过执行以下代码对上面配置进行测试,请分别打开注释进行测试

// 不允许修改
// user.name = "王洪硕"; //Error

// 不能遍历
// console.log(Object.keys(user));

//不允许删除
// delete user.name;
// console.log(user);

//不允许配置
// Object.defineProperty(user, "name", {
//   value: "whs",
//   writable: true,
//   enumerable: false,
//   configurable: false
// });

使用 Object.defineProperties 可以一次设置多个属性,具体参数和上面介绍的一样。

"use strict";
let user = {};
Object.defineProperties(user, {
  name: { value: "王洪硕", writable: false },
  age: { value: 18 }
});
console.log(user);
user.name = "whs"; //TypeError

禁止添加

Object.preventExtensions 禁止向对象添加属性

"use strict";
const user = {
  name: "王洪硕"
};
Object.preventExtensions(user);
user.age = 18; //Error

Object.isExtensible 判断是否能向对象中添加属性

"use strict";
const user = {
  name: "王洪硕"
};
Object.preventExtensions(user);
console.log(Object.isExtensible(user)); //false

封闭对象

Object.seal()方法封闭一个对象,阻止添加新属性并将所有现有属性标记为 configurable: false

"use strict";
const user = {
  name: "whs",
  age: 18
};

Object.seal(user);
console.log(
  JSON.stringify(Object.getOwnPropertyDescriptors(user), null, 2)
);

Object.seal(user);
console.log(Object.isSealed(user));
delete user.name; //Error

Object.isSealed 如果对象是密封的则返回 true,属性都具有 configurable: false。

"use strict";
const user = {
  name: "王洪硕"
};
Object.seal(user);
console.log(Object.isSealed(user)); //true

冻结对象

Object.freeze 冻结对象后不允许添加、删除、修改属性,writable、configurable都标记为false

"use strict";
const user = {
  name: "王洪硕"
};
Object.freeze(user);
user.name = "whs"; //Error

Object.isFrozen()方法判断一个对象是否被冻结

"use strict";
const user = {
  name: "王洪硕"
};
Object.freeze(user);
console.log(Object.isFrozen(user));

属性访问器

getter方法用于获得属性值,setter方法用于设置属性,这是JS提供的存取器特性即使用函数来管理属性。

用于避免错误的赋值
需要动态监测值的改变
属性只能在访问器和普通属性任选其一,不能共同存在
#getter/setter
向对是地用户的年龄数据使用访问器监控控制

"use strict";
const user = {
  data: { name: 'whs', 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 = 99;
console.log(user.age);

下面使用getter设置只读的课程总价

let Lesson = {
  lists: [
    { name: "js", price: 100 },
    { name: "mysql", price: 212 },
    { name: "vue.js", price: 98 }
  ],
  get total() {
    return this.lists.reduce((t, b) => t + b.price, 0);
  }
};
console.log(Lesson.total); //410
Lesson.total = 30; //无效
console.log(Lesson.total); //410

下面通过设置站网站名称与网址体验getter/setter批量设置属性的使用

const web = {
  name: "whs",
  url: "icebound",
  get site() {
    return `${this.name} ${this.url}`;
  },
  set site(value) {
    [this.name, this.url] = value.split(",");
  }
};
web.site = "whs,hdcms.com";
console.log(web.site);

下面是设置token储取的示例,将业务逻辑使用getter/setter处理更方便,也方便其他业务的复用。

let Request = {
  get token() {
    let con = localStorage.getItem('token');
    if (!con) {
    	alert('请登录后获取token')
    } else {
    	return con;
    }
  },
  set token(con) {
  	localStorage.setItem('token', con);
  }
};
// Request.token = 'houdunren'
console.log(Request.token);

定义内部私有属性

"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;
      }
    }
  });
}
let demo = new User("whs", 33);
console.log(demo.name);
demo.name = "向军1";
console.log(demo.name);

上面的代码也可以使用语法糖 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 demo = new User("whs", 33);
console.log(demo.name);
demo.name = "向军1";
console.log(demo.name);
console.log(demo);

闭包访问器

下面结合闭包特性对属性进行访问控制

下例中访问器定义在函数中,并接收参数v
在get() 中通过闭包返回 v
在set() 中修改了v,这会影响get()访问的闭包数据v

let data = {
  name: 'icebound',
}
for (const [key, value] of Object.entries(data)) {
  observer(data, key, value)
}

function observer(data, key, v) {
  Object.defineProperty(data, key, {
    get() {
      return v
    },
    set(newValue) {
      v = newValue
    },
  })
}
data.name = 'whs'
console.dir(data.name) //whs

代理拦截

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

读写属性时代码更简洁
对象的多个属性控制统一交给代理完成
严格模式下 set 必须返回布尔值
#使用方法

"use strict";
const demo = { name: "whs" };
const proxy = new Proxy(demo, {
  get(obj, property) {
    return obj[property];
  },
  set(obj, property, value) {
    obj[property] = value;
    return true;
  }
});
proxy.age = 10;
console.log(demo);

代理函数

如果代理以函数方式执行时,会执行代理中定义 apply 方法。

参数说明:函数,上下文对象,参数
下面使用 apply 计算函数执行时间

function factorial(num) {
  return num == 1 ? 1 : num * factorial(num - 1);
}
let proxy = new Proxy(factorial, {
  apply(func, obj, args) {
    console.time("run");
    func.apply(obj, args);
    console.timeEnd("run");
  }
});
proxy.apply(this, [1, 2, 3]);

截取字符

下例中对数组进行代理,用于截取标题操作

const stringDot = {
  get(target, key) {
    const title = target[key].title;
    const len = 5;
    return title.length > len
      ? title.substr(0, len) + ".".repeat(3)
      : title;
  }
};
const lessons = [
  {
    title: "媒体查询响应式布局",
    category: "css"
  },
  {
    title: "FLEX 弹性盒模型",
    category: "css"
  },
  {
    title: "MYSQL多表查询随意操作",
    category: "mysql"
  }
];
const stringDotProxy = new Proxy(lessons, stringDot);
console.log(stringDotProxy[0]);

双向绑定

下面通过代理实现vue 等前端框架的数据绑定特性特性。

<input type="text" v-model="title" />
<input type="text" v-model="title" />
<div v-bind="title"></div>
</body>
<script>
function View() {
	//设置代理拦截
  let proxy = new Proxy(
    {},
    {
      get(obj, property) {},
      set(obj, property, value) {
        obj[property] = value;
        document
          .querySelectorAll(
            `[v-model="${property}"],[v-bind="${property}"]`
          )
          .forEach(el => {
            el.innerHTML = value;
            el.value = value;
          });
      }
    }
  );
  //初始化绑定元素事件
  this.run = function() {
    const els = document.querySelectorAll("[v-model]");
    els.forEach(item => {
      item.addEventListener("keyup", function() {
        proxy[this.getAttribute("v-model")] = this.value;
      });
    });
  };
}
let view = new View().run();

表单验证

<style>
  body {
    padding: 50px;
    background: #34495e;
  }
  input {
    border: solid 10px #ddd;
    height: 30px;
  }
  .error {
    border: solid 10px red;
  }
</style>
<body>
  <input type="text" validate rule="max:12,min:3" />
  <input type="text" validate rule="max:3,isNumber" />
</body>
<script>
  "use strict";
  //验证处理类
  class Validate {
    max(value, len) {
      return value.length <= len;
    }
    min(value, len) {
      return value.length >= len;
    }
    isNumber(value) {
      return /^\d+$/.test(value);
    }
  }

  //代理工厂
  function makeProxy(target) {
    return new Proxy(target, {
      get(target, key) {
        return target[key];
      },
      set(target, key, el) {
        const rule = el.getAttribute("rule");
        const validate = new Validate();
        let state = rule.split(",").every(rule => {
          const info = rule.split(":");
          return validate[info[0]](el.value, info[1]);
        });
        el.classList[state ? "remove":"add"]("error");
        return true;
      }
    });
  }

  const nodes = makeProxy(document.querySelectorAll("[validate]"));
  nodes.forEach((item, i) => {
    item.addEventListener("keyup", function() {
      nodes[i] = this;
    });
  });
</script>

JSON

json 是一种轻量级的数据交换格式,易于人阅读和编写。
使用json 数据格式是替换 xml 的最佳方式,主流语言都很好的支持json 格式。所以 json 也是前后台传输数据的主要格式。
json 标准中要求使用双引号包裹属性,虽然有些语言不强制,但使用双引号可避免多程序间传输发生错误语言错误的发生。

声明定义

基本结构

let demo = {
  "title": "whs",
  "url": "icebound",
  "teacher": {
  	"name": "王洪硕",
  }
}
console.log(demo.teacher.name);

数组结构

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 demo = {
  "title": "whs",
  "url": "icebound",
  "teacher": {
  	"name": "王洪硕",
  }
}
console.log(JSON.stringify(demo)); 
//{"title":"whs","url":"icebound","teacher":{"name":"王洪硕"}}

根据第二个参数指定保存的属性

console.log(JSON.stringify(demo, ['title', 'url']));
//{"title":"whs","url":"icebound"}

第三个是参数用来控制TAB数量,如果字符串则为前导字符。

let demo = {
  "title": "whs",
  "url": "icebound",
  "teacher": {
  	"name": "王洪硕",
  }
}
console.log(JSON.stringify(demo, null, 4));

为数据添加 toJSON 方法来自定义返回格式

let demo = {
    "title": "whs",
    "url": "icebound",
    "teacher": {
        "name": "王洪硕",
    },
    "toJSON": function () {
        return {
            "title": this.url,
            "name": this.teacher.name
        };
    }
}
console.log(JSON.stringify(demo)); //{"title":"icebound","name":"王洪硕"}

反序列化

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

let demo = {
  "title": "whs",
  "url": "icebound",
  "teacher": {
  	"name": "王洪硕",
  }
}
let jsonStr = JSON.stringify(demo);
console.log(JSON.parse(jsonStr));

使用第二个参数函数来对返回的数据二次处理

let demo = {
  title: "whs",
  url: "icebound",
  teacher: {
    name: "王洪硕"
  }
};
let jsonStr = JSON.stringify(demo);
console.log(
  JSON.parse(jsonStr, (key, value) => {
    if (key == "title") {
      return `[推荐] ${value}`;
    }
    return value;
  })
);

你可能感兴趣的:(js基础,js原理,js进阶)