在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 声明的变量存在于最近的函数或全局作用域中,没有块级作用域的机制。
没有块作用域很容易污染全局,下面函数中的变量污染了全局环境
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);
与 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 用来声明常量,这与其他语言差别不大,比如可以用来声明后台接口的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);
当条件为真时执行表达式代码块。
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
下面是使用多条件判断密码强度的示例
<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 理解为 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('错误信息');
}
在switch 与 case 都可以使用表达式
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");
}
循环执行语句,需要设置跳出循环的条件否则会陷入死循环状态。下面是循环输出表格的示例。
let row = 5;
document.write(``);
while (row-- != 0) {
document.write(`${row} `);
}
document.write(`
`);
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 (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用于退出当前循环返回循环起始继续执行。
获取所有偶数,所有奇数使用 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) 为程序定义位置,可以使用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主要用于遍历对象,不建议用来遍历数组。
遍历数组操作
let hd = [
{ title: "第一章 走进JAVASCRIPT黑洞", lesson: 3 },
{ title: "ubuntu19.10 配置好用的编程工作站", lesson: 5 },
{ title: "媒体查询响应式布局", lesson: 8 }
];
document.write(`
标题 课程数
`);
for (let key in hd) {
document.write(`
${hd[key].title}
${hd[key].lesson}
`);
}
document.write("
");
遍历对象操作
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]);
}
用来遍历 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 用于返回以下原始类型
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);
字符串类型是使用非常多的数据类型,也是相对简单的数据类型。
声明定义
使用对象形式创建字符串
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
布尔类型包括 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;
}
使用对象方式声明
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结果。
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 对象提供了众多方法用来进行数学计算,下面我们介绍常用的方法,更多方法使用请查看 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 类型提供的丰富功能可以非常方便的操作。
获取当前日期时间
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是一个轻量级的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 与 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可将类数组转换为数组,类数组指包含 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"]
压入元素,直接改变元数组,返回值为数组元素数量
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));
从末尾弹出元素,直接改变元数组,返回值为弹出的元素
let arr = ["icebound", "whs"];
console.log(arr.pop()); //whs
console.log(arr); //["icebound"]
从数组前面取出一个元素
let arr = ["icebound", "whs"];
console.log(arr.shift()); //icebound
console.log(arr); //["whs"]
从数组前面添加元素
let arr = ["icebound", "whs"];
console.log(arr.unshift('向军大叔', 'icebound')); //4
console.log(arr); //["向军大叔", "icebound", "icebound", "whs"]
使用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 方法从数组中截取部分元素组合成新数组(并不会改变原数组),不传第二个参数时截取到数组的最后元素。
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 方法可以添加、删除、替换数组中的元素,会对原数组进行改变,返回值为删除的元素。
删除数组元素第一个参数为从哪开始删除,第二个参数为删除的数量。
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连接成字符串
let arr = [1, "icebound", "whs"];
console.log(arr.join('-')); //1-icebound-whs 使用join可以指定转换的连接方式
split 方法用于将字符串分割成数组,类似join方法的反函数。
let price = "99,78,68";
console.log(price.split(",")); //["99", "78", "68"]
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 从数组中复制一部分到同数组中的另外位置。
语法说明
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 从前向后查找元素出现的位置,如果找不到返回 -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 从后向前查找元素出现的位置,如果找不到返回 -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 查找字符串返回值是布尔类型更方便判断
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 方法找到后会把值返回出来
如果找不到返回值为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
下面使用自定义函数
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);
反转数组顺序
let arr = [1, 4, 2, 9];
console.log(arr.reverse()); //[9, 2, 4, 1]
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 循环来遍历数组
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使函数作用在每个数组元素上,但是没有返回值。
下面例子是截取标签的五个字符。
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);
遍历时的 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/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]));
通过迭代对象获取索引
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);
}
通过迭代对象获取值
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);
}
返回数组所有键值对,下面使用解构语法循环
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 用于递归的检测元素,要所有元素操作都要返回真结果才为真。
查看班级中同学的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 函数可以递归的检测元素,如果有一个返回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 可以过滤数据中元素,下面是获取所有在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 映射可以在数组的所有元素上应用函数,用于映射出新的值。
获取数组所有标题组合的新数组
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 与 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 求和的例子
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就是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>
改变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
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()是将函数绑定到某个对象,比如 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());
对象是属性和方法的集合即封装
将复杂功能隐藏在内部,只开放给外部少量方法,更改对象内部的复杂逻辑不会对外部调用造成影响即抽象
继承是通过代码复用减少冗余代码
根据不同形态的对象产生不同结果即多态
使用字面量形式声明对象是最简单的方式
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 时发生的错误场景
删除了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方法用来处理所有的转换场景
let demo = {
num: 1,
[Symbol.toPrimitive]: function() {
return this.num;
}
};
console.log(demo + 3); //4
可以自定义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
以往我们使用类似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遍历对象属性
const demo = {
name: "whs",
age: 10
};
for (let key in demo) {
console.log(key, demo[key]);
}
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 数据格式是替换 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;
})
);