从零开始的JavaScript学习之旅——Week 3

409 阅读17分钟

正则表达式

定义: 定义字符串中【字符出现规则】的表达式 使用场景: 切割、替换、验证

定义正则表达式

语法:/正则表达式/

最简单的正则: 关键字原文本身:例:"no"的正则:/no/ig(i:忽略大小写,g:全部)

备选字符集

一个备选字符集:规定了一位字符可用的备选字符列表

语法:/[备选字符列表]/

注意:

  1. 一个中括号,只能匹配一位字符
  2. 正则默认只要满足条件后,就不再管理后续操作,后续用户可以随意输入
    • 解决:只要是做验证!!!必须使用/^[备选字符集]$/,前加^后加$代表要求从头到尾完全匹配 特殊:
  3. 如果备选字符集中,unicode号是连续,那么中间部分可用-代替
举例:
    一位数字:[0-9]
    一位字母:[A-Za-z]
    一位数字、字母、下划线:[0-9A-Za-z_]
    一位汉字:[\u4e00-\u9fa5]
  1. 除了数字、英文字母之外,其他都可以使用,但是很少使用,因为范围太广了

预定义字符集

预定义字符集: 前辈们提供好了一些常用的字符集的简化写法:

    一位数字:\d	==>	[0-9]
    一位数字、字母、下划线:\w  ==> [0-9A-Za-z_]
    一位空白字符:\s  ==>   空格、制表符、换行
    一位除了换行外的任意字符:.	-	范围太广了			
建议:优先使用预定义字符集,如果预定义字符集没有,在用备选字符集补充

注意: 不管是备选字符集,还是预定义字符集,一个都只管一位

量词

量词: 规定了一个字符集出现的次数

  1. 有明确数量 字符集{n,m}: 前边相邻的字符集,最少出现n次,最多出现m次

字符集{n,}: 前边相邻的字符集,最少出现n次,多了不限

字符集{n}: 前边相邻的字符集,必须出现n次

  1. 没有明确数量 字符集?: 前边相邻的字符集,可有可无,最多一次 字符集* 前边相邻的字符集,可有可无,多了不限 字符集+: 前边相邻的字符集,至少一次,多了不限

指定匹配的位置

^:以xxx开头

$:以xxx结尾

注意: 如果^和同时出现:前同时出现:前加^后加,代表要求从头到尾完全匹配(验证必须加)

选择和分组

选择: 规则1|规则2 - 可以再多个规则中选择满足的规则进行执行

分组: 添加子规则:(规则1|规则2)

预判

举例:
    密码强度:2-4位字母和数字的组合,至少一位大写字母和一位数字
    公式:
    (?![0-9]+$)字符集量词	-  不能全由数字组成
    (?![0-9A-Z]+$)字符集量词 -  不能全由数字组成、不能全由大写组成、不能全由数字和大写的组合组成
    (?![a-z0-9]+$)(?![a-zA-Z]+$)(?![0-9A-Z]+$)[0-9A-Za-z]{2,4}
                                       -不能是纯数字、纯大写、纯小写
					也不能是小写和数字的组合
					也不能是小写和大写的组合
					也不能是数字和大写的组合

字符串中支持正则表达式的API:分割、替换:

分割字符串

语法:var arr=str.split(reg);

替换字符串:

  1. 基本替换法: 语法:var newStr=str.replace(reg,"新内容");

注意:

  • 默认只会替换第一个关键字,想要替换所有记得加上后缀g
  • 替换的内容只能是一个固定的新内容
  1. 高级替换法
举例:
    var newStr=str.replace(/[我卧窝][槽操艹草肏去]+/g,function(a,b,c){
        console.log(a);//正则匹配到的关键字
        console.log(b);//正则匹配到的关键字的下标
        console.log(c);//原文本身
        return a.length==2?"**":"***";
    });
  1. 格式化:如果替换API使用正则时,并且里面带有分组,那么你会得到更多的形参
举例:
    var newStr=str.replace(reg,function(a,b,c...){
        console.log(a);//正则匹配到的关键字
        console.log(b);//第一个分组获取到的内容
        console.log(c);//第二个分组获取到的内容
        return "格式化的东西"
    });

