es和数据类型
js=es+dom+bom,dom和bom前面已经讲完了
es是js的本体,是指数据类型,和对于数据的操作手段,他的版本更新得很快
这些功能不是html文件提供的,也不是浏览器提供的,即使脱离了dom和bom,在nodejs服务器端,es照常使用,照样运行,他是最底层的操作方式,所有的js框架都是基于es的api封装的,包括前端的三大框架,vue,react,anglues;所以框架可以不学,es的更新一定要跟上,跟不上es的更新就真的落伍了,而且es学得好,框架什么的上手特别的快
数据类型在所有电脑语言上都是一样的,js有数字,有字符串,java有,php有,python有,而且连操作方法都几乎是一样的,虽然写法不一样,但原理相同,所以懂一个语言去学另一个速度飞快
es是写在script标签里或者js文件里的,否则不会被识别
》》正式内容(这一篇只会讲es第五代和五代以下的内容)
声明
声明就是被一个数据起名字的意思
// 把数字123叫做aa
var aa = 123;
// 把字符串123叫做bb
var bb = "123";
// 后续可以直接用aa,bb进行操作,并且数据被aa和bb的名字锁住了,于是可以反复操作
es5总共有数据类型是
- number 数字类型,整数,小数,没内容可讲
- Boolean 布尔类型,只有true/false
- string 字符串
- RegExp 正则表达式
- date 日期
- math 数学
- array 数组
- object 对象
- function 函数/方法
- json/xml
- error 错误
如何检查数据类型
有三种方法
- typeof "aa"
- "aa" instanceof String
- Object.prototype.toString.call("aa")
前两种验证都有漏洞,用第三种,具体看面试题二
number
// 精度丢失
// 为了应付这种情况,需要乘法要套一层四舍五入
console.log(0.7*0.1) //0.06999999999999999
console.log(0.7*0.7) //0.48999999999999994
// 解决方案,都乘以100,1000后变成整数再计算
boolean
常用在判断是不是第一次执行,也就是只有对错的判断上使用
// true 和 false 不能加双引号""
var isCan = true;
isCan = false;
// 取反
isCan = !isCan;
String
字符串用双引号""声明,用单引号''也行,只是java的字符串只能用双引号,做个习惯记忆
var str = "a1b2c3d4";
// 字符串的长度
str.length
// 拼接字符串,加等于+=是逻辑运算符,下面会讲
str += "e5f6" + "...";
str.concat("e5f6","...") //参数是无限的
// 如果数据里有相同的引号会出错
// 比如 str = "aa"666"
// 他会把"aa"当做一部分然后666"没办法被识别这是什么意思,于是报错,需要转义
str = "aa\"666"
// 【\】斜杆在字符串里用于转义他的后一位,然后自己消失,那怎么才能让斜杆出现在字符串里呢,用斜杆转义斜杆
str = "aa\\666"
// 转义最常用的就是在拼接标签元素时
str = "<div style=\"width=100px\" onclick=\"click(\"aa\")\">哈哈</div>"
// 字符串的拼接不能直接换行,换行需要用加号收尾,而且这样的转义太恶心,也很难理解
// 但是在新es还有更简易的方法,查看《es678910篇》
// 字符串的切割split,会改变原值,支持正则
// 是一个从开始到结尾的循环,只要找到符合条件的,会切割,然后判断切割前后是否都有数据,没有会补上一个空字符串
var str = "aaxaa"
str.split("x") // ["aa","aa"]
var str = "xaax"
str.split("x") // ["","aa",""]
// 字符串的替换,不会改变原值,所以需要新声明,返回的是新字符串
// 把str里的第一个找到的x改成y
var newStr = str.replace("x","y")
// 如果要改所有的x,不是第一个,使用正则,第一个参数支持正则
var newStr = str.replace(/x/g,"y")
// 第二个参数可以是函数,可以做更多的处理,比如所有的数字加一,所有的字母大写,但需要返回值
var newStr = str.replace(/\d/g,function(x){ return x+1; })
// 字符串的大小写,不会改变原值,所以需要新声明,返回的是新字符串
var newStr = "1a2x45".toUpperCase()
var newStr = "X66L6".toLowerCase()
// 去除字符串两边的空格,不会改变原值,所以需要新声明
var newStr = " 666 ".trim() // "666"
// 找出想要的某些数据match,不会改变原值,所以需要新声明,返回的是数组
// 可以传字符串,但是这个函数是设计给正则使用的,传字符串毫无意义,
// 常用在把日期的数字取出来,重新改格式
var arr = "2019-11-05".match(/\d+/g) //["2019","11","05"]
// 转码加密加密,用于把字符串里的特殊符号
encodeURIComponent(",/?:@&=+$#"); // %2C%2F%3F%3A%40%26%3D%2B%24%23
decodeURIComponent("%2C%2F%3F%3A%40%26%3D%2B%24%23") // ",/?:@&=+$#"
RegExp
正则是用来操作字符串的,除了上面String里出现的split,match,replace外
正则需要去记住背诵他的操作方式,表达式怎么写,可以百度,如果能手写就更牛逼了
全面的笔记查看《正则RegExp》笔记
// 声明正则
var reg = /\d/
var reg = new RegExp("/\d/")
// 检查正则的格式对不对,test方法,返回的是boolean
// 常用来检查电话号码,邮箱,身份证
/\d/.test(1)
reg.test(2)
Date
// 当前时间对象
var date = new Date();
// 当前时间戳
var now = new Date().getTime()
// 当前时间的详细信息
now.getFullYear() // 年份(4位,1970)
now.getMonth() // 月份(0-11,0代表1月,用的时候记得加上1)
now.getDate() // 日(1-31)
now.getTime() // 时间戳(从1970.1.1开始的毫秒数)
now.getHours() // 获取小时数(0-23)
now.getMinutes() // 分钟数(0-59)
now.getSeconds() // 秒数(0-59)
now.getDay() // 星期几(0-6,0代表星期天)
// 这个月第一天0点时刻
var monthBegDate = new Date(now.getFullYear(),now.getMonth(),1)
// 这个月最后一天0点时刻
let monthEndDate = new Date(date.getFullYear(),date.getMonth() + 1,0)
// 日期转时间戳
// str支持的格式有"2019-06-11 12:12:50" 或者 "2019/06/11 12:12:50"
// 但是第一个横杆拼接的数据苹果手机api不识别,统一用第二种,还有其他兼容查看兼容篇
var time = new Date(str).getTime()
Math
// 字符串转数字,最准确,但是是骚操作,
// 不能用加号,加号除了是数学运算符,还能拼接字符串,自带强转类型的缺点
"15.11" - 0
// 丢弃小数部分,保留整数部分,这个不是Math的方法,传(字符串 | 数字)都行
parseInt("2.75")
// 向上取整,有小数就整数部分加1 ,传(字符串 | 数字)都行
Math.ceil("2.15")
// 向下取整,传(字符串 | 数字)都行
Math.floor("2.75")
// 四舍五入,传(字符串 | 数字)都行
Math.round("2.4")
// 去小数点后几位,没有会留0,最后一位数字会四舍五入
(21.5).toFixed(2) // 21.50
(21.55).toFixed(1) // 21.6
// 随机数
Math.random()
// max | min,可以无限传
Math.max(10,"4",8)
Math.min(10,"4",8)
// 知识点apply,让max和min支持传 数组
Math.max.apply("",[10,"4",8])
Math.min.apply("",[10,"4",8])
Array
有序的有长度的有很多数组方法的特殊对象,是复杂数据类型
// 用构造函数的方式去创建数组
var arr = new Array(1,2,3)
// 简易写法,这其实也是调用了构造函数的方法,跟上面一样的,这叫语法糖
var arr = [1,"2",["a","b"],"c",{name:"name",age:18}];
// 数组的长度
arr.length
// 存入数据,可以无限传
arr.push("d","e","f")
// 快速合并,方式一
arr.push.apply(arr,[1,2,3])
// 跟上面实际是一样的
Array.prototype.push.apply(arr1, arr2);
// 存入多个数据,方式二
arr.concat([1,2,3])
// 新es还有更简易的方法,查看新es篇
// 数组转字符串, 对象数据类型不适合转字符串
// 方式一,join,参数决定了拼接间隔
[1,"2",["a",[2],"b"],"c"].join(",") //"1,2,a,2,b,c"
// 方式二,toString,是用逗号拼接的,还能自动解开多维数组
[1,"2",["a",[2],"b"],"c"].toString() //"1,2,a,2,b,c"
// 排序英文单词
arr.sort()
// 排序数字,sort会把数字转字符串在排序,不行,需要二次封装一下
arr.sort(function(x,y){ return x-y });
// 反转数组
arr.reverse()
// 添加/删除/替换
arr.splice(start,deleteNum,...add)
// 删除数组的第一个元素,并返回被删掉的值
arr.shift()
// 删除数组的最后一个元素,并返回被删掉的值
arr.pop()
// 查找数组中某元素的第一个索引值。[不改变原数组] 如果没有就返回-1
arr.indexOf("xx")
// 这个方式有些特殊数据找不到,新es里有新方法
// 数组的循环(for,forEach)
// js里有一期关于遍历的总结,去哪里看吧
谈一谈伪数组
伪数组Array-Like
,是带有数字序号以及length属性的对象格式
打印出来的时候看着跟数组一样,但是数组的很多方法伪数组是没有的
最常见的伪数组是document.querySelectorAll取到的伪数组,dom元素的自定义属性以及所有参数arguments伪数组
// 常见伪数组一
Object.prototype.toString.call(document.querySelectorAll('div')) // [object NodeList]
// 常见伪数组二
function xx(){
Object.prototype.toString.call(arguments) //[object Arguments]
}
xx(1,2,3,4)
// 常见的伪数组三
document.querySelector('#aa').attributes
// 还有这个也是伪数组
var arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
// 除了用正常的办法鉴别伪数据,也可以执行一下forEach,伪数组没有forEach的方法
// 这也是为什么document.querySelectorAll取到的数据没办法forEach的原因
// 伪数组转数组
// 方法一,创建一个数组,原生for循环依次存进去
// 方法二,用call
Array.prototype.slice.call(伪数组)
// 新es还有更简易的方法,查看新es篇
Object
无序的无长度的,键值对(key-value)格式的数据储存模式,复杂数据类型
你能遇到的对象有window对象,dom对象,自己创建的对象(来自自定义构造函数)
对象有属性(专业点叫静态属性)和方法(专业点叫静态方法)和原型属性和原型方法
// 这是用构造函数去创造对象
var obj = new Object();
// 简易写法,这其实也是调用了构造函数的方法,跟上面一样的,这叫语法糖
var obj = {name:"name",age:18,str:"aa666bb",obj:{},arr:[1,2,3]}
// 取值
obj.name
obj["name"]
// 可以传参,但不能加双引号,也不能直接用点【.】
var a = "name";
obj[a]
// 判断有没有这个key,答案是(true/false),如果直接用取值去判断,没有会得到underfind
console.log(key in obj)
// 有就修改,没有就添加
obj.name = "newName"
obj["name"] = "newnewName"
var a = "name";
obj[a] = "newnewnewName"
// 执行对象的方法
obj.say()
// 查看对象的原型,返回是对象格式,获取原型对象的属性,执行原型对象的方法【同上】
obj.__proto__
// 删除key-value
delete obj.name
// 多层也一样
delete obj1.obj2.name
// 对象的遍历,虽然对象没有长度,但也能循环
// for-in,往下看有关于遍历的
// 这个返回一个key的无序数组
Object.keys(obj)
// 对象的合并
// 只能通过上面的遍历方式往另一个对象添加
// 对象的一个神级API
Object.defineProperty(obj,key, handle)
// 这个牛逼的地方就在于它是Vue2.0的设计核心,这个放在《vue的设计原理》说
Function
function叫函数,也叫方法,他的参数写在中括号里,他的内容在大括号里,他自带作用域(函数作用域),并且函数被执行时有个隐藏的this对象
,在dom就说过,一切的函数执行都有执行者,执行者就是函数内部隐藏的this对象
// 声明一个函数
var init = function(){ ... }
function init(){ ... }
var init = new Function(["canshu1","canshu2"],`console.log(arguments)`)
// 两种声明方式用第二种,第一种有变量提升的缺点
// 参数,js的参数是无限的,什么类型都能传
// 实参,实参在括号里拿,如果没有就是underfind,如果数量不够就拿不到
function init(x,y){ console.log(x,y) }
init(1) //x是1,y是underfind
init(1,2,3) // 第三个拿不到
// 形参,形参就是不需要从中括号里拿的,在作用域里有一个隐藏的值叫arguments
// 就是上面说过的伪数组之一
function init(){ console.log(arguments) } //[1,2,3]
init(1,2,3)
// 自执行函数,window加载就会自动执行,不需要名字,匿名
// 自执行函数被用来制造插件
// 自执行函数也是第一版本的全局变量安全作用域
(function(){ ... })();
// 构造函数
// 构造函数是保存作用域变量,用来批量生产统一格式但内容不同的对象,是一个对象的生产工厂
// 构造函数是用new去执行的,new命令把函数的静态属性/方法,原型属性/方法赋予一个空对象
// 一般来说静态用添加属性,原型添加方法
function User(name){
// 静态属性
this.name = name;
// 静态方法
this.say = function(){ ... }
}
// 原型属性
User.prototype.prototypeName = "proName";
// 原型方法
User.prototype.prototypeSay = function(){ ... }
// new一个对象,这个user就是上面对象部分的截图
var user = new User("pdt")
// 查看function的原型,返回格式是对象,可以往里面添加属性和方法
User.prototype
// 跳出function执行
return;
// 其实最上面的声明function的写法也是一种语法糖,function是可以用构造函数创造的
// 没错function也是一个对象,是一个畸形的变异的对象
var init = new Function()
// 因为function是对象,所以他有自己的属性和方法,继承自Function
// function的原型上有个name属性是指这个function的名字
// function的原型上还有apply,call,bind三个改变执行者的方法
// 在上面的笔记里有Array.prototype.slice.call() 和 Math.max.apply() 就是执行了Function的方法
// 这些方法不常用,但是要知道,详细查看《上下文和作用域》
新手建议 如果你不想制作一些BUG出来加班,声明函数都写在最高级的作用域里,不要在函数里声明函数,不要在if或者for里声明函数,函数可以在任何需要执行的地方并且可以执行的地方调用,但是不是说可以在任何地方声明的,否则后果自负
JSON/XML
前后端数据交互的数据格式,为什么要存在着两种格式,因为语言不通,前端直接往后端甩一个对象{}或者一个数组[] ,后端是不识别的,而共同的数据格式是JSON和XML
在最开始是用xml用标签的格式去传的
<name>pdt</name>
<age>18</age>
后来xml不好写,而且数据量大,升级成了json
// json可以长这样
var json = '{"name":"pdt","age":18,"arr":[1,"2","tom"]}'
// json也可以长这样
var json = '[1,2,{"name":"pdt"}]'
注意,json不是对象,也不是数组,他是字符串,是字符串
ajax篇里说的能传递复杂数据类型方式就是用post-json,因为那个上传方式是把数据转成了一个json字符串,后端拿到一个字符串后可以用自己的方法拆成他看得懂的格式
// 由对象转字符串
JSON.stringify({
name: "name",
age: 18
})
// 由数组转字符串
JSON.stringify([1,"2","tom"])
// 由字符串转对象,最两边不能是双引号
JSON.parse('{"name":"pdt","age":18,"arr":[1,"2","tom"]}')
// 由字符串转数组,最两边不能是双引号
JSON.parse('[1,2,{"name":"pdt"}]')
注意,JSON.stringify() 的参数是有规定的,这个参数内容必须是有限的,也就是有尽头的,什么是没有尽头的,function,如果是一个function,或者含有function,是没法转字符串的,不信你可以试试能不能把function一层层打开直到打不开为止
Error
错误对象,抛出错误,这个对象可以用来阻断正在执行的无法return的函数,是个骚操作
throw new Error("我是一个错误")
》》数学运算符
等于,加减乘除余,加加,减减
- 等于【=】用在声明变量上作为
赋值
- 加号【+】
// 当一侧为String类型,被识别为字符串拼接,并会优先将另一侧转换为字符串类型。
// 当一侧为Number类型,另一侧为原始类型,则将原始类型转换为Number类型。
// 当一侧为Number类型,另一侧为引用类型,将引用类型和Number类型转换成字符串后拼接。
123 + '123' // 123123 (规则1)
123 + null // 123 (规则2)
123 + true // 124 (规则2)
123 + {} // 123[object Object] (规则3)
- 减号【-】,普通的数学计算
- 乘号【*】,普通的数学计算
- 除号【/】,普通的数学计算
- 取余【%】,取余最常用在循环判断间隔
for(var i==0;i<100;i++){
if(i%3==0){ ... }
else if(i%3==1){ ... }
else if(i%3==2){ ... }
}
- 加加【++】,加加就是加一的意思,跟加一不同的是,加加会把值添加给原值,加一不会
var i = 0;
i++; // 实际是指 i = i+1;
i+1; // 实际是指 i+1; 没有赋值给原值
// 还有一个面试题叫i++和++i的区别,一个是先加后赋值,一个是赋值后加
- 减减【--】,同上
》》逻辑运算
大于小于
// 基础用法
if(a > 1){ ... }
// 多条件
if(a > 1 && a < 5){ ... }
// 上面的多条件不能这么写
if(1 < a < 5){ ... }
// 为什么
1 < 2 < 5 // true
1 < 10 < 5 // true
// 因为1<10 是true,true是1,1<5 所以是true
等于等于和全等于
- 等于等于【】用在判断上,若两侧类型相同,则比较结果和=相同,否则会发生隐式转换,隐式转换表在面试题二
- 全等于【===】用在判断上,应该习惯用这个
或者,并且
// 或者【||】,这个就是一个if-else,一个个往下判断,直到找到一个true的值就返回这个值,所有到了最后一个还没有true,就返回最后一个
console.log(0 || 1); //1
console.log(0 || ""); //""
console.log(0 || 1 || NaN ); //1
// 并且【&&】,这也是一个if-else,一个个往下判断,直到找到一个false的值就返回这个值,所有到了最后一个还不是,就返回最后一个
console.log(1 && 2) //2
console.log(1 && 0) //0
console.log(1 && 0 && 2) //0
//两者并用
console.log( false&&"A" || 1&&"B" || ""&&"C") //"B",自己捋捋
if-else
if是可以不写大括号的,但是别这么做,不仅不装而且很傻
// 如果符合就...,如果不符合什么事都不做
if(true){ ... }
// 如果符合就...,如果不符合就...
if(true){ ... }else{ ... }
// 如果符合A就...如果符合B就...最后支持else
if(A){ ... }else if(B){ ... }else{ ... }
// if-if
if(A){ ... }
if(B){ ... }
// 当两个if并列会同时判断,那个符合就执行那个,当两个同时符合会同时执行
// 但是这是不建议这么写的,因为当判断量很大,逻辑很乱
// 如果只想执行一个,可以改用if-else
// 如果想执行多个可以改成先判断A,再A里再判断B
// if的代替写法
function find(color){
if(color=="red"){ return "..." }
else if(color=="green"){ return "..." }
}
// 改成
function find(color){
var ifObj = {
"red": "..."
"green": "..."
}
return ifObj[color];
}
// 0是代表false,"0"是true,""是false,underfind是false,null是false,
// NaN是false,空数组[]是false,非数组![]也是false,空对象{}是false
循环
原生的循环有for,for-in
封装在数组方法上的遍历方法有forEach
更优秀的遍历方式出现在新es版本里,会有一期循环的专题
// 循环数组
for(var i=0;i<5;i++){ console.log(i) }
// 循环对象
for(var key in obj){ console.log(key,obj[key]) }
// for-in会遍历原型链上的属性,需要判断
if(obj.hasOwnProperty(key)){ console.log("这个key是原型属性") }
// forEach,回调函数第一个参数是value,第二个参数是序号
[1,2,3].forEach(function(x,index){ console.log(x,index) })
三元运算符
// 如果A就X,否则Y
var realSex = (sex=="1") ? "男" :"女"
》》其他
变量提升
查看《js树和调试》那一篇
作用域
作用域会专门挑出来发
外层的作用域没有办法取到内层的作用域的数据
内层可以取到外层作用域的数据
这是就是如果把所有的js写在非全局作用域,比如写在一个自执行函数里,我们就没办法在F12的console调试区里取到别的作用域里的值,这也就是数据安全的第一层保护机制
目前的已知的作用域是function
var a = 1;
function x(){
var b = 2;
}
console.log(a); // 1
console.log(b); // underfind
队列和栈和堆
这三个是看起来高端但是一点都不高端的词
队列是一种模式,是指先进先出
的一种存取模式,用数组模拟队列,就是存值用push,取值有shift,被人称为拉屎模式
栈也是一种模式,是指先进后出
的一种存取模式,用数组模拟栈,就是存值用push,取值用pop,被人称为呕吐模式
堆也是一种模式,没有顺序,没有存的顺序也没有取的顺序,对象的存取就是堆模式,内存的分配也是堆模式
复杂数据类型
简单数据类型和复杂数据类型的区别就是
简单数据类型A被另一个变量引用B时,会被完全拷贝,即使修改了B的值,A的值也不会改变
var A = 1;
var B = A;
B = B+1;
console.log(B) //2
console.log(A) //1
复杂数据类型因为数据量太大,拷贝成本太高,所以被重新指向的是同一个数据,这种拷贝叫浅拷贝
var A = {
name:"tom"
age:18
}
var B = A;
B.age = 19;
console.log(B.age) //19
console.log(A.age) //19
// 数组和对象和函数都属于复杂数据类型
// 但是也有需要用到完全拷贝的情况,所以就有了一个技术叫【深拷贝】
// 深拷贝的最简单应用就是JSON
// 把数据改成字符串再改回来,就跟原来灯独立了
// 有个缺点是JSON不能操作无限无尽的数据,所以JSON只是个骚操作
// 要完全适用所有的格式拷贝,是个很高深的学问,需要判断很多的数据类型
var B = JSON.parse(JSON.stringify(A));
实战:array 和 object 的灵活应用
需求是:页面上要显示一个列表,列表需要有删除功能,当请求数据获得一个装着10个对象的数组,每个对象都有id,name等详细数据,我们把数组循环,显示到页面上,要求用户操作后最后一次性把数据提交
首先应该把请求到的数据存起来,看到这样的需求,我一开始想在按钮上添加序号,当点击按钮时获取序号,用splice去删掉数据的对应序号的数组里的对象,但是问题来了,删掉第三个按钮后,数组里只剩9个对象,这时再点击删掉第四个对象时,原本的第四个序号已经自动变成三个,所以我们删掉的第四个其实是第五个,千万不要用顺序去当标识
所以改成了用id做标识,每次删除一个数据后需要遍历整个对象数组,找到一样的id,然后删掉这个对象,计算量大了很多
正确操作: 先把一开始的数组循环,拆成一个只装着id的数组,和一个用id做key,value等于自身的大对象,然后点击删除时,用index去数组找到位置,把id删掉,最后提交的时候,把数组遍历,把数据从大对象里重新取出来装回到数组对应id的序号里,提交上去