js常见问题
1.变量提升和函数提升,函数表达式和函数声明的区别
-
会把var声明的变量提前到当前作用域声明,只提前声明,不提前赋值
-
会提前声明函数,只提前声明,不提前调用
-
当变量和函数同名时,先提升同名的变量,再提升同名的函数,函数的权重高(函数会覆盖变量)
题目一:
var a =1;
console.log(a);
a();
function a(){
console.log('aaa')
}
console.log(a)
预解析
//a 变量
//a 函数
执行
//a = 1
// a()
结果
// 1
//报错 a is not a function
题目二
console.log(a)
a();
var a =1;
function a(){
console.log('aaa')
}
console.log(a);
预解析
//a 变量
//a 函数
执行
//log a ->是一个函数
// a() -> 'aaa'
//log a ->1
结果
// 函数
//'aaa'
//1
注意上面2个题目的区别,一个报错,一个不报错,主要在于函数的执行阶段。题目一是先把a赋值再调用;题目二是先调用函数再给a赋值
- 函数声明会提升,函数表达式不会提升(函数表达式会提升等号左边的变量为undefined,不会提升函数)
<!-- 题目一:函数表达式 -->
// console.log(fn); //undefined
// fn() //报错fn is not defined
// var fn = function() {
// console.log('fn')
// }
<!-- 题目二:函数声明 -->
console.log(fn); //函数
fn() //'fn'
function fn() {
console.log('fn')
}
再看看下面这个题吧:
// 词法作用域的一个坑,可以画图理解
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事件循环机制
闭包
-
概念:定义在函数内部的函数,该函数中使用了外部函数的变量
-
作用:实现数据的私有化,因为全局变量会被污染
-
缺点:内存溢出,怎么溢出的?(面试的时候问过) 因为闭包函数中引用的变量一直在内存中,没有被垃圾回收机制回收掉。
-
那js的内存管理和垃圾回收机制又是咋样的呢?
在 JS 中创建一个变量的时候,系统会根据,变量的类型,自动为其分配对应的内存(基础类型 -> 栈内存,固定大小;对象类型 -> 堆内存,根据需要分配大小)。
正常情况下,当这些变量不再被使用的时候,就会被回收,内存被释放。JavaScript 中全局变量的在浏览器卸载页面才会被销毁;函数中用var,let,const定义的变量,一般是在函数调用结束后就会被销毁
现在各大浏览器通常采用的垃圾回收有两种方法:标记清除、引用计数。一般都是标记清除
-
应用场景?思考:现在有了块级作用域,应该不需要了吧?(fix)
下面是一些闭包函数的例子: 参见 闭包
1.什么是闭包
function lazy_sum(arr) {
var sum = function() {
return arr.reduce(function(x, y) {
return x + y;
});
}
return sum;
}
// 注意到返回的函数在其定义内部引用了局部变量arr,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用 fix 这句话怎么理解
2.理解var定义的变量和立即执行函数
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()); //16
console.log('f2', f2()); //16
console.log('f3', f3()); //16
//打印结果 全部都是`16`!原因就在于返回的函数引用了变量`i`,但它并非立刻执行。
//等到3个函数都返回时,它们所引用的变量`i`已经变成了`4`,因此最终结果为`16`。
那如果想得到1,4,9要怎么写呢
function count() {
var arr = [];
for (var i = 1; i <= 3; i++) {
arr.push((function () {
return i * i;
})(i));
}
return arr;
}
var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];
console.log('f1', f1);
console.log('f2', f2);
console.log('f3', f3);
3.闭包可以防止全局变量的污染
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)
4.实现第一秒打印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 防抖:n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
function debounce(fn, wait) {
let timer;
return function() {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(fn, wait)
}
}
//throttle 节流:n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时
// 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)