正则对象

创建正则对象:

直接量:var reg=/正则表达式/后缀 构造函数:var reg=new RegExp("正则表达式","后缀")

方法

语法:var bool=reg.test(user);

布尔值如果是true,说明用户输入的符合我们的要求,false说明不通过


Math

Math:专门提供了数学计算的API

注意: Math不能创建,不需要创建,直接使用!

唯一的属性: Math.PI

API:

取整:3种

  1. 上取整:只要超过一点点,就会取下一个整数,此方法小数位数不能超过15位否则会失效:Math.ceil(num);
  2. 下取整:不管超过多少,都会省略掉小数部分,此方法小数位数不能超过15位否则会失效:Math.floor(num);
  3. 四舍五入取整:Math.round(num);
    • 问题:四舍五入虽然不错,但是以上三个API只能取整
    • 解决:棒的:parseFloat(num.toFixed(d));//既有四舍五入功能,又具有保留自定义小数位数的操作,结果是一个字符串
举例:
鄙视题:
    封装一个函数,实现可以自定义保留小数位数并且四舍五入的功能,但是不允许使用toFixed?
    function round(num,d){
        num*=Math.pow(10,d);
        num=Math.round(num)
        num/=Math.pow(10,d);
        return num.toString();
    }

乘方和开方

乘方:Math.pow(底数,幂);

开方:Math.sqrt(num);(只能开平方)

最大值和最小值

语法:Math.max/min(a,b,c,d,e,f....);

获取到最大的一个数或者最小的一个数

  • 问题:不支持数组参数
  • 解决:Math.max/min.apply(Math,arr); apply:自己没有的方法可以去借用,可以将数组打散为单个参数悄悄进行传入

绝对值:将负数转为整数

语法:Math.abs(num);

随机数

随机数:只要页面上具有随机的功能,底层一定用到了随机数

语法:Math.random(); 已经是一个随机数了,随机的小数0-1,有可能取到0,但是绝对不可能取到1即能取到最小值,但是取不到最大值

公式:parseInt(Math.random()*(max-min+1)+min);


Date

Date:封装了一个日期对象,提供了对日期事件进行操作的API

使用场景: 只要网页上跟事件日期相关的,都要使用date对象

创建日期对象

  1. 创建当前时间: 语法:var now=new Date();

  2. 创建自定义时间: 语法:var birth=new Date("yyyy/MM/dd hh:mm:ss");

  3. 创建自定义时间: 语法:var birth=new Date(yyyy,MM,dd,hh,mm,ss);

缺点:月份需要修正:计算机中月份是从0开始到11的

  1. 复制一个日期对象: 原因: 日期对象的API都是直接修改原日期对象,使用API后,无法同时保存住旧的日期对象

使用场景: 在调用日期对象的API之前都要先复制,在使用API

语法:var end=new Date(now);

  1. 毫秒数 语法:var xxx=new Date(毫秒数); 计算器其实保存的就是从1970年1月1日至今的毫秒数

操作

日期相减

两个日期对象之间可以相减,得到毫秒差,换算出你想要的任何一部分,其实日期对象保存的就是一个毫秒数

API

分量:(时间单位)

FullYear(年)、Month(月)、Date(日)、Day(星期)、Hours(时)、Minutes(分)、Seconds(秒)

  1. 每一个分量都有一对儿方法:getXXX()/setXXX() 特殊:
  • 取值范围: 年:当前年份

月:0-11

日:1-31

时:0-23

星期:0-6,0代表星期天

  • 星期只有get,没有set
  1. 固定套路:对着某个日期直接做加减 语法:date.setXXX(date.getXXX()+3);

格式化为字符串

  • 国际化日期格式化为本地化日期:date.toLocaleString();
    • 缺点:
      1. 具有浏览器的兼容性问题
      2. 则不可以再使用日期对象的API,也不会再带有进制操作了
    • 好处:转为了字符串,可用字符串的API
    • 解决:自定义format格式化

Error(错误对象)

浏览器自带4种错误类型

语法错误:SyntaxError - 多半都是哪里的符号写错了

