致敬我们前端遇到的那些坑(2)---js篇
致敬我们前端遇到的那些坑(2)—js篇
1. 数据类型及判断
在 JavaScript 中有 6种不同的数据类型:
• string
• number
• boolean
• object //对象类型
• function
symbol //表示独一无二的值
2 个不包含任何值的数据类型:
• null 声明了变量为空值
• undefined 声明了变量,但没有赋值
检测数组的几种方式:
定义一个数组:
var arr1 = [1,2,3];
typeof arr1; //object
1、instanceof
2、isArray
这个方法也可以用来分辨数组和对象,但前提是必须知道是数组或者对象
Array.isArray([]); //true
Array.isArray({}); //false
3、constructor
arr1.constructor === Array; //true
4、利用原型方法
由于在iframe中创建的Array并不共享prototype,此时可以:
var obj=
Object.prototype.toString.call(obj)
2.三元表达式
• (关系表达式)?表达式1:表达式2;
• 如果条件为true,运算后的结果是表达式1;
• 如果条件为false,运算后的结果是表达式2;
B:示例:
- 获取两个数中大数。
- int x=3,y=4,z;
- z = (x>y)?x:y;//z变量存储的就是两个数的大数
3.js操作正则表达式
test方法,该方法用来测试某个字符串是否与正则匹配,匹配就返回true,否则返回false。
该方法接受一个字符串作为参数
代码:
var reg=/^text/;
console.log(reg.test(“text”));
4. JS中的事件委托(事件代理)
一步一步来说说事件委托(或者有的资料叫事件代理)
- js中事件冒泡我们知道,子元素身上的事件会冒泡到父元素身上。
- 事件代理就是,本来加在子元素身上的事件,加在了其父级身上。
- 那就产生了问题:父级那么多子元素,怎么区分事件本应该是哪个子元素的?
- 答案是:event对象里记录的有“事件源”,它就是发生事件的子元素。
- 它存在兼容性问题,在老的IE下,事件源是 window.event.srcElement,其他浏览器是 event.target
- 用事件委托有什么好处呢?
- 第一个好处是效率高,比如,不用for循环为子元素添加事件了
- 第二个好处是,js新生成的子元素也不用新为其添加事件了,程序逻辑上比较方便
例子1. 页面有个ul包含着4个li,鼠标移动到li上,li背景变成红色,移出,背景恢复原色。
5.jsonp原理
JSONP实现跨域请求的原理简单的说,就是动态创建
缺点:只能发送get请求
function getdata(data){
console.log(data)
}
实例:
html:
js:
$.ajax({
url: ‘http://127.0.0.1:8001/list’,
method: ‘get’,
dataType: ‘jsonp’, //=>执行的是JSONP的请求
success: res => {
console.log(res);
}
});
服务器:
let express = require(‘express’),
app = express();
app.listen(8001, _ => { //_为了占位
console.log(‘OK!’);
});
app.get(’/list’, (req, res) => {
let {
callback = Function.prototype //解构并赋默认值是一个匿名空函数
} = req.query;
let data = {
code: 0,
message: ‘珠峰培训’
};
res.send(${callback}(${JSON.stringify(data)})
);
});
6.闭包
用处:1.读取函数内部的变量;
2.这些变量的值始终保持在内存中,不会在外层函数调用后被自动清除。
3.扩展了变量的作用范围
优点:1:变量长期驻扎在内存中;
2:避免全局变量的污染;
3:私有成员的存在 ;
特性:1:函数套函数;
2:内部函数可以直接使用外部函数的局部变量或参数;
3:变量或参数不会被垃圾回收机制回收 GC;
缺点:
常驻内存 会增大内存的使用量 使用不当会造成内存泄露,详解:
(1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
(2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。
function makeFunc() {
var name = “Mozilla”;
function displayName() {
alert(name);
}
return displayName;
}
var myFunc = makeFunc();
myFunc();
// 经典面试题
function a() {
for(var i = 0 ; i< 10 ; i++) {
setTimeout(function() {
console.log(i)
}, 0)
}
} // 会输出10个10,要求输出1,2…10
// 方法1 将var改成let //let的块级作用域
// 方法2 闭包
function a() {
for (var i = 0; i < 10; ++i) {
setTimeout(
(function(i) {
return console.log(i)
})(i), 0);
}
}
a()
//1.使用闭包实现点击li,输出第几个
for (var i = 0; i < lis.length; i++) {
(function(i) {
lis[i].onclick = function() {
console.log(i)
}
})(i)
}
//2.延时3秒,打印对应li中获得内容
for (var i = 0; i < lis.length; i++) {
setTimeout((function(i) {
lis[i].onclick = function() {
console.log(lis[i].innerHTML)
}
})(i), 3000)
}
7.call(),apply(),bind()
1.call
1.立即执行函数
2.改变this指向
3.传参 .call(thisObj, arg1, arg2, arg3, arg4); //后面的参数都是实参
4.适用于继承
2.apply
1.立即执行函数
2.改变this指向
3.传参 .apply(thisObj, [args]); //后面的参数都是实参
4.有关数组的操作
<script>
var arr = [1, 2, 3]
var max = Math.max.apply(Math, arr)
console.log(max)
</script>
函数内部通过arguments获取参数 //console.log(arguments)
3.bind()
1.不会立即执行函数
2.改变this指向
3.传参 .call(thisObj, arg1, arg2, arg3, arg4); //后面的参数都是实参
4.适用于不会立即调用但又需要改变this,比如定时器
<button>点击禁用3秒</button>
<script>
var num = {}
var btn = document.querySelector("button")
console.log(btn)
btn.onclick = function() {
this.disabled = true
setTimeout(function() {
this.disabled = false
}.bind(this), 3000) //this btn
}
</script>
8. js中的this指向
this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是那个调用它的对象(一般情况下)
如果函数没有调用者,this就是window
自执行函数
方式一:(func)
方式二:(func)() //()就是函数执行
1.
function add(){
var a=b=3 //预解析 a是局部变量,b是全局变量 var a=b;b=3(b会被提升至全局变量)
}
console.log(a) //err
console.log(b) //3
-
var n=1
function show(){
console.log(n)
n=455
console.log(n)
}
show(n)
console.log(n)
输出—
1
455
455 -
var n=1
function show(){
console.log(n)
function n(){}
}
show(n)
输出—
func
只要没有再对n赋值,就是func
-
var obj1 = {
a: “hellow”
}
var obj2 = obj1
obj2.a = “world”
console.log(obj1) //{a: “world”} -
function c0() {
var b = 1function a0() { console.log(b) //un var b = 2 console.log(b) //2 } a0() console.log(b) //1 } c0()
<script type="text/javascript">
var f = true
if (f === true) {
var a = 10
}
function fn() {
var b = 20
c = 30
}
fn()
console.log(a) //10
console.log(b) //err
console.log(c) //30
</script>
9. 防抖和节流
防抖:事件发生后,n秒只触发一次,若在n秒内再次触发,则重新计算(再等n秒)。
分为两类:
第一类:先延时再执行 【延时可以重新计算】
只要在每次执行事件前,先清除定时器,再开启一个定时器即可
第二类:先执行后延时 【延时可以重新计算】
需要借助一个变量来存储当前的定时器的状态,通过不同的状态,来执行不同的事件
点击吧
节流:事件发生后,n秒只能触发一次。
先设一个定时器句柄(let time)
只写定时器没有的情况,在里面添加一个定时器,并调用相关事件
当无法使用定时器时,使用时间戳(时间间隔),同样可以实现类似效果
获取时间:
function show(){
var time1=new Date()
var time2=Date.now() //比较两次时间的间隔
console.log(“time1—”+time1)
console.log(“time2—”+time2)
}
输出----
time1—Tue Jan 28 2020 14:51:51 GMT+0800 (中国标准时间)
time2—1580194311067
10. 对象拷贝
- 浅拷贝,
是地址引用,可以利用 for in来完成
法一:
var obj = {
name: “1”
}
var obj1 = {}
for (key in obj) {
obj1[key] = obj[key] //不能用.
}
console.log(obj1)
法二:
let Person = {
name: ‘123’
}
var person = {}
Object.assign(person, Person) //浅拷贝
person.name = “111”
console.log(person === Person) //false
console.log(person.name) //111
console.log(Person.name) //123
2.深拷贝
通过递归方式实现深拷贝
有瑕疵
//1.深拷贝
obj = {
name: “tom”,
id: 18,
goods: {
id: 123
},
list: [1, 23, 1, 4]
}
function deepClone(obj) {
var target = {};
for (var key in obj) {
//JavaScript中Object对象原型上的hasOwnProperty()用来判断一个属性是定义在对象本身而不是继承自原型链。
if (Object.prototype.hasOwnProperty.call(obj, key)) {
if (typeof obj[key] === 'object') { //对象
target[key] = deepClone(obj[key]);
} else { //数组
target[key] = obj[key]; //输入一个数组,最后返回值是对象
}
}
}
return target;
}
console.log(deepClone(obj))
</script>
通过json的方式实现
完美解决
obj = {
name: “tom”,
id: 18,
goods: {
id: 123
},
list: [1, 23, 1, 4]
}
function deepClone(obj) {
let result = JSON.parse(JSON.stringify(obj));
return result;
}
console.log(deepClone(obj))
</script>