js常见问题(javascript错误解决方案)
js常见问题
1.变量提升和函数提升,函数表达式和函数声明的区别
会把var声明的变量提前到当前作用域声明,只提前声明,不提前赋值
会提前声明函数,只提前声明,不提前调用
当变量和函数同名时,先提升同名的变量,再提升同名的函数,函数的权重高(函数会覆盖变量)
函数声明会提升,函数表达式不会提升(函数表达式会提升等号左边的变量为undefined,不会提升函数)
该题目考查同名变量和同名函数得提升,js的预解析和执行
a(); //我是函数a里面的log console.log(a) //函数 var a = 1; console.log(a) //1 function a() { console.log('我是函数a里面的log') } console.log(a) //1 fix这里为啥还是打印1 // 预解析阶段 // a undefined // a 函数 // 执行阶段 // a(); // console.log(a); // a = 1; // console.log(a);复制代码
函数表达式和函数声明复制代码
<!-- 题目一:函数表达式 --> // console.log(fn); //undefined // fn() //报错fn is not defined // var fn = function() { // console.log('fn') // } <!-- 题目二:函数声明 --> console.log(fn); //函数 fn() //报错fn is not defined function fn() { console.log('fn') }复制代码
补充2道题目练习一下啦:
var a = 25; function abc() { alert(a); //undefined现在自己内部找 只提前声明,不提前赋值 var a = 10; } abc(); // 如果变量和函数同名的话,函数优先 console.log(a); //25 function a() { console.log('aaaaa'); //不打印,函数没有调用 } a(); //报错 ,a 不是一个函数 var a = 1; console.log(a); //1 // 词法作用域的一个坑,可以画图理解 var a = { x: 1 } var b = a; a.x = a = { n: 1 }; // 运算符的优先级问题: // 算术运算符>关系运算符>逻辑运算符>赋值运算符 console.log(a.x); //undefined 点运算符大于赋值运算符 console.log(b.x); // {n : 1}复制代码
浅拷贝和深拷贝
浅拷贝
对象的浅拷贝
const obj = { name: "bwf", age: 18 } // 法一 Object.assign const obj1 = Object.assign({}, obj); obj1.name = 'wmy' console.log(obj) // 法二 扩展运算符 const obj2 = { ...obj1 } obj2.name = 'wmy' console.log(obj)复制代码
数组的浅拷贝
// 法一:扩展运算符 const arr = [1, 2, 3] const arr2 = [...arr] arr2[0] = 9 console.log(arr, arr2) // 法二 slice const arr = [1, 2, 3] const arr1 = arr.slice(); arr1[0] = 9 console.log(arr, arr1) // 法三 concat const arr = [1, 2, 3] const arr3 = arr.concat() arr3[0] = 9 console.log(arr, arr3)复制代码
深拷贝
const person = { name: 'bwf', age: 18, job: { name: 'fe', salary: 14 }, sayHello() { console.log(`大家好,我叫${this.name}`) } } // Object.assign只实现了对象第一层的拷贝 const p1 = Object.assign({}, person); p1.name = 'wmy' p1.job.salary = 20 console.log(person) // 法一:递归拷贝 function deepClone(obj) { // 定义一个新的对象或数组,遍历源对象或数组,复制到新对像或数组中 const target = Array.isArray(obj) ? [] : {}; if (obj && typeof obj === "object") { for (const key in obj) { // 如果对象的属性又是一个对象,则递归复制 if (obj[key] && typeof obj[key] === "object") { target[key] = deepClone(obj[key]) } else { target[key] = obj[key] } } } return target; } const p2 = deepClone(person) p2.name = 'wmy' p2.job.salary = 20 console.log(person) // 法二:JSON.stringify和JSON.parse // 缺点: 无法实现对对象中方法的深拷贝,会显示为undefined function deepClone2(obj) { var _obj = JSON.stringify(obj) var objClone = JSON.parse(_obj) return objClone; } const p3 = deepClone2(person) p3.name = 'wmy' p3.job.salary = 20 p3.sayHello = function() { console.log('hello') } console.log(person) person.sayHello()复制代码
js事件循环机制
1请描述事件循环机制 js是单线程的,调用栈中的同步任务执行完毕后,会去轮询堆中的任务,流程: 1执行同步任务,清空call stack 2执行当前的微任务 3尝试dom渲染 4触发事件循环机制
2什么是微任务,什么是宏任务,2者的区别,与dom渲染的关系 同步任务执行完毕后再渲染dom 微任务在dom渲染前执行,宏任务在dom渲染后执行 宏任务是浏览器定义的,常见的宏任务有:setTimeout ajax dom事件 微任务是es6中的 ,常见的微任务有promise async/await
示例:可以体验宏任务,微任务与dom渲染的关系
//#region // const demo = document.getElementsByClassName('demo')[0] // const p1 = `<p>我是p1</p>` // const p2 = `<p>我是p2</p>` // const p3 = `<p>我是p3</p>` // demo.innerHTML += p1 // demo.innerHTML += p2 // demo.innerHTML += p3 // console.log('length', demo.children.length) // alert('同步任务执行完毕') // setTimeout(() => { // alert('setTimeout 宏任务', demo.children.length) // }) // Promise.resolve().then(() => { // alert('promise 微任务', demo.children.length) // }) //#endregion复制代码
题目一:promise 与同步任务
//#region 1. // Promise.resolve().then(() => { // console.log(1) // }).catch(() => { // console.log(2) // }).then(() => { // console.log(3) // }) 2. // Promise.resolve().then(() => { // console.log(1) //1 // throw new Error('fail') // }).catch(() => { // console.log(2) //2 catch捕获后又返回一个成功的promise // }).then(() => { // console.log(3) //3 // }) 3. // Promise.resolve().then(() => { // console.log(1) // throw new Error('fail') // }).catch(() => { // console.log(2) // }).catch(() => { // console.log(3) // }) //#endregion复制代码
题目二 同步任务与async/await
1. async function fn() { return 100 } (async function() { const a = fn(); console.log(a) //promise async定义的函数返回的是一个promise const b = await fn() //await 后返回的是函数定义的结果 console.log(b) //100 })() 2. (async function() { console.log('start') //start const a = await 100; console.log('a', a) //100 const b = await Promise.resolve(200) console.log('b', b) //200 const c = await Promise.reject(300) console.log('c', c) //报错了 console.log('end') })()复制代码
闭包
概念:函数中返回函数
作用:实现数据的私有化,因为全部变量会被污染
缺点:内存溢出,怎么溢出的?(面试的时候问过)
应用场景?思考:现在有了块级作用域,应该不需要了吧?(fix)
function lazy_sum(arr) { var sum = function() { return arr.reduce(function(x, y) { return x + y; }); } return sum; } // 注意到返回的函数在其定义内部引用了局部变量arr,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用 fix 这句话怎么理解 // https://www.liaoxuefeng.com/wiki/1022910821149312/1023021250770016 function count() { var arr = []; for (var i = 1; i <= 3; i++) { arr.push(function() { return i * i; }); } return arr; } var results = count(); var f1 = results[0]; // var f2 = results[1]; // var f3 = results[2]; console.log('f1', f1); function fn() { var a = 1; return function() { return a } } var f1 = fn() //返回函数 var a1 = f1(); //1 a1++; a1++; var a2 = f1(); //1 a2++; a2++; console.log(a1, a2) // fix 怎么被污染的 var a = 1; var a1 = a; a1++; a1++; var a2 = a; a2++; a2++; console.log(a1, a2); //3 5 // 实现第一秒打印1,第二秒打印2,。。。 for (var i = 1; i < 10; i++) { setTimeout(function() { //打印10次10 主线程执行完成之后, 才会去轮巡事件循环队列, 看有没有满足条件的函数, 如果有, 拿过来执行 console.log(i); }, 1000 * i); } // 1 for (let i = 1; i < 10; i++) { setTimeout(function() { console.log(i); }, 1000 * i); } // 2 for (var i = 1; i < 10; i++) { (function(i) { setTimeout(function() { console.log(i); }, 1000 * i); })(i) } // 3 for (var i = 1; i < 10; i++) { var fn = (function(num) { return function() { console.log(num); } })(i); setTimeout(fn, 1000 * i); } // 4 // setTimeout( fn, 毫秒数, 需要传递给fn的参数 ) // fix 这个是一次性打印出来的呀? for (var i = 1; i < 10; i++) { setTimeout(function(i) { console.log(i); }, 1000, i); }复制代码
数组和字符串常用方法
数组的方法 juejin.cn/post/703479…
字符串的方法 juejin.cn/post/703481…
节流和防抖的实现
// debounce 防抖:持续触发事件时不会立马执行回调,结束触发事件后过1s才执行。 function debounce(fn, wait) { let timer; return function() { if (timer) { clearTimeout(timer) } timer = setTimeout(fn, wait) } } // throttle 节流:时间戳实现 const throttle1 = function(func, delay) { var prev = Date.now(); return function() { var context = this; var args = arguments; var now = Date.now(); if (now - prev >= delay) { func.apply(context, args); prev = Date.now(); } } } // throttle 节流:定时器实现 const throttle2 = function(func, delay) { var timer = null; return function() { var context = this; var args = arguments; if (!timer) { timer = setTimeout(function() { func.apply(context, args); timer = null; }, delay); } } } function handle() { console.log('handle') } const scrollBox = document.getElementById('scroll-box') scrollBox.addEventListener('scroll', throttle2(handle, 1000))复制代码
call,apply,bind的区别和共同点
改变函数执行时的上下文,call,apply的第二个参数不同,call传参数列表,apply传数组,bind返回的是一个函数,调用后才会执行
call,apply,bind的应用场景:
// 2.1求数组中的最大值 const arr = [1, 9, 3, 4] const res1 = Math.max(...arr) const res2 = Math.max.apply(null, arr) const res3 = Math.max.call(null, ...arr)复制代码
// 2.2判断变量类型 const obj = { name: 'bwf' } console.log(typeof obj) //object console.log(typeof arr) //object console.log(typeof null) //object console.log(Object.prototype.toString.call(obj)) //[object Object] console.log(Object.prototype.toString.call(arr)) //[object Array] console.log(Object.prototype.toString.call(null)) //[object Null]复制代码
// 2.3继承 function Person(name, age) { this.name = name; this.age = age; } function Student(name, age) { Person.call(this, name, age) } const s = new Student('bwf', 18)
作者:xiaobaoyu
链接:https://juejin.cn/post/7034820590036844575