引用错误:ReferenceError - 根本就没有创建过,就去使用了

类型错误:TypeError - 不是你的方法,你却使用了

范围错误:RangeError - 只有一个API:num.toFixed(d);//d的范围只是0-100之间

注意: 需要记忆错误类型,帮助我们快速找错。

错误处理

错误处理: 当程序发生错误时,保证程序不会异常中断的机制 原因: 只要报错,就会导致后续代码终止(闪退),用户体验感降低

语法:
        try...catch...语句
            try{
            可能出错的代码
            }catch(err){
            console.log(err);//提示用户错误的原因是什么
            }
        后续代码都可以执行到
不推荐:try...catch...的执行效率非常的低下,更推荐的是if...else
想要用if...else去预判用户的错误的前提:丰富的经验(其实我一直在带你们防用户!isNaN、正则)

抛出自定义错误

语法:throw new Error("自定义错误信息");

Function(函数对象 - 方法)

创建

  1. 声明方式 语法:function 函数名(形参列表){函数体;return 结果;} 具有完整的声明提前

  2. 直接量方式 语法:* 函数名=function(形参列表){函数体;return 结果;} 也有声明提前,但是赋值留在原地

  3. 构造函数方式 - 函数体不是固定的 语法:var 函数名=new Function("形参1","形参2",...,"函数体"); 函数体是动态拼接时候,注意不能省略""部分

调用

语法:函数名();元素.onclick();

函数考点

  1. 创建

  2. 作用域:全局(成员哪里都可以使用)、函数(成员只能在函数调用时内部可用)

    • 变量的使用规则:优先使用局部的,局部没有找全局,全局没有就报错
  3. 声明提前:在程序正式执行之前,会将var声明的变量和function声明的函数集中提前到当前作用域的顶部,但是赋值留在原地,变量比函数轻

  4. 按值传递:如果传递的是原始类型,互不影响。如果传递的是引用类型,相互影响。

  5. 重载:相同的函数,根据传入的实参的不同,会自动选择对应的函数执行

    • 原因:减少程序员的负担
    • 问题:JS不支持重载,JS不允许同时存在多个同名函数,如果存在,最后的一个会覆盖之前的所有函数
    • 解决:arguments对象 - 只能在函数内部可用,是一个类数组对象:
      • 作用:接住所有的实参,【甚至不用形参都可以了】
      • 只有3点相同:1. 支持下标2. 支持length3. 支持遍历 变相实现重载:通过判断arguments的不同,执行不同的操作
  6. 匿名函数:没有名字的函数,没有变量名/函数名引用着,调用完毕后会立马释放 - 一次性函数

    • 匿名函数自调:代替全局作用域 (function(){ 函数体; - 好处:调用完毕会【立刻释放】 })()

    • 匿名函数回调:某个函数调用时,传入的实参又是一个函数,而且不需要我们调用自动执行

       举例:
         arr.sort(function(a,b){return a-b})
         str.replace(reg,function(){})
       匿名函数,不是自调,就一定是回调
      

Function

闭包

作用域:2种

  1. 全局作用域:成员 随处可用,可以反复使用,容易被污染

  2. 函数作用域:成员 只有当前函数调用时内部可用,调用完了,就会释放了,一次性

函数的执行原理:

  1. 程序加载时:

    • 创建执行环境栈(ECS):保存函数调用顺序的数组
    • 首先压入全局执行环境(全局EC)
    • 全局EC中引用着全局对象window
    • window中保存着全局变量
  2. 定义函数时:

    • 创建函数对象:封装函数的定义
    • 在函数对象中创建scope属性,记录着自己来自的作用域
    • 全局函数的scope都是window
  3. 调用函数前

    • 在执行环境栈ECS压入新的函数的EC
    • 创建活动对象AO:保存着本次函数调用时用到的局部变量
    • 在EC中添加scope chain属性引用AO
    • 设置AO的parent属性为函数的scope引用的对象
  4. 调用时:

    • 变量的使用规则:优先使用局部的,局部没有才找全局,全局没有才报错
  5. 调用完:

    • 函数的EC会出栈,AO会自动释放,局部变量也就自动释放了
