一、异常捕获(了解)
JS引擎执行JS代码时,会发生各种错误,比如:
- 语法相关问题(程序报错)
- 业务逻辑相关问题(即使出错了,浏览器不报错,也会继续执行后面的代码)比如 console.log(2= =2= =1);
try{// 可能出错的代码throw // 手动抛出自定义异常}catch(e){ // 处理try代码块中抛出的异常// e 是错误信息 error}finally{// 无论什么情况都会执行代码块}:
需求:判断函数参数是不是数字,不是则抛出异常。
function fun(num) {if (typeof num !== "number") {throw "数据类型错误";}console.log(num);}try {fun("1");} catch (e) {if(e==="数据类型错误"){console.log('本程序猿知道了,马上就改')};}
二、严格模式(了解)
为什么使用严格模式:
- 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;
- 消除代码运行的一些不安全之处,保证代码运行的安全;
- 提高编译器效率,增加运行速度;
- 为未来新版本的Javascript做好铺垫。
“严格模式”体现了Javascript更合理、更安全、更严谨的发展方向,包括IE 10在内的主流浏览器,都已经支持它,许多大项目已经开始全面拥抱它。
另一方面,同样的代码,在”严格模式”中,可能会有不一样的运行结果;一些在”正常模式”下可以运行的语句,在”严格模式”下将不能运行。掌握这些内容,有助于更细致深入地理解Javascript,让你变成一个更好的程序员。
严格模式的限制:
进入严格模式的方法很简单,只需要在脚本或者函数的开头输入“use strict”即可,值得一提的是,在无法执行严格模式的旧版浏览器中(IE10之前),该条指令会自动被忽略。
限制1:不允许使用未声明的变量
"use strict";x = 1; // Uncaught ReferenceError: x is not defined
限制2:严格模式定义在脚本开头,会对整个脚本执行严格模式
"use strict";function fn () {x = 1; // Uncaught ReferenceError: x is not defined}fn();
限制3:如果严格模式定义在函数头部,那么只在当前函数中使用严格模式,对函数外部的代码没有影响。
x = 1;//不报错function fn () {"use strict"y = 2; // Uncaught ReferenceError: y is not defined}fn();
限制4:在严格模式下,不能对对象的只读属性赋值
"use strict";// 给不可写属性赋值var obj1 = {};Object.defineProperty(obj1, "x", { value: 42, writable: false });obj1.x = 9; // 抛出TypeError错误// 给不可扩展对象的新属性赋值var fixed = {};Object.preventExtensions(fixed);fixed.newProp = "ohai"; // 抛出TypeError错误
限制5:在严格模式下,试图删除不可删除的属性时,会抛出异常
"use strict";delete Object.prototype; // 抛出TypeError错误
限制6:在严格模式下,不允许重名属性
"use strict";var o = { p: 1, p: 2 }; // 语法错误
限制7:严格模式要求函数参数名唯一
function sum(a, a, c){ // 语法错误"use strict";return a + a + c; // 代码运行到这里会出错}
限制8:禁止八进制数字语法
"use strict";//0开头8进制 0x开头16进制 0b开头2进制var sum = 015 + // 语法错误197 +142;
限制9:禁止设置原始类型(primitive)值的属性
(function() {"use strict";false.true = ""; //TypeError(14).sailing = "home"; //TypeError"with".you = "far away"; //TypeError})();
限制10:禁用with
var a = {name:"zs",b:{age:10,c:{color:"red",des:"description"}}}console.log(a.b.c.color);console.log(a.b.c.des);// with语句可以在不造成性能损失的情况下,减少变量的长度,同时简化我们的代码。with(a.b.c){console.log(color);console.log(des);}
限制11:严格模式下,eval()创建变量不能被调用
"use strict";eval ("var x = 2");alert (x); // Uncaught ReferenceError: x is not defined
限制12:严格模式禁止删除声明变量
"use strict";var x;delete x; // 语法错误
限制13:不能使用eval 和 arguments作为标识
"use strict";var arguments = 1;// Uncaught SyntaxError: Unexpected eval or arguments in strict modevar eval = 2; // Uncaught SyntaxError: Unexpected eval or arguments in strict mode
限制14:严格模式下,函数的 arguments 对象会保存函数被调用时的原始参数。arguments[i] 的值不会随与之相应的参数的值的改变而变化,同名参数的值也不会随与之相应的 arguments[i] 的值的改变而变化。
"use strict";function f(a,b) {a=10;console.log(arguments[0]);//1arguments[0] = 20;console.log(a);//10}f(1,2);
限制15:不再支持arguments.callee
"use strict";var f = function() { return arguments.callee; };f(); // TypeError
限制16:保留部分关键字,这些字符包括implements, interface, let, package, private, protected, public, static和yield。在严格模式下,你不能再用这些名字作为变量名或形参名。
限制17:禁止this指向全局对象,当this指向全局对象时,自动转为undefined
总结
当你想要提升原生JS代码的健壮性和可读性,回避JS一些被人诟病的语法,严格模式是你不二的选择。
三、立即执行函数(掌握)
定义:声明一个函数,并马上调用这个匿名函数就叫做立即执行函数或者即时函数(匿名函数自执行)
全称:Immediately-Invoked Function Expression (IIFE)
特点:
- 首先是个匿名函数,无需函数命名。
- 主要作用是:防止全局变量污染,便于多人协作开发。
- 函数需要立即执行,且只需要执行一次,执行一次之后不再调用,通常用于需要初始化的变量。
语法结构:
// 掌握这一种(function (形参) {console.log("方式一");})(实参);// 了解下面的(function () {console.log("方式二");}());!function () {console.log("方式三");}();+function () {console.log("方式四");}();-function () {console.log("方式五");}();~function () {console.log("方式六");}();
立即执行函数的作用:
- 初始化变量(避免了污染全局变量)
- 创建独立作用域,全局变量私有化(封装变量)
- 需求:点击不同的按钮,显示对应的索引
<button>1</button><button>2</button><button>3</button><button>4</button><button>5</button>var btns = document.getElementsByTagName("button");
var btns = document.getElementsByTagName("button");for (var i = 0; i < btns.length; i++) {btns[i].onclick = function () {console.log(i);}}
问题:for循环会在一瞬间完成,当你触发点击事件的时候,i已经成为了4
用之前学过的方法解决
先把索引存起来,变成自己的私有属性
for (var i = 0; i < btns.length; i++) {btns[i].myIndex = i;btns[i].onclick = function () {console.log(this.myIndex);}}
用立即执行函数解决
给每个 button创造一个独立作用域,立即执行函数执行的时候,i (实参)的值被赋值给 ii ,此后 ii 的值一直不变,实现了全局变量私有化。
for (var i = 0; i < btns.length; i++) {// 每一次循环,都会创建一个独立的作用域// 功能: 全局变量私有化(function (ii) {btns[ii].onclick = function () {console.log(ii);}})(i)// 实参}
四、闭包(理解)
4.1、函数外部如何访问局部变量
- JS中每个函数都有自己的作用域,在当前函数中定义的变量,只能在该函数或该函数的嵌套函数中访问。如:
function fn(){var a = 123;console.log(a); // 123}console.log(a);// 报错, 局部变量不能在外部使用
- 在开发中,如果需要在当前函数作用域外访问函数的局部变量能实现吗?看下面的代码:
- 在函数fn中定义了另一个函数fn2,fn2中访问函数外部的局部变量a,最后将fn2返回。
- 调用fn得到返回值(fn2函数),因为该返回值是一个函数,所以我们调用得到函数中返回的a的值。
function fn() {var a = 123;function fn2() {return a;}return fn2; // 跟之前学的特权方法一样,将函数的私有成员拿到外部使用}var fn3 = fn();console.log(fn3()); // 123
- 需求: 将局部变量拿到外部访问
function fn() {// 直接返回变量a--------注意!!!返回的是值(10)var a = 10;return a;}// 改进写法,函数中返回一个函数的结构------闭包function fn() {// 返回一个函数-------返回的是一个执行环境----返回的是变量本身var a = 123;return function () {return a;}}var a = fn(); // 函数fn调用完的时候,局部变量a还没有被销毁,并且永远不会被销毁console.log(a()); // 123
- 定义:像这种函数中返回另一个函数的结构称为闭包。
特点:
- 可以避免使用全局变量,防止全局变量污染
- 保护了私有成员的安全,不会被修改
- 打破作用域的限制,让外部访问函数内部变量成为可能,延长变量的生命周期
- 局部变量会常驻在内存中,会造成内存泄露的问题(有一块内存永远被占用不会释放)
4.2、定时器时间和闭包的执行
- 需求:使用setTimeout开启10个定时器,每隔一秒输出一个数字 0,1,2,3,4,5,6,7,8,9。
这么简单的需求,直接用for循环:
for (var i = 0; i < 10; i++) {// console.log(i);setTimeout(function () {// 定时器执行的时候,for循环已经执行完毕console.log(i);}, 1000);}
for循环会一瞬间完成,定时器是异步,此时已经全部是10。结果输出了10个10,而不是我们需要的连续的数字。
方法一:立即执行函数实现
那么结果方案其实已经出来了,因为定时器中的函数没能立即执行才造成这个问题,那我们让它立即执行不就OK了吗!
for (var i = 0; i < 10; i++) {// console.log(i);setTimeout((function () {// 定时器执行和立即执行函数没关系// setTimeout(fn,time)// 定时器的第一个参数是函数 此时立即执行完返回的是undefined,传的是undefined-----定时器bugconsole.log(i);}()), 1000*i);}
setTimeout第一个参数需要一个函数,此时传递的确是一个undefined。结果是一瞬间输出0到9。
方法二:闭包实现 —— 打破作用域的限制.让局部变量在全局访问成为可能
function fn() {return function (j) {console.log(j);}}for (var i = 0; i < 10; i++) {// 定时器允许接受第三个以及以上的参数 ---- 这些参数是传递给 定时器第一个参数(函数)的 参数// setTimeout(fn,time,a,b....) a,b...是fn函数的参数setTimeout(fn(), 1000 * i, i);}
这里的 fn() 返回了 function (j) { console.log(j); } 匿名函数 j形参 i实参
结果是每隔1秒输出一个数字0到9,成功。
setTimeout(function (a, b) {console.log(a + b);}, 1000, 3, 4);// 输出7
4.3、闭包实现点按钮,显示对应的索引
<button>1</button><button>2</button><button>3</button><button>4</button><button>5</button>var btns = document.getElementsByTagName("button");
function fn(ii) { // 2. 保护了私有成员(ii)的安全,不会被修改return function () {// 异步console.log(ii);}}for (var i = 0; i < btns.length; i++) { // 1. 可以避免使用全局变量,防止全局变量(i)的污染// 同步// 3. 让函数外部访问局部变量成为可能,打破了作用域的限制,延迟了变量的生命周期btns[i].onclick = fn(i); // 4. 会造成内层泄露的问题(有一块内存永远被占用不会释放)}
五、递归
5.1、递归的基本结构(掌握)
定义:函数中用调用函数自己的结构称作递归
function f1() {console.log('反复调用');f1();};f1(); // 重复打印无数次。浏览器崩溃,因为没有结束条件——死循环
递归两个要素
1.递归的边界——找到出口,在什么情况下跳出递归
2.递归的逻辑——找到入口,什么情况下重复调用自己,调用自己做什么
var i = 0;function fn() {i++;if (i < 5) {// 入口: 进入递归(递归的逻辑)console.log('反复调用');fn();}// 出口: 什么时候跳出递归}fn();
需求:封装一个方法,计算正整数num的阶乘(递归阶乘)
什么是阶乘(factorial):所有小于及等于该数的正整数的积
// 阶乘: 3! 3*2*1// 需求: 求n的阶乘(函数封装)function fn(n) {// return 3*2*1;// 需要一个出口if (n <= 1) {return 1;}return n * fn(n - 1); // 入口/*3*fn(2)3*2*fn(1)3*2*1*/}console.log(fn(10));
5.2、拷贝(掌握)
- 需求:将p1完全拷贝到p2
let p1 = {name: '父亲',age: 30,sz: ['前端', 'Java', 'C语言'],dx: {name: '母亲',age: 26,sz2: ['香奈儿', '爱马仕', '古驰']}}// name和age存放在栈,sz数组跟dx对象存放在堆内存中,// dx的堆内存又是一个新空间有栈堆,在dx的栈堆里又有name,age,sz2let p2 = {};
3.2.1、浅拷贝实现
把地址给原封不动拷贝进去了
// 浅拷贝----for in 循环 将p1的每一个键值对赋值给p2for (var k in p1) {p2[k] = p1[k];}p2.name = '母亲2'; // p1不会改 (基本类型拷贝的是值不影响)p2.dx.age = 27; // 将拷贝完的p2里面的dx里面的name母亲年龄改成27 (改了地址里面的东西对p1有影响)console.log(p2);console.log(p1); // 发现p1里面的母亲也改成了27,会发生一改全改的情况(数据安全的问题)
缺点:对象内属性是引用数据类型的话,拷贝过来的就是引用地址,并没有实现真正的拷贝,依然同一份数据。
3.2.2、深拷贝实现
所以要实现深拷贝,当我们发现属性对应的值是一个对象或数组的时候,应该将该对象或数组再拷贝一份,然后赋值给当前属性。
5.3、排序(至少掌握一种)
5.3.1、冒泡排序法(Bubble Sort)
一种计算机科学领域的较简单的排序算法。
基本思路是:重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来
需求:将数组[5,3,9,4,6]按从大到小的顺序重新排序(降序排列),目标 [9,6,5,4,3]
// 冒泡排序算法 实现: 重复走访每两个元素,两两比较, 如果不符合逻辑(降序,升序), 两两交换顺序------对数据进行排序var arr = [5, 3, 9, 6, 4];// 轮数---- 为什么是arr.length - 1 因为只有4轮for (var i = 0; i < arr.length - 1; i++) {// 为什么是i+1 在第i轮的时候 比较 是从i+1个数开始的// 次数---- 为什么是arr.length 因为每次都要比较到最后一个数字for (var j = i + 1; j < arr.length; j++) {// 判断 左边小于右边的话.两两交换位置if (arr[i] < arr[j]) {// 左边是变量,右边是数组----解构[arr[i], arr[j]] = [arr[j], arr[i]];}}}console.log(arr); // [9, 6, 5, 4, 3]
5.3.2、Array的原型方法sort排序
// sort是js封装好的一种排序方法,Array的原型方法,可以给数组的所有实例使用console.dir(Array.prototype.sort);// var arr = [2, 5, 1, 7, 3];var arr = [2, 5, 1, 4000, 3];// 直接调用的方式(默认升序),只适合简单0到9数字。console.log(arr.sort()); // [1, 2, 3, 4000, 5] 只认第一个数值来排序// 真 sort 排序--------工作要用var newArr = arr.sort((a,b)=>{// 升序 a-breturn a-b; // [1, 2, 3, 5, 4000]// 降序 b-areturn b-a; // [4000, 5, 3, 2, 1]})console.log(newArr);
5.3.3、快速排序法(Quick Sort)
快速排序是对冒泡排序的一种改进。
基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
需求:随机获取10到999之间的100个整数,并且要从小到大排序,要求使用快速排序算法。
//笔试题: 随机获取10到999之间的100个整数,并且要从大到小排序,要求使用快速排序算法。// 获取10到999之间的100个整数// 参数设计: min最小值,max最大值,total多少个function getRandomNum(min, max, total) {let arr = [];for (let i = 0; i < total; i++) {arr.push(Math.floor(Math.random() * (max - min + 1) + min))}return arr;}// console.log(getRandomNum(1, 5, 10));// let arr = [9, 5, 8, 3, 7];// 小需求: 将arr进行从小到大排序let arr = getRandomNum(10, 999, 100);function arrSort(_arr) {// 5.*** 递归的出口if (_arr.length <= 1) {return _arr;}// 1. 取出中间数,并且改变原数组let index = Math.floor(_arr.length / 2);// console.log(index);let mid = _arr.splice(index, 1)[0];// console.log(mid);// console.log(_arr);// 2. 比较原数组,小于自己的数放在左边 反之放在右边let left = [],right = [];_arr.forEach(item => {// if (item > mid) {if (item > mid) { // <从大到小,>从小到大right.push(item);} else {left.push(item);}})// 3. 数组的拼接// console.log(right,left);// 4. 递归调用 (入口)return arrSort(left).concat(mid, arrSort(right));}console.log(arrSort(arr));
六、数组去重
function getRandomNum(min, max, total) {let arr = [];for (let i = 0; i < total; i++) {arr.push(Math.floor(Math.random() * (max - min + 1) + min))}return arr;}let arr = getRandomNum(1, 5, 10);console.log(arr); // [3, 1, 5, 1, 3, 4, 2, 1, 5, 3]// 1. 利用对象的特征,key不能重复// obj.name = name 如果对象有这个key就是覆盖,没有就添加let obj = {};arr.forEach(item => {obj[item] = item;})console.log(Object.values(obj)); // [1, 2, 3, 4, 5],因为对象是按照索引排序所以从小到大排序// 2. 利用Set类型----ES6console.log(new Set(arr)); // {3, 1, 5, 4, 2} 得到的是一个去重的对象console.log(Array.from(new Set(arr))); // [3, 1, 5, 4, 2]
七、设计模式简单介绍(了解)
7.1、单例模式
定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
核心:确保只有一个实例,并提供全局访问。
实现:假设要设置一个管理员,多次调用也仅设置一次,我们可以缓存一个内部变量来实现这个单例。
function Person(name) {this.name = name;}var p1 = new Person("张三");var p2 = new Person("李四");// 正常来说,创建出来的是两个实例console.log(p1 === p2); // false// 一个构造函数有且仅有一个实例var instance = nullfunction Person(name) {if (instance) {// 第二次实例化-------p2// 此时 instance = p1-----此时已经产生实例了return instance;// 返回的还是p1的地址} else {// 第一次实例化------p1 张三// 此时 instance = nullthis.name = name;instance = this;// instance = p1}}var p1 = new Person("张三");var p2 = new Person("李四");console.log(p1 === p2); // true 因为是同一个地址
7.2、观察者模式(Observer pattern)(直接联系)
定义:定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。
举例:
- 售楼部的客服小姐姐—-有新的房源—-被观察者Subject
- 直接通知给不同的潜在客户—-观察者Observer
- 不同客户做出不同的反应
画图说明:
7.3、发布-订阅模式(Publish–subscribe pattern)(间接联系)
定义:类似观察者模式,只有中间多了一个调度中心。
举例:
- 售楼部的客服小姐姐—-有新的房源—-发布者(Publish)
- 小姐姐将新的房源信息发布到一个公众号平台(调度中心)
- 需要买房的用户(订阅者subscribe)看到公众号信息
- 不同的客户做出不同的反应
画图说明:
易混淆点 :很多资料都会将观察者模式和发布订阅模式混为一谈,因为都是存在两个角色实现一对多关系(被观察者-观察者,发布者-订阅者),确实容易混淆,他们忽略了发布订阅模式里的经纪人角色或者叫调度中心。
八、原生AJAX(xhr)
8.1、响应处理和响应流程(掌握)
原生xhr对象 : XMLHttpRequest-----内置构造函数,用于在后台与服务器交换数据。
响应处理,即对服务响应回浏览器的数据根据状态码和 AJAX 对象的状态信息进行不同的处理,
在绑定状态改变的处理函数中写对应的逻辑代码即可。
XMLHttpRequest中有 4 个属性:
- readyState 总共有 5 个状态值,分别为 0 ~ 4,每个值代表了不同的含义:
- 0:初始化,AJAX 对象还没有完成初始化
- 1:载入,AJAX 对象开始发送请求
- 2:载入完成,AJAX 对象的请求发送完成
- 3:解析,AJAX 对象开始读取服务器的响应
- 4:完成,AJAX 对象读取服务器响应结束
- status 表示响应的 HTTP 状态码,常见状态码如下:
- 200 OK:请求成功
- 302 Found:重定向,新的 URL 会在 response 中的 Location 中返回,浏览器将会使用新的 URL 发出新的 Request
- 304 Not Modified:已缓存,文档已经被缓存,直接从缓存调用
- 400 Bad Request:客户端请求有语法错误,不能被服务器所理解
- 403 Forbidden:服务器收到但拒绝服务,引用外部资源触发防盗链
- 404 Not Found:找不到资源,请求资源不存在
- 500 Internal Server Error:服务端错误,服务器发生了不可预期的错误
- 503 Server Unavailable:服务器当前不能处理客户端的请求,一段时间后可能恢复正常
- responseText 获得字符串形式的响应数据。
- responseXML 获得 XML 形式的响应数据。(现在基本不用)
综合以上,在状态改变的处理函数一般针对 readyState == 4 且 status == 200 的情况才处理,再根据后台返回的数据类型决定从 responseText 或者 responseXML 获取服务器响应回去来的数据。
8.2、使用ajax发送get请求
步骤:
// 1、创建 AJAX 对象;new XMLHttpRequest()// 2、设置请求路径,请求方式等;ajax.open(请求方式,路径); get请求在url后面带参// 3、绑定监听状态改变的处理函数,在处理函数可获取响应数据;ajax.onreadystatechange// 4、发送请求;ajax.send(); post请求在send()里带参
代码:
8.3、使用ajax发送post请求
post请求需要传递参数给后台。
请求头的 Content-Type:浏览器告诉服务器请求数据的类型
响应头的 Content-Type:服务器返回给浏览器的数据类型
常见请求头 :
- application/x-www-form-urlencoded ———- 浏览器默认
- application/json ————- JSON 字符串
- multipart/form-data ———- 文件类型:表单上传文件
代码:
用户名:<input id="user" type="text"><br>密码: <input id="pwd" type="password"><br><button id="btn">登录</button>ajax.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')ajax.send(`username=${user.value}&password=${pwd.value}`)
九、JSON数据处理(掌握)
9.1、eval函数的基本使用
作用:将字符串类型的参数转换成JS代码。
eval函数Function的区别:
- Function:需要调用函数,才会执行代码
- eval:转换之后立即执行
在开发中的应用场景:
当获取到一个字符串类型的数据,但是想将其转换成JS代码执行的时候使用该函数可以实现
var jsonStr = "({name:'Neld',age:10})";console.log(eval(jsonStr));//转换成JS的对象
在实际开发中不建议使用eval函数,原因:
- 存在安全隐患
- 影响程序的执行性能
- 事实上,目前只有后端在用eval这种方式,前端开发有更好的方式:JSON方法。
9.2、JSON
JSON数据格式
定义:JSON 格式(JavaScript Object Notation 的缩写)是一种用于数据交换的文本格式
前后端数据交互的常见格式(尤其是后台数据,基本上都是json)。
JSON 对值的类型和格式有严格的规定:
- 复杂数据类型的值只能是数组或对象,不能是函数、正则表达式对象、日期对象。
- 原始类型的值只有四种:字符串、数值(必须以十进制表示,八进制十六进制….都不行)、布尔值和
null(不能使用NaN,Infinity,-Infinity和undefined)。 - 字符串必须使用双引号表示,不能使用单引号。
- 对象的键名必须放在双引号里面。
- 数组或对象最后一个成员的后面,不能加逗号。
- 不能写注释
// 以下都是合法的 JSON:["one", "two", "three"]{ "one": 1, "two": 2, "three": 3 }{"names": ["张三", "李四"] }[ { "name": "张三"}, {"name": "李四"} ]// 以下都是不合法的 JSON:{ name: "张三", 'age': 32 } // 属性名必须使用双引号[32, 64, 128, 0xFFF] // 不能使用十六进制值{ "name": "张三", "age": undefined } // 不能使用 undefined{ "name": "张三","birthday": new Date('Fri, 26 Aug 2011 07:13:10 GMT'),"getName": function () {return this.name;}} // 属性值不能使用函数和日期对象
JSON对象
JSON对象是 JavaScript 的原生对象,用来处理 JSON 格式数据。它有两个静态方法:
JSON.stringify(JS对象); // 序列化:JS对象转JSON字符串JSON.parse(JSON字符串); // 反序列化:JSON字符串转成相应的数据格式(JS对象)
需求:后台需要前端传参 传递 json数据格式
// JSON.parse-----------js对象化----------反序列化(json字符串转换js对象)// JSON.stringify------字符串化 ------- 序列化(js对象转换json字符串)let obj = {name: '父亲',age: 30,sz: ['前端', 'Java', 'C语言'],dx: {name: '母亲',age: 26,sz2: ['香奈儿', '爱马仕', '古驰']}}let jsonStr = JSON.stringify(obj);console.log(jsonStr); // {"name":"父亲","age":30,"sz":["前端","Java","C语言"],"dx":{"name":"母亲","age":26,"sz2":["香奈儿","爱马仕","古驰"]}}console.log(typeof jsonStr); // stringlet newObj = JSON.parse(jsonStr);console.log(newObj); // 一个对象{name: '父亲', age: 30, sz: Array(3), dx: {…}}
需求:使用JSON方法实现深拷贝
let newObj = JSON.parse(JSON.stringify(obj));// 上述代码已经完成深拷贝------工作中(开发)用的-----实现原理(字符串赋值的是值不是引用地址)newObj.dx.age = 27; // 将拷贝完的newObj里面的dx里面的name母亲年龄改成27console.log(obj);console.log(newObj); // 发现obj里面的母亲还是26,obj对象是和newObj完全不同的两份数据,此时(不再存在数据共享的问题)
十、正则表达式(理解)
10.1、正则的创建
正则的作用就是:判断字符串是否满足一定的规则
在js里面,已经封装好了一种对象:正则对象,所以我们每次使用,只需要创建出来就可以直接使用。创建正则对象的方式分种:
// 内置构造函数创建let reg = new RegExp('规则','匹配模式')// 字面量创建 /正则表达式/修饰符let reg = /aaabbcc/img;/*修饰符主要包括:1. i 忽略大小写匹配2. m 多行匹配,即在到达一行文本末尾时还会继续寻常下一行中是否与正则匹配的项3. g 全局匹配 模式应用于所有字符串,而非在找到第一个匹配项时停止*/
- 内置构造函数RegExp的原型方法 test exec 演示
let str = "you miss someone goto so much that you just want to pick them from your dreams and hug them for real! Dream what you want to dream;go where you want to go;be what you want to be,because you have only one life and one chance to do all the things you want to do";let reg = /you/g;// test 用于检测一个字符串是否匹配某个模式,如果字符串中含有匹配的文本,则返回 true,否则返回 falseconsole.log(reg.test(str)); // truelet result;// 1、将结果存入result 2、判断result条件是否成立 result==null:不成立// exec 函数返回一个数组,其中存放匹配的结果。如果未找到匹配,则返回值为 nullwhile (result = reg.exec(str)) {console.log(result); // 返回很多个长度为一的数组console.log(result[0], result.index); // [0]取出里面的值you,index取索引}
10.2、正则表达式的规则
正则的基本使用还是比较简单的,难在我们要表达出我们想要的规则,而在正则中,使用不同的元字符来实现不同的规则。
特定规则
\d 匹配数字 \D 和\d相反
\w 匹配数字 加 字母 加 _ \W 和\w相反
\s 匹配空白字符 \S 和\s相反
数量限定
*匹配特定规则出现任意次
+匹配特定规则出现至少1次? 匹配特定规则现出1或者0次(前面的字符可有可无)
{n} 匹配特定规则出现n次
{n,} 匹配特定规则出现至少n次
{n,m} 匹配特定规则出现n到m次
ab+ * ?{1,3} c
开关结尾限定
^ 匹配特定规则开关
$ 匹配特定规则结尾
例如:
// 匹配手机号// 1.手机号以1开关 2.共11位数字/^1\d{10}$/
范围限定
范围限定使用的[]用法比较特殊,大概分为这么三种
1.连续范围
[0-9] 匹配数字 [a-z] 匹配小写字母 [3-8] 匹配3到8之间的数字
2.范围集合
[0-9a-zA-Z] 匹配数字+字母
3.无规律集合
[259a-f*] 匹配 2 和 5 和 9 和 a到f 和 `` 中的任意字符
4.取反
[^0-9] 匹配非数字
其它
. 匹配任意单个字符
\ 反斜杠 转义
() 分组
| 或者
// 手机号匹配/^1[3|4|5|7|8][0-9]{9}$/.test(phone)// 校验16进制颜色 7个字符: #开头 剩下6个字符是十六进制0-9a-fA-F/^#[a-fA-F0-9]{6}$/.test("#FBB2h0")
十一、异步任务(理解)
11.1、执行栈
想要想明白异步编程的执行顺序,首先要知道js代码是如何执行的。此时有一个概念一定要先知道:执行栈
执行栈,也称“调用栈”,是一种拥有 后进先出 的数据结构,被用来存储代码运行时创建的所有执行上顺序。
当 JavaScript 引擎第一次遇到你的脚本时,它会创建一个全局的执行环境并且压入当前执行栈。每当引擎遇到一个函数调用,它会为该函数创建一个新的执行环境并压入栈的顶部。
引擎会执行处于栈顶的执行环境的函数。当该函数执行结束时,执行环境从栈中弹出,控制流程到达当前栈中的下一个执行环境。
让我们通过下面的代码示例来理解:
// 执行栈的调用规则: 先入后出 (包括栈内存也是先入口出,堆内存是列队----先入先出)// 也叫执行环境的 入栈(压栈) 和 出栈(弹栈)console.log('全局环境开始');//1let a = 10;function first() {console.log('函数1');// 2second();console.log('两次回到函数1');//4}function second() {console.log('函数2');// 3}first();console.log('全局环境结束');//5
上面的代码可以用这样的过程来理解
1.首先是全局的执行环境入栈
2.在全局环境下调用了first函数,再把first函数的环境压入栈中
3.在first函数里面调用了second函数,再把second函数的环境压入栈中
4.second执行完毕,于是把second的执行环境从栈中移除(先进后出,后入先出)
5.回到first的执行环境,再把fist的代码执行完成,从执行栈中再移除
6.最后把全局的执行环境也出栈,整个程序执行完成
11.2、javascript是单线程的
JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊。
JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。
// 异步任务-任务列队-------先入先出setTimeout(() => {console.log("定时器1");// 定时器3 在500ms之后进入列队,此时 定时器2只剩500mssetTimeout(() => {console.log("定时器3");}, 500) // 定时器3改400 控制台输出 :循环2000次,定时器1,定时器3,定时器2}, 500)setTimeout(() => {console.log("定时器2");}, 1000)// 先执行同步再执行异步---------js的单线程for (var i = 0; i < 2000; i++) {console.log("循环2000次");}// 进入顺序:定时器1,定时器2,定时器3// 出来顺序:定时器1先出来后定时器2进入并且剩下500 定时器3最后进入也剩下500 同样剩下500定时器2跟1同级[可以理解为宏任务]先执行// 控制台输出 :循环2000次,定时器1,定时器2,定时器3
11.3、EventLoop和任务队列
Event Loop即事件循环,是指浏览器或Node的一种解决javaScript单线程运行时不会阻塞的一种机制,也就是我们经常使用异步的原理。
单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。
如果排队是因为计算量大,CPU忙不过来,倒也算了,但是很多时候CPU是闲着的,因为IO设备(输入输出设备)很慢(比如Ajax操作从网络读取数据),不得不等着结果出来,再往下执行。
JavaScript语言的设计者意识到,这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。
于是,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入”任务队列”(task queue)的任务,只有”任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
具体来说,异步执行的运行机制如下。
1.所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。2.主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。3.一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。4.主线程不断重复上面的第三步。
于是就会形成下面这样一个执行模型
11.4 Task(宏任务)和 MicroTask(微任务)
而在代码的执行过程中,我们还把所有的分为两个大类,宏任务和微任务
| 宏任务 | 微任务 |
|---|---|
| script环境(同步) | Promise(同步)的then/catch回调(then/catch异步) |
| setInterval/setTimeout 定时器(异步) | Object.observe(先忽略) |
| requestAnimationFrame 浏览器的帧循环(先忽略) | Proxy(先忽略) |
| UI Rendering 浏览器的UI渲染(先忽略) |
Event Loop中,每一次循环称为tick,每一次tick的任务如下:
执行栈在执行完同步任务后,查看执行栈是否为空,如果执行栈为空,就会去执行Task(宏任务),每次宏任务执行完毕后,检查微任务(microTask)队列是否为空,如果不为空的话,会先执行完微任务(microTask)后,设置微任务(microTask)队列为null,然后再执行宏任务,如此循环
以下面的代码为例:
console.log('1');setTimeout(function() {console.log('2');new Promise(function(resolve) {console.log('3');resolve();}).then(function() {console.log('4')})})new Promise(function(resolve) {console.log('5');resolve();}).then(function() {console.log('6')})console.log('7')
执行过程如下
- 全局打印1
- 创建定时器,宏任务未执行
- 创建Promise对象是同步,先执行打印5
- 再次全局打印7
- 执行微任务.then打印6
- 执行宏任务定时器
- 进入主线程,打印2
- 同步代码创建Promise对象,打印3
- 执行微任务.then打印4
所以最终的结果为: 1,5,7,6,2,3,4
