常用的浏览器
webkit内核(v8)引擎
Gecko
presto
Trident
JS做客户端语言
JS中的变量 variable
变量:可变的量,在编程语言中,就是一个名字,用来存储和代表不同值的东西
//ES3
var a = 12;
a = 13;
console.log(a);//输出的是a,值为13
//ES6
let b = 100;
b = 200;
const c = 1000;
c = 2000;//报错,因为const创建的值不能被修改(可以理解为常量)
=========================================
``**但是**``如果const定义的对象是引用数据类型,则const只能保证指针不变而修改引用类型的值还是可以变化
=========================================
// const person = {
name : '老王',
sex : '男'
}
person.name = '老李'
console.log(person.name)//输出老李
person = {
name : 'test',
sex : '男'
}//则会报错
//创建函数也相当于创建变量
function fn(){}
//创建类也相当于创建变量
class A{}
//ES6的模块导入也相当于创建变量
import B from './B.js'
//Symbol 创建唯一值
let n = Symbol(100);
let m = Symbol(100);
n==m;//会返回false
JS中的命名规范
let $box//一般用jquery获取以$开头
let _box//一般公共变量都是_开头
JS中常用的数据类型
number数字类型
包含:常规数字、NaN
NaN
not a number:不是一个数,但是隶属于数字类型
NaN和任何值(包括自己)相比较,都不相等,NaN!=NaN,所以我们不能用相等的方式判断是否为有效数字
isNaN
检测一个值是否为
非
有效数字,如果不是
有效数字则返回true,否则返回false
在使用isNaN来检测时,会首先验证检测的值是否为数字类型,如果不是,先基于Number()这个方法,把值转化为数字类型,然后再检测
ex:console.log(isNAN('10'))//返回false,先Number('10')变成10,isNaN(10)返回false
ex1:console.log(isNaN('AA'))//先进行Number('AA')变成NaN,然后isNaN(NaN),结果返回true
使用Number()来转换
console.log(Number(‘12.5’))//结果为12.5
console.log(Number(‘12.5px’))//结果为NaN
console.log(Number(‘12.5.0’))//结果为NaN
console.log(Number(’’))//空串结果返回0,注意
空串不等同于空格
console.log(Number(true))//结果为1
console.log(Number(false))//结果为0
console.log(Number(isNaN(true)))//结果也为0,过程则是先判断isNaN(true),将true用Number(true)变为1,然后isNaN(1),返回false,结果就是Number(false)返回0
console.log(isNan(true))//结果返回false
console.log(Number(null))//结果为0
console.log(Number(undefined))//结果为NaN
console.log(Number({}))//结果为NaN,过程先是({}).toString()转化为"[object Object]",然后再Number("[object Object]"),返回NaN
console.log(Number([]))//结果为0,过程也是先将数组使用toString()转化为"",然后Number(""),结果返回0
console.log(Number([10]))//结果为10
console.log(Number([1,2]))//结果为NaN
Number()是按照浏览器从底层机制,把其他数据类型转换为数字
字符串:看是否包含非有效数字字符,包含其结果就是NaN,其中""结果为0
布尔:true->1 false->0
null->0
undefined->NaN
引用类型值都要先转换为字符串,再转换为数字
使用parseInt()和parseFloat()
它们都是遵循按照字符串从左到右查找的机制找到有效数字字符(所以传递的值一定是字符串,如果不是字符串也一定要转换为字符串然后再查找)
parseInt(undefined)->parseInt(“undefined”)->NaN
parseInt("")->NaN(因为没有找到有效数字,如果是Number()方法则会返回0)
使用==进行比较
String字符串数字类型
所有用单引号、双引号、反引号包起来的都是字符串
注意
null和undefined不能直接toString,普通对象{}.toString不是直接加引号,而是变成"[object Object]"ex:
boolean布尔数据类型
只有两个值,true和false
除了0、NaN、’’、null、undefined五个值转为false,其余都为true(没有任何特殊情况)
if(1){
console.log("你好");
}//1转换boolean类型为true,则输出
if('3px' + 3){
console.log("你好");
}//'3px' + 3 进行字符串拼接,变为'3px3',然后再转换为Boolean值为true,最后输出
if('3px - 3'){
console.log("你好");
}//'3px' - 3 进行四则运算,结果为NaN,再转换为Boolean为false,结果则是不输出
**`ex:`**
console.log(Boolean(0))//false
console.log(Boolean(''))//false
console.log(Boolean(' '))//true
console.log(Boolean(null))//false
console.log(Boolean(undefined))//false
console.log(Boolean([]))//true
console.log(Boolean([10]))//true
console.log(Boolean(-1))//true
console.log(!1)//结果为false,先转为Boolean,再取反
console.log(!!1)//结果为true,!!取反再取反,等于没取反,只是转为Boolean
null和undefined
都表示没有
let num = null;//定义了一个变量,赋值null
let num;//定义了一个变量没有赋值,则默认undefined
object对象数据类型-普通对象
{[key]:[value],…}任何一个对象都是由零到多组键值对(属性名:属性值)组成的,并且属性名不能重复
获取对象的方法:
let person = {
name:'小王',
age:'20',
sex:'boy',
height:'185cm',
1:'100分'
} //person.name/person['name']都可以获取到name这个属性
console.log(person.weight)//结果为undefined
如果一个对象内的属性名为数字,则不能使用对象.属性名来获取
console.log(person['1'])//输出100分
console.log(person.1)//SyntaxError语法错误
person.girlFriend = '小玉'
console.log(person.girlFriend)//输出小玉
person.name = 'null'//给person内的name属性置为空
delete person['1']//给person内这个1属性删除
object对象数据类型-数组对象
数组是特殊的对象数据类型
let ary = [10,20,'你好',true]//相当于下面这样
let ary = {
0:10,
1:20,
2:'你好',
3:true,
length:4
}//所以我们在数组中写的内容都为属性值,而数组的属性名从0开始依次递增
索引数组
console.log(ary.length-1)//索引数组最后一个元素内容
console.log(ary[0])//索引数组第一个元素内容
添加数组内容
ary[ary.length] = 5;//在数组末尾添加一个内容为5的属性值,数组长度自增
console.log(ary);
基本类型按值操作
引用类型按引用地址操作
ex:
let a =12;
let b = a;
b = 13;
console.log(a);//输出12
let n = {
name:"boy"
}
let m = n;
m.name = "girl";
console.log(n.name);//输出girl
1.首先是一个字符串
2.字符串中包含对应类型
1.typeof null => “object”,但是null并不是对象
2.基于typeof无法细分出当前值是普通对象还是数组对象等,因为只要是对象数据类型,返回的结果都是"object"
ex:
console.log(typeof typeof typeof [])//输出结果为"string",最里面的typeof []返回结果为"object",外层的typeof检测为"string"
条件成立做什么?条件不成立做什么?
条件?条件成立处理的事情:条件不成立处理的事情
如果处理的事情比较多,用括号包裹,每一件事用逗号分隔
如果不需要处理事情,则用null或者undefined占位
let a = 10;
a >=0 && a<20 ? (a++,console.log(a)):null
一个变量在不同值情况下的不同操作
在每种case结束时,最好加上break,不加break后面条件不管是否成立都会执行,直到遇到break为止
每种case情况的比较都是用的===“绝对相等”,而ifelse的情况比较使用的是==“相等”
=========================================================
==:相等(如果两边的数据类型不相同,则会转化为相同的数据类型,再进行比较)
=:绝对相等(如果两边类型不相同,肯定不相等,不会默认转换数据类型),项目中为了保证业务严谨性,推荐使用=
双等号==:
(1)如果两个值类型相同,再进行三个等号(===)的比较
(2)如果两个值类型不同,也有可能相等,需根据以下规则进行类型转换在比较:
1)如果一个是null,一个是undefined,那么相等
2)如果一个是字符串,一个是数值,把字符串转换成数值之后再进行比较
三等号===:
(1)如果类型不同,就一定不相等
(2)如果两个都是数值,并且是同一个值,那么相等;如果其中至少一个是NaN,那么不相等。(判断一个值是否是NaN,只能使用isNaN( ) 来判断)
(3)如果两个都是字符串,每个位置的字符都一样,那么相等,否则不相等。
(4)如果两个值都是true,或是false,那么相等
(5)如果两个值都引用同一个对象或是函数,那么相等,否则不相等
(6)如果两个值都是null,或是undefined,那么相等
=======================================================
let b= 1;
switch(b){
case 1:
console.log('执行条件为1的事件');
break;
case 2:
console.log('执行条件为2的事件');
break;
case 3:
console.log('执行条件为3的事件');
break;
default:
console.log('上述条件都不满足时执行的事件');
}//输出结果为'执行条件为1的事件'
//当不同的条件需要做相同的事情时就可以利用不加break来实现
switch(num){
case 1:
case 2:
num++;
break;
default:
console.log(num);
}//这段代码就能够让当num=1和num=2时,都能执行num++这个条件
i++是先拿值进行操作,最后再自增
++i是先将该值自增,最后再拿自增后的值进行计算
i++是纯粹的进行数学计算,而i+=1以及i=i+1如果i为字符串则会进行拼接
**ex:**
let i="10";
i=i+1;// => i="101"
i+=1// => i="101"
i++// => i="11"
区别与i++与++i一样,但是i-=1与i=i-1没有区别,因为减法操作只有数值运算
传统操作DOM的方式实现业务需求
1.想操作谁就先获取谁
2.给某元素绑定某事件
3.事件触发时候做些什么事情
ex:
鼠标点击
购物车
<=script>
let box = document.getElementById("shopbtn");//获取外部盒子点击的目标
let detail =document.getElementById("content");//将显示目标赋给变量detail
box.onclick = function(){
let n = detail.style.display;//将detail的样式值获取赋给变量n
if(n==="none"){ //判断n当前的值为none还是block
detail.style.display="block";
box.style.borderBottomColor = "white";
}else{
detail.style.display="none";
box.style.borderBottomColor = "red";
}
}
=script>
重复做某些事情
ex1:
for (int i = 0; i < 4; i++ )
{
System.out.println("i==>"+i);
for (int j = 0; j < 3; j++ )
{
if( j==1){
continue;
}
System.out.println(j);
}
}
//输出结果为:
i==>0
0
2
i==>1
0
2
i==>2
0
2
i==>3
0
2
ex2:
for (var i=10;i>4;i-=2){
if(i<7){
i++;
}else{
i--;
}
}
console.log(i);
//第一次:i=10判断i>4?,结果成立执行循环体操作,i=10判断i<7?,不成立执行i--,此时i=9,最后执行i-=2,i=7;
//第二次:i=7判断i>4?,结果成立执行循环体操作,i=7判断i<7?,不成立执行i--,此时i=6,最后执行i-=2,i=4;
//第三次:i=4判断i>4?,结果不成立退出循环输出结果最终i=4
循环中的两个关键词:
ex3
://question:一共输出几个i,分别是多少?
for(var i=0;i<10;i++){
if(i>=2){
i+=2;
continue;
}
if(i>=6){
i--;
break;
}
i++;
console.log(i);
}
console.log(i);
//i=0,判断i<10?,成立判断i>=2?,不成立,判断i>=6?,不成立,执行i++,输出i为1,最后循环自增变为2.
//i=2,判断i<10?,成立判断i>=2?,成立,执行i+=2,i=4,遇见continue后面代码不执行,循环自增1,i=5.
//i=5,判断i<10?,成立判断i>=2?,成立,执行i+=2,i=7,遇见continue后面代码不执行,循环自增1,i=8.
//i=8,判断i<10?,成立判断i>=2?,成立,执行i+=2,i=10,遇见continue后面代码不执行,循环自增1,i=11.
//i=11,判断i<10?,不成立,循环结束,执行最后一个输出语句,输出i=11;
//一共输出两个i,第一个i=1,第二个i=11.
box.style.color="red";
//下面的几种方式是否都可以修改元素样式
//第一种方式
let a = box.style;
a.color="red";//可以
//第二种方式
let b = box.style.color;
b = "red";//不可以
奇偶行变色
- 我是第1个li
- 我是第2个li
- 我是第3个li
- 我是第4个li
- 我是第5个li
<=script>
// let liBox=document.getElementById("liBox");
let newsList=liBox.getElementsByTagName("li");
//1.实现奇偶行变色
//循环获取每一个li,判断每个li奇偶行,让其拥有不同的颜色
for(let i=0;i
函数就是一个方法或者一个功能体,函数实际上就是把实现某个功能的代码放到一起进行封装,以后想要操作实现该功能,只需要把函数执行即可=>“封装”,减少代码的冗余,提高代码的重复使用率,也就是高内聚,低耦合
洗衣机就是一个函数,生产洗衣机就是封装一个函数,生产的时候不知道用户在洗衣服的时候会放什么水、衣服、洗衣液等,我们需要提供出入口(提供的入口在函数中叫形参,执行中具体放入的东西叫做实参),洗完衣服需要拿出来,洗衣机提供一个出口(函数中叫做返回值:把函数处理的结果能够返回给外面使用)
//es5老方式
function [函数名]([形参变量1],[形参变量2]...){
//函数体:基于JS实现的功能;
return [处理后的结果];
}
//执行函数
[函数名]([实参1]...);
ex:
//定义一个函数,求两个数的和,然后将该值乘以10再除以2,将结果返回输出
function sum(n,m){
//初始化形参
if(n === undefined){
n = 0;
}
if(typeof m === "undefined"){
m = 0;
}//跟上面初始化方式一样,一般采用这种
let res = n+m;
res *= 10;
res /= 2;
console.log(res)
}
sum(1,2);//调用函数,传递两个实参1和2
函数如果不设置返回值,则外部调用函数则会默认undefined
返回值return返回的一定是值
函数体中遇见return不一定有返回值,也可能是后面代码不再执行
<=script>
function sum(m,n){
//初始化变量
if(typeof m==="undefined"){
m=0;
}
if(typeof n === "undefined"){
n=0;
}
let res = m+n;
return res;//return返回的一定是值,如果没有返回值,函数外面调用结果则为undefined
}
//console.log(res);=>Uncaught ReferenceError: res is not defined
let a=sum(10,20);//外面想要调用该函数返回值则需要定义一个变量来接受返回值
console.log(a);
=script>
匿名函数之表达式:把一个匿名函数本身赋给其它东西,这种函数一般不是手动触发执行,而是靠其它程序触发执行
ex:
document.body.onclick=function(){}
setTimeout(function(){},1000)//设置一个定时器,1000ms后执行匿名函数
匿名函数之自执行函数:创建完一个匿名函数后,紧接着就在后面加个小括号立刻执行该函数
ex:
(function(n){})(100)//函数内的n值为100
选项卡
读书使我快乐
编程使我难受
运动使我健康
<=script>
//获取ID为selBox的DIV
var selBox=document.getElementById("selBox");
//获取ID为selBox的DIV下的3个DIV,将他们赋给selBoxList
var selBoxList=selBox.getElementsByTagName("div");
//获取ID为selNav的UL
var selNav=document.getElementById("selNav");
//获取ID为selNav的UL下的3个LI,将他们赋给selNavList
var selNavList=selNav.getElementsByTagName("li");
//循环三个LI,给每个LI绑定点击事件
//selNavList[i]是当前循环下我们需要操作的那个li,例如selNavList[0]则是第一个li,内容为读书
//============以下方式无法实现===============================
// for(var i=0;i
dir输出一个对象的详细键值对信息
把一个多维json数组在控制台按照表格方式输出
三种结果输出方式必先经过toString转换为字符串
三种方式都会阻断当前JS代码的执行
放在头部如果JS中代码需要获取元素来操作元素,则会获取不到,如果非要放在头部可以使用window.οnlοad=function(){},但是window方法只能用一次,JQ中也有一个类似的方法$(document).ready(function(){})
<=script>
window.onload=function(){
JS代码
}
=script>
var name=10;
var obj ={
name:“小王”
};
console.log(obj.name);//输出小王
console.log(obj[“name”]);//输出小王
console.log(obj[name]);//相当于obj[10] => 输出undefined
var name =“小王”
var obj={
name:name,
name,//es6中的简化,相当于name:name
}
var obj={
name:“小王”,
age:“18”,
1:10,
2:20,
3:30
}
for(var key in obj){
console.log(“属性名:”+key+“属性值:”+obj[key])
}key是用来输出当前obj中的所有属性名,并且从属性名为最小的数字开始,而obj[key]是输出所有的属性值
前期把一些值存储到自定义的属性上,后期需要用到的时候,直接从属性上获取这些值即可
隔行变色,并且离开还原本色
- 这是第1个li
- 这是第2个li
- 这是第3个li
- 这是第4个li
- 这是第5个li
<=script>
let box = document.getElementById("box");
let liList=box.getElementsByTagName("li");
//循环遍历每个li
for(var i=0;i
1.类数组集合,集合中存储着所有函数执行时,传递的实参信息
2.不论是否设置形参,arguments都存在
3.不论是否设置实参,arguments都存在
1.传递的实参个数不确定
2.传递的值是否为有效数字不确定
3.需要把传递的有效数字求和并输出结果
**ex:**
``**注意:**``箭头函数中没有arguments,但是可以用...变量名(剩余运算符来获取传递的实参集合),它与arguments不同在于它是数组,而arguments是类数组,那么以后就能用数组的处理方式来进行后续操作
思考:
let sum=(…arg)=>{
return eval(arg.join(’+’));
}
sum(1,2,3,4)//10
Math.max(sum(1,2,3))//6
Math.max(sum([1,2,3]))//3
sum([1,2,3])//3
sum(1,2,3)//6
sum([1])//1
sum([1,2])//2
Math数学函数:它本身不是一个函数,而是一个对象,对象中存储了很多操作数学的属性方法,因此被称为数学函数
Math.abs([number value]) 取一个数的绝对值,如果里面不是一个数则会基于Number()这个方法将它转化为数字
Math.ceil/floor([number value]) 向上(取整一定比原数大)/向下(取整一定必原数小)取整,如果里面不是一个数则会基于Number()这个方法将它转化为数字
Math.round([number value]) 四舍五入法(如果是负数.5则会舍去),如果里面不是一个数则会基于Number()这个方法将它转化为数字
Math.max/min([val1],[val2]…) 获取一堆数中的最大值/最小值
**思考题:
**求Math.max/min([10,20,30,40,50])
Math.sqrt([number value]) 给一个数开方(负数不能开方)
Math.pow([val],[val]) 给一个数计算幂次
Math.random() 获取0-1之间随机小数
let ary=[12,23,34,45];
console.log(typeof ary);=>object
console.dir(ary);
ary={
0:12,
1:23,
2:34,
3:45,
length:4
}
数字作为索引
length代表数组长度
ary[0] 代表根据索引获取指定项内容
ary.length 获取数组长度
ary.length-1 获取最后一项的索引
push:
向数组末尾添加内容
let ary =[10,20];
let res=ary.push(30,'AA',{name:'小王'}); => 给数组末尾添加内容
ary[ary.length]=40; => 基于原生JS向数组最后一项添加内容
console.log(ary,res); => 数组长度从2变为5,res=5 ary=[10,20,30,'AA',{name:'小王'}]
unshift:
向数组开头添加内容
let ary =[10,20];
let res=ary.unshift(30,'AA',{name:'小王'}); => 给数组开头添加内容
ary=[50...ary]; => 基于原生ES6展开运算符,把原有数组克隆一份,在新数组创建第一项,其余内容使用原有数组内容,也算实现了向开头添加
console.log(ary,res); => 数组长度从2变为5,res=5 ary=[30,'AA',{name:'小王'},10,20,]
shift:
删除数组中的第一项
let ary =[10,20,30,40];
let res=ary.shift(); => 删除数组开头内容
delete ary[0]; => 基于原生JS中的delete,把数组当作普通对象,确实可以删除某一项,但是不会影响数组本身结构特点(length长度不会发生变化),真实项目中不能使用这种方法删除
console.log(ary,res); => res=10 ary=[20,30,40]
pop:
删除数组中的最后一项
let ary =[10,20,30,40];
let res=ary.pop(); => 删除数组最后一项
ary.length--; => 基于原生JS,让数组删除一位(默认删除最后一位)
console.log(ary,res); => res=40 ary=[10,20,30]
splice:
实现数组的增加、删除、修改
let ary =[10,20,30,40,50,60,70,80,90];
let res=ary.splice(2,4); => 删除数组索引从2开始,删除四个内容(如果只有一个数字,则从它开始一直删到末尾)
console.log(ary,res); => 数组长度从9变为5,res=[30,40,50,60] ary=[10,20,70,80,90]
ary.splice(ary.length-1) => 删除最后一项
ary.splice(0,1) => 删除第一项
splice:
实现数组的增加、删除、修改
let ary =[10,20,30,40,50,60,70,80,90];
let res=ary.splice(2,4); => 删除数组索引从2开始,删除四个内容(如果只有一个数字,则从它开始一直删到末尾)
console.log(ary,res); => 数组长度从9变为5,res=[30,40,50,60] ary=[10,20,70,80,90]
//实现修改
ary.splice(2,4,'老王','老李'); => res=[70,80,90] ary=[10,20,'老王','老李']
//实现添加
ary.splice(2,0,'老陈'); => res=[] ary=[10,20,'老陈','老王','老李']
//向数组末尾添加
ary.splice(ary.length,0,'老雷'); => res=[] ary=[10,20,'老陈','老王','老李','老雷']
//向数组开头添加
ary.splice(0,0,'老牛'); => res=[] ary=['老牛',10,20,'老陈','老王','老李','老雷']
slice:
实现数组的查询
索引
n开始找到索引
为m的这一项(不包含m这一项) let ary=[10,20,30,40,50];
let res=ary.slice(1,3);
console.log(res,ary) => res=[20,30] ary=[10,20,30,40,50]
=> m不写(找到末尾)
ary.slice(1) => ary=[20,30,40,50]
=> 数组克隆(浅克隆,参数0不写也可以)
res=ary.slice(0) => res=[10,20,30,40,50]
思考:
1.如果n/m为负数会咋样?
2.如果n>m会咋样?
3.如果是小数会咋样?
4.如果是非有效数字会咋样?
5.如果m或者n的值比最大索引还要大会咋样?
解答:1.暂时没发现规律
2.n>m 则找不到
3.小数则向下取整
4.如果n为非有效数字,m为有效数字,则n默认为0.若m为非有效数字,则找不到
5.如果n的索引比数组大,则找不到,如果m的索引比数组大,则从n索引开始找完整个数组
concat:
实现数组的拼接
let ary1=[10,20,30,40];
let ar2=[40,50,60,70];
let res=ary1.concat('小陈',ary2);
console.log(res); => res=[10,20,30,40,'小陈',40,50,60,70];
toString():
把数组转换为字符串
let ary=[10,20,30];
let res=ary.toString();
console.log(res); => res='10,20,30'
join():
把数组转换为字符串
let ary=[10,20,30];
let res=ary.join(); => res="10,20,30"
res=ary.join(""); => res="102030"
res=ary.join("|"); => res="10|20|30"
res=ary.join("+"); => res="10+20+30"
console.log(eval(res)); => 60 eval把字符串变成JS表达式来执行
indexOf(x,y)/lastIndexOf(x,y):
检测当前项x在数组中第一次/最后一次出现位置的索引值
let ary=[1,2,3,1,2,3];
console.log(ary.indexOf(2)); => 1
console.log(ary.lastIndexOf(3)); => 5
检测数组中是否包含"小陈"
if(ary.indexOf("小陈")===-1){
不包含
}else{
包含
}
includes():
检测当前项在数组中是否出现过
let ary=[1,2,3];
let res=ary.includes(4); => res=false
reverse():
把数组倒过来排列
let ary=[1,2,3];
let res=ary.reverse(); => res=[3,2,1] ary=[3,2,1]
sort():
实现数组的排序(从小打大)
注意:
**如果不传递参数,只能排序10以下的(不包括10),默认按照每一项第一个字符来排) let ary=[7,8,5,2,4,6]
let res=ary.sort(); => res=[2,4,5,6,7,8]
let ary1=[12,15,9,28,10,22];
res=ary1.sort(); => res=[10,12,15,22,28,9]
=> 如果要实现降序或者升序则需要在sort内写入函数
let res=ary1.sort(function(a,b){
return a-b; => 实现升序res=[9,10,12,15,22,28]
return b-a; => 实现降序 res=[28,22,15,12,10,9]
});
=> 改写为箭头函数
let res=ary1.sort((a,b)=>a-b); res=[9,10,12,15,22,28];
let res=ary1.sort((a,b)=>b-a); res=[28,22,15,12,10,9];
forEach()
map()
filter()
find()
reduce()
some()
every()
…
forEach():
遍历数组中的每一项
=> 基于for循环解决数组遍历问题
let ary=['我','是','老','王','啊'];
for(let i=0;i使用forEach()
let ary=['我','是','老','王','啊'];
ary.forEach((item,index)=>{
console.log('索引:'+index+'内容是:'+item); =>箭头函数中的参数第一个为索引内容(不管取什么名字),第二个为索引(不管取什么名字)
});
=> 方法一:
1.建立一个新数组,遍历原数组的每一项
2.如果新数组中没有这一项就将它添加进新数组
let ary=[1,3,2,4,2,3,3,4,1,2,2,1,3,4,4];
let newAry=[];
for(let i=0;i 循环原数组中每一项给item
if(newAry.includes(item)){
continue; => 如果新数组中包含这一项就跳过当前循环;
}
newAry.push(item);
}
console.log(newAry);
优化方法一(IE6、7、8不兼容):
let newAry=[];
ary.forEach((item)=>{
if(newAry.includes(item)) return;
newAry.push(item);
});
newAry.sort((a,b)=>{
return a-b;
});
console.log(newAry);
=> 方案二(在原数组中操作,如何解决数组塌陷):
1.先分别拿出数组的每一项A
2.用这一项A和后面的每一项依次进行比较,如果遇到和当前项A相同的,则在原来的数组中把这一项删除
let ary=[1,3,2,4,2,3,3,4,1,2,2,1,3,4,4];
for(let i=0;ii=0 item=1 j=1 compare=3 1!==3
=>i=0 item=1 j=2 compare=2 1!==2
=>i=0 item=1 j=3 compare=4 1!==4
=>i=0 item=1 j=4 compare=2 1!==2
=>i=0 item=1 j=5 compare=2 1!==3
=>i=0 item=1 j=6 compare=2 1!==3
=>i=0 item=1 j=7 compare=2 1!==4
=>i=0 item=1 j=8 compare=2 1===1 ary.splice(8,1) ary=[1,3,2,4,2,3,3,4,(1),2,2,1,3,4,4] j-- =>j=7 j++ =>j=8
=>i=0 item=1 j=8 compare=2 1!==2
=>...
for(let j=i+1;i 因为每当删除时,后面元素的索引会向前移动一位,所以为了保证后续操作不变,则将索引先减少再增加
}
}
}
ary.sort((a,b)=>{
return a-b;
});
console.log(ary);
=> 方案三(创建一个对象,如果数组中含有对象,则不能使用这种方法):
let ary=[1,3,2,4,2,3,3,4,1,2,2,1,3,4,4];
let obj={};
=> 循环数组中的每一项,把每一项向数组中进行存储(item:item)(数组中的内容作为对象obj的属性名和属性值)
for(let i=0;i i=0 ary[0]=1 item=1 obj[1]==undefined obj[1]=1;
=> i=1 ary[1]=3 item=3 obj[3]==undefined obj[3]=3;
=> i=2 ary[2]=2 item=2 obj[2]==undefined obj[2]=2;
=> i=3 ary[3]=4 item=4 obj[4]==undefined obj[4]=4;
=> i=4 ary[4]=2 item=2 obj[2]!==undefined splice(4,1) ary=[1,3,2,4,(2),3,3,4,1,2,2,1,3,4,4] i-- =>i=3 i++=>i=4;
=> i=4 ary[4]=3 item=3 obj[3]!==undefined splice(4,1) ary=[1,3,2,4,(3),3,4,1,2,2,1,3,4,4] i-- =>i=3 i++=>i=4;
=> i=4 ...
let item=ary[i];
if(obj[item]!==undefined){
=>每一次存储前进行判断,如果obj中存在这项,则需要删除
ary.splice(i,1);
i--;=>解决数组塌陷问题
continue;
}
obj[item]=item;
}
=> 方案三(优化性能)
let ary=[1,3,2,4,2,3,3,4,1,2,2,1,3,4,4];
let obj={};
=> 循环数组中的每一项,把每一项向数组中进行存储(item:item)
for(let i=0;i 把要删除的那项跟最后一项互换位置,然后把最后一项删除
ary.length--;
i--;
continue;
}
obj[item]=item;
=> 写成函数
/*
* unique: 实现数组去重方法
* @params
* ary [Array] 要去重的数组
* @return
* 去重后的数组
* by cly on 2021.5.3
*/
function unique(ary){
let obj={};
for(let i=0;i
=> 方法四(Set去重,基于ES6,该方法可以实现快速去重)
let let ary=[1,3,2,4,2,3,3,4,1,2,2,1,3,4,4];
ary=[...new Set(ary)];
console.log(ary);
所有用单引号、双引号、反引号包裹的都是字符串
let str='xiaochen'
str.length => 字符串长度
str[0] => 获取索引为0的(第一个)字符
str[str.length-1] => 获取最后一个字符str.length-1最后一项索引
=> 循环输出字符串中的每一个字符
let str="xiaowang"
for(let i=0;i
charAt():
根据索引获取指定位置的字符
charCodeAt():
获取指定字符的ASCII码值(Unicode编码值)
let str='xiaoxiao'
console.log(str.charAt(0)); => 'x'
console.log(str[0]); => 'x'
console.log(str.charAt(10000)); => ''
console.log(str[10000]); => undefined
console.log(str.charCodeAt(0)); => 120;
console.log(String.fromCharCode(121)); => 'y'
substr(n,m):
字符串的截取(在原来的字符串中查找到自己想要的)
substring(n,m):
字符串的截取
slice(n,m):
字符串的截取
let str='xiaoxiao'
console.log(str.substr(2,3)); => 'aox'
console.log(str.substring(2,3)); => 'a'
console.log(str.slice(-4,-1)); => 'xia' str.length=8 str.slice(8-4,8-1);
indexOf(x,y)/lastIndexOf(x,y):
获取x字符在字符串中首次/最后一次出现位置的索引
let str='xiaoxiaonihaowoshixiaochen';
console.log(str.indexOf('a')); => 2
console.log(str.lastIndexOf('o'); => 21
console.log(str.includes('@')); => false
console.log(str.indexOf('@')); => -1
console.log(str.indexOf('ni')); =>8
console.log(str.indexOf('niho')); => -1
console.log(str.indexOf('x',5)); => 18 查找从索引为5后面首次出现字符为'x'的索引
toUpperCase()/toLowerCase():
字母转大写/小写
let str='xiaoxiao'
console.log(str.toUpperCase()); => XIAOXIAO
console.log(str.toLowerCase()); => xiaoxiao
=> 实现第一个字母大写,其余字母小写
str=str.substr(0,1).toUpperCase()+str.substr(1); => Xiaoxiao
split([分隔符]):
把字符串按照指定的分隔符拆分成数组(与数组中的join相对应)
=> 把竖线分隔符变为逗号分隔符
let str='music|movie|eat|sport';
let ary=str.split("|"); => ["music", "movie", "eat", "sport"]
str=ary.join(","); => "music,movie,eat,sport"
console.log(str);
replace(老字符,新字符):
实现字符串的替换(经常伴随正则一起使用)
let str='小陈@小王@小雷';
str=str.replace('@','-'); => "小陈-小王@小雷" 在不使用正则表达式的情况下,执行一次replace只能替换一次字符
str=str.replace(/@/g,'-'); => "小陈-小王-小雷" /@/g,为正则表达式,g代表全局
时间字符串的处理
let time='2021-5-14 11:43:20'
=> 变为自己需要的格式,例如:
'2021年5月25日 11时43分20秒'
方案一,一直replace
time = time.replace('-','年').replace('-','月').replace(' ','日 ').replace(':','时').replace(':','分')+'秒';
console.log(time);
方案二,获取值的方法
let n=time.indexOf('-'); =>获取到字符串'-'首次出现的索引,将它赋给变量n
let m=time.lastIndexOf('-'); =>获取到字符串'-'最后一次出现的索引,将它赋给变量m
let x=time.indexOf(' '); =>获取到字符串' '首次出现的索引,将它赋给变量x
let y=time.indexOf(':'); =>获取到字符串':'首次出现的索引,将它赋给变量y
let z=time.lastIndexOf(':'); =>获取到字符串':'最后一次出现的索引,将它赋给变量z
let year=time.substring(0,n); =>从索引0开始,截取到索引为n的项(不包含n),获取到年
let month=time.substring(n+1,m); =>从索引n的后一项开始,截取到索引为m的项(不包含m),获取到月
let day=time.substring(m+1,x); =>从索引m的后一项开始,截取到索引为x的项(不包含x),获取到日
通过split一项项的拆分
let n=time.split(' '); =>通过字符串' ',把整个时间拆分成两个数组["2021-5-14", "11:43:20"]
let m=n[0].split('-'); =>通过字符串'-',把数组中第一个内容又拆成数组["2021", "5", "14"]
let x=n[1].split(':'); =>通过字符串':',把数组中第二个内容又拆成数组["11", "43", "20"]
通过正则表达式直接全拆分
let ary=time.split(/(?: |-|:)/g); =>ary=["2021", "5", "14", "11", "43", "20"]
time=addZero(ary[0])+'年'+addZero(ary[1])+'月'+addZero(ary[2])+'日'+' '+addZero(ary[3])+'时'+addZero(ary[4])+'分'+addZero(ary[5])+'秒'
=>如果时间不足两位数,则需要在前面补个0
function addZero(val){
if(val.length<2){
val='0'+val;
}
return val;
}
得到一个对象,里面包含{
lx:‘1’,
name:‘xiaoyu’,
age:‘18’,
HASH:‘box1’
}
let url1='https://sports.qq.com/kbsweb/game.htm?lx=1&name=xiaoyu&age=18#box1';
let url2='https://sports.qq.com/kbsweb/game.htm?mid=100000:55077655#box2';
let result={};
let askIndex=url1.indexOf('?'); =>获取到?首次出现的索引
let wellIndex=url.indexOf('#'); =>获取到#首次出现的索引
let askText=url1.subString(askIndex+1,wellIndex); =>获取到?到#之间的内容(不包含两端),lx=1&name=xiaoyu&age=18
let wellText=url1.subString(wellIndex+1) =>获取到#后面的内容(不包含两端),box1
let askAry=askText.split('&'); =>["lx=1", "name=xiaoyu", "age=18"]
askAry.forEach((item)=>{
let n=item.split('=');
//console.log(n) ["lx", "1"]["name", "xiaoyu"]["age", "18"]
let key=n[0];
let value=n[1];
//console.log(key,value); lx 1 name xiaoyu age 18
result[key]=value;
});
result['HASH']=wellText;
将queryURLparameter封装成一个方法
function queryURLParams(url){
let askIndex=url.indexOf('?'),
wellIndex=url.indexOf('#'),
askText='',
wellText='',
result={};
if(wellIndex===-1){
wellIndex=url.length; =>没有#则让索引为整个url的长度
}else{
wellText=url.substring(wellIndex+1); =>将#索引后面内容截取给wellText
result['HASH']=wellText;
}
if(askIndex!==-1){
askText=url.substring(askIndex+1,wellIndex); =>截取从索引为?后一项开始,到索引为#(不包含这项)
let askAry=askText.split('&'); =>将从?到#截取的内容按&分成数组内容
askAry.forEach((item)=>{
let n=item.split('='); =>将每个item分别以=划分
result[n[0]]=n[1]; =>n[0]为上面划分的属性名 n[1]为上面划分的属性值
});
}
return result;
}
let paramsObj=queryURLParams(url1); =>{HASH: "box1", lx: "1", name: "xiaoyu", age: "18"}
let time = new Date(); => 获取当前客户端(本地电脑)时间,用户可以自行修改,不能作为参考依据
getFullYear() 获取年
getMonth() 获取月
getDate() 获取日
getDay() 获取星期(0-6 星期日-星期六)
getHours() 获取小时
getMinutes() 获取分
getSeconds() 获取秒
getMilliseconds() 获取毫秒
getTime() 获取当前日期距离1970/1/1 00:00:00 这个日期之间的毫秒差
toLocaleDateString() 获取年月日
toLocaleString() 获取完整的年月日,时分秒
小时钟案例
/引入script
let clockBox=document.getElementById('clockBox');
//封装一个日期位数不为两位补0的方法
function addZero(val){
val=Number(val);
return val<10?'0'+val:val;
}
//封装一个自定义时间格式的方法
function queryTimeDate(){
let time= new Date(),
year=time.getFullYear(),
month=time.getMonth()+1,
day=time.getDate(),
week=time.getDay(),
hour=time.getHours(),
minute=time.getMinutes(),
second=time.getSeconds();
let weekAry=['天','一','二','三','四','五','六'];
let result=year+'年'+addZero(month)+'月'+addZero(day)+'日'+' '+'星期'+weekAry[week]+' '+addZero(hour)+'时'+addZero(minute)+'分'+addZero(second)+'秒';
clockBox.innerHTML=result;
}
queryTimeDate();//执行上述函数
setInterval(queryTimeDate, 1000);//定时器,1秒执行一次方法
let time='2021-5-16 12:0:0'
//位数不够补0
function addZero(val){
val=Number(val);
return val<10?'0'+val:val;
}
//把时间字符串变为标准格式
function handleDate(time){
time=time.replace(/-/g,'/');
time=new Date(time);
let year=time.getFullYear(),
month=addZero(time.getMonth()+1),
day=addZero(time.getDate()),
hour=addZero(time.getHours()),
minute=addZero(time.getMinutes()),
second=addZero(time.getSeconds());
return year+'年'+month+'月'+day+'日'+' '+hour+'时'+minute+'分'+second+'秒'
}
time=handleDate(time);
console.log(time);
document.getElementById() 指定在文档中,基于元素的ID或者这个元素对象
[context].getElementsByTagName() 在指定上下文中,通过标签名获取一组元素集合
[context].getElementsByClassName() 在指定上下文中,通过样式类名获取一组元素集合(不兼容IE6-8)
document.getElementsByName() 在整个文档中,通过标签的Name属性值获取一组节点集合(在IE中只有表单元素的Name才能识别到,所以我们一般只用于表单元素的处理)
document.head/document.body/document.documentElement 获取页面的head/body/整个页面
[context].querySelector([selector]) 在指定上下文中,通过选择器获取到指定的元素对象(不兼容IE6-8)
[context].querySelectorAll([selector]) 在指定上下文中,通过选择器获取到指定的元素集合(不兼容IE6-8)
元素选择器:
let box=document.querySelector('#box'); => 获取到ID为box的对象
let links=box.querySelectorAll('a'); => 获取到box下的所有a标签
节点node (页面所有内容都是由节点组成)
节点集合nodeList (getElementsByName/querySelectorAll获取的都是节点集合)
元素节点(元素标签)
文本节点
nodeType:3
nodeName:’#text’
nodeValue:文本内容
注释节点
nodeType:8
nodeName:’#commen’
nodeValue:注释内容
文档节点(document)
nodeType:9
nodeName:’#document’
nodeValue:null
…
childNodes:获取所有的子节点 =>所有儿子
children:获取所有元素的子节点(子元素标签集合) => 所有元素儿子
parent:获取父亲节点
firstChild:获取第一个子节点(啥节点类型都有可能) => 大儿子
lastChild:获取最后一个子节点(啥节点类型都有可能) => 小儿子
firstElementChild/lastElementChild:获取第一个/最后一个元素子节点(不兼容IE6-8) 大元素哥哥/弟弟
previousSibling:获取上一个哥哥节点(啥节点类型都有可能) => 上一个哥哥
nextSibling:获取下一个弟弟节点(啥节点类型都有可能) => 下一个弟弟
previousElementSibling/nextElementSibling:获取哥哥/弟弟元素节点(不兼容IE6-8) => 上一个元素哥哥/下一个元素弟弟
…
- git和npm操作
- 面向对象和原型:研究插件源码,自己写插件
- 闭包作用域:堆栈内存处理
- ES6:从入门到放弃
- 同步异步编程及核心:微任务、宏任务、时间循环
- DOM及其事件模型
- jQuery及源码分析
- 设计模式:发布订阅、单例、Promise、PromiseA+
- AJAX及跨域解决方案:封装一款超牛X的AJAX库
- 一入HTTP深似海
- 性能安全优化
- VUE全家桶:vue-cli\vue\vue-router\vuex\vue element...
- vue和vuex的核心源码
- Recat全家桶:create-react-app、antd、antdpro、react、react-dom、react-native、react-router-dom、redux、react-redux、dva、redux-saga、mobx...
- react核心原理
- redux源码和中间件编写
- webpack
- node和express
- type script
- ...
script>
let box=document.getElementById('box');
//标准浏览器(非IE6-8)中会把空格和换行当作文本节点处理
console.log(box.childNodes);
//但是IE6-8下使用children会把注释也当作元素节点
console.log(box.children);
//===================================================
//找所有为元素儿子的节点方法
function queryChildren(context){
//获取到所有的孩子节点
let res=[],
nodeList=context.childNodes;
//遍历整个孩子节点,如果是元素节点就放进数组中
for(i=0;i #text 因为它的上一个哥哥是空格
console.log(prev(vueBox.previousSibling)); //=> 性能安全优化 成功找到
//找上一个为元素哥哥的方法
function prev(context){
//找到第一个哥哥,然后判断它是否是元素哥哥,不是则找哥哥的哥哥
var pre=context.previousSibling;
while(pre.nodeType!==1){
pre=pre.previousSibling;
}
return pre;
}
createElement 创建元素对象
createTextNode 创建文本对象
appendChild 把元素添加到指定容器的末尾 语法:容器.appendChild(元素)
insertBefore 把元素添加到指定容器中指定元素的前面 语法:容器.insertBefore([新增元素],[指定元素])
cloneNode(true/false) 克隆元素或者节点(深克隆:连子元素都克隆/浅克隆:不克隆子元素)
removeChild 移除容器中的某个元素
setAttribute 设置元素的自定义属性信息
getAttribute 获取元素的自定义属性信息
removeAttribute 移除元素的自定义属性信息
============两种自定义属性方式======================
NODE模块管理工具,根据npm我们可以快速安装,卸载我们所需要的资源(例如:jQuery、Vue、Vue-router…)
node -v :查看node版本
npm -v :查看npm版本
https://www.npmjs.com/ 基于npm是从npmjs.com平台上下载安装
npm install xxx :把模块安装在当前项目中(node_modules)
npm install xxx -g :把模块安装在全局环境中
npm i [email protected] :安装指定版本号的模块
npm view xxx versions > xxx.version.json :查看某个模块的版本信息(输出到指定JSON文件中)
npm init -y :初始化当前项目的配置清单(项目文件夹的名字不能包含大写字母、中文、特殊符号,创建成功后会在当前项目中生成一个package.json的文件清单,dependencies:生产依赖模块:项目开发和部署都需要,devDependencies:开发依赖模块:只有在开发的时候需要,scripts:配置本地可执行的命令)
npm i xxx --save :把模块保存到清单生产依赖中
npm i xxx --save-dev :把模块保存到清单开发依赖中
npm install :跑环境,按照清单安装所需模块
npm root -g :查看全局安装模块目录
npm uninstall xxx :
npm uninstall xxx -g :卸载安装过的模块
1.创建一个项目文件夹
2.把它作为一个新仓库进行代码管理(可以基于$ git clone把远程仓库克隆下来即可)
3.初始化模块配置清单package.json($ npm init -y)
4.安装所需的模块($ npm i jquery bootstrap@3 less)[@3代表安装bootstrap3代中最后一个版本]
5.开始编写代码
6.开发中:可能需要在本地配置命令去完成一些功能(例如LESS文件编译,此时需要配置npm可执行命令,在配置清单package.json中找到scripts,添加你需要的编译代码)
"scripts": {
"lesscly": "lessc css/index.less css/index.min.css -x" //这段作用就是编译index.less这个文件,将它转换为可执行的css,编译的时候执行npm run lesscly即可执行该命令
}
7.开发过程中我们需要基于git把文件进行管理:生产对应的历史版本,提交到暂存区、历史区、远程仓库的时候,项目中很多文件是无需上传的,例如node_modules、ideas…,我们生产一个gitignore忽略文件
8.由于每次git提交的时候,我们都不去提交node_modules,所以团队协作开发中,每当我们拉下来程序后,都需要’跑环境’,执行:$ npm install,按照项目中的package.json中的依赖信息,把缺失的模块都安装一遍
当浏览器开辟出供代码执行的栈内存后,然后代码并没有自上而下执行,而是做了很多事情,其中一件事情就是把当前作用域中所有带var和function关键字进行提前声明和定义(这就是变量提升机制)
console.log(a);//=> a=undefined
var a=12;
var b=a;
b=13;
console.log(a);//=> a=12
console.log(sum(10,20));//=>30
function sum(n,m){
return n+m;
}
//函数表达式方式,由于使用var创建sum,变量提升阶段只会声明变量,不会赋值,因此函数没有值是无法执行(真实项目中,这种方式更常用,因为这种操作更严谨)
console.log(sum(10,20));//=>报错 Uncaught TypeError: sum is not a function
var sum=function(n,m){
return n+m;
}
/* => 在全局作用域下的区别
*
* 不带var的,相当于给全局对象window设置了一个属性a
* window.a=13;
*/
a=13;
console.log(a); //=> 13
/*
* 栈内存变量存储空间
* b
* 带var的:在全局作用域下声明了一个变量b(全局变量),但是在全局下声明的变量也同样相当于给window添加了一个对应的属性(只有全局作用域具备这个特点)
*/
var b=14;
console.log(b);//=> 14
console.log(window.b);//=> 14
1.let和const不存在变量提升机制
创建变量的六种方式中,只有var和function有变量提升,let、const、import、class都不存在变量提升机制
2.var允许被重复声明,而let不能重复声明
在相同的作用域/执行上下文中,如果使用var和function关键词声明变量并进行重复声明,是不会有影响的(声明第一次后,之后再遇到就不会再重复声明了)
但是使用let或者const就不行,浏览器会校验当前作用域是否已经存在这个变量,如果存在,再次声明就会报错
=> 在浏览器开辟栈内存供代码自上而下执行前,不仅有变量提升操作,还有很多其他操作,其中还有个操作叫'词法解析'或叫'词法检测',作用就是检测当前即将执行的代码是否会存在'语法'错误,如果出现错误,代码将不会执行,直接报告错误
console.log(1); =>由于下面语法错误,所以不输出
let a=12;
console.log(a); =>由于下面语法错误,所以不输出
let a=13; =>Uncaught SyntaxError: Identifier 'a' has already been declared
console.log(a); =>由于上面面语法错误,所以不输出
console.log(1); =>这里的1能够输出
console.log(b); =>Uncaught ReferenceError: Cannot access 'a' before initialization
let b=12;
=>所谓的重复是指,不管之前是通过什么办法,只要当前栈内存中存在这个变量,我们使用let/const再次声明这个变量的时候就是语法错误(在词法解析阶段,而不是在代码执行阶段)
console.log(a);
var a=12;
let a=13; =>Uncaught SyntaxError: Identifier 'a' has already been declared
console.log(a);
fn();
function fn(){console.log(1);};
fn();
function fn(){console.log(2);};
fn();
var fn=function(){console.log(3);};
fn();
function fn(){console.log(4);};
fn();
function fn(){console.log(5);};
fn();
/* 全局作用域
* 1.变量提升阶段.不管if中条件是否成立,都要先执行变量提升这个阶段
* var a =>此时的a为undefined,同时创建了个全局变量a,同时也给window一个属性值window.a
* 2.执行代码阶段
* [property] in [object]:检测当前属性是否属于这个对象,返回boolean型结果
*/
console.log(a); => undefined
if(!('a' in window)){ => 'a' in window===true !true===false
var a = 14;
}
console.log(a); => undefined
/* 全局作用域
* 1.变量提升阶段
* 但是'函数'如果在'条件判断'中,不管条件成立与否,提升变量阶段只会提升变量而'不会赋值',在老版本浏览器中会提升也会赋值,但是在新版本浏览器中,为了兼容ES6严谨的语法规范,条件中的函数在变量提升阶段只会提前声明,而不会赋值
* 2.执行代码阶段
*
*/
//fn(); => Uncaught TypeError: fn is not a function,因为fn在if判断中,提升变量阶段没赋值,因此此时的fn()相当于只是声明的一个变量,而不是一个函数,然后我们把它注释掉,执行下面代码
if('fn' in window){
fn(); => 'fn' in window===true 条件成立,进来的第一件事就是给fn赋值,然后再执行代码,因此这个fn()能够输出,内容为你好!
function fn(){
console.log('你好!');
}
}
fn(); => 你好!
形如(fn([形参]){})([实参]),也可以+fn([形参]){}([实参])、fn([形参]){}([实参])、!fn([形参]){}([实参])、-fn([形参]){}([实参]),前面加()、、+、-只是为了语法不报错,没有别的特殊意思
自执行函数本身由于没有函数名,因此不进行变量提升
f=function(){return ture;} => 全局变量,window.f=...
g=function(){return false;} => 全局变量,window.g=...
~function(){ => 自执行函数
/*
* 函数执行会形成一个私有作用域
* 1.变量提升 function g(由于在if条件中,因此只是提升变量,而没有赋值)
* 2.代码执行
/
if(g()&&[]==![]){ => Uncaught TypeError: g is not a function 由于此时g()还不是一个函数,因此条件不成立,所以会报错,后面代码将不再执行
f=function(){return false;}
function g(){return true;}
}
}();
console.log(f());
console.log(g());
console.log(a); =>Uncaught ReferenceError: a is not defined
console.log(typeof a); =>'undefined'这是浏览器的一个BUG,因为a没有定义,应该报错
console.log(typeof a); =>Uncaught ReferenceError: Cannot access 'a' before initialization
let a;
console.log(a,b);
var a=12;
var b=12;
function fn(){
console.log(a,b);
var a=b=13;
console.log(a,b);
}
fn();
console.log(a,b);
console.log(a,b,c);
var a=12,
b=13,
c=14;
function fn(a){
console.log(a,b,c);
a=100;
c=200;
console.log(a,b,c);
}
b=fn(10);
console.log(a,b,c);
如果全局变量和私有变量指向同一个地址,那么它们就有关系(全局变量和私有变量毫不相干的说法是错误的)
var ary=[12,23];
function fn(ary){
console.log(ary);
ary[0]=100;
ary=[100];
ary[0]=0;
console.log(ary);
}
fn(ary);
console.log(ary);
从函数创建开始,作用域就已经制定好了
当前函数是在哪个作用域(N)下创建的,那么函数执行形成的作用域(M)的上级作用域就是N,和函数在哪执行没有关系,只和创建的地方有关
var n=1;
function fn(){
var n=2;
function f(){
n--;
console.log(n);
}
f();
return f;
}
var x=fn();
x();
console.log(n);
创建函数
函数执行
堆栈内存释放问题
堆内存释放
let obj ={
name:'小王';
}
let oop=obj;
=> 此时obj和oop都占用着对象的堆内存,想要释放堆内存,需要手动解除变量和值的关联(null空对象指针)
obj=null;
oop=null;
栈内存
栈内存释放
function x(){
return function(){...}
}
let f=x();
闭包的两大作用
var i=5;
function fn(i){
return function(n){
console.log(n+(++i));
}
}
var f=fn(1);
f(2);
fn(3)(4);
fn(5)(6);
f(7);
console.log(i);
var i=20;
function fn(){
i-=2;
return function (n){
console.log((++i)-n);
}
}
var f=fn();
f(1);
f(2);
fn()(3);
fn()(4);
f(5);
console.log(i);
var a=9;
function fn(){
a=0;
return function(b){
return b+a++;
}
}
var f=fn();
console.log(f(5));
console.log(fn()(5));
console.log(f(5));
console.log(a);
从性能的角度上讲,我们真实项目中应该减少对闭包的使用(因为闭包会产生不释放的栈内存,过多使用就会导致内存溢出或者降低性能)
保护
保存
函数执行的主体(不是上下文):意思是谁把函数执行,那么执行主体就是谁,this非常的不好理解,因此以后遇见this,想一句话:“你以为你以为的就是你以为的”
给元素的某个事件绑定方法,当事件触发方法执行的时候,方法中的this是当前操作的元素本身
如何确定主题:当方法前如果有点,则this为方法前的内容,没有点,this则为window或者undefined(严格和非严格的区别)
var name='老王';
function fn(){
console.log(this.name);
};
var obj={
name:'老李',
fn:fn
};
obj.fn(); => obj执行的,则this为obj,输出老李
fn(); => 非严格模式下,fn()为window.fn(),则this为window,输出老王
var fullName='language';
var obj={
fullName:'javascript',
prop:{
getFullName:function(){
return this.fullName;
}
}
};
console.log(obj.prop.getFullName()); =>this:obj.prop return obj.prop.fullName undefined
var test=obj.prop.getFullName;
console.log(test()); =>this:window return window.fullName language
var num=10;
var obj={num:20};
obj.fn=(function(num){
this.num=num*3;
num++;
return function(n){
this.num+=n;
num++;
console.log(num);
}
})(obj.num);
var fn=obj.fn;
fn(5);
obj.fn(10);
console.log(num,obj.num);
A || B :先验证A的值真假情况,若A为真,则将A的值返回,否则返回B的值
A && B :先验证A的值真假情况,若A为真,则将B的值返回,否则返回A的值
&&的优先级高于||
var foo='hello';
(function(foo){
console.log(foo); => 私有变量hello
var foo=foo||'world'; hello||world => 返回私有变量hello
console.log(foo); => 私有变量hello
})(foo); => 这里是把全局变量下的foo的值'hello'当作实参传递给函数的形参
console.log(foo); => 全局变量hello
自己能够创造出自定义类和对应的实例,构建起一套完整的面向对象模型
function CreatePerson(name,age){
this.name=name;
this.age=age;
}
=> CreatePerson('张三',18) 这里是普通函数执行
let person1=new CreatePerson('李四',20);
=> new CreatePerson()执行和普通函数执行的联系
=> 1.new这种执行方式叫做'构造函数执行模式',此时的CreatePerson不仅仅是一个函数名,被称为'类',而返回的结果(赋值给person1的)是一个对象,我们称之为'实例',而函数体中出现的this都是这个实例
实例 instanceof类 属于返回true,否则返回false
局限性:1.要求检测的实例必须是对象数据类型,基本数据类型是无法基于它检测出来
typeof能检测基本数据类型,但是引用数据类型无法具体区分,null检测为object等,刚好与instanceof互补
let n=10;
console.log(n.toFixed(2));
console.log(typeof n); => 'number'
let m=new Number('10');
console.log(typeof m); => 'object'
console.log(m.toFixed(2));
=> 和实例有关的操作一定是this.xxx=xxx,因为this是当前类创造出来的实例
=> 私有变量和实例没有必然联系
function Fn(n){
let m=10;
this.total=n+m;
this.say=function(){
console.log(this.total);
};
}
let f1=new Fn(10);
let f2=new Fn(20);
let f3=new Fn;
console.log(f1.m);
console.log(f2.n);
console.log(f1.total);
f2.say();
console.log(f1===f2);
=> 执行步骤(new执行也会把类当作普通函数执行,当然也有类执行一面)
=> 1.创建一个私有栈内存
=> 2.形参赋值&变量提升
=> 3.浏览器创建一个对象出来(这个对象就是当前类的一个新实例),并且让当前函数中的this指向这个实例对象(构造函数模式中,方法中的this是当前类的实例)
=> 4.代码执行
=> 5.在我们不自己设置return返回情况下,浏览器会把当前创建的实例对象默认返回
function Fn(){
this.x=100;
this.y=200;
}
let f1=new Fn();
let f2=new Fn();
1.每一个函数数据类型的值,都有一个天生自带的属性:prototype(原型),这个属性的属性值是一个对象(‘用来存储实例公用属性和方法’)
函数数据类型
2.在prototype这个对象中,有一个天生自带的属性constructor,这个属性存储的是当前函数本身
Fn.prototype.constructor===Fn => true
3.每一个对象数据类型的值,也有一个天生自带的属性:proto,这个属性指向’所属类的原型prototype’
对象数据类型
1.先找自己私有属性,有则调取使用,没有就继续找
2.基于__proto__找所属类原型上的方法(Fn.prototype),如果还没找到则继续基于__proto__往上找,直到找到Object.prototype为止
'hasOwnProperty’用来检测某个属性名是否为是否为当前对象的私有属性
'in’用来检测这个属性是否属于某个对象(不管是私有属性还是公有属性,只要是它的属性,结果就为true)
let ary=[1,2,3];
console.log('0' in ary); => true
console.log('push' in ary); => true
console.log(ary.hasOwnProperty('0')); => true
console.log(ary.hasOwnProperty('push')); => false push是公有属性而不是私有属性
console.log(Array.prototype.hasOwnProperty('push')); => true 公有还是私有得看是相对谁来的 自己堆中有的就是私有的属性,需要基于__proto__(__proto__这个属性在IE浏览器中不能使用,edge除外)查找的就是公有的
=>扩展一个hasPubProperty方法用来检测属性是否为公有
=>在object上绑定一个hasPubProperty的方法
Object.prototype.hasPubProperty=function(prop){
=>首先得先判断传入的属性是否为number、string、boolean等
let propAry=['number','string','boolean'];
let resProp=typeof prop;
if(!propAry.includes(resProp)){
return false;
}
=>再判断传入的属性是否为该对象的属性
let x=prop in this;
=>再判断传入的属性是否为该对象的私有属性
let y=this.hasOwnProperty(prop);
=>如果满足是该对象的属性且不是私有属性则返回true,否则返回false
if(x===true&&y===false){
return true;
}else{
return false;
}
};
console.log([].hasPubProperty('push')); //true
!function (){
function myUnique(){
//创建一个空对象,用来接收循环数组中的内容
let obj={};
//循环遍历传入的数组,定义一个item将传入数组中的内容作为属性名和属性值
for(let i=0;i
它们都是原型上提供的三个公有方法
每一个函数都可以调用这些方法
这些方法都是用来改变函数中的this指向
window.name='WINDOW'
let obj={name:'OBJ'};
function(){
console.log(this.name);
}
fn() => this:window(严格模式下是undefined,非严格模式下是window)
fn.call(obj); => this:obj
语法:函数.call([context],[params1]…)
函数基于原型链查找时,找到Function.prototype.call这个方法,并且把它执行,在call方法执行的时候,完成了以下一些操作
~function(){
function call(context){
context = context || window;
let args=[],
result;
for(let i=1;i
和call方法一样,都是把函数执行,并且改变里面this的指向,唯一区别就是传递的参数形式不同
call是一个个传递,apply则是传递一个数组
和call和apply一样,也是用来改变this的指向,只不过bind改变指向时并没有执行该函数
let与var的区别:let不存在变量提升(当前作用域中,不能在let声明前使用)
同一个作用域中,let不能重复声明
let解决了typeof暂时性死区
全局作用域中,使用let声明变量不会给window添加属性
let会存在块级作用域(除对象外的大括号都可以被看作块级私有作用域)
const fn=([形参])=>{
//函数体
}
fn([实参])
箭头函数的创建都是函数表达式的方式,所以不存在变量提升,函数只能在创建完成后被执行
形参只有一个,则小括号可以不加
大括号内只有一条语句且为return,则可以省略大括号
箭头函数没有arguments,但是可以基于剩余运算符(…)获取实参集合,而且ES6中支持给形参设定默认值
箭头函数中没有自己的this,它的this都是自己所处的执行上下文中的this
让左侧出现和右侧值相同的结构,以此快速获取到我们需要的内容(项目中用的比较多的是对数组和对象的解构赋值)
const ary=[10,20,30,40,50];
const [n,m,...x]=ary; => ...x拓展运算符:把剩下的内容存储到x数组中,但是这个表达式只能出现在最后
console.log(n,m,x); => 10 20 [30,40,50]
======================================================
const ary=[10,[20,30,[40,50]]];
const [n,[,,[,m]]]=ary;
console.log(n,m); => 10 50
======================================================
const obj={
name:'老王',
age:18,
sex:'boy',
friends:['老李','老刘','老陈','老张']
}
const {name,age,xingbie}=obj
console.log(name,age,xingbie) => 老王 18 undefined 对象的结构赋值中,左边的属性名必须跟右边保持一样(顺序可以随意)才能取到对应的值,如果非要自己定义属性名可以sex:xingbie
const {name,sex:xingbie,age}=obj;
console.log(name,xingbie,age) => 老王 boy 18
======================================================
const {
name,
friends:[firstFrineds]
}=obj;
console.log(name,firstFrineds) => 老王 老李
======================================================
const data={
'code':0,
'data':{
'today':'2021-07-02',
'data':[{
'date':'2021-07-01',
'number':'10',
'weekday':'\u661f\u671f\u4e09'
},{
'date':'2021-07-02',
'number':'20',
'weekday':'\u661f\u671f\u56db'
}]
}
}
const {
code,
data:{
today,
data:calenderData
}
}=data;
console.log(code,today,calenderData); => 0 "2021-07-02" 后面对象中的两条数据
calenderData.forEach((item)=>{
let weekday=item.weekday,
date=item.date;
console.log(weekday,date);
})
拓展运算符(多用在解构赋值中)
展开运算符(多用在传递实参中)
剩余运算符(多用在接收实参中)
=> 拓展运算符
const ary=[10,20,30];
const [n,...m]=ary;
console.log(n,m) => 10 [20,30]
=> 展开运算符
const ary=[10,20,30];
let min=Math.min(...ary); =>相当于Math.min(10,20,30)
=> 数组克隆(浅克隆)
const cloneAry=ary.slice(0);
const cloneAry=[...ary]
=> 对象克隆(浅克隆)
const obj={
name:'老王',
age:18,
sex:'男'
}
const cloneObj={...obj,age:20,sex:'女'}
console.log(cloneObj); => {name: "老王", age: 20, sex: "女"}
=> 剩余运算符
const fn=(n,...m)=>{
//n:10 ...m:[20,30]
}
fn(10,20,30);
=> 传统方式创建类
function Fn(){
this.x=100;
}
Fn.prototype.getX=function(){
console.log(this.x);
}
var f1=new Fn();
f1.getX();
// 也可以当作普通函数执行
Fn();
// 还可以把Fn当作普通的对象
Fn.queryX=function(){}
Fn.queryX();
=>ES6中class类创建
class Fn{
constructor(n,m){
this.x
}
getX{
console.log(this.x)
} //=> 写在原型上的公有方法
static queryX(){};//=> 前面加上static,把当前Fn当做普通对象设置键值对
static z=100;
y=100; //给实例设置私有属性
}
反引号括起来${}
let year=2021,
month=07,
day=02;
let res=`今天是${year}年${month}月${day}日`;
console.log(res); =>今天是2021年7月2日
=> 实现一个求和方法
function sum(){
const ary=[];
let total=null;
ary.forEach.call(arguments,(item)=>{ //将数组中的this指向arguments
total=total+item;
})
return total;
}
sum(10,20,30,40); //100
当用户在浏览器窗口的地址栏输入网址,到最后看到页面,中间都经历的哪些东西?
URL(Uniform Resource Locator): 统一资源定位符,根据地址找到相对应的资源
URI(Uniform Resource Identifier): 统一资源标识符,URN和URL是URI的子集
URN(Uniform Resource Name): 统一资源名称,一般指国际上通用的(标准的)一些名字(例如:国际统一发版的编号)
https://www.bilibili.com/video/BV1rV411n72v?p=294&spm_id_from=pageDriver
协议(http://)
域名(www.bilibili.com)
端口号(:80):端口号的取值范围0-65535,用端口号来区分同一台服务器上的不同项目
请求资源路径名称(/video/BV1rV411n72v)
问号传参信息(?p=294&spm_id_from=pageDriver)
HASH值(#…)
DNS服务器:域名解析服务器,在服务器上存储着域名 <=> 服务器外网IP 的相关记录
而我们发送请求的时候,所谓的DNS解析,其实就是根据域名,在DNS服务器上查找对应服务器的外网IP
1-5开头,三位数字
1.创建AJAX实例
let xhr=XMLHttpRequest //=> IE6以下用的是 new ActiveXObject()
2.打开URL(配置发送请求的信息)
method:http请求方式
URL:请求地址(API接口地址)
ASYNC:设置同步或者异步,默认true是异步,false是同步
user-name:传递给服务器的用户名
user-pass:传递给服务器的密码
xhr.open(‘GET’,’./json/xxx.json’,true)
3.监听ajax状态,在状态为X的时候,获取服务器响应的内容
Ajax状态码: 0 1 2 3 4
4.发送请求(send中放的是请求主体的内容)
真实项目中最好使用对应的请求(语义化)
get系列一般用于从服务器获取信息,post一般用于给服务器发送信息,但是两个都能把信息传递给服务器和从服务器上获取信息,只是两者谁多谁少而已
客户端是如何把信息传递给服务端?
服务端是如何把信息返回给客户端?
get系列和post系列本质区别
get系列传递给服务器的方式一般为:问号传参
post系列传递给服务器的方式一般为:设置请求主体
1.get传递给服务器的内容比post少,因为URL有最大长度限制(IE:2k google:4-8K),超出的部分被浏览器截取了
2.get会产生缓存(这个缓存是不可控的),因为请求的地址(尤其是问号传递的信息),浏览器有时候会认为你要和上次请求的信息一样,就会拿取上一次的信息,因此我们需要去除这个缓存,方法是在地址后面加随机数
3.get相比post不安全(虽然两个都不安全):get基于问号传参,容易被URL劫持,而post是请求主体,不容易被篡改
获取状态码:xhr.readyState
let promiseExample=new Promise((resolve,reject)=>{
//这里一般存放我们要操作的异步任务,任务成功执行resolve函数,失败执行reject函数
})
=> promise的第一个参数必须传递一个函数,我们把它称作executor
=> 当我们new一个promise的实例对象的时候就会把这个executor执行
=> promise不仅会把executor执行,还会给executor传递两个参数,resolve函数和reject函数
=> resolve函数代表promise处理的异步事件是成功的,然后把promise的状态改为fulfilled
=> reject函数代表promise处理的异步事件是失败的,然后把promise的状态改为rejected
=> promise.prototype上有三个常用方法,分别为then,catch,finally
=> then是在执行异步操作完成之前,往事件池中添加两个方法(成功和失败需要做的事情),等到异步操作完成时,再根据resolve或者reject去执行对应的事件,catch代表捕获错误,finally是不论成功还是失败他都会执行里面自己设置的事情
let promiseExamp=new Promise((resolve,reject)=>{
setTimeOut(()=>{
let ran=Math.random();
ran<0.5?reject(ran):resolve(ran)
},1000)
})
promiseExamp.then((success)=>{
console.log(‘成功’+success)
},(error)=>{
console.log(‘失败’+error)
})
promiseExamp.catch((message)=>{
console.log(‘失败’+message)
})
promiseExamp.finally(()=>{
console.log(‘不论成功失败我都会出现!’)
})