面试题:
作用域链scope chain:
    以EC中的scope chain属性为起点,经过AO逐级引用,形成的一条链式结构,就称之为叫做作用域链
    作用:查找变量

闭包的目的

目的: 保护一个可以反复使用的局部变量的一种词法结构

结合了全局和局部的优点

唯一的缺点:受保护的变量永远不能释放,用多了会导致内存泄漏

尽量的少用:只有一个点会用到防抖节流

使用:

  1. 创建一个外层函数
  2. 在其中创建一个受保护的局部变量
  3. 外层函数调用要返回内层函数,此内层函数在操作受保护的变量
鄙视题:
		  1、判断闭包,找到受保护的变量,确定其值
		  2、外层函数调用几次,就创建了几个闭包,受保护的变量就有了几个副本
		  3、同一次外层函数调用,返回的内层函数,都是在使用同一个受保护的变量
固定语法:
    function 外层函数(){
        受保护的变量;
        return function(){
            不断的操作受保护的变量
            return 结果;
        }
    }	
    var 内层函数=外层函数();

防抖节流

防抖节流:减少DOM树的渲染,DOM数据渲染的次数越频繁页面的效率越底下 使用场景

  1. elem.onmousemove - 鼠标移动事件,每次移动就会触发
  2. input.oninput - input每次修改内容就会触发
  3. window.onresize - 屏幕每次改变大小就会触发
固定公式:
    function fdjl(){
        var timer=null;//3
        return function(){
            //判断有没有定时器,如果有定时器就清除定时器
            if(timer){clearTimeout(timer)}
                timer=setTimeout(function(){
                //你要做的操作
            },毫秒数)
            console.log(timer);//定时器的序号
        }
    }
    var animate=fdjl();

Object

Object:面向对象开发方式 - 三大特点(封装、继承、多态)

什么是面向对象:

  • 程序中都使用对象来描述现实中的一个事物
  • 实中事物的属性,代码中就成为了对象的属性
  • 实中事物的方法,代码中就成为了对象的函数
  • 实中所有数据都必须包含在一个事物中才有具体的意义

创建/定义/声明/实例化:自定义创建对象:3种

  1. 直接量方式:
语法:
    var obj={
        "属性名":属性值,
        ...
        "方法名":function(){},
        ...
    }

注意:

  • 其实属性名和方法名的""可以省略,但是不推荐,以后JSON必须加""

  • 如何访问对象属性和方法:obj.属性名 === obj["属性名"]obj.方法名(); === obj["方法名"]();

  • 访问到不存在的属性:返回undefined

  • 也可以在后续随时随提的添加自己想要的东西

       特殊:this在当前对象的方法内,指向的当前调用方法对象
             面试题:this指向:
       	1、单个元素绑定事件 this->单个元素
       	2、多个元素绑定事件 this->当前触发的元素
       	3、函数中也可以使用 this->当前调用函数的对象
       	4、构造函数中如果出现了this this->当前正在创建的对象
             只要以后对象的方法想要使用对象自己的属性,那么就写为this.属性名
    
  1. 预定义构造函数:
语法:
        var obj=new Object();//空对象
        obj.属性名=属性值;
        ...
        obj.方法名=function(){}
        ...
以上两个方法仅适合创建单个对象,如果想要创建多个对象则太繁琐
  1. 自定义构造函数:2步
    • 创建一个构造函数 function 类名(形参,...){ this.属性名=形参1; this.属性名=形参2; ... }

    • 反复调用构造函数创建出多个对象 var xxx=new 类名(实参,...)

 面试:聊一聊你了解面向对象和面向过程的开发方式?
	 1、面向过程:开始->经过->结束,我们一直使用的开发方式。
	 2、面向对象:对象:属性和方法
			一个人是一个对象
				属性:
				  身高
				  体重
				  年龄
				  姓名
				  三维
				方法:
	 			  吃饭
				  睡觉
				  玩耍
所有的属性和方法全都是包含在一个对象中的,比如:轮播对象、选项卡对象...

面向对象的优缺点

