js小结
JavaScript包含:
ES:描述了该语言的语法和基本对象
DOM:文档对象模型,描述处理网页内容的方法和接口
BOM:浏览器对象模型,描述与浏览器进行交互的方法和接口
创建变量:var,let,const,函数,类,模块导入,symbol创建唯一值
命名规范:区分大小写、驼峰命名、关键字保留字
数据类型:
-
基本数据类型:
-
数字number、
常规数字和NaN
-
字符串string、
所以用单引号,双引号,反引号包起来的都是字符串。反引号为es6的模板字符串
-
布尔boolean、
true,false
-
空对象指针null、
-
未定义undefined
-
-
引用数据类型
-
对象数据类型object
- {}普通对象
- []数组对象
- 正则对象
- Math数学函数对象
- 日期对象
- …
-
函数数据类型function
-
number数字类型
包含:常规数字、NaN
NaN
not a number:不是一个数,但它隶属于数字类型。NaN和任何值包括自己都不相等
// 验证是否为数字 NaN:不是一个数
console.log('aa' == NaN);//false
console.log(10 == NaN);//false
console.log(NaN == NaN); //false
所以不能用相等的方式判断是否为有效数字
isNaN
检测一个值是否为非有效数字,不是有效数字返回true,是有效数字返回false
//isNaN([val])
console.log(isNaN(10)); //false
console.log(isNaN('a'));//true
console.log(isNaN('10'));//false
在使用isNaN进行检测的时候,首先会验证检测的值是否为数字类型,如果不是,先基于Number()这个方法,把值转换为数字类型,再进行检测。
console.log(isNaN('10'));//false
// Number('10') => 10
// isNaN(10) =>false
把其它类型值转换为数字类型
-
Number([val])
-
parseInt/parseFloat([val],[进制]):也是转换为数字的方法,对于字符串来说,它是从左到右依次查找有效数字字符,直到遇到非有效数字字符,停止查找(不管后面是否还有数字,都不再找了),并把找到的当做数字返回。
-
==进行比较的时候,可能出现把其它类型值转为数字:
eg: ‘10’ == 10结果为true,因为是先将字符串转为数字再作比较。
布尔转为数字
console.log(Number(true)); //1 console.log(Number(false)); //0 console.log(isNaN(false)); //false
console.log(Number(null));//0 console.log(Number(undefined));//NaN
把引用数据类型转换为数字,是先把他基于toString方法转换为字符串,然后再转换为数字
console.log(Number({name:'10'}));//NaN console.log(Number({}));//NaN console.log(Number([]));//0 console.log(Number([12]));//12 console.log(Number([12,23]));//NaN
总结:出现非有效数字就是NaN
let str = '12.5px' console.log(Number(str)); //NaN console.log(parseInt(str));//12 console.log(parseFloat(str));//12.5 console.log(parseFloat('width:12.5px'));//w不是有效数字所以返回NaN
String字符串类型
所有用单引号,双引号,反引号抱起来的都是字符串
把其它类型值转换为字符串
- [val].toString()
- 字符串拼接
null和undefined禁止直接toString()
let a = 12
console.log(a.toString());//'12'
console.log('10' + 10); //1010
console.log('10' - 10); //0
let a = 10 + null + true + [] + undefined + 'lj' + null + [] + 10 + false
console.log(a); //11undefinedljnull10false
/*
10 + null -> 10 + 0 =10
10 + true -> 10 + 1 =11
11 + [] -> 11 + '' = '11' 空数组变为数字,先要经历变为空字符串,遇到字符串,啥也别想直接变为字符 串拼接
*/
boolean布尔数据类型
只有两个值true/false
把其它类型值转换为布尔类型
只有0,NaN,’ ',null,undefined五个值转换为false,其余都转换为true(而且没有任何特殊情况)
-
Boolean([val])
-
!/!! !!是取反再取反,相当于Boolean()
-
条件判断
if(1) { console.log('hh'); } if('3px' + 3) { //'3px3' console.log('heihei'); } if('3px' - 3) { // =>NaN-3=NaN =>0 console.log('hehe'); }
只输出前两个
null/undefied
null和undecided都代表没有
-
null:意料之中(一般开始不知道值,我们手动先设置为null,后期再给予赋值操作)
eg:
let num = null; //=>let num = 0 一般最好用null作为初始空值,因为0不是空值,他在栈内存中有自己的存储空间 num = 12;
-
undefined:意料之外 (不是我能决定的) 创建一个变量没有赋值,默认值是undefined
Object对象数据类型-普通对象
-
{[key]:[value],…} 任何一个对象都是由零到多组键值对(属性名:属性值)组成的,并且属性名不能重复
let person = { name:'lj', age:22, height:185 } //获取属性名对应的属性值 =>对象.属性名 // =>对象[属性名] 属性名是数字或者字符串格式的 //如果当前属性名不存在,则为undefined //如果属性名是数字,则不能使用点的方式获取属性值,[]可以获取 console.log(person.name); console.log(person['name']); //删除属性 //=>真删除 属性彻底删掉 delete person['height']; //=>假删除 属性还在,值为空 person.age = null; console.log(person);
记得中括号里是字符串
数组
//数组是特殊的对象 1.我们中括号中设置的是属性值,它的属性名默认生产的有序数字,从0开始递增,并把其称为索引
// 2.天生默认一个属性名length,存储数字的长度
let ary = [12,'hh',23,true];
console.log(ary);
console.log(ary.length);//4
console.log(ary['length']);//4
//输出数组的属性值 第一项索引为0 最后一项索引为ary.length-1
console.log(ary[1]); //hh
//向数组末尾增加元素
ary[ary.length] = 100
console.log(ary); //12,'hh',23,true,100
JS的数据类型检测
-
typeof [val]:用来检测数据类型的运算符(不是方法)
/* 基于typeof检测出来的结果 1.首先是一个字符串 2.字符串中包含对应的类型 局限性 1.typeof null => object 但是null不是对象 2.基于typeof无法细分出当前值是普通对象还是数组对象,因为只要是对象数据类型,返回的结果都是"object" */ let a = NaN console.log(typeof a); //number console.log(typeof typeof typeof[]);//从右到左检测 //=>typeof [] => "object" //=>typeof "object" => "string" //因为typeof检测的结果都是字符串,所以只要两个及以上同时检测,最后结果必然是"string"
-
instanceof:用来检测当前实例是否隶属于某个类
-
constructor:基于构造函数检测数据类型(也是基于类的方式)
-
Object.prototype.toString.call():用来检测类型最好的方法
vscode 07代码…
循环
- for循环
- for in循环
- for of循环(ES6新增)
- while循环
- do while循环
continue和break
continue:结束当前这轮循环(continue后面的代码不再执行),继续执行下一轮循环
break:强制结束整个循环(break后面的代码不再执行),而且整个循环直接结束
元素对象的深层理解
let box = document.getElementById('box'); //返回的是一个对象
//通过方法获取的元素是对象数据类型的值
//console.log(typeof box) =>"object"
//基于dir能看到对象的详细信息
/*
id:操作元素的ID值
className:操作元素的class样式类的值
innerHTML:操作的元素的内容(可以识别标签)
innerText:和innerHTML的区别是不能识别标签
style:操作元素的行内样式 属性值是一个新的对象(CSSStyleDeclaration) box.style.display 即box是对象 style又是一个新的对象 display是style里的属性
tagName:获取元素的标签名(一般大写)
....
使用dir可以看到有许多onxxx事件,box本身是一个对象 所以可以通过对象名.属性名操作
eg:box['onclick'] = function(){} 等同于box.onclick = function(){} 底层原理是因为onclick是它的一个属性
*/
console.dir(box);
函数
函数就是一个方法或者一个功能体,函数就是把实现某个功能的代码放在一起进行封装。以后想再次实现这个功能,只需调用此函数。
- 创建函数
- 形参
- 返回值
- 执行函数
- 实参
函数返回值相关
//函数中的返回值
//函数执行的时候,函数体内部创建的变量我们是无法获取和操作的,后续闭包。如果想要获取内部的信息,我们需要return 没有写return 那么函数默认返回值是undefined
function sum(n,m) {
let result = n + m;
//return的一定是值:此处是把result变量存储的值返回给外面
return result;
}
let a = sum(10,20);
console.log(a);
// console.log(result); 直接在外面输出不写return的话会报uncaught referenceError
//函数体遇到return 后面代码不再执行了
匿名函数
//匿名函数
//匿名函数之函数表达式:把一个匿名函数本身作为值赋值给其他东西,这种函数一般不是手动触发执行,而是靠其他程序驱动触发执行,例如触发某个事件的时候把它执行。
// document.body.onclick = function() {};
// setTimeout(() => {}, timeout);
//立即执行函数
(function(n) {
//n=>100
})(100);
函数中的arguments
arguments为函数内置的实参集合
任意数求和(执行函数的时候,传递N个值实现求和)
1.传递实参的个数不定
2.传递的值是否为有效数字不定
=>把传递的有效数字进行相加求和
arguments:函数内置的实参集合
1.类数组集合,集合中存储着所有函数执行时,传递的实参信息
2.不论是否设置形参,arguments都存在
3.不论是否传递实参,arguments也都存在
你传或不传 arguments就在这里
arguments.callee:存储的是当前函数本身(一般不用的,因为JS严格模式下禁止使用这些属性)
箭头函数
简单
function sum(n,m) {
return n + m
}
//改写成箭头函数
let sum = (n,m) => {
return n + m
}
//如果函数体中只有一行return,可以省略return和大括号;只有一个参数可省略小括号
let sum = (n,m) => n + m
console.log(sum(10,30));
function fn(n) {
return function(m) {
return n + m
}
}
let fn = n => m => n + m //同上
//形参赋值默认值:当没有给形参传递实参的时候,执行默认值
let sum = (n = 0,m = 0) => n + m
箭头函数中没有arguments,但是可以使用剩余运算符…arg
Math
数学函数 :但它不是一个函数,是一个对象,对象中存储了很多操作数字的属性方法。
扩展:获取[n-m]之间的随机整数
包含n也包含m
n<m
Math.round(Math.random()*(m-n)+n)
数组及数组中常用的方法
-
方法的作用和含义
-
方法的实参(类型和含义)
-
方法的返回值
-
原来的数组是否会发生改变
1.实现数组增删改的方法
这一部分方法都会修改原数组
push
向数组末尾增加内容
@params 多个任意类型
@return 新增后数组的长度
let ary = [10,20];
let res = ary.push(30,'a');
console.log(res,ary);//4 [10,20,30,'a']
ary[ary.length-1+1] = 40;//原生js末尾添加元素
两种方法,但是push可以返回数组长度
unshift
向数组开始位置增加内容
@params 多个任意类型
@return 新增后数组的长度
let ary = [10,20];
let res = ary.unshift(30,'a');
console.log(res,ary);//4 [30,'a',10,20]
shift
删除数组第一项
@params 无
@return 删除的那一项
let ary = [10,20];
let res = ary.shift();
console.log(res,ary);//10 [20]
原生:基于原生中的delete,把数组当成普通的对象,确实可以删除掉某一项内容,但是不会影响数组本身的结构特点(length不会跟着修改),真实项目中不会这样使用
let ary = [20,40,50];
delete ary[0]
console.log(ary); //20 [empty,40,50]
pop
删除数组最后一项
@params 无
@return 删除的那一项
let ary = [10,20,30];
let res = ary.pop();
console.log(res,ary);//30 [10,20]
原生删除最后一项:数组长度干掉一位,默认最后一项
ary.length--;
splice
实现数组的增加,删除,修改。
@params n,m都是数字 从索引n开始删除m个元素(m不写的话,是默认删除到末尾)
@return 把删除的部分用新数组存储起来返回
删除:
let ary = [10,20,30,40,50,60,70];
let res = ary.splice(2,4)
console.log(res,ary);//[30,40,50,60,70] [10,20,30]
清空数组
ary.splice(0)
删除最后一项和第一项,可以代替pop和shift
//删除最后一项和第一项,可以代替pop和shift
ary.splice(ary.length-1)
ary.splice(0,1)
所以删除数组最后一项一共三种方法:pop,length–,ary.splice(ary.length-1)
增加、修改
@params n,m,x 从索引n开始删除m个元素,用x占用删除的部分(修改)
n,0,x 从索引n开始,一个都不删,把x放到所以n的前面(增加)
@return 把删除的部分用新数组存储起来返回
//修改
let ary = [10,20,30,40,50];
let res = ary.splice(2,2,90,'zz');
console.log(res,ary);//[30,40] [10,20,90,"zz",50]
//增加
let ary = [10,20,30,40,50];
let res = ary.splice(2,0,90,'zz');
console.log(res,ary);//[10,20,90,'zz',30,40,50]
//向数组末尾追加
let ary = [10,20,30,40,50];
let res = ary.splice(ary.length,0,'z','h');
console.log(res,ary);
//向数组开头追加
ary.splice(0,0,'z')
总结
- 向数组末尾追加元素:
- push:返回结果为新增数组长度,数组不改变
- ary.length:操作原生JS键值对
- splice:
- 向数组开始追加元素:
- 删除最后一项:
- 删除第一项:
- shift:
数组的查询和拼接
此组学习的方法,原数组不会改变
slice
实现数组查询
@params n,m 都是数字 从索引n开始,找到索引为m的地方(不包含m这一项) 不写m就找到末尾
@return 把找到的内容以一个新数组的形式返回
let ary = [10,20,30,40,50];
let res = ary.slice(1,3)
console.log(res); //[20,30]
concat
实现数组拼接 (也可以作为追加末尾元素使用)
@params 多个任意类型值
@return 拼接后的新数组(原来数组不变)
let ary1 = [10,20,30];
let ary2 = [40,50];
let res = ary1.concat(ary2)
console.log(res);//[10,20,30,40,50]
把数组转换为字符串
把数组转换为字符串:toString
原有数组不变
@params
@return 转换后的字符串 用逗号分隔
let ary = [10,20,30];
let res = ary.toString()
console.log(res);//10,20,30
join
把数组转换为字符串:join
原有数组不变
@params 指定的分隔符(字符串格式)
@return 转换后的字符串 用指定分隔符分隔
let ary = [10,20,30];
let res = ary.join('')//没有指定分隔符,空字符串所以输出102030 一般加' '
console.log(res);
eval把字符串变为JS表达式 此处用于数组求和:
let ary = [10,20,30];
let res = ary.join('+')
console.log(eval(res)); //60
检测数组中是否包含某一项
indexOf
/lastIndexOf
检测当前项在数组中第一次或最后一次出现位置的索引值(IE6-8不兼容)
原来数组不变
@params 要检索的这一项内容
@return 这一项出现的位置索引值(数字),如果数组中没有这一项,返回-1
let ary = [10,20,30];
let res = ary.indexOf(10)
console.log(res); //0
let ary = [10,20,30,10];
let res = ary.lastIndexOf(10);
console.log(res); //3
ES6中includes也可以判断 返回true/false
数组的排序或者排列
reverse
翻转数组
原来数组会改变
@params
@return 返回排列后的新数组
let ary = [10,20,30,90];
ary.reverse()
console.log(ary); //[90,30,20,10]
sort
数组排序
原来数组会改变
@params 可以没有,也可以是个函数
@return 返回排序后的新数组
let ary = [12,15,9,28,10,22];
let res = ary.sort()
console.log(res); // [10, 12, 15, 22, 28, 9] 是有问题的
sort方法中如果不传递参数,是无法处理10以上数字排序的(它默认按照每一项第一个字符来排)
实现多位数正常排序需要在sort中传递一个函数:a-b为升序;b-a为降序
let ary = [12,15,9,28,10,22];
let res = ary.sort((a,b) => {
//a,b是相邻的两项
return a - b; //b-a是由大到小
})
console.log(res); // [9, 10, 12, 15, 22, 28]
遍历数组中每一项的方法forEach
遍历数组中的每一项
原来数组会改变
@params 回调函数
@return 无
let ary = [12,15,9,28,10,22];
ary.forEach((item,index) => {
//数组中有多少项,函数就会被默认执行多少次
console.log(item);
})
array.prototype 查看数组方法
map
filter
find
reduce
some
every
…
数组去重
方案一:
let ary = [1,1,2,3,4,1,3,2];
//方案一:循环原有数组中的每一项,每拿到一项都往新数组中添加
//添加之前验证新数组中是否存在这一项,不存在则添加
let newAry = [];
for (let i = 0; i < ary.length; i++) {
let item = ary[i];
if (newAry.includes(item)) {
//如果存在这一项,就不再增加到新数组中
continue;
}
newAry.push(item);
}
console.log(newAry);
foreach优化:
//使用foreach优化上述代码
let ary = [1,1,2,3,4,1,3,2];
let newAry = [];
ary.forEach((item) => {
if (newAry.includes(item)) return;
newAry.push(item);
})
console.log(ary);
方案二:
方案三:
字符串中常用的方法
所有用单引号,双引号,反引号 包起来的都是字符串
let str = 'hhhhnihaoa'
str.length //获取字符串长度
str[0] //获取索引为0的字符
str[str.length-1] //获取最后一项的索引
str[10000] //undefined 不存在这个索引
for(let i=0;i<str.length;i++){
let char = str[i];
console.log(char);
}
charAt/charCodeAt
charAt:根据索引获取指定位置的字符
charCodeAt:获取指定字符的ASCII码值
@params:n[number] 获取字符指定的索引
@return 返回查找到的字符(找不到返回空字符串而不是undefined,或者对应的编码值)
let str = 'nihaojintian';
console.log(str[0]);//n
console.log(str.charAt(0));//n
//虽然都能实现,但当不存在时
console.log(str[1000]);//undefined
console.log(str.charAt(1000));//不输出东西
substr/substring/slice
实现字符串的截取
substr(n,m) 从索引n开始,截取m个字符 m不指定的话就截取到末尾。后两个方法相同
substring(n,m) 从索引n开始找到索引为m处(不含m)
slice(n,m) 和substring一样,从索引n开始找到索引为m处(不含m)。但slice可以支持负数索引(不改变原数组)
indexOf/lastIndexOf
验证字符是否存在
indexOf(x,y) 获取x第一次出现位置的索引,y是控制查找起始位置索引
lastIndexOf(x) 最后一次出现位置的索引 如果没有这个字符 返回-1
同includes
toUpperCase/toLowerCase
字符串中字母的大小写转换
toUpperCase:转大写
toLowerCase:转小写
split
split([分隔符]):把字符串按照指定的分隔符拆分成数组(对应数组中的join)
split支持传递正则表达式
//把字符串的|变为,
//首先变为数组,然后使用join再变成字符串
let str = 'hh|heihei|xixi';
let ary = str.split('|');
str = ary.join(',')
console.log(str);//hh,heihei,xixi
replace
replace(老字符,新字符):实现字符串替换
//在不使用正则表达式的情况下,执行一次replace只能替换一个字符
let str = '我-是-谁'
str = str.replace('-','+')
console.log(str);//我+是-谁
//使用正则
let str = '我-是-谁'
str = str.replace(/-/g,'+')
console.log(str);//我+是+谁
实现一些常用的需求
时间字符串的处理
//方案一:一路replace
//2020年6月10日12时09分22
time = time.replace('-','年').replace('-','月').replace(' ','日').replace(':','时').replace(':','分').replace(':','秒')
//方案二:获取年月日时分秒的索引值,最后再拼接
//获取值的方法一:基于indexOf获取指定符号索引,使用substring一点点截取
let n = time.indexOf('-')
let m = time.lastIndexOf('-')
let x = time.indexOf(' ')
let y = time.indexOf(':')
let z = time.lastIndexOf(':')
let year = time.substring(0,n)
let month = time.substring(n+1,m)
let day = time.substring(m+1,x)
console.log(year,month,day); //2020 6 01
//获取值的方法二:基于split一项项的拆分
let n = time.split(' ') //["2020-6-01", "12:09:22"]
let m = n[0].split('-') //["2020", "6", "01"]
let x = n[1].split(':')
console.log(x); //["12", "09", "22"]
//获取值的方法三:使用正则最快捷
let ary = time.split(/(?: |-|:)/g)
console.log(ary); //["2020", "6", "01", "12", "09", "22"]
let addZero = val => val = val.length < 2 ? '0' + val : val
time = ary[0] + '年' + addZero(ary[1]) + '月' + addZero(ary[2]) + '日'
console.log(time); //2020年06月01日
合理利用正则省事
实现方法queryURLParameter
有一个URL地址’http://www.baidu.com/stu/?lx=1&name=AA&sex=man#teacher’;地址问号后面的内容是我们需要解析出来的参数信息
{
lx:1,
name:‘AA’,
sex:‘man’HASH:‘zz’
}
//实现效果:{
// lx:1,
// name:'AA',
// sex:'man'
// HASH:'zz'
// }
var url = 'http://www.baidu.com/stu/?lx=1&name=AA&sex=man#teacher';
//1.获取问号和井号在字符串中索引位置
let askIndex = url.indexOf('?')
let wellIndex = url.indexOf('#')
//问号井号中间内容
let askText = url.substring(askIndex + 1,wellIndex) //"lx=1&name=AA&sex=man"
let wellText = url.substring(wellIndex + 1) //"teacher"
//2.问号后面值的详细处理
let result = {}
let askAry = askText.split('&') //["lx=1", "name=AA", "sex=man"]
askAry.forEach(item => {
let n = item.split('=') //["lx", "1"] ["name", "AA"] ["sex", "man"]
//使拆分后的每个数组第一项作为属性名,第二项作为属性值
let key = n[0]
let value = n[1]
result[key] = value //给一个空对象加入属性名为key的value值
})
//还差一个属性值 自己手动加入
result['HASH'] = 'zz'
console.log(result);
hash值如果有一定在最后。封装成一个方法
queryURLParameter :获取URL地址中问号传参的信息和哈希值
@params url[string] 要解析的url字符串
@return [object] 包含参数和哈希值信息的对象
/*
* 1.先找到问号,把问号后面的信息截取下来即可
* A.首先我们需要验证是否存在#哈希值,存在我们从问号开始截取到#,不存在我们直接截取到字符串的末尾
* 2.以&进行拆分(数组)
* 3.遍历数组中的每一项,把每一项在按照=进行拆分,把拆分后的第一项作为对象的属性名,第二项作为属性值进行存储即可
*/
function queryURLParameter (url) {
//获取?和#后面的信息
//分情况:?存在 #不存在时:直接从?截取到末尾即可; 末尾索引为length-1 后一项为length 所以截取到length
// ?不存在 #存在时:
let askIn = url.indexOf('?'),
wellIn = url.indexOf('#'),
askText = '',
wellText = '';
//#不存在
wellIn === -1 ? wellIn = url.length : null;
//?存在
askIn >= 0 ? askText = url.substring(askIn + 1,wellIn) : null;
wellText = url.substring(wellIn + 1)
//2.获取每一部分信息
let result = {}
wellText !== '' ? result['HASH'] = wellText : null;
if(askText !== ''){
let ary = askText.split('&')
ary.forEach(item => {
let itemAry = item.split('=')
result[itemAry[0]] = itemAry[1]
})
}
}
var url = 'http://www.baidu.com/stu/?lx=1&name=AA&sex=man#teacher';
let obj = queryURLParameter(url);
console.log(obj);
提高眼界:
~function (pro) {
pro.queryURLParameter = function () {
var obj = {},
reg = /([^?=&#]+)(?:=([^?=&#]+)?)/g;
this.replace(reg, function () {
var key = arguments[1],
value = arguments[2] || null;
obj[key] = value;
});
return obj;
}
}(String.prototype);
var str = 'http://www.zhufengpeixun.cn/stu/?lx=1&name=&sex=#teacher';
console.log(str.queryURLParameter());
实现一个low的验证码:数字+字符共四位
验证码目的:防止外挂程序恶意批量注入
Dom
获取DOM元素的方法
getElementById
通过元素的ID获取指定的元素对象,使用的时候都是
document.getElementById('')
此处的document是限定了获取元素的范围,我们把它称之为“上下文(context)”
- getElementById的上下文只能是document
因为严格意义上,一个页面中的ID是不能重复的,浏览器规定在整个文档中既可以获取这个唯一的ID
2.如果页面中的ID重复了,我们基于这个方法只能获取到第一个元素,后面相同ID元素无法获取
3.在IE6~7浏览器中,会把表单元素(input…)的name属性值当做ID来使用(建议:以后使用表单元素的时候,不要让name和id的值有冲突)
getElementsByTagName
[context].getElementsByTagName
在指定的上下文中,根据标签名获取到一组元素集合(所有)(HTMLCollection)
-
获取的元素集合是一个类数组(不能直接的使数组中的方法)
-
它会把当前上下文中,子子孙孙(后代)层级内的标签都获取到(获取的不仅仅是儿子级的)
-
基于这个方法获取到的结果永远都是一个集合(不管里面是否有内容,也不管有几项,它是一个容器或者集合),如果想操作集合中具体的某一项,需要基于索引获取到才可以
getElementsByClassName
[context].getElementsByClassName()
在指定的上下文中,基于元素的样式类名(class=‘xxx’)获取到一组元素集合
1.真实项目中,我们经常是基于样式类来给元素设置样式,所以在JS中,我们也会经常基于样式类来获取元素,但是此方法在IE6~8下不兼容
兼容处理方案参考:
Node.prototype.queryElementsByClassName = function queryElementsByClassName() {
if (arguments.length === 0) return [];
var strClass = arguments[0],
nodeList = utils.toArray(this.getElementsByTagName('*'));
strClass = strClass.replace(/^ +| +$/g, '').split(/ +/);
for (var i = 0; i < strClass.length; i++) {
var reg = new RegExp('(^| +)' + strClass[i] + '( +|$)');
for (var k = 0; k < nodeList.length; k++) {
if (!reg.test(nodeList[k].className)) {
nodeList.splice(k, 1);
k--;
}
}
}
return nodeList;
};
getElementsByName
document.getElementsByName()
它的上下文也只能是document,在整个文档中,基于元素的name属性值获取一组节点集合(也是一个类数组)
1.在IE浏览器中(IE9及以下版本),只对表单元素的name属性起作用(正常来说:我们项目中只会给表单元素设置name,给非表单元素设置name,其实是一个不太符合规范的操作)
querySelector
[context].querySelector()
在指定的上下文中基于选择器(类似于CSS选择器)获取到指定的元素对象(获取的是一个元素,哪怕选择器匹配了多个,我们只获取第一个)
querySelectorAll
在querySelector的基础上,我们获取到选择器匹配到的所有元素,结果是一个节点集合(NodeList)
- querySelector/querySelectorAll 都是不兼容IE6~8浏览器的(不考虑兼容的情况下,我们能用ById或者其它方式获取的,也尽量不要用这两个方法,这两个方法性能消耗较大)
let navList = document.querySelectorAll('.tab li:nth-child(2)')
//获得tab下的第二个li
document.head
获取HEAD元素对象
document.body
获取BODY元素对象
document.documentElement
获取HTML元素对象
document.documentElement
获取HTML元素对象
//=>需求:获取浏览器一屏幕的宽度和高度(兼容所有的浏览器)
document.documentElement.clientWidth || document.body.clientWidth
document.documentElement.clientHeight || document.body.clientHeight
面试题:获取当前页面中所有ID为HAHA的和元素(兼容所有的浏览器)
//=>不能使用querySelectorAll
/*
* 1.首先获取当前文档中所有的HTML标签
* 2.依次遍历这些元素标签对象,谁的ID等于HAHA,我们就把谁存储起来即可
*/
function queryAllById(id) {
//->基于通配符*获取到整个文档中所有的HTML标签
var nodeList = document.getElementsByTagName('*');
//->遍历集合中的每一项,把元素ID和传递ID相同的这一项存储起来
var ary = [];
for (var i = 0; i < nodeList.length; i++) {
var item = nodeList[i];
item.id === id ? ary.push(item) : null;
}
return ary;
}
console.log(queryAllById('HAHA'));
节点 (node)
在一个HTML文档中出现的所有东西都是节点
- 元素节点(HTML标签)
- 文本节点(文字内容)
- 注释节点(注释内容)
- 文档节点(document)
- …
每一种类型的节点都会有一些属性区分自己的特性和特征
- nodeType:节点类型
- nodeName:节点名称
- nodeValue:节点值
元素节点
nodeType:1
nodeName:大写标签名
nodeValue:null
文本节点
nodeType:3
nodeName:’#text’
nodeValue:文本内容
在标准浏览器中会把空格/换行等都当做文本节点处理
注释节点
nodeType:8
nodeName:’#comment’
nodeValue:注释内容
文档节点
nodeType:9
nodeName:’#document’
nodeValue:null
描述节点之间关系的属性
parentNode
获取当前节点唯一的父亲节点
childNodes
获取当前元素的所有子节点
- 子节点:只获取儿子级别的
- 所有:包含元素节点、文本节点等
children
获取当前元素所有的元素子节点
在IE6~8中会把注释节点也当做元素节点获取到,所以兼容性不好
previousSibling
获取当前节点的上一个哥哥节点(获取的哥哥可能是元素也可能是文本等)
previousElementSibling:获取上一个哥哥元素节点(不兼容IE6~8)
nextSibling
获取当前节点的下一个弟弟节点
nextElementSibling:下一个弟弟元素节点(不兼容)
firstChild
获取当前元素的第一个子节点(可能是元素或者文本等)
firstElementChild
lastChild
获取当前元素的最后一个子节点
lastElementChild
需求一:获取当前元素的所有元素子节点
基于children不兼容IE低版本浏览器(会把注释当做元素节点)
let box = document.getElementById('box')
//标准浏览器(非IE6-8)中会把空格和换行当做文本节点处理(childNodes包含所有节点)
//如果只要元素节点 但在IE6-8下 使用children会把注释也当做元素节点
// console.log(box.children);
//获取指定上下文中所有的元素子节点
//@params context[element object] 指定的上下文元素信息
//@return [array] 返回所有的元素子节点集合
function getChildren(context) {
//1.先获取所有的子节点
let res = []
let nodeList = context.childNodes
//2.循环遍历所有的子节点,找出元素子节点 存储到res中
for(var i = 0;i < nodeList.length;i++) {
var item = nodeList[i]
item.nodeType === 1 ? res.push(item) : null //因为元素节点nodeType为1
}
return res
}
//假如想获得box下所有的元素子节点
console.log(getChildren(box)); //3个li
需求二:获取当前元素的上一个哥哥元素节点
previousSibling:上一个哥哥节点
previousElementSibling:上一个哥哥元素节点,但是不兼容
/*
* prev:get the last elder brother element node of the current element
* @parameter
* curEle:[object] current element
* @return
* [object] last elder brother element
* by team on 2018/04/07 12:44
*/
function prev(curEle) {
//=>先找当前元素的哥哥节点,看是否为元素节点,不是的话,基于哥哥,找哥哥的上一个哥哥节点...一直到找到元素节点或者已经没有哥哥了(说明我就是老大)则结束查找
var pre = curEle.previousSibling;
while (pre && pre.nodeType !== 1) {
/*
* pre && pre.nodeType !== 1
* pre是验证还有没有,这样写代表有,没有pre是null
* pre.nodeType是验证是否为元素
*/
pre = pre.previousSibling;
}
return pre;
}
回去后扩展:jQuery中有next下一个弟弟元素节点,prevAll获取所有哥哥元素节点,nextAll获取所有弟弟元素节点,siblings获取所有兄弟元素节点,index获取当前元素的索引…
关于DOM的增删改
createElement 创建元素对象 document.createElement([标签名])
createTextNode 创建文本对象
appendChild
把一个元素对象插入到指定容器的末尾
[container].appendChild([newEle])
insertBefore
把一个元素对象插入到指定容器中某一个元素标签之前
[container].insertBefore([newEle],[oldEle])
cloneNode
把某一个节点进行克隆
[curEle].cloneNode()
:浅克隆,只克隆当前的标签[curEle].cloneNode(true)
:深克隆,当前标签及其里面的内容都一起克隆了
removeChild
在指定容器中删除某一个元素
[container].removeChild([curEle])
set/get/removeAttribute
设置/获取/删除 当前元素的某一个自定义属性
var oBox=document.getElementById('box');
//=>把当前元素作为一个对象,在对象对应的堆内存中新增一个自定义的属性
oBox.myIndex = 10;//=>设置
console.log(oBox['myIndex']);//=>获取
delete oBox.myIndex; //=>删除
//=>基于Attribute等DOM方法完成自定义属性的设置
oBox.setAttribute('myColor','red'); //=>设置
oBox.getAttribute('myColor'); //=>获取
oBox.removeAttribute('myColor'); //=>删除
上下两种机制属于独立的运作*,不能互相混淆使用
- 第一种是基于对象键值对操作方式,修改当前元素对象的堆内存空间来完成
- 第二种是直接修改页面中HTML标签的结构来完成(此种办法设置的自定义属性可以在结构上呈现出来)
基于setAttribute设置的自定义属性值都是字符串
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-42BcQaEz-1594872955042)(E:/1Study/1前端笔记/珠峰/2018年第二期源码、笔记/基础知识公开课/0407DAY9/1523088229070.png)]
2020 6.11
复习前面的知识
var a = 12;
/*
* 1.先声明一个变量a,没有赋值(默认值是undefined)
* 2.在当前作用域中开辟一个位置存储12这个值
* 3.让变量a和12关联在一起(定义:赋值)
*/
var b = a;
b = 13;
console.log(a);//12
var ary1 = [12, 23];
var ary2 = ary1;
ary2.push(100);
console.log(ary1); //[12, 23, 100]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HPJPKiaI-1594872955043)(E:\1Study\1前端笔记\info\images\Snipaste_2020-06-11_14-28-17.png)]
function sum() {
var total = null;
for (var i = 0; i < arguments.length; i++) {
var item = arguments[i];
item = parseFloat(item);
!isNaN(item) ? total += item : null;
}
return total;
}
console.log(sum(12, 23, '34', 'AA'));
//直接写sum就是函数本身 sum()是执行这个函数 sum()结果为return的结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H7xHtCx3-1594872955043)(E:\1Study\1前端笔记\info\images\Snipaste_2020-06-11_14-36-47.png)]
变量提升
/*
* 变量提升:
* =>当栈内存(作用域)形成,JS代码自上而下执行之前,浏览器首先会把所有带 “VAR”/“FUNCTION” 关键词的进行提前 “声明” 或者 “定义” ,这种预先处理机制称之为 “变量提升”
*
* =>声明(declare):var a (默认值undefined)
* =>定义(defined):a=12 (定义其实就是赋值操作)
*
* [变量提升阶段]
* =>带“VAR”的只声明未定义
* =>带“FUNCTION”的声明和赋值都完成了
*
* =>变量提升只发生在当前作用域(例如:开始加载页面的时候只对全局作用域下的进行提升,因为此时函数中存储的都是字符串而已)
* =>在全局作用域下声明的函数或者变量是“全局变量”,同理,在私有作用域下声明的变量是“私有变量” [带VAR/FUNCTION的才是声明]
*
* =>浏览器很懒,做过的事情不会重复执行第二遍,也就是,当代码执行遇到创建函数这部分代码后,直接的跳过即可(因为在提升阶段就已经完成函数的赋值操作了)
*/
console.log(a);//=>undefined
var a = 12;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lIICsZ1A-1594872955044)(E:\1Study\1前端笔记\info\images\Snipaste_2020-06-11_16-10-29.png)]
带var和不带var的区别
//=>在全局作用域下声明一个变量,也相当于给WINDOW全局对象设置了一个属性,变量的值就是属性值(私有作用域中声明的私有变量和WINDOW没啥关系)
/*
console.log(a);//=>undefined
console.log(window.a);//=>undefined
console.log('a' in window); //=>TRUE 在变量提升阶段,在全局作用域中声明了一个变量A,此时就已经把A当做属性赋值给WINDOW了,只不过此时还没有给A赋值,默认值UNDEFINED in:检测某个属性是否隶属于这个对象
var a = 12;//=>全局变量值修改,WIN的属性值也跟着修改
console.log(a);//=>全局变量A 12
console.log(window.a);//=>WINDOW的一个属性名A 12
a = 13;
console.log(window.a);//=>13
window.a = 14;
console.log(a);//=>14
//=>全局变量和WIN中的属性存在 “映射机制”
*/
//=>不加VAR的本质是WIN的属性
/*
// console.log(a);//=>Uncaught ReferenceError: a is not defined
console.log(window.a);//=>undefined
// console.log('a' in window);//=>false
a = 12;//=>window.a=12
console.log(a);//=>12
console.log(window.a);//=>12
*/
var a = 12,
b = 13; //这样写b带var
var a = b = 12;//这样写b是不带var的 等同于var a = 12;b = 12
习题:
console.log(a,b); //undefined undefined
var a = 12;
b = 12;
function fn() {
console.log(a,b); //undefined 12
var a = b = 13;
console.log(a,b);//13 13
}
fn();
console.log(a,b); //12 13
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-no5KsvgK-1594872955045)(E:\1Study\1前端笔记\info\images\Snipaste_2020-06-11_18-05-39.png)]
作用域链的扩展
function fn() {
/!*
* 变量提升:无
*!/
// console.log(b);//=>Uncaught ReferenceError: b is not defined
b = 13;
//console.log('b' in window);//=>true 在作用域链查找的过程中,如果找到WIN也没有这个变量,相当于给WIN设置了一个属性B (window.b=13)
console.log(b);//=>13
}
fn();
console.log(b);//=>13
在全局作用域下声明的变量都相当于给window加了一个属性 window是一个对象
重名问题的处理
/*
* 1.带VAR和FUNCTION关键字声明相同的名字,这种也算是重名了(其实是一个FN,只是存储值的类型不一样)
*/
var fn = 12;
function fn() {
}
/*
* 2.关于重名的处理:如果名字重复了,不会重新的声明,但是会重新的定义(重新赋值)[不管是变量提升还是代码执行阶段皆是如此]
*/
/*
* 变量提升:
* fn = ...(1)
* = ...(2)
* = ...(3)
* = ...(4)
*/
/*
fn();//=>4
function fn() {console.log(1);}
fn();//=>4
function fn() {console.log(2);}
fn();//=>4
var fn=100;//=>带VAR的在提升阶段只把声明处理了,赋值操作没有处理,所以在代码执行的时候需要完成赋值 FN=100
fn();//=>100() Uncaught TypeError: fn is not a function
function fn() {console.log(3);}
fn();
function fn() {console.log(4);}
fn();
*/
let创建的变量不存在变量提升
/*
* 在ES6中基于LET/CONST等方式创建变量或者函数,不存在变量提升机制
*
* =>切断了全局变量和WINDOW属性的映射机制
*
* =>在相同的作用域中,基于LET不能声明相同名字的变量(不管用什么方式在当前作用域下声明了变量,再次使用LET创建都会报错)
*
* 虽然没有变量提升机制,但是在当前作用域代码自上而下执行之前,浏览器会做一个重复性检测(语法检测):自上而下查找当前作用域下所有变量,一旦发现有重复的,直接抛出异常,代码也不会在执行了(虽然没有把变量提前声明定义,但是浏览器已经记住了,当前作用域下有哪些变量)
*/
// console.log(a);//=>Uncaught ReferenceError: a is not defined
// let a = 12;
// console.log(window.a);//=>undefined
// console.log(a);//=>12
/*let a = 12;
console.log(a);
let a = 13;//=>Uncaught SyntaxError: Identifier 'a' has already been declared
console.log(a);*/
/*b = 12;
console.log(b);//=>12
a = 12;//=>Uncaught ReferenceError: a is not defined
console.log(a);
let a = 13;
console.log(a);*/
let a = 10,
b = 10;
let fn = function () {
console.log(a, b);//=>Uncaught ReferenceError: a is not defined
let a = b = 20;
/*
* let a=20;
* b=20; //=>把全局中的 b=20
*/
console.log(a, b);
};
fn();
console.log(a, b);
习题1:
var a = 12,
b = 13,
c = 14;
function fn(a) {
console.log(a,b,c); //12 undefined 14 (c是全局的)
var b = c = a = 20; //带var的在私有作用域变量提升阶段都声明为私有变量,和外界没有关系。
//不带var不是私有变量,会向上级作用域查找
console.log(a,b,c); //20 20 20
}
fn(a)
console.log(a,b,c); //12 13 20 ?
/*
* JS中的内存分为堆内存和栈内存
* 堆内存:存储引用数据类型值(对象:键值对 函数:代码字符串)
* 栈内存:提供JS代码执行的环境和存储基本类型值
*
* [堆内存释放]
* 让所有引用堆内存空间地址的变量赋值为null即可(没有变量占用这个堆内存了,浏览器会在空闲的时候把它释放掉)
*
* [栈内存释放]
* 一般情况下,当函数执行完成,所形成的私有作用域(栈内存)都会自动释放掉(在栈内存中存储的值也都会释放掉),但是也有特殊不销毁的情况:
* 1.函数执行完成,当前形成的栈内存中,某些内容被栈内存以外的变量占用了,此时栈内存不能释放(一旦释放外面找不到原有的内容了)
* 2.全局栈内存只有在页面关闭的时候才会被释放掉
* ...
* 如果当前栈内存没有被释放,那么之前在栈内存中存储的基本值也不会被释放,能够一直保存下来
*/
闭包
/*
* [闭包]
* =>函数执形成一个私有的作用域,保护里面的私有变量不受外界的干扰,这种保护机制称之为“闭包”
*
* =>市面上的开发者认为的闭包是:形成一个不销毁的私有作用域(私有栈内存)才是闭包
*/
/*
//=>闭包:柯理化函数
function fn() {
return function () {
}
}
var f = fn();
*/
/*
//=>闭包:惰性函数
var utils = (function () {
return {
}
})();
*/
//=>闭包项目实战应用
//==>真实项目中为了保证JS的性能(堆栈内存的性能优化),应该尽可能的减少闭包的使用(不销毁的堆栈内存是耗性能的)
//1.闭包具有“保护”作用:保护私有变量不受外界的干扰
//> 在真实项目中,尤其是团队协作开发的时候,应当尽可能的减少全局变量的使用,以防止相互之前的冲突(“全局变量污染”),那么此时我们完全可以把自己这一部分内容封装到一个闭包中,让全局变量转换为私有变量
/*
(function () {
var n = 12;
function fn() {
}
//...
})();
*/
//> 不仅如此,我们封装类库插件的时候,也会把自己的程序都存放到闭包中保护起来,防止和用户的程序冲突,但是我们又需要暴露一些方法给客户使用,这样我们如何处理呢?
//1.JQ这种方式:把需要暴露的方法抛到全局
/*
(function () {
function jQuery() {
//...
}
//...
window.jQuery = window.$ = jQuery;//=>把需要供外面使用的方法,通过给WIN设置属性的方式暴露出去
})();
// jQuery();
// $();
*/
//2.Zepto这种方式:基于RETURN把需要共外面使用的方法暴露出去
/*
var Zepto=(function () {
//...
return {
xxx:function () {
}
};
})();
Zepto.xxx();
*/
//2.闭包具有“保存”作用:形成不销毁的栈内存,把一些值保存下来,方便后面的调取使用
面向对象设计模式(OOP)
单例设计模式 :
/*
* 单例设计模式(singleton pattern)
* 1.表现形式
* var obj = {
* xxx:xxx,
* ...
* };
* 在单例设计模型中,OBJ不仅仅是对象名,它被称为“命名空间[NameSpace]”,把描述事务的属性存放到命名空间中,多个命名空间是独立分开的,互不冲突
*
* 2.作用
* =>把描述同一件事务的属性和特征进行“分组、归类”(存储在同一个堆内存空间中),因此避免了全局变量之间的冲突和污染
* var pattern1={name:'xxx'}
* var pattern2={name:'xxx'}
*
* 3.单例设计模式命名的由来
* =>每一个命名空间都是JS中Object这个内置基类的实例,而实例之间是相互独立互不干扰的,所以我们称它为“单例:单独的实例”
*/
/*
var name = "陆相莹";
var age = 18;
var sex = "girl";
var name = "刘司南";
var age = 81;
var sex = "boy";
*/
/*
var person1={
name:"陆相莹",
age:18
};
var person2={
name:"刘司南",
age:81
};*/
/*
* 高级单例模式
* 1.在给命名空间赋值的时候,不是直接赋值一个对象,而是先执行匿名函数,形成一个私有作用域AA(不销毁的栈内存),在AA中创建一个堆内存,把堆内存地址赋值给命名空间
*
* 2.这种模式的好处:我们完全可以在AA中创造很多内容(变量OR函数),哪些需要供外面调取使用的,我们暴露到返回的对象中(模块化实现的一种思想)外边想用谁就暴露谁
*/
/*var nameSpace = (function () {
var n = 12;
function fn() {
//...
}
function sum() {
}
return {
fn: fn,
sum: sum
}
})();*/
/*
* THIS
* 1.给当前元素的某个事件绑定方法, 当事件触发方法执行的时候,方法中的THIS是当前操作的元素对象
* oBox.οnclick=function(){
* //=>this:oBox
* }
*
* 2.普通函数执行,函数中的THIS取决于执行的主体,谁执行的,THIS就是谁(执行主体:方法执行,看方法名前面是否有“点”,有的话,点前面是谁this就是谁,没有this是window)
* function fn(){//=>AAAFFF000
console.log(1);
}
var obj={
fn:fn //=>fn:AAAFFF000
};
//=>执行的是相同的方法(不同地方在于函数执行方法中的this是不一样的)
obj.fn();//=>this:obj
fn();//=>this:window
//=>自执行函数执行,方法中的this是window
~function(){
//=>this:window
}();
*/
/*
var n = 2;
var obj={
n:3,
fn:(function (n) {
n*=2;
this.n+=2;
var n=5;
return function (m) {
this.n*=2;
console.log(m + (++n));
}
})(n)//=>obj.n会报错
};
var fn = obj.fn;
fn(3);
obj.fn(3);
console.log(n, obj.n);*/
/*
* 模块化开发
* 1.团队协作开发的时候,会把产品按照功能板块进行划分,每一个功能板块有专人负责开发
* 2.把各个版块之间公用的部门进行提取封装,后期在想实现这些功能,直接的调取引用即可(模块封装)
*/
var utils=(function () {
return {
aa:function () {
}
}
})();
//=>少帅
var skipRender = (function () {
var fn = function () {
//...
};
//...
return {
init: function () {
},
fn:fn
}
})();
skipRender.init();
//=>敏洁
var weatherRender = (function () {
var fn = function () {
};
return {
init: function () {
fn();//=>调取自己模块中的方法直接调取使用即可
skipRender.fn();//=>调取别人模块中的方法
}
}
})();
weatherRender.init();
/*
* 基于构造函数创建自定义类(constructor)
* 1.在普通函数执行的基础上“new xxx()”,这样就不是普通函数执行了,而是构造函数执行,当前的函数名称之为“类名”,接收的返回结果是当前类的一个实例
*
* 2.自己创建的类名,最好第一个单词首字母大写
*
* 3.这种构造函数设计模式执行,主要用于组件、类库、插件、框架等的封装,平时编写业务逻辑一般不这样处理
*/
/*function Fn() {
}
// Fn();//=>普通函数执行
var f = new Fn();//=>Fn是类 f是类的一个实例
var f2 = new Fn();//=>f2也是Fn的一个实例,f2和f是独立分开的,互不影响*/
/*
* JS中创建值有两种方式
* 1.字面量表达式
* 2.构造函数模式
*/
// var obj = {};//=>字面量方式
// var obj = new Object();//=>构造函数模式
// //=>不管是哪一种方式创造出来的都是Object类的实例,而实例之间是独立分开的,所以 var xxx={} 这种模式就是JS中的单例模式
//=>基本数据类型基于两种不同的模式创建出来的值是不一样的
//> 基于字面量方式创建出来的值是基本类型值
//> 基于构造函数创建出来的值是引用类型
//->NUM2是数字类的实例,NUM1也是数字类的实例,它只是JS表达数字的方式之一,都可以使用数字类提供的属性和方法
// var num1 = 12;
// var num2 = new Number(12);
// console.log(typeof num1);//=>"number"
// console.log(typeof num2);//=>"object"
//=================================
/*function Fn(name, age) {
var n = 10;
this.name = name;
this.age = age + n;
}
//=>普通函数执行
/!*
//1.形成一个私有的作用域
//2.形参赋值
//3.变量提升
//4.代码执行
//5.栈内存释放问题
Fn();
*!/
//=>构造函数执行
var f1 = new Fn('xxx', 20);
var f2 = new Fn('aaa', 30);
console.log(f1 === f2);//=>false:两个不同的实例(两个不同的堆内存地址)
console.log(f1.age);//=>30
console.log(f2.name);//=>'aaa'
console.log("name" in f1);//=>true name&age在两个不同的实例都有存储,但是都是每个实例自己私有的属性
console.log(f1.n);//=>undefined 只有this.xxx=xxx的才和实例有关系,n是私有作用域中的一个私有变量而已(this是当前类的实例)*/
/*
* 构造函数执行,不写RETURN,浏览器会默认返回创建的实例,但是如果我们自己写了RETURN?
* 1.return是的一个基本值,返回的结果依然是类的实例,没有受到影响
* 2.如果返回的是引用值,则会把默认返回的实例覆盖,此时接收到的结果就不在是当前类的实例了
*
* =>构造函数执行的时候,尽量减少RETURN的使用,防止覆盖实例
*/
function Fn() {
var n = 10;
this.m = n;
// return;//=>这样RETURN是结束代码执行的作用,并且不会覆盖返回的实例
// console.log(1);
}
var f = new Fn();//=>new Fn; 在构造函数执行的时候,如果Fn不需要传递实参,我们可以省略小括号,意思还是创建实例(和加小括号没有区别)
console.log(f);
//=>instanceof:检测某一个实例是否隶属于这个类
// console.log(f instanceof Fn);//=>TRUE
// console.log(f instanceof Array);//=>FALSE
// console.log(f instanceof Object);//=>TRUE (万物皆对象:所有的对象,包含创建的实例都是Object的实例)
//=>in:检测当前对象是否存在某个属性(不管当前这个属性是对象的私有属性还是公有属性,只要有结果就是TRUE)
// console.log('m' in f);//=>TRUE
// console.log('n' in f);//=>FALSE
// console.log('toString' in f);//=>TRUE toString是它的公有属性
//=>hasOwnProperty:检测当前属性是否为对象的私有属性(不仅要有这个属性,而且必须还是私有的才可以)
// console.log(f.hasOwnProperty('m'));//=>TRUE
// console.log(f.hasOwnProperty('n'));//=>FALSE 连这个属性都没有
// console.log(f.hasOwnProperty('toString'));//=>FALSE 虽然有这个属性但是不是私有的属性
//=>思考题:编写一个方法hasPubProperty,检测当前属性是否为对象的公有属性,和hasOwnProperty对应
function hasPubProperty(obj, attr) {
//=>OBJ:要检测的对象
//=>ATTR:要检测的属性
//...
}
hasPubProperty(f, 'm');//=>FALSE
hasPubProperty(f, 'n');//=>FALSE
hasPubProperty(f, 'toString');//=>TRUE
/*
* 原型(prototype)、原型链(__proto__)
*
* [函数]
* 普通函数、类(所有的类:内置类、自己创建的类)
*
* [对象]
* 普通对象、数组、正则、Math、arguments...
* 实例是对象类型的(除了基本类型的字面量创建的值)
* prototype的值也是对象类型的
* 函数也是对象类型的
* ...
*
* 1.所有的函数数据类型都天生自带一个属性:prototype(原型),这个属性的值是一个对象,浏览器会默认给它开辟一个堆内存
* 2.在浏览器给prototype开辟的堆内存中有一个天生自带的属性:constructor,这个属性存储的值是当前函数本身
* 3.每一个对象都有一个__proto__的属性,这个属性指向当前实例所属类的prototype(如果不能确定它是谁的实例,都是Object的实例)
*/
function Fn() {
var n = 100;
this.AA = function () {
console.log(`AA[私]`);
};
this.BB = function () {
console.log(`BB[私]`);
};
}
Fn.prototype.AA = function () {
console.log(`AA[公]`);
};
var f1 = new Fn;
var f2 = new Fn;
console.log(f1.n);
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-riHUkw2a-1594872955046)(E:\1Study\1前端笔记\info\images\Snipaste_2020-06-16_18-09-44.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xPvAtujW-1594872955046)(E:\1Study\1前端笔记\info\images\Snipaste_2020-06-16_18-10-27.png)]
less
它是CSS预编译语言,和它类似的还有sass/stylus…
css是标记语言,不是编程语言,没有类、实例、函数、变量等东西;而less等预编译语言就是让css具备面向对象编程的思想;但是浏览器不能直接识别和渲染less代码,需要我们把less代码预先编译为正常的css后,再交给浏览器渲染解析;
less的编译
- 在开发环境下编译(产品没有开发完,正在开发中,这个是开发环境)
导入less.js即可
//=>rel="stylesheet/less" 这块有修改
<link rel="stylesheet/less" href="css/demo1.less">
//=>导入JS文件即可
<script src="js/less-2.5.3.min.js"></script>
- 在生产环境下编译(产品开发完成了,需要部署到服务器上)
项目上线,不能把less部署,这样用户每一次打开页面都需要重新的编译,非常耗性能,我们部署到服务器上的是编译后的css
1.在当前电脑的全局环境下安装less模块
$ npm install less -g
验证是否安装成功:$ lessc -v
2.基于命令把我们的less编译成css
$ lessc xxx/xxx.less xxx/xxx.min.css -x
把指定目录中的less编译成为css(并且实现了代码的压缩),把编译后的css存入到具体指定路径中的文件中;上线前在HTML中导入的是css文件;
- 目前基于webpack和框架实现工程化开发的时候,我们都是在webpack配置文件中,配置出less的编译(需要安装less/less-loader等模块),这样不管是开发环境下的预览,还是部署到生产环境下,都是基于webpack中的less模块编译的
超级简单的less语法
1.变量
用变量存储一个公共值,后期需要使用这个值,直接调取变量即可,以后如果值需要修改,只需要更改变量的值,那么所有用到这个变量的地方都跟着修改了
//定义变量
//对于颜色
@link-color:#555;
.hover{
color:@link-color;
}
//对于图片 避免因为换目录图片无法显示
@bg-src: "../img";
.box{
background: url("@{bg-src}/news_1.png") no-repeat;
}
& > .bg { /*.pub > .bg*/
}
&.bg { /*.pub.bg*/
}
&:hover { /*.pub:hover*/
}
@H: 200;
.pub {
@H: 100;
.bg { /*.pub .bg*/
a {
width: unit(@H, px); /*300 找最近的 变量提升*/
}
@H: 300;
}
原型相关
-
函数的prototype属性
-
每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)
-
原型对象中有一个属性constructor, 它指向函数对象
-
-
给原型对象添加属性(一般都是方法) 实例对象可以访问
- 作用: 函数的所有实例对象自动拥有原型中的属性(方法)
显示原型和隐式原型
-
每个函数function都有一个prototype,即显式原型(属性)
-
每个实例对象都有一个__ proto __,可称为隐式原型(属性)
-
对象的隐式原型的值为其对应构造函数的显式原型的值
-
总结:
-
函数的prototype属性: 在定义函数时自动添加的, 默认值是一个空Object对象
-
对象的__ proto __属性: 创建对象时自动添加的, 默认值为构造函数的prototype属性值
-
程序员能直接操作显式原型, 但不能直接操作隐式原型(ES6之前)
-
//定义构造函数
function Fn() { // 内部语句: this.prototype = {}
}
// 1. 每个函数function都有一个prototype,即显式原型属性, 默认指向一个空的Object对象
console.log(Fn.prototype)
// 2. 每个实例对象都有一个__proto__,可称为隐式原型
//创建实例对象
var fn = new Fn() // 内部语句: this.__proto__ = Fn.prototype
console.log(fn.__proto__)
// 3. 对象的隐式原型的值为其对应构造函数的显式原型的值
console.log(Fn.prototype===fn.__proto__) // true
//给原型添加方法
Fn.prototype.test = function () {
console.log('test()')
}
//通过实例调用原型的方法
fn.test()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N10vVBbL-1594872955047)(E:\1Study\1前端笔记\info\images\Snipaste_2020-06-17_14-33-24.png)]
原型链
访问一个对象的属性时,先在自身属性中查找,找到返回。
如果没有, 再沿着 __ proto __这条链向上查找, 找到返回如果最终没找到, 返回undefined。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SRlXdxw5-1594872955048)(E:\1Study\1前端笔记\info\images\Snipaste_2020-06-17_16-07-26.png)]
我们可以使用对象的hasOwnProperty()
来检查对象自身中是否含有该属性;使用in
检查对象中是否含有某个属性时,如果对象中没有但是原型中有,也会返回true
原型链的属性问题
- 读取对象的属性值时: 会自动到原型链中查找
- 设置对象的属性值时: 不会查找原型链, 如果当前对象中没有此属性, 直接添加此属性并设置其值
- 方法一般定义在原型中, 属性一般通过构造函数定义在对象本身上
function Fn() {
}
Fn.prototype.a = 'xxx'
var fn1 = new Fn()
console.log(fn1.a) //xxx
var fn2 = new Fn()
fn2.a = 'yyy' //另一个实例改变a的值=>不能改变
console.log(fn1.a) //xxx
console.log(fn2.a) //yyy
console.log(fn2);
另种:
函数有三种角色
-
普通函数
- 堆栈内存释放
- 作用域链
-
类
- prototype:原型
- __ proto __:原型链
- 实例
-
普通对象
- 和普通的一个OBJ没啥区别,就是对键值对的增删改查
三种角色间没有什么必然关系
function Fn() {
var n = 10;
this.m = 100;
}
Fn.prototype.aa = function () {
console.log('aa');
};
Fn.bb = function () {
console.log('bb');
};
//=>普通函数 这里执行的fn和后面两个原型和键值对的增删改查无关系
Fn();//=>this:window 有一个私有变量n 和原型以及属性bb没有关系 window的m设为200
//=>构造函数执行
var f = new Fn;//=>这里的this就不是window了 而是fn的实例f
console.log(f.n);//=>undefined:Fn里的n是私有变量和实例没有关系
console.log(f.m);//=>100 实例的私有属性 this.m this指的f所以有关系
f.aa();// 'aa' =>实例通过__proto__找到Fn.prototype上的方法
console.log(f.bb);//=>undefined:bb是把Fn当做一个普通的对象设置的属性而已,和实例等没有半毛钱关系
//=>普通对象
// Fn.bb();
//============================================
//=>JQ这个类库中提供了很多的方法,其中有一部分是写在原型上的,有一部分是把它当做普通对象来设置的
~function () {
function jQuery() {
//...
return [JQ实例]
}
jQuery.prototype.animate=function(){}
//...
jQuery.ajax=function(){}
//....
window.jQuery = window.$ = jQuery;
}();
// $().ajax() //=>调不了
// $().anaimte() //=>这样可以调取
// $.ajax() //=>直接的对象键值对操作 $就是function jQuery
// $.animate() //=>对象上没有animate这个属性,这个属性在和实例相关的原型上
总结:
原型上的公共方法,只能通过 实例.方法 调取使用
普通对象的内置键值对 只能Number.属性名
this的不同情况:
- 以函数的形式调用时,this永远都是window
- 以方法的形式(前面有点)调用时,this就是调用方法的对象
- 以构造函数的形式调用时,this就是新创建的对象
- 使用call和apply调用时,this就是指定的那个对象
- 在全局作用域中this代表window
练习题:
function Foo() {
getName = function () {
console.log(1);
};
return this;
}
Foo.getName = function BBB() {
console.log(2);
};
Foo.prototype.getName = function AAA() {
console.log(3);
};
var getName = function () {
console.log(4);
};
function getName() {
console.log(5);
}
Foo.getName();//=>2 把Foo当做一个对象,找Foo的私有方法执行
getName();//=>4 执行全局下的GET-NAME
Foo().getName();//=>1 先把FOO当做普通函数执行,执行返回的结果在调取GET-NAME执行
getName();//=>1 执行的依然是全局下的GET-NAME
console.log(new Foo.getName());;//=>A:(Foo.getName) =>new A() =>2
new Foo().getName();//=>B:new Foo() =>B.getName() =>3
console.log(new new Foo().getName());//=>C:new Foo() =>new C[Foo的实例].getName() =>D:C.getName =>new D(); =>3 (先计算new Foo()创建一个实例f,然后new f.getName(),先找到f.getName,在把这个函数new一下,最后其实相当于把f.getName当做一个类,返回这个类的一个实例)
-
每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)
-
原型对象中有一个属性constructor, 它指向函数对象本身
var和let的区别:let不能变量提升,var可以;let不能重复声明;全局作用域下,let声明的变量和window没有关系;暂时性死区;let能形成块级作用域
call,apply,bind改变某一个函数this指向
window.name = 'zz';
let fn = function () {
console.log(this.name);
};
let obj = {
name: "OBJ",
fn: fn
};
let oo = {name: "OO"};
fn();//=>this:window "zz"
obj.fn();//=>this:obj "OBJ"
call:
// [fn].call([this],[param]...)
// fn.call:当前实例(函数FN)通过原型链的查找机制,找到Function.prototype上的call方法
// =>function call(){[native code]}
// fn.call():把找到的call方法执行
// 当call方法执行的时候,内部处理了一些事情
// =>首先把要操作函数中的THIS关键字变为CALL方法第一个传递的实参值
// =>把CALL方法第二个及第二个以后的实参获取到
// =>把要操作的函数执行,并且把第二个以后的传递进来的实参传给函数
fn.call(oo) //=>this:00
fn.call(obj) //=>this:obj
fn.call(obj,10) //把fn中的this变成obj,10和20作为实参传给obj
CALL中的细节:
- 非严格模式下,如果参数不传,或者第一个传递的是null/undefined,THIS都指向WINDOW
- 在严格模式下,第一个参数是谁,THIS就指向谁(包括null/undefined),不传THIS是undefined
let fn = function (a, b) {
console.log(this,a,b);
};
let obj = {name: "OBJ"};
// fn.call(obj, 10, 20);//=>this:obj a=10 b=20
// fn.call(10, 20);//=>this:10 a=20 b=undefined
// fn.call();//=>this:window a=undefined b=undefined 没有指定this,填null/undefined时就是window
// fn.call(null);//=>this:window
// fn.call(undefined);//=>this:window
apply:
- 和call基本上一模一样,唯一区别在于传参方式
- APPLY把需要传递给FN的参数放到一个数组(或者类数组)中传递进去,虽然写的是一个数组,但是也相当于给FN一个个的传递:fn.apply(obj,[10,20])
bind:
- 语法和call一模一样,唯一的区别在于立即执行还是等待执行
fn.call(obj,10,20) //改变FN中的THIS,并且把FN立即执行
fn.bind(obj,10,20) //改变FN中的THIS,此时的FN并没有执行(不兼容IE6~8)
正则
是一个用来处理字符串的规则
-
正则只能用来处理
字符串
-
处理一般包含两方面:
A:验证当前字符串是否符合某个规则 “正则匹配”
B:把一个字符串中符合规则的字符获取到 “正则捕获”
-
学习正则其实就是在学习如何编写规则,每一个正则都是由修饰“元字符”、“符”两部分组成
创建正则的两种方式
let reg1 = /^\d+$/g;//=>字面量方式
let reg2 = new RegExp("^\\d+$", "g");//=>构造函数方式
正则两个斜杠之间包起来的都是“元字符”,斜杠后面出现的都是“修饰符”
常用的修饰符
- i:ignoreCase 忽略大写小匹配
- m:multiline 多行匹配
- g:global 全局匹配
一些细节:
-
中括号中出现的元字符一般都是代表本身含义的
-
中括号中出现的两位数,不是两位数,而是两个数字中的任意一个
-
一个正则设置了^和$,那么代表的含义其实就是只能是xxx
上一篇: 设计模式-工厂模式