JS的介绍
语言类型
- HTML: 是一门超文本标记语言,“超文本”就是指页面内可以包含图片、链接,甚至音乐、程序等非文字元素。
- CSS : 层叠样式表。CSS不仅可以静态地修饰网页,还可以配合各种脚本语言动态地对网页各元素进行格式化。
- JS:JavaScript:是脚本语言,是基于对象的语言,是弱数据类型的语言,是基于事件驱动的语言,可以写业务逻辑
基于对象:就是说对象已经被定义好了,只需要用对象即可
弱数据类型:对数据类型不敏感,(java是强数据类型的语言)
基于事件驱动:即点击,键盘等各种事件驱动
运行在浏览器:有浏览器即可。无需编译:由浏览器直接解释
一般用来给HTML网页增加动态、交互、拖拽等功能。
JS的基本组成部分
1、ECMAScript(ES):
ES是JavaScript的核心,描述了语言的基本语法(var、for、if、array等)和数据类型(数字、字符串、布尔、函数、对象(obj、[]、{}、null)、未定义)ECMAScript是一套标准,定义了一种语言(比如JS)是什么样子。
2、文档对象模型(DOM)
document object model文档对象模型,里面提供了元素属性啥的,可以让我们操作页面中的元素操作DOM时,想操作谁,先获取谁。
操作DOM的本质是=获取(想操作谁就先获取谁)+触发(获取后可以绑定自己想要的事件)+改变(事件函数触发后就能发生对应的改变)
DOM元素的获取方式
- 直接通过元素的ID获取 只支持高版本浏览器
- 通过getElementById('ID'),
- getElementsByTagName('标签名'),
- getElementsByClassName('类的名称')
- document.querySelector('选择器')
- document.querySelectorAll('选择器')
3、浏览器对象模型(BOM):
对浏览器窗口进行访问和操作。这是浏览器里默认的了解就行
JS书写的注意事项
- script脚本推荐放在最下边好处:防止因网络问题,页面加载时间长,出现空白;即便网络问题,浏览器也把DOM加载渲染完成,等待从服务端下载完js脚本,出现效果
- css不放在最下面原因通俗地讲:好比先穿好衣服在出去,浏览器先把css样式准备好,在加载结构层,使得页面有样子;如果放在下面,因为页面是从上往下加载,没有css,就剩下裸露的标签,很难看,使得用户体验不好
- 综上两点所述故css脚本必须在script上面
JS的导入方式
- 内嵌式 在页面中直接写入
<script type="text/javascript">js代码</script>。
- 外链式 在页面中引入外部文件
<script src="xx.js"></script>。
- 行内式 在标签中(也就是元素里)直接写
<input type="button" value="点击有惊喜" onclick="javascript:alert('哈哈哈哈')"/>
JS的输出方式
- console.log(js,cs,html);在控制台输出,可以输出一个或者多个,其间用逗号隔开
- console.dir(js);在控制台输出详细信息,用法同上
- alert('hello world');在页面上弹出helloworld
- alert都是把要输出的内容首先转换为字符串然后在输出(调用了toString)
- document.write("hello world"); 在页面中直接显示hello world
- document.getElementById("search").value = "hello world" ;给id为search这个文本框添加的内容
- innerHTML/innerText ->给除了表单元素的标签赋值内容
- document.getElementById("div1").innerHTML = "吧啦吧啦吧啦吧啦";
JS中的常量及变量
变量命名规范
- 由字母(a-zA-Z)数字(0-9)下划线(_)以及美元符号($)
- 不能由数字开头
- 命名尽量用英文并且具有一定的含义
- 如果有多个英文单词,后面单词的首字母大写(遵循驼峰式命名)
- 不能使用关键字
- 首字母不要大写,大写是有特殊含义的
常量
常量的值是不可变的,反之为变量。常量用const定义
变量(var,let)
JS数据类型的分类、检测以及区别
数据类型
一、基本数据类型(值类型)
注意:基本数据类型是操作值的
1、数字类型
数字类型包括整数、小数、NAN
数据类型的转换
注意:引用数据类型要转为数字类型,先转为字符串在转为数字类型(隐式转换)
1、Number()将其他数据类型转化为纯数字类型,若没有数字类型则返回NAN
2、parseInt()将其他数据类型转为数字类型并取整,若没有数字则返回NAN
3、parseFloat()将其他数据类型转为数字类型并取小数点,若没有数字则返回NAN
4、~~ 双非按位取反运算,~~ 的作用是去掉小数部分,并且将字符串转换为数字类型
let num = "12";
let num2 = '-1';
let num3 = '212.258'
console.log(~~num); //12
console.log(~~num2); //-1
console.log(~~num3) //212
5、+ 当做单目操作符的时候,+ 操作符不会对Number类型产生影响,但是如果应用在字符串上,会将其转换为数字类型。等价于=+,注意区别+=。
let num = '12';
console.log(+num) //12
let a = 'xiaohomg'
console.log(+a) //NaN
2、字符串
字符串的各种方法
所有字符串的方法都不会改变原字符串
1、字符串的拼接
- 使用+号进行拼接 注意:只要数据两边有一边是字符串就行
- ES6用法,用反引号包住,变量需要用${}包住。例如:
2、获取字符串中对应索引的数据
- str[0] 获取字符串str中第一个字符
- str.charAt(0) 获取字符串str中第一个字符 注意:以上两者的区别是当str中无此索引时,str[0]返回的是undefined,charAt[0]返回的是空字符串
- str.charCodeAt(0) 获取字符串对应索引字符的ASCII码
3、字符串的转换
-
str.toLocaleUpperCase()
将字符串转为大写 -
str.toLoCaleLowerCase()
将字符串转为小写 -
str.slice(n,m)
从索引n复制到索引m(但是不包括索引m) -
str.substr(n,m)
从索引n获取m个 -
str.split('+')
以指定的分割符,把字符串分割为数组- 注意:这个返回值是数组。例如:'123'.split('2') 返回值['1','3']
-
str.replace('要被替代的字符','用来代替的字符') 替换
-
eval('str'):将字符串str当成有效的表达式来求值并返回计算结果。eval里面必须是字符串,如果其他数据类型想调用eval函数,则需要先转化为字符串再进行计算,例如eval(ary.jion('+'))返回值是改数组各项值相加的结果
3、布尔类型
- Boolean() 将其他数据类型转化为布尔类型,返回值只有ture或者false
- 注意:'' , null ,NAN ,0,undefined 返回值都是false
- 取反(!)取反的返回值也是布尔类型,其中有隐式转换
4、Symbol(唯一值)
什么是Symbol
Symbol
作为原始数据类型的一种,表示独一无二的值,在之前,对象的键以字符串的形式存在,所以极易引发键名冲突问题,而Symbol
的出现正是解决了这个痛点,它的使用方式也很简单
Symbol
的使用
创建一个Symbol
与创建Object
不同,只需要a = Symbol()
即可
let a = Symbol()
console.log(a) //Symbol()
typeof a //object typeOf判断数据类型是不准确的
使用时需要注意的是:不可以使用new
来搭配Symbol()
构造实例,因为其会抛出错误
let a = new Symbol()
typeof a // Symbol is not a constructor
问题来了,Symbol
看起来都一样,我们怎么区分呢?我们需要传入一个字符串的参数用来描述Symbol()
let a = Symbol()
let b = Symbol()
console.log(a==b) //false
let c = Symbol("c")
let d = Symbol("d")
console.log(c) //Symbol(c)
console.log(d) //Symbol(d)
Symbol
的应用
Symbol
的应用其实利用了唯一性的特性。
大家有没有想过,如果我们在不了解一个对象的时候,想为其添加一个方法或者属性,又怕键名重复引起覆盖的问题,而这个时候我们就需要一个唯一性的键来解决这个问题,于是Symbol
出场了,它可以作为对象的属性的键,并键名避免冲突。
let a = Symbol()
let obj = {}
obj[a] = "hello world"
全局共享Symbol
如果我们想在不同的地方调用已经同一Symbol
即全局共享的Symbol
,可以通过Symbol.for()
方法,参数为创建时传入的描述字符串,该方法可以遍历全局注册表中的的Symbol
,当搜索到相同描述,那么会调用这个Symbol
,如果没有搜索到,就会创建一个新的Symbol
。
let a = Symbol.for("a")
let b = Symbol.for("a")
a === b // true
如上创建Symbol
- 首先通过
Symbol.for()
在全局注册表中寻找描述为a
的Symbol
,而目前没有符合条件的Symbol
,所以创建了一个描述为a
的Symbol
- 当声明
b
并使用Symbol.for()
在全局注册表中寻找描述为a
的Symbol
,找到并赋值 - 比较
a
与b
结果为true
反映了Symbol.for()
的作用
再来看看下面这段代码
let a = Symbol("a")
let b = Symbol.for("a")
a === b // false
woc,结果竟然是false
,与上面的区别仅仅在于第一个Symbol
的创建方式,带着惊讶的表情,来一步一步分析一下为什么会出现这样的结果
-
使用
Symbol("a")
直接创建,所以该Symbol("a")
不在全局注册表中 -
使用
Symbol.for("a")
在全局注册表中寻找描述为a
的Symbol
,并没有找到,所以在全局注册表中又创建了一个描述为a
的新的Symbol
-
秉承
Symbol
创建的唯一特性,所以a
与b
创建的Symbol
不同,结果为false
问题又又又来了!我们如何去判断我们的Symbol
是否在全局注册表中呢?
Symbol.keyFor()
帮我们解决了这个问题,他可以通过变量名查询该变量名对应的Symbol
是否在全局注册表中
let a = Symbol("a")
let b = Symbol.for("a")
Symbol.keyFor(a) // undefined
Symbol.keyFor(b) // 'a'
如果查询存在即返回该Symbol
的描述,如果不存在则返回undefined
以上通过使用Symbol.for()
实现了Symbol
全局共享
二、引用数据类型(引用数据类型都是操作地址的)
1、数组
数组的创建
- 字面量创建:var ary = [1,2]
- 实例化-构建函数创建:var ary = new Array(1,2,3,4)
数组的增删改查
1、原数组发生改变的方法
- ary.push(1,2) 向数组的最后一项或者多项添加数据
- ary.unshift(1,2) 向数组的最前面一项添加一项或者多项数据
- ary.pop() 删除数组中的最后一项
- ary.shift() 删除数组中最前面的一项
- ary.length-1 删除数字对应索引位置的数据
- ary.splice()
- 情况一:splice(x) 只传一个参数,表示从索引x开始删除到末尾;
- 情况二:splice(x,y) 传两个参数,表示从索引x开始向后删除y个
- 情况三: splice(x,y,z,f) 传三个或者三个以上的参数时,表示从索引x往后删除y项,并将第三位后面的参数替换删除的地方
2、不改变原数组的方法
- indexOf:查找数组中对应数据的索引值,如果没有则返回-1。这是从左到右查找对应值;只要找到就会输出结果;如果后面有重复的内容也不会输出其索引值。普遍用于数组的查重
- lastIndexOf:查找数组中对应值的索引值;如果没有返回-1;普遍用于数组的查重;这是从右到左查找对应值;只要找到就会输出结果;如果前面有重复的内容也不会输出其索引值
- ary.includes("x"):查找数组中是否有x这个数据;如果有返回结果ture;如果没有返回false。可以用来判断数组中是否有这个数据
- ary.slice() 返回值都是一个新的数组
- 情况一:ary.slice() 如果没有传参,则表示复制一份原有数组,返回值是一个新的数组
- 情况二:ary.slice(x) 只传了一个参数,则表示从索引x开始一直复制到数组的末尾
- 情况三:ary.slice(x,y) 若传了两个参数,则表示从索引x开始往后复制y个
- 情况四:ary.slice(-x) 若传了一个负数则表示从倒数的索引x开始往后复制
- ary.sort():数组的自动排序,数字从0到9依次递增,如果大于10,则会按照十位数的数接着排
- 数字大于10时;且想用sort排序则可以这样用ary.sort(function(a,b){return a-b})当return里的值是a-b时表示数组从小到大进行排序,若是b-a则表示从大到小排序
- ary.resever():表示将数组倒序排列,返回的倒序数组和原数组相等
- ary.concat()
- ary.concat(x,y,z):表示将x,y,z拼接到数组里,返回值是一个拼接好的新数组如果拼接的是数组则先将其拆开后再进行拼接
- ary.concat():若没有传参则表示复制一份原有数组
- ary.toString():表示将数组转化为新的字符串
- ary.join():表示以指定分割符将数组转化为字符串。若没有传参则默认是逗号为分割符
数组的去重
- 利用对象属性名不能重复去重:先创建一个空数组和空对象,然后循环老数组,把老数组的每一项赋值给对象的属性名,然后再循环对象;将对象中的属性名添加到新数组中
let ary = [1,2,2,4,55,6];
let obj = {};
let arr = [];
function removeRep(ary){
ary.forEach(item=>{
obj[item] = 1
})
for(var i in obj){
arr.push(i*1)
//因为对象的属性名是字符串,要将其转换为数字再放进数组;
//Number(K)也可以换车给k*1 需求是一样的
}
return arr
}
console.log(arr)//[1,2,4,6,55]
- 利用双for循环,用数组的第一项和后边的每一项进行比较,判断他们是否相等,如果相等则删除掉后边的该项;并将后边的索引往前减一位
let ary = [1,1,2,3,4,5,4]
function removeRep (ary){
for(let i = 0 ; i<ary.length-1 ; i++){
let before = ary[i]
for(let k = i+1 ; k<ary.length ; k++){
if(ary[i]===ary[k]){
ary.splice(k,1);
k = k-1
}
}
}
return ary
}
console.log(ary)//[1,2,3,4,5]
- 最简单的方法就是利用indexOf去重,创建一个空数组,循环老数组,判断新数组里是否存在老数组的数据,如果没有就将老数组的这一项添加到新数组中。
let ary = [1,2,3,4,5,7,11,5,2,1];
function removeRep(a){
let arr = [];
a.forEach(item=>{
if(arr.indexOf(item)===-1){
arr.push(item)
}
})
return arr
}
removeRep(ary)
console.log(arr)//[1, 2, 3, 4, 5, 7, 11]
2、普通对象
普通对象;例如{属性名:属性值};普通对象是用大括号包住的。其中属性值可以时数字。而属性名和属性值成为键值对,一个对象中可有多个键值对,多个键值对间用逗号隔开普通对象中的属性名不可以是数字加字母,但可以是数字。且属性名都是字符串,只是平时将其省略了
对象的方法
- 查
- 打点的方式:obj.k
- 中括号的方式:obj[Number]
- 注意:以上两种方式使用方法不同,其中打点的方式时,k不能是数字,而中括号的方式是适用于k是数字的时候
- 改和增
改和增的方式和用法和上面一致,只需要直接赋值就可以。例如:
obj.name='hello';obj[2]=3
- 删
- 假删除:obj.name = null 赋值为null只是假删除,obj中还是有这一项只是它的值为null 而已
- 真删除:delete obj[k] 直接将obj中属性名为k的那一项删除
3、函数
1、函数的应用
- 回调函数:回调函数就是一个函数以参数的形式传给另一个参数执行。
function fn(num1 , num2 , callback){
return callback(num1 , num2)
}
function fn2(num3 , num4){
return num3 + num 4
}
fn(1,2,)
- 递归函数:递归函数是回调函数的特殊例子,递归函数是自己调用自己,使用递归函数时必须要设置边界点,否则会陷入死循环。一般用于求和。
let num = 0;
function sum(n){
if(n<5){
num = n + sum(n+1)
}
return num
}
sum(1)
console.log(num) //10
- 不定项求和:求0到100累加求和
let total = 0;
function sum(num){
for(let i = 0 ; i<num+1 ; i++){
total += i
}
return total
}
sum(100)
console.log(total) // 5050
- 使用递归求和
let total = 0
function sum(num){
if(num<101){
total = num + sum(num+1)
}
return total
}
sum(0)
console.log(total) // 5050
- 利用函数模拟验证码
function getNum(n){
let str = 'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890';
let str2 = '';
function num(n){
n = n || 4;
let ary = [];
while(ary.length<n){
let index = Math.round(Math.random()*61);
if(ary.indexOf(index)==-1){
ary.push(index)
}
}
return ary
}
function getCode(n){
n = n || 4;
num(n)
ary.forEach(item=>{
str2 += str[item]
})
}
getCode(n)
return str2
}
consloe.log(getNum(5))
- 奇偶行变色
<body>
<ul>
<li>1<li>
<li>2<li>
<li>3<li>
<li>4<li>
</ul>
</body>
<script>
let lis = document.getElementByTagName('li');
for(let i = 0 ; i<lis.length ; i++){
if(i%2===0){
lis.style.backgound = 'red'
}else{
lis.style.background = 'green'
}
}
</script>
4、Math
Math的各种方法
- Math.abc():取绝对值
- Math.ceil():向上取整
- Math.floor():向下取整
- Math.round():四舍五入
- Math.random():取0到1中间的随机小数(不包括1)
- 取随机数
function sum(min,max){ return Math.floor(Math.random()*(max-min)+min) } sum(1,100)
- 取随机数
- Math.max():取其中数字的最大值
- Math.min():取其中数字的最小值
- Math.PI:获取圆周率
- Math.pow(x,y):x的y次方
- Math.sqrt(x,y):x开y次方
5、Date(日期)
日期的各种方法
- new Date():获取本机此刻时间
- new Date().getFullYear():获取本年份
- new Date().gerMonth():获取本月份;注意:它的月份中是0到11,0为咱们的1月份
- new Date().getDay():获取星期几;注意:它的星期也是0到6;同上
- new Date().getData():获取日期
- new Date().getHours():获取当前的小时
- new Date().getMinutes():获取当前分钟数
- new Date().getSeconds():获取当前秒数
- new Date().getTime():得到现在时间减去1970年1月1日0时0分0秒的毫秒数
JS中数据类型的检测
- typeOf():检测该数据类型。typeof()可以准确的判断基本数据类型,但是不能判断引用数据类型;返回值都是字符串的形式;注意:null返回的是object;NAN返回的是number。
console.log(typeof('你好'))// 'string';
console.log(typeof(null)) // 'object';
console.log(typeof(undefined)) //'undefined';
console.log(typeof(5)) //'number';
console.log(typeof(Function)) //'function'
console.log(typeof(false)) //'boolean'
console.log(typeof(NaN)) //'number'
- instanceof():判断已知数据类型的方法;注意:这个只能判断引用数据类型,若是值类型则返回的都是false。原理是获取当前实例在原型链中是否在原型上。
console.log([] instanceof Array) // true
console.log(function fn(){} instanceof Function) // true
console.log({} instanceof Object) // true
- constructor:获取当前实例的构造器
console.log([].constructor) // f Array{} [native code]
console.log({}.constructor) // f Object{} [native code]
- Object.prototype.toString.call()精确判断对象类型。
console.log(Object.prototype.toString.call(5)) // [object Number];
console.log(Object.prototype.toString.call(null)) // [object Null];
console.log(Object.prototype.toString.call(NAN)) // [object Number];
console.log(Object.prototype.toString.call('')) // [object String];
堆栈内存和垃圾回收详解
- 内存中分动态存储区(栈内存、堆内存)、静态存储区、程序代码区
- 堆栈内存的区别
- 从存放内容空间上看:
- 栈内存的内存空间要远远小于堆内存,主要是存放对象的地址、基本数据类型。由于内存很小,使用死循环会出现内存泄露。而内存泄露的结果就是内存溢出。
- 堆内存的空间要大一些,主要用于引用数据类型。
- 内存泄露:一直往内存里面存储内容,而不进行清理。(例如死循环)
- 内存(堆栈)溢出:当存储的数据达到一定的限制时,就会造成内存(堆栈)溢出。
- 堆栈内存的区别
- 垃圾回收
- 基本数据类型在当前环境执行完后会自动销毁,而引用数据类型不会。所以就有了垃圾回收机制。
- 垃圾回收是将对象的变量赋值为null,并且没有其他变量引用这个对象的地址,系统就会将他清理。但是不会及时清除,垃圾回收车会根据内存的情况在适当的时候清除堆中的孤儿对象。(没有被引用的对象就是孤儿对象)
js作用域
- 作用域:用于存储和访问变量;换句话说就是变量的可访问范围。
- 全局作用域、块级作用域
- 全局作用域:所存储的变量在全局都可以访问;在函数外定义的变量和函数内直接赋值的变量都是在全局作用域内。
- 局部作用域:变量在代码的局部片段才可以访问;常见的有函数作用域以及块级作用域。函数的形参也是局部变量。
- 函数作用域:在函数内部声明的变量在函数作用域内,只有在函数里面的代码才可以访问。
- 块级作用域:在{}中使用let和const声明变量就会形成块级作用域。
作用域链
- 作用域链是查找变量的一种机制;在查找变量时会一级一级向上查找;直到找到所需的变量。如果找不到就宣布放弃,代码报错。
赋值以及深浅拷贝的浅析
赋值与浅拷贝的区别
- 赋值是将某一数值或者对象赋给变量;当赋值的仅仅是一个基本类型时,a与b互不影响;但是赋值是一个对象时,a与b属于同一地址,改变其中的任何数据都会相互影响。
- 将a对象赋值给b时
let a1 = 2; let b2 = a1; b2 = 10 console.log(a1) //2 let a = { name:'小可爱', obj:{ age:20 } } let b = a b.name = '大可爱' console.log(a) //{name:'大可爱',obj:age:20}
深浅拷贝的区别
- 深浅拷贝只是针对object和array这样的引用数据类型。
- 浅拷贝只是复制指向某个对象的指针,而不是复制对象本身,新旧对象还是共享同一块内存。浅拷贝在改变最外层的基本数据类型时,原对象不会改变,但是如果改变对象下的引用数据类型,原对象也会发生改变。
- 深拷贝则会另外创造一个一摸一样的对象,新旧对象不共享内存,修改新对象不会影响旧对象。
- 常见的浅拷贝
- Object.assign(),合并对象
let a = { name:'小可爱', obj:{ age:20 } }; let b = { name:'乖乖', b:2 } let target = {} Object.assign(traget,a,b) console.log(traget)//{name:'乖乖',b:2,age:20} target.b = 20; console.log(b)//{name:'乖乖',b:2} target.obj.age = 30 console.log(a)//{name:'小可爱',obj:{age:30}}
- 展开语法Spread syntax
let a = { name:'小可爱', obj:{ age:20 } }; let target = {...a}; target.name = '糟老头'; target.obj.age = 10; console.log(a)//{name:'小可爱',obj:{age:10}}
- splice复制
let a = ['小可爱',{age:20}]; let target = a.slice(); target[0] = '糟老头'; target[1].age = 30; console.log(a)//['小可爱',{age:30}]
- 常见的深拷贝
- JOSN.parse(JSON.stringify):用JOSN.stringify()将对象转为JOSN字符串;再用JOSN.parse()把字符串解析为对象,一来一去会产生新的对象,实现深拷贝;这种方法虽然可以实现对象和数据的深拷贝,但是不能处理函数。这是因为JOSN.stringify()方法是将js值(数组或者对象)转换成JSON字符串,不能接收函数。
let ary = [1,{age:10}]; let arr = JOSN.parse(JSON.stringify(ary)); arr[2].age = 20 console.log(ary)//[1,{age:10}]; let obj = {age:2,name:'小可爱'}; let obj2 = JSON.parse(JSON.stringify(obj)); obj2.name = '糟老头子'; console.log(obj)//{age:2,name:'小可爱'}; //JSON.stringify(),将普通对象转化为JSON字符串,被转化的参数必须是对象。 //注意:JSON.prase(),转化的对象必须是JSON字符串,他的作用是将JSON字符串转化为JSON对象
- jQery.extend()合并多个对象
原型/构造函数/实例
- 原型:基本数据类型没有原型,只有引用数据类型有原型。数组的原型是Array.prototype、对象的原型是Object.prototype、函数的原型是Function.prototype、而他们的原型的原型指向基类Oject的原型,最终的原型就是基类Object,万物皆对象就是这么来的。原型的作用是存储一些公共的方法和属性供实例使用。
- 所有的函数都有一个prototype属性,其属性值都是一个对象,每一个对像都有一个__proto__属性;这个属性值为指向当前所属类的原型。
实例、原型、构造器函数三者的关系
- 实例.proto === 原型
- 原型.contructor === 构造函数
- 构造函数的.prototype === 原型
- 实例.constructor === 构造函数
原型链定义以及它的作用
- 原型链:原型链是由原型对象组成的,每个对象都有__proto__属性,执行了该对象的的构造器的原型;__proto__将对象链接起来,组成原型链。
- 原型链的作用:
- 属性的查找机制:当查找对象的属性时,当前实例如若没有则通过原型链向上一级查找,找到则输出,找不到则输出undefined。
- 属性的改变机制:如果需要在原型上添加或者修改属性时,可以通过b.prototype.x = 123;其中B是构造函数。
- 两道题助你更加理解原型链
//第一题 function F(){}; function O(){};//原型是对象 O.prototype = new F(); //O现在原型是对象 let obj = new O(); //obj是O的实例,原型是Obj.prototype console.log(obj instanceof O) //true obj在O的原型链上 console.log(obj instanceof F) //true obj也在F的原型链上 obj.__proto__ == O.prototype //true obj.__proto__.__proto__ === F.prototype //true //第二题 function F(){}; function O(){}; // O的原型是对象 let obj = new O() // O.prototype = new F() console.log(obj instanceof O) //false console.log(obj instanceof F)//false console.log(obj.__proto__ === obj.prototype) //false
闭包
- 闭包函数:声明在函数内部的函数叫做闭包函数;
- 闭包:内部的函数可以访问其所在外部函数声明的参数和变量。更好的解释:函数执行后返回结果是一个内部函数,并被外部变量所引用。
-
function a (){ var b = 2 return function(){ b++; console.log(b) } } let c = a() c() //3 c() //4 如果是每月将a()赋值给c,直接a()()是不会产生闭包的 a()() //3 a()() //3
-
闭包的特点
- 优点:
- 避免使用全局变量,保护全局变量被污染
- 让外部函数可以访问到内部变量
- 缺点:
- 局部变量会常驻在内存中
- 导致内存泄漏,内存长期被占用,而不被释放。解决内存泄漏的问题,在退出函数前,删除掉不用的局部变量。
- 一句话,内部函数的可以访问到外部的变量,将内部函数的变量保存在内存条中,防止变量被污染,因为闭包内部函数的变量会不断的占用内存,故而滥用闭包会导致内存泄漏,所在在使用完后,将内部函数对象的变量赋值为null
闭包的创建
- 每次外部函数执行的时候,外部函数的引用地址不同,都会重新创建一个新的地址
new运算符执行的时候都发生了什么
- 先创建一个对象,这个对象继承了该构造函数的原型
- 执行构造函数,并将this指针指向新对象
- 返回新对象
let new1 = function(foo){
let obj1 = Object.create(foo.prototype)//创建一个新对象,这个对象继承了foo构造器的原型
let obj2 = foo.call(obj1)//执行foo,并把this指针指向obj1;new创建一个实例时;实例的this
//指向它本身。这里的obj2返回的时[],因为Array()执行了
if(obj2 === 'object'){
return obj2
}else{
return obj1
}
}
new1(Array)
- 使用new来调用构造函数时,如果return的是一个非对象(数字、字符串、布尔类型、null等)会忽略返回值;如果return的是一个对象,则会返回该对象。下面两道题带你理解new。
//第一题
function Person(name){
this.name = name;
return name
}
let p = new Peron('tom');//Person{name:tom}
// 第二题
function Person(name){
this.name = name;
return {}
}
let p = new Person('tom');//{}
模块化
- 模块化开发大大提高了项目的可维护性,可拓展性和可协作性;通常我们在浏览器中使用ES6的模块支持;在node中使用common.js的模块支持。
- ES6
- import 导入
- export 导出
- common.js
- require 导入
- exports 导出
- module.exports 导出
- require和import的区别
- require支持动态导入,import不支持
- require 是同步导入,import是异步导入
- require是值拷贝,导出值变化不会影响导入值,import是指向内存地址,导入值会随着导出值变化而变化
防抖和节流
防抖
-
防抖的作用:将多次高频的操作优化为最后一次执行,使用场景是:用户输入。只需在输入完成后,进行校验。换句话说:短时间内连续触发的的事件,让其在某个时间的限期(比如1秒内)内只触发一次
-
function debounce(fn,delay){ let timer = null; //借助闭包实现 return function(){ if(timer){ clearTimeout(timer) } timer = setTimeout(fn,delay) } } function fn(){ let i = 0; console.log(i++) } window.onscroll = debounce(fn,1000)//页面滚动一秒触发一次函数
-
-
节流:每隔一段时间执行,将高频转化为低频。使用场景:短信验证60s后重发,滚动条事件等等。
function throttle(fn, delay) { let timer = null; clearTimeout(timer) return function () { if (!timer) { timer = setTimeout(() => { fn() timer = null }, delay) } } } function fn() { let i = 0 console.log(i++); } window.onscroll = throttle(fn, 1000)
-
举个例子,比如我们在抢票时,狂点抢票这个按钮
- 防抖:分为立即执行和非立即执行
- 防抖(非立即执行):无论你狂点多少次,我等你不点了才提交你的最后一次提交
- 防抖(立即执行):无论你狂点多少次,我只提交你第一次
- 节流:无论你狂点几次,一定时间内只算你一次
- 防抖:分为立即执行和非立即执行
定时器的区别
- setTimeout:指定毫秒数后,执行内部函数,只执行一次,定时器执行完后timer为1;如果有两个为2
let timer = setTimeout(()=>{},2000)
- setInterval:按毫秒数,每毫秒数执行一次内部函数,直至clearInterval
this的作用以及改变this的方法和差别
- this的作用:
- js的设计原理:在函数中可以引用运行环境的变量。因此就需要一个机制来让我们在函数体内部获取当前的运行环境。而这个机制就是this。
- 运行环境指的就是函数被谁调用,而谁就是this。
- 判断this的指向
- obj.fn(),这里的fn里的this指的就是obj,fn2()的主体就是window,fn2()函数中this指的就是window。
- 自执行函数的this都是指向window。
- 给元素绑定事件,当事件触发时,绑定方法里的this执行这个元素。
function fn(){ console.log(this) } docoment.getElementById('div').onclick = function(){ console.log(this)//div fn() //window } document.getElementById('div').onclick = fn;//div
- 改变this指向的方法。
- fn.call(obj,1,2,3);将fn的this指向obj,传参为1,2,3参数列表,并立即执行函数fn。
- fn.apply(obj,[1,2]);将fn的this指向obj,传参为1,2;apply的传参格式为数组,立即执行函数fn
- let call = fn.bind(obj,1,2);call返回一个函数体,函数需要时调用;调用call执行后4后,将fn的this指向obj,传参为1,2。
- call、apply、bind的区别:
- call和apply都是立即执行,只是call传参是参数列表的格式,apply是数组。
- call和bind的参数传参格式是一样的,只是bind不是立即执行。
手写实现call,apply,bind
- 手写call
Function.prototype.newCall = function(arg){
let ctx = arg || window;
ctx.fn = this;
let params = [...arguments].slice(1);
let res = ctx.fn(...params);
delete ctx.fn
return res
}
let obj = {
name:'你好',
fn:function(...arg){
console.log(this.name,...arg)
}
}
obj.fn.newCall(obj,'年龄22','体重25Kg')
2.手写apply
Function.prototype.newApply = function (context, arg) {
let ctx = context || window;
ctx.fn = this;
let res = ctx.fn(...arg);
delete ctx.fn
return res
}
let obj = {
name: '你好',
fn: function (...arg) {
console.log(this.name, ...arg)
}
}
obj.fn.newApply(obj, ['年龄22', '体重25kg'])
3.手写bind
Function.prototype.newBind = function (context=window){
let ctx = context;
ctx.fn = this;
let arg = [...arguments].slice(1);
return function(){
ctx.fn(...arg)
}
}
let obj = {
name: '你好',
fn: function (...arg) {
console.log(this.name, ...arg)
}
}
ES6基础知识
变量提升
- 变量提升:该变量还未被声明,在代码执行之前,先在词法环境种进行注册。如果变量声明的是一个全局变量就会将变量声明提升到全局作用域顶部。如果变量的声明是在函数里,则将变量提升到函数作用域的开头。
- 变量的提升:var既声明一个变量又定义;只有var有变量提升;let和const没有变量提升。
- 函数的提升:函数既声明也定义。函数的声明优先级高于变量。
let const var三者的区别
- var存在变量提升;let和const不会。
- var声明的全局变量会挂载到window上,let和const不会。
- 在{}用let和const声明的变量会产生块级作用域,var不会。
- let和const声明的变量不能再进行声明,但是可以修改。不存在暂时性死区(在{}内使用let或者const就会产生块级作用域,他所声明的变量在这个区域内在它之前使用都会报错)
var sun = 1;
if(true){
sun = 1;//报错;
let sun = 2;
}
map、filter、reduce、forEach
- forEach和map的区别:forEach没有返回值;map有返回值;而且必须用return返回,否则外界拿不到。
- forEach例子;forEach第一个参数为element,第二个参数时index,第三个参数是原数组
let ary = [1,2,3]; let sum = 0; ary.forEach((item,index,arr)=>{ sum += item //6 console.log(index) //0,1,2 console.log(arr) // [1,2,3] })
- map例子;map第一个参数为element,第二个参数时index,第三个参数是原数组
let ary = [1,2,3]; let ary = ary.forEach((item,index,arr)=>{ return item*2 }) console.log(ary)//[2,4,6]
- filter的作用是用来过滤(筛选),返回的是一个新数组。传入的函数返回值为true就放入新数组,为false不放入新数组。
- 例子如下:
let ary = [1,2,3]; let arr = ary.filter((item,index,arr2)=>{ return item>2 }) console.log(arr)//[3]
- reduce可以将数组的元素通过回调函数最终转为一个值,常用于求和。
- 例子如下:
let ary = [1,2,3]; let sums = ary.reduce((sum,element,index,arr)=>{ //sum 上一次的累加值;elememt当前元素;index当前元素索引;arr原数组 return sum + element }) console.log(sums)//6
解构赋值
- 解构赋值语法是JS的一种表达式,通过解构赋值可以将属性或者值从对象或者数组中取出,赋值给其他变量。
解构赋值语法
- 解构数组
let ary = ['one','two','three'];
let [a,b,c] = ary;
console.log(a,b,c);// 'once','two','three'
- 默认值
- 为了避免从数组中拿到undefined,我们可以给变量设置一个默认值
let a,b; [a = 2,b = 3] = [1,2]; console.log(a,b);//1,2
- 交互变量
- ES6解构赋值时,每句话结束必须加分号,否则有些地方会报错。
let [a,b] = [1,2]; [b,a] = [a,b] console.log([a,b]) //2,1
- 解析从函数中返回的数组
function fn(){
return [1,3]
};
[a,b] = fn();
console.log(a,b);//1,3
- 忽略某些值,用
,
表示忽略的值。一个逗号代表一个值。
let ary = [1,2,3,4];
[a,,b] = ary;
console.log(a,b)//1,3
- 利用展开运算符将剩余的值赋值给一个变量
let ary = [1,2,3,4];
[a,...b] = ary;
console.log(a,b)//1,[2,3,4]
- 解构对象
- 解构对象时需要注意,如果需要赋值给新的变量名则需要将属性和属性值写出来
let obj = {key:1,all:2}; let {key:num1,all:num2} = obj; console.log(num1,num2);//1,2
- 如果赋值的变量名不变则可以只写变量名
let obj = {key:1,all:2}; let {key,all} = obj; console.log(key,all);//1,2
- 无声明解构对象时,需要用()包住,否则报错
let obj = { key : 1 , all : 2}; ({key,all} = obj); console.log(key,all)
箭头函数和普通函数的区别。
- 箭头函数是匿名函数,不能用new执行。
- 箭头函数的this是指向上级作用域的this。
let obj = {
a:()=>{
console.log(this)
}
}
obj.a() //window;因为obj的this是window
- 箭头函数没有arguments
- 箭头函数没有原型属性
let obj = {
a:()=>{
console.log(this)
}
}
console.log(obj.a.prototype)//undefined
- 箭头函数不能改变this指向。
Class类的声明和继承
- 声明类
class Obj {
constructor(x,y){
//this是obj类的实例;这些属性是私有属性
this.x = x;
this.y = y;
},
to(){
return this.x+this.y
}
}
let obj = new Obj(1,2);
console.log(obj); {x:1,y:2}
obj.to()//3
- 类的继承
class Obj {
constructor(x,y){
//this是obj类的实例;这些属性是私有属性
this.x = x;
this.y = y;
},
to(){
return this.x+this.y
}
}
class Arr extends Obj {
constructor(a){
super(2,3)
this.c = a
}
fn(){
return this.x+this.y+this.c
}
}
let arr = new Arr(5)
console.log(arr){x:2,y:3,c:5}
arr.fn()//8
Promise的使用和实现
- promise是解决回调地狱的。回调地狱指的是多层回调嵌套,导致代码很难阅读。
function fn(num1,num2){
return new Promise((resolve,reject)=>{
let sum = num1 + num2;
resolve([sum,num1])
})
}
fn(1,2).then(([sum,num1])=>{
console.log(sum,num1) //3,1
return sum
}).then((num)=>{
console.log(num+5) //8
})
- promise中的resolve只能接收一个参数,如果需要传多个参数,则需要用数组或者对象进行传参。
Array.from()的理解
- Array.from()是将类数组对象或者是可遍历的对象或者字符串转为一个真正的数组。
- Array.from()的使用方法
- 将字符串转为数组
let str = 'abc' let arr = Array.from(str) console.log(str)//['a','b','c']
- 利用Set类数组结合Array.from()实现数组的去重
let ary = [1,1,2,4,5,4,5,6,7]; let set = new Set(ary); let a = Array.from(set) console.log(a)//[1,2,4,5,6,7]
- 将一年的销售额存在一个对象里,将其处理为数组
let obj = {1:220,2:300,5:400} let result = Array.from({length:5},(item,index)=>{ return obj[index+1] || null }) console.log(result)//[220,300,null,null,400] //必须有item,否则返回的数组都是null
什么叫回调函数?回调函数有什么优点。
- 回调函数的定义:回调函数就是一个函数作为另一个函数的参数,这个参数的就是回调函数。
function callback(num1,num2,back){
let sum = num1+num2;
back(sum)
}
function print(num){
console.log(num)
}
callback(1,2,print)
//用promise实现
function fn(num1,num2){
return new Promise((resolve,reject)=>{
let sum = num1+num2
resolve(sum)
})
}
fn(1,2).then((sum)=>{
console.log(sum)
})
- 例如axios请求和点击事件中就使用了回调函数
- 回调函数的优点:
- 回调函数不会立刻执行,回调函数和普通的函数一样,在需要用到的地方调用才会执行。
- 回调函数是个闭包。它可以访问到其外层定义的变量。
发布订阅设计模式
- 发布订阅指的是发布函数和订阅函数。
- 订阅函数:将需要发布的函数放入是事件池中。
- 发布函数:发布的时候执行相应的回调。
- 应用场景
- js在dom元素绑定事件。订阅就是在dom元素绑定事件,发布就是点击时执行回调函数。
面向对象(oop)和面向过程(opp)
- 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步的实现。使用的时候再一一调用就行。
- 面向对象:面向对象就是把需要解决的问题都归类,拿蛋炒饭来比喻的话就是饭是一个对象,蛋是另一个对象,咱们可以先炒蛋在炒饭,最后把蛋倒在饭上,如果用面向过程就是把蛋和饭放到一起翻炒。
- 面向对象和面向过程的优缺点。
- 面向对象的耦合度低,可维护性高,易复用。面向过程的性能高。
- 对象一般是由属性和方法组成,属性用来描述某个对象的静态特征,方法是用来描述对象的动态特征.对象是类的一个实例化,比如小明和小红都是人类,他们都是人类的实例化。
js继承
- 原型继承
- 子类.prototype = new 父类
- 子类.prototype.constructor = 子类
- 原型继承的特点:会将父类的私有属性和公有属性完成继承到子类的公有属性
function Father(){ this.x = 2;//私有属性 this.method = function(){ console.log(this.x) } } Father.prototype.pub = 5 //公有属性 function Son(){ this.a = 4 } Son.prototype = new Father Son.prototype.constructor = Son let son = new Son() console.log(son) //{a:4}=>__proto__=>Father=>{x:5}
- 借用构造函数
- 在子类的构造函数中,通过call方法调用父类函数,当作一个普通函数执行,并将父类的this(运行环境)指向子类。这样就相当于将父类的私有属性复制一份到子类的实例中。
- 特点:只了父类的私有属性,作为自己的私有属性。
function Father(){ this.x = 2;//私有属性 this.method = function(){ console.log(this.x) } } Father.prototype.pub = 5 //公有属性 function Son(){ Father.call(this)//将父类的函数当作一个普通函数执行 }
- 寄生组合
- 公有继承公有,私有继承私有
function Father(){ this.x = 2; this.method = function(){ console.log(this.x) } }; Father.prototype.pub = 5; //继承公有属性 Son.prototype = Object.create(Father);//创建一个新对象,这个对象的原型指向Father.prototype Son.prototypr.constructor = Son; function Son(){ Father.call(this);//将Father的私有属性赋给Son的私有 } let son = new Son()
js中的事件循环(代码执行顺序)
- js是单线程,所以任务只能一个一个的去执行。当我们打开网页时,网页的渲染和dom解析就是同步任务,而加载图片、音乐等大量的下载任务就是异步任务。
- 事件循环的顺序
- 任务进入执行栈
- 判断任务是同步还是异步。
- 同步进入主线程;异步进入Event table,注册函数等待被调用,并将函数移入Event Queue(事件队列)。
- 当js引擎检测到主线程执行栈为空时,开始执行异步,js引擎去Event Queue 中执行宏任务。宏任务执行完后,看看是否有同级的微任务,如若有执行微任务,执行完微任务再进入下一个宏任务,这就是事件循环。
- 宏任务有:setTimeout、setInterval
- 微任务有:promise;注意:promise.then属于微任务,如果是new Promise则是立即执行函数,不是微任务。process.nextTick()定义让代码再下个时间点执行,在同级的宏任务执行完后、ajax、axios
事件
事件流
HTML中与js的交互是通过事件来实现的,例如点击事件、页面滚动事件onscroll。
什么是事件流:事件流指的是从页面中接收事件的顺序。 事件流的顺序是先捕获后冒泡。
事件流有下面几个阶段:
- 事件捕获阶段
- 处于目标阶段
- 事件冒泡阶段
阻止事件
- 事件冒泡是默认的,所以阻止事件冒泡的方法是
e.stopPropagation()
- 阻止事件捕获
e.stopImmediatePropagation()
- 阻止默认事件
e.preventDefault()
事件的监听
e.addEventListener(event,function,useCapture)
方法,用于向指定元素添加事件,他可以更简单的控制事件的触发阶段。
- 第一个参数指的是定义的事件,例如click、mousedown
- 第二个参数是事件触发后的执行函数
- 第三个参数指的是事件触发的阶段
- true 事件在捕获阶段触发
- false 事件在冒泡阶段触发
常用的事件
鼠标事件
- onclick 点击事件
- ondbclick 双击事件
- onmouseover 鼠标移入
- onmouseout 鼠标移出
- onmousedown 鼠标按下
- onmouseup 鼠标抬起
- onmousemove 鼠标移动
- onwheel 鼠标滚动
- oncontextmenu 点击鼠标右键
键盘事件
- onkeydown 按下键盘
- onkeyup 抬起键盘
表单事件
- onfocus 元素获取焦点
- onblur 元素失去焦点
- onchange 元素内容改变
- oninput 元素获取用户输入内容
- onsubmit 提交按钮时触发
拖动事件
- ondrag 元素正在拖动时触发
- ondragend 元素结束拖动时触发
框架/对象事件
- onresize 浏览器窗口
- onload 元素加载完成
- onscroll 元素滚动时触发
- onunload 用户退出登录页面时触发
事件委托(事件代理)
事件委托就是利用事件冒泡,大大的提高了性能优化。在需要触发事件的父元素上设计函数监听,父元素通过监听子元素上触发的事件来触发事件。最经典就是ul下的li。
好处:比较适合动态元素的绑定,新增加的新元素也会有监听函数。也可以有事件触发机制。
Ajax
实现一个Ajax
let xhr = new XMLHttpRequest() //创建一个ajax对象
//必须在open()之前指定onreadystatechange 事件处理程序才能保证跨浏览器的兼容性
xhr.onreadystatechange = function(){
if (xhr.readystate === 4 ){
if(xhr.state === 200 && xhr.state < 300 || xhr.state === 304){
console.log(xhr.responseText)
}else{
console.log('Error'+ xhr.status)
}
xhr.open('get','url',true) // 第三个参数true代表异步,如果是false代表是同步
xhr.send(data) // 参数作为请求主体发送的数据
}}
js盒子模型
- client获取当前元素可视区域的宽高
- clientHeight = height + 上下paddiing
- clientWidth = width + 左右padding
- clientTop = 上border边框的宽度
- clientLeft = 左border边框的宽度
- offset获取
图片懒加载
window.onLoad和DOMContentLoaded事件的先后顺序
- window.onload:在页面载入完成的时候执行。
- DOMContentLoaded:在DOM树构建完成时执行。
- 触发形式
document.addEventListenner(DOMContenLoaded,function(){},flase)
- 触发形式
- 执行顺序:DOMContentLoaded先执行,window.load后执行。
js中处理异步的方法有哪些
- 回调函数
function callback(fn){
setTimeout(()=>{
fn()
},100)
}
function fn2(){
console.log(11)
}
callback(fn2)
- 发布订阅;简单的例子就是在元素上绑定点击事件,点击元素后触发响应的函数。
- promise
function p(num1,num2){
return new Promise((resolve,rejece)=>{
setTimeout(()=>{
let sum = num1 + num2
resolve(sum)
},100)
})
};
p(1,2).then(sum=>{
console.log(sum)
})
- Generator函数(演变为async和await语法)
- 创建一个generator函数的方法,在function关键字与函数名之间加一个*;二是函数体内部用yield,定义内部不同的状态。
- 调用函数,函数不会立即执行,会返回一个遍历器对象,需要调用next()方法,函数才会执行。next()会返回一个对象,对象中有value表示yield或者return的值,done表示函数 是否执行结束,如果结束返回的则是done:true;之后的每一次next都是从上一次yield开始。
function* gen(){ let res = yield 'hello'; yield res; return '你好'; } let g = gen() console.log(g.next())//{value:"hello",done:false}; conso.log(g.next(20))//{value:20,done:false}//在next中传入的参数会作为上次yield的返回值 console.log(g.next())//{value:'你好',done:true}
- 但是如何使用generator函数来进行异步编程呢;这里使用ES2017中的async和await语法(其实属于generator函数的语法糖)
- 使用async代替函数后的*,await代替yield就将generator改造成一个async函数。
function fnA(n){ return new Promise(resolve=>{ //异步操作 resolve(n) }) } function fnB(n){ return new Promise(resolve=>{ //异步操作 resolve(n) }) } async function gen(){ let resA = await fnA(1) let resB = await fnB(resA) } gen()
- async和await的详解
- await只能放在async里面
- await后面一般跟return new Promise
- 最好把await和成功后的操作放到try中,失败的放在catch中。
function getNum(n){ return new Promise((resolve,reject)=>{ let sino = parseInt(Math.random()*6+) if(sino>3){ if(n=="大"){ resolve(sino) }else{ reject(sino) } }else{ if(n == '大'){ reject(sino) }else{ resolve(sino) } } }) } async function test(n){ try{ let num = await getNum(n) console.log('赢了',num) }catch(error){ console.log('输了',error)//这里error接收的是reject的值 } } test('大') console.log(test('大'))//返回的是一个Promise对象。
- async函数的返回值是一个Promise对象,而且这个对象的状态是resolve的,如果没有写return,则resolve的值是undefined,如果写了这个返回值就是resolve的值。
- 特别注意,await后面的promise函数如果没有resolve值,对await来说就算失败了,下面的代码不会执行
function fn(){ return new Promise(resolve=>{ console.log(1) //resolve }) } async function f1(){ await fn() console.log(2) } f1() console.log(3) //1,3 //如果有resolve,1,3,2
- async await和promise哪个更好。
- promise通过.then链来解决多层回调的问题,写法较为发杂,async/await的写法更加简单。使异步代码看起来更像同步。
数组扁平化
- 使用递归,主要思想是循环数组的每一项,如果不是数组则将其添加到数组中,如果是数组,则将之前和数组和现在的每一项进行合并。
let ary = [1, [25, 23, 6, [256, [254, [35]]]], 3]
function fn(ary) {
let arr = []
ary.forEach(e => {
if (Array.isArray(e)) {
arr = arr.concat(fn(e))
} else {
arr.push(e)
}
});
return arr
}
fn(ary)
console.log(fn(ary));
null和undefined的区别
- undefined未被定义的
- 声明变量但是未赋值,就等于undefined
- 调用函数时,应该提供的参数没有提供,参数就为undefind
- 函数没有返回值的时候,默认返回undefined
- null尚未存在的对象
- 作为函数的参数,表示函数的参数不是对象
- 最为对象原型链的终点
- null == undefined //true
- null === undefined //fales
理解运算符&&和||和!!
- && 逻辑与(且);判断是否为true,前面是true取后面,前面是false取前面。使用场景如下
- 在if判断语句中,充当且的意思,全部都为true才为true,其中一个为flase则为false。
- &&后面为函数的情况,n>5 && fn();前面为true则执行fn,前面为flase则不执行函数。
- 在取值情况下,let n = 5 && 3;前面成立取后面,故取n = 3;若前面不成成立取后边。
- || 逻辑或(或);判断是否为true,前面为true取前面,前面为flase取后面;使用场景如下
- 取值 let n = n || 5;如果n为true则为n,如果n为false则为5
- !! 意思是将右侧的值强制转为boolean类型;例如!!NAN = false;!!5 = true
js中虚值指的是什么?
- js中的虚值指的是在转化为布尔类型时值为false的值。例如空字符串""、NAA、0、undefined、false
event.target和event.currentTarget的区别
- event.target是触发事件的元素
- event.currentTarget是触发事件所绑定的元素
原生头像上传和文件上传
<div class="main">
<input type="file" onchange="change(event)" onclick="click()" style="display: none;" id="fileEle"
multiple='multiple'>
<div class="avatar" style="width: 50px; height: 50px; border-radius: 100%; background: yellow;" onclick="fn()"></div>
<img src="" id="img" onclick="getImg()" style="width: 50;height: 50px;background: red;">
</div>
<script>
//multiple="multiple"表示支持选中多个文件
let fileEle = document.getElementById('fileEle');
let img = document.getElementById('img');
function getImg() {
fileEle.click()
}
function change(event) {
console.log(event.target.files[0])
let files = fileEle.files[0]; //fileEle.file是一个Filelist对象
let render = new FileReader();
render.readAsDataURL(files);
render.onload = function () {
img.setAttribute('src', render.result);
}
}
function fn(){
fileEle.click()
}
</script>