优:

  1. 逼格高

  2. 所有的操作都包含在一个对象中,显得更有意义

  3. 维护时非常的舒服

  4. 铁索连舟 - 一个方法调用,触发了一大堆操作 缺:

  5. 难度大 - this的指向

继承

继承定义:父对象的成员(属性和方法):子对象可以直接使用

继承原因:代码重用!节约内存空间!

使用场景:只要多个子对象公用的属性和【方法】,都应该集中定义在父对象中

JS的面向对象是基于原型(父对象)的

什么是原型:保存一类子对象共有属性和共有方法的原型对象(父对象)

  • 如何去找到原型对象:
    1. 对象名.proto - 至少要创建一个对象才可以使用
    2. 构造函数名.prototype

new 构造函数(Object RegExp Date Function String Number Boolean...)

  • 在原型对象中添加共有属性和共有方法
    1. 原型对象.属性名=属性值
    2. 原型对象.方法名=function(){}
    • 每一个对象都有一个.__proto__的属性指向着自己的原型
    • 每一个构造函数都有一个.prototype属性指向着自己的原型
面试题:两链一包:
		原型链:自己没有的属性和方法,可以顺着原型链一直向上找,直到最顶层:Object.prototype - 万物皆对象
		作用:查找属性和方法

	自有和共有
	自有:保存在对象本地的
	共有:保存在原型对象中的,子对象都可以直接使用
鄙视题:
	  1、判断一个属性是自有还是共有:
		1、判断自有:obj.hasOwnProperty("属性名");
			如果结果为true,说明是自有
			如果结果为false,可能是共有也可能是没有

		2、判断共有:2个条件
			obj.hasOwnProperty("属性名")==false;//可能是共有也可能是没有
			"属性名" in obj;//in关键字会查找自己的原型
			if(obj.hasOwnProperty("属性名")==false && "属性名" in obj){
				console.log("共有")
			}else{
				console.log("没有")
			}
		完整的:
			if(obj.hasOwnProperty("属性名")){
				console.log("自有");
			}else{
				if(obj.hasOwnProperty("属性名")==false && "属性名" in obj){
					console.log("共有")
				}else{
					console.log("没有")
				}
			}

	  2、修改/删除属性
		自有:修改:obj.属性名=新值;
		      删除:delete obj.属性名;

		共有:修改:千万不要在本地做操作,那会导致在本地添加上一个同名属性,优先使用自己的,但并没有修改原型对象
		      删除:千万不要在本地做操作,那会导致白做没有任何效果
		      强调:一定要找到原型再做操作

	  3、为一类人添加方法:
		比如:最常见的一道题:为老IE的数组添加indexOf方法 - 原本只有字符串可以使用,是后续升级数组才能使用的
			if(Array.prototype.indexOf === undefined){//老IE
				Array.prototype.indexOf = function(key,starti){
					starti===undefined&&(starti=0);
					for(var i=starti;i<this.length;i++){
						if(this[i]==key){
							return i;
						}
					}
					return -1;
				}
			}
		
		比如:为一人添加共有方法
			构造函数名.prototype.函数名=function(){
				this->函数中的代表当前调用此函数的对象
			}

	   4、判断x是不是一个数组:41、判断当前x对象是否是继承自Array.prototypeArray.prototype.isPrototypeOf(x);
			true说明是一个数组

		2、判断当前x对象是否是由此构造函数所创建
			x instanceof Array
			true说明是一个数组

		3Array.isArray(x); - 只有数组才有此方法
			true说明是一个数组

		4、最麻烦,但是我个人最喜欢
		   在Object的prototype中保存着最原始的toString方法
		   原始的toString输出的结果:[object 构造函数名]
		***多态:子对象觉得父对象提供的方法不好用,可以再本地定义一个同名成员,优先使用离自己更近的方法
			 同一个函数名,但根本不是同一个方法
		   固定套路:
			 if(Object.prototype.toString.apply(arr)==="[object Array]"){
				数组
			 }
		
	   5、如何设置自定义继承
		设置单个对象的继承:
			obj.__proto__=新对象

		设置多个对象的继承:
			构造函数名.prototype=新对象
			注意时机:在创建对象之前就设置好父对象