前端面试题(JS篇)

目录

1.闭包

2.原型链

3.作用域链

4.防抖、节流

5.常用数组方法

6.常用字符串方法

7.深浅拷贝

8.数据类型以及判断方法

9.判断一个变量是否数组

10.事件委托

11.事件循环

12.垃圾回收机制

13.var、const和let的区别

14.this的指向

15.new操作符

16.ES6新特性

17.模块化

18.实现继承的方式

19.ES5和ES6实现继承的区别

20.箭头函数与普通函数的区别

21.call、apply、bind的区别

22.for in和for of的区别

23.对Promise的理解

24.async和await

25.柯里化

26.判断元素是否出现在视口

27.浮点数精度问题

28.怎样理解JS是单线程的

29.正则表达式

30.Ajax 


1.闭包

概念:闭包就是能够读取其他函数内部变量的函数,能够实现函数外部访问到函数内部的变量。(JS中内层函数可以访问外层函数的变量,外层函数无法操作内层函数的变量)

简单理解:“函数”和“函数内部能访问到的变量”的总和,就是一个闭包。

优点:可以重复利用变量,并且该变量不会污染全局;隔离作用域,保护私有变量;让函数外部可以操作(读写)函数内部的数据(变量/函数);变量长期驻扎在内存中,不会被内存回收机制回收,即延长变量的生命周期。

注意:函数执行完后,函数内部声明的局部变量一般不存在,但闭包中的变量可能存在。在函数外部不能直接访问函数内部的局部变量,但是可以通过闭包让外部操作它。

缺点:闭包较多时,会消耗内存,导致页面性能下降。如果不释放内存,会引起内存泄漏。

function fn1() {
    let arr = new Array(100)
    fnction fn2() {
        lconsole.log(arr.length)
    }
    return fn2
}
let f = fn1()
f()
f = null  // 让内部函数成为垃圾对象,回收闭包
// 也可以调用时直接f()()运行即可,匿名函数用完自动销毁

闭包的生命周期:

  • 产生:在嵌套内部函数定义执行完时就产生了(不是调用)。
  • 死亡:在嵌套的内部函数成为垃圾对象时。

使用场景:防抖,节流,函数嵌套函数避免全局污染的时候。

笔试题:

// 补全add函数,输出对应结果
function add(n) {
    //TODO
}
add(1)(2)()  // 3
add(1)(2)(3)(4)()  // 10

//答案
function add(n) {
    if(!n) { return res }
    res = n
    return function(n) {
        return add(res + n)
    }
}
console.log(add(1)(2)(3)())  // 6
console.log(add(1)(2)(3)(4)())  // 10

2.原型链

(1)原型对象:每一个函数在创建时都会被赋予一个prototype属性,它指向函数的原型对象,这个对象包含所有实例共享的属性和函数。

(2)原型链:对象的每个实例都具有一个__proto__属性,指向构造函数的原型对象。而原型对象同样存在一个__proto__属性指向上一级构造函数的原型对象,就这样层层往上,直到最上层某个原型对象为null。

注意:原型链上的所有节点都是对象,不能是字符串、数字、布尔值等原始类型。要求原型链必须是有限长度的。由于无法访问null的属性,起到了终止原型链的作用。

3.作用域链

(1)作用域:在运行代码时某些特定部分的变量、函数和对象的可访问性。作用域规定了如何设置变量,也就是确定当前执行代码对变量的访问权限。JavaScript采用词法作用域,也就是静态作用域,即在函数定义的时候就已经确定了。

(2)变量对象:变量对象是当前代码段中,所有的变量(变量,函数,形参,arguments)组成的一个对象。变量对象是在执行上下文中被激活的,只有变量对象被激活了,在这段代码中才能使用所有的变量。变量对象分为全局变量对象和局部变量对象。全局简称为Variab Object VO,函数由于执行才被激活,成为Active Object AO。

(3)作用域链:在js中,函数存在一个隐式属性[scopes](域),这个属性用来保存当前函数的执行上下文环境。由于在数据结构上是链式的,因此也被称作作用域链。可以将它理解为一个数组,一系列的AO对象所组成的一个链式结构。

JavaScript关于作用域、作用域链和闭包的理解-CSDN博客

4.防抖、节流

防抖和节流_防抖节流-CSDN博客

防抖、节流的介绍_防抖节流_Donp1的博客-CSDN博客

5.常用数组方法

(1)push():向数组的末尾添加一个或多个元素,并返回新的数组长度。原数组改变。

let arr = [1, 2, 3, 4]
arr.push(5, 6, 7)
arr = [1, 2, 3, 4, 5, 6, 7]

(2)pop():删除并返回数组的最后一个元素,若该数组为空,则返回undefined。原数组改变。

let arr = [1, 2, 3, 4, 5, 6, 7]
let del = arr.pop()
// del = 7
// arr = [1, 2, 3, 4, 5, 6]

(3)unshift():向数组的开头添加一个或多个元素,并返回新的数组长度。原数组改变。

let arr = [1, 2, 3, 4, 5, 6, 7]
let res = arr.unshift(0)
// res = 8
// arr = [0, 1, 2, 3, 4, 5, 6, 7]

(4)shift():删除数组的第一项,并返回该元素的值。若该数组为空,则返回undefined。原数组改变。

let arr = [1, 2, 3, 4, 5, 6, 7]
let res = arr.shift()
// res = 1
// arr = [2, 3, 4, 5, 6, 7]

(5)concat(arr1,arr2,...):合并两个或多个数组,生成一个新的数组。原数组不变。

let arr = [1, 2, 3, 4, 5, 6, 7]
let arr1 = ["a", "b", "c"]
let arr2 = ["x", "y", "z"]
let res = arr.concat(arr1, arr2)
// res = [1, 2, 3, 4, 5, 6, 7, "a", "b", "c", "x", "y", "z"]
// arr = [1, 2, 3, 4, 5, 6, 7]

(6)join():将数组的每一项用指定字符连接形成一个字符串,默认连接字符为逗号。

let arr = [1, 2, 3, 4, 5, 6, 7]
let str1 = arr.join()
let str2 = arr.join("-")
// str1 = 1,2,3,4,5,6,7
// str2 = 1-2-3-4-5-6-7

(7)reverse():将数组倒序。原数组改变。

let arr = [1, 2, 3, 4, 5, 6, 7]
arr.reverse()
// arr = [7, 6, 5, 4, 3, 2, 1]

(8)sort():对数组元素进行排序,按照字符串UniCode码排序。原数组改变。

// 从小到大排序
let arr = [1, 4, 3, 55, 12, 5]
let sortNum = function(a, b) {
    return a-b
}
arr.sort(sortNum)  // [1, 3, 4, 5, 12, 55]

// 从大到小排序
let arr = [1, 4, 3, 55, 12, 5]
let sortNum = function(a, b) {
    return b-a
}
arr.sort(sortNum)  // [55, 12, 5, 4, 3, 1]

// 按照数组对象中的某个值排序
let arr = [
    {name:"张三", age:18},
    {name:"李四", age:16},
    {name:"王五", age:20}
]
function compare(param) {
    return function sortAge(a, b) {
        return a[param]-b[param]
    }
}
arr.sort(compare("age"))  // [{name:"李四", age:16},{name:"张三", age:18},{name:"王五", age:20}]

(9)map(function):接收一个函数作为参数,返回一个新的数组。原数组不变。(注意和forEach的区别)。

let arr = [1, 2, 3, 4, 5]
let arr1 = arr.map(item => item = 2)
// arr = [1, 2, 3, 4, 5]
// arr1 = [2, 2, 2, 2, 2]

(10)slice():按照条件截取出其中的部分内容。

  • array.slice(n, m):从索引n开始查找到索引m处(不包含m)。
  • array.slice(n):从索引n开始查找到末尾。
  • array.slice(0):原样输出数组,可以实现数组克隆。
  • array.slice(-n, -m):从最后一项开始算起,-1为最后一项,-2为倒数第二项,返回一个新数组。原数组不变。

(11)splice(index, n, arr1, arr2...):用于添加或删除数组中的元素。从index位置开始删除n个元素,并将arr1、arr2...数据从index位置依次插入。n为0时,不删除元素。原数组改变。

let fruits = ["Banana", "Orange", "Apple", "Mango"]
fruits.splice(2, 1, "Lemon", "Kiwi")
// fruits = ["Banana", "Orange", "Lemon", "Kiwi", "Mango"]

(12)forEach(function):为每个数组元素调用一次函数(回调函数)。原数组不变。(注意和map的区别,若直接打印Array.forEach,结果为undefined)。

(13)filter(function):过滤数组中符合条件的元素并返回一个新的数组。

let arr = [1, 2, 3, 4, 5]
let arr1 = arr.filter(x => x>3)
// arr1 = [4, 5]

(14)every(function):对数组中的每一项进行判断,若都符合则返回true。否则返回false。

(15)some(function):对数组中的每一项进行判断,若都不符合则返回false。否则返回true。

(16)reduce(function):在每个数组元素上(从左到右)运行函数,以生成单个值。

let arr = [1, 2, 3, 4, 5]
let total = arr.reduce((a, b) => a+b)
// total = 15

(17)indexOf(item,index):检测当前值在数组中第一次出现的位置索引。item为查找的元素,index为字符串中开始检索的位置,返回第一次查到的索引,未找到返回-1。原数组不变。

(18)includes():判断数组是否包含一个指定的值。原数组不变。

改变原数组的方法:push,pop,shift,unshift,reverse,sort,splice

不改变原数组的方法:concat,map,filter,join,every,some,indexOf,slice,forEach

slice和splice的区别:splice改变原数组,slice不改变原数组。splice除了删除之外,还可以插入。splice可传入3个参数,slice接受2个参数。

map和forEach的区别:forEach()方法不会返回执行结果,而是undefined,map()方法会得到一个新的数组并返回。同样的一组数组,map()的执行速度优于forEach()。forEach()适用于并不打算改变数据的时候,而只想用数据做一些事情(比如存入数据库),map()适用于要改变数据值的时候,它更快,并且返回一个新的数组。

JavaScript中的数组方法总结+详解_js中数组的方法-CSDN博客

6.常用字符串方法

JavaScript 字符串方法

js字符串方法大全_haoge568的博客-CSDN博客

7.深浅拷贝

内存中一共分为栈内存和堆内存两大区域,所谓深浅拷贝主要是对js引用类型数据进行拷贝一份,浅拷贝就是引用类型数据相互赋值之后,例obj1=obj2;如果后面的操作中修改obj1或者obj2,这个时候数据是会进行相应的变化的,因为在内存中引用类型数据是存储在堆内存中,堆内存中存放的是引用类型的值,同时会有一个指针地址指向栈内存,两个引用类型数据地址一样,如果其中一个发生变化另外一个都会有影响。而深拷贝则不会,深拷贝是会在堆内存中重新开辟一块空间进行存放。

简单来说就是B复制了A,如果A发生了改变,如果B随之变化,那么是浅拷贝,如果B并没有发生变化,则是深拷贝。

(1)浅拷贝:创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性值是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址。

  • 拷贝对象:Object.assign() / {...obj}
  • 拷贝数组:arr.concat() / [...arr]
// Object.assign
let a = {id:1,name:'a',obj:{id:999}};
function fun(obj){
    let o = {};
    Object.assign(o,obj);
    return o;
}
let a2 = fun(a);
a2.name ='a2';
a2.obj.id = 888;
console.log(a);  // {id:1,name:'a',obj:{id:888}}
console.log(a2);  // {id:1,name:'a2',obj:{id:888}}

// concat()
let list = ['a','b','c'];
let list2 = list.concat();
list2.push('d');
console.log(list);  // ['a','b','c']
console.log(list2);  // ['a','b','c','d']

// slice()
let list = ['a','b','c'];
let list2 = list.slice();
list2.push('d');
console.log(list);  // ['a','b','c']
console.log(list2);  // ['a','b','c','d']

// 二维数组
let list = ['a','b','c',['d','e','f']];
let list2 = list.concat();
list2[3][0] = 'a';
console.log(list);  // ['a','b','c',['a','e','f']]
console.log(list2);  // ['a','b','c',['d','e','f']]

(2)深拷贝:不管原数据中值是什么类型的数据,拷贝后的新数据跟原数据是相互独立,没有关联的。

  • 通过JSON.stringify()实现:
let a = {
    name : 'a',
    age : 20,
    obj : {id:999},
    action : function(){
        console.log(this.name)
    }
}
let b = JSON.parse(JSON.stringify(a))
a.name = 'b'
a.obj.id = 888
console.log(a)  // {name:'b',age:20,obj:{...},action:f}
console.log(b)  // {name:'a',age:20,obj:{...}}

缺点:取不到值为undefined的键、NaN和无穷转变为null、无法获取自己原型链上的内容,只能获取Object原型内容、date对象转为date字符串。 

  • 通过递归实现:
let a = {
    name: 'a',
    skin: ['red', 'blue', 'yellow', ['123', '456']],
    child: {
        work: 'none',
        obj: {
            id: 111
        }
    },
    action: function() {
        console.log(this.name)
    }
}
// 封装的递归方法
function deepClone(obj) {
    let newObj = Array.isArray(obj) ? [] : {}
    for (let i in obj) {
        // 判断是不是对象
        if(typeof obj[i] === 'object') {
            // 递归解决多层拷贝
            newObj[i] = deepClone(obj[i])
        } else {
            newObj[i] = obj[i]
        }
    }
    return newObj
}
let b = deepClone(a)
b.child.obj.id = 222
b.skin[3][0] = 'pink'
console.log(a)
console.log(b)

JavaScript深拷贝看这篇就行了!(实现完美的ES6+版本)_javascript 深拷贝-CSDN博客

8.数据类型以及判断方法

(1)基本数据类型:基本类型值在内存中占据固定大小,直接存储在栈内存中。

  • number数字型:正数、负数、小数等
  • string字符串型:通过''、""、``号包裹的数据都叫字符串
  • boolean布尔型:两个固定的值true和false
  • undefined未定义型:只声明变量,不赋值
  • null空类型:代表“无”、“空”、或“值未知”的特殊值

(2)引用数据类型(Object对象):Array、Function、Date、RegExp等。引用类型在栈中存储了指针,这个指针指向内存中的地址,真实的数据存放在堆内存里。假如两个引用类型同时指向了一个地址,修改其中一个,另一个也会改变。 

(3)数据类型判断方法:

  • typeof():用于判断基本数据类型
  • instanceof():只能判断引用数据类型
  • constructor:可以判断基本数据类型和引用数据类型
  • object.prototype.toString.call():完美解决方案

判断数据类型的几种方式_判断数据类型的方式_染墨^O^的博客-CSDN博客

一文读懂堆与栈的区别_堆栈-CSDN博客

9.判断一个变量是否数组

(1)arr instanceof Array

(2)arr.constructor === Array

(3)Array.isArray(arr)

(4)Object.prototype.toString.call(arr) === '[object Array]'

(5)arr.__proto__ === Array.prototype

10.事件委托

又叫事件代理,原理就是利用了事件冒泡的机制来实现,也就是说把子元素的事件绑定到了父元素的身上。如果子元素阻止了事件冒泡,那么委托也就不成立。

addEventListener('click', function, true/false):默认是false(事件冒泡),true(事件捕获)

阻止事件冒泡:event.stopPropagation()

阻止默认行为:event.preventDefault()

好处:提高性能,减少事件的绑定,减少内存的占用。

终于弄懂了事件冒泡和事件捕获!-CSDN博客

11.事件循环

JS是一个单线程的脚本语言。主线程先执行同步任务,然后才去执行任务队列里的任务,如果在执行宏任务之前有微任务,那么要先执行微任务。全部执行完之后等待主线程的调用,调用完之后再去任务队列中查看是否有异步任务,这样一个循环往复的过程就是事件循环(Event Loop)。

宏任务:setTimeout、setInterval、Ajax、DOM事件

微任务:process.nexttick、promise、async/await

宏任务与微任务的区别_宏任务和微任务区别_刘 同 学的博客-CSDN博客

面试题:

async function async1() {
    console.log('async1 start')
    await async2()
    console.log('async1 end')
}
async function async2() {
    console.log('async2')
}
console.log('script start')
setTimeout(function() {
    console.log('setTimeout')
}, 0)
async1()
new Promise(function(resolve) {
    console.log('promise1')
    resolve()
}).then(function() {
    console.log('promise2')
})
console.log('script end')

输出结果:

12.垃圾回收机制

概念:JS中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收。

内存泄漏:JS里已经分配内存地址的对象,但是由于长时间没有释放或没办法清除,造成长期占用内存的现象。这会让内存资源大幅浪费,最终导致运行速度慢,甚至崩溃的状况。

因素:一些为生命直接赋值的变量、一些未清空的定时器、过度的闭包、一些引用元素没有被清除等。

引用计数法:

  • 跟踪记录被引用的次数。
  • 如果被引用了一次,那么就记录次数1,多次引用会累加。
  • 如果减少一个引用就减1。
  • 如果引用次数是0,则释放内存。

注意:如果两个对象相互引用,尽管它们已不再使用,但垃圾回收器不会进行回收,导致内存泄漏。

标记清除法:

  • 标记清除法将“不再使用的对象”定义为“无法达到的对象”
  • 从根部(在JS中就是全局对象)出发定时扫描内存中的对象,凡是能从根部到达的对象,都是还需要使用的。
  • 那些无法由根部出发到达的对象被标记为不再使用,稍后进行回收。

面试题——js垃圾回收机制和引起内存泄漏的操作_js垃圾回收机制面试题-CSDN博客

13.var、const和let的区别

(1)var是ES5提出的,let和const是ES6提出的。

(2)var声明的变量存在变量提升现象,let和const声明的变量不存在变量提升现象。

(3)var允许重复声明同一个变量,let和const在同一作用域下不允许重复声明同一个变量。

(4)var声明的变量不存在块级作用域(函数作用域),let和const声明的变量存在块级作用域,并且声明的变量只在所声明的块级作用域内有效。

(5)var不存在暂时性死区,let和const存在暂时性死区。(let和const会形成封闭的作用域,若在声明之前使用变量,就会报错)

(6)var声明的变量在window上,let和const声明的不在。

14.this的指向

this是一个指针型变量,它动态指向当前函数的运行环境。

(1)箭头函数中:this→声明时所在作用域下的this

(2)全局作用域中:this→window

(3)普通函数中:this→调用者

(4)事件绑定中:this→事件源

(5)定时器中:this→window

(6)构造函数中:this→实例化对象

彻底搞懂JavaScript中的this指向问题 - 知乎

15.new操作符

(1)在内存中创建一个新的空对象。

(2)把空对象和构造函数通过原型链进行链接。

(3)把构造函数的this绑定到新的空对象身上。

(4)执行构造函数里面的代码,给这个新对象添加属性和方法。

(5)根据构造函数返回的类型判断,如果是值类型,则返回对象。如果是引用类型,则返回这个引用类型。(构造函数里不需要return)

16.ES6新特性

(1)let、const关键字

(2)箭头函数

(3)展开运算符

(4)解构赋值

(5)模板语法

(6)rest参数

(7)简化对象写法

(8)函数参数默认值

(9)for of 和 for in

(10)symbol数据类型

(11)Iterator迭代器

(12)Gnerators生成器

(13)Promise

(14)Set

(15)Map

(16)class类

(17)模块化

ECMAScript 6 入门 - 《阮一峰 ECMAScript 6 (ES6) 标准入门教程 第三版》 - 书栈网 · BookStack

17.模块化

(1)CommonJS:module.exports={ }导出,require导入

  • CommonJS可以动态加载语句,代码发生在运行时。
  • CommonJS混合导出如果使用exports导出单个值后,就不能再导出一个对象值。
  • CommonJS导出值是拷贝,可以修改导出的值,这在代码出错时,不好排查引起变量污染。

(2)ESM:export default导出,import导入

  • ES Module是静态的,不能动态加载语句,只能声明在该文件的最顶部,代码发生在编译时。
  • ES Module混合导出,单个导出,默认导出互不影响。
  • ES Module导出和引用值之间都存在映射关系,并且值都是可读的,不能修改。

【精选】JS模块化-CSDN博客

18.实现继承的方式

(1)原型链继承:让一个构造函数的原型是另一个类型的实例,那么这个构造函数new出来的实例就具有该实例的属性。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。(子类型的原型为父类型的一个实例对象)

function Parent() {
   this.isShow = true
   this.info = {
       name: "mjy",
       age: 18,
   }
}
 
Parent.prototype.getInfo = function() {
   console.log(this.info)
   console.log(this.isShow)
}
 
function Child() {}
Child.prototype = new Parent()
 
let Child1 = new Child()
Child1.info.gender = "男"
Child1.getInfo() // {name: 'mjy', age: 18, gender: '男'} ture
 
let child2 = new Child()
child2.isShow = false
console.log(child2.info.gender) // 男
child2.getInfo() // {name: 'mjy', age: 18, gender: '男'} false

优点:写法方便简洁,容易理解。

缺点:对象实例共享所有继承的属性和方法。传教子类型实例的时候,不能传递参数,因为这个对象是一次性创建的(没办法定制化)。

(2)借用构造函数继承:在子类型构造函数的内部调用父类型构造函数;使用 apply() 或 call() 方法将父对象的构造函数绑定在子对象上。

function Person(name, age) {
    this.name = name
    this.age = age
}
function Student(name, age, price) {
    // 利用call(),将Student的this传递给Person构造函数
    // 相当于 this.Person(name, age)
    Person.call(this, name, age)
    // this.name = name
    // this.age = age
    this.price = price
}
let stu = new Student('Tom', 20, 14000)
console.log(stu.name, stu.age, stu.price)

优点:解决了原型链实现继承的不能传参的问题和父类的原型共享的问题。

缺点:借用构造函数的缺点是方法都在构造函数中定义,因此无法实现函数复用。在父类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。

(3)组合继承:将原型链和借用构造函数组合到一块。使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有自己的属性。

function Person(name, age) {
    this.name = name
    this.age = age
}
Person.prototype,setName = function(name) {
    this.name = name
}
function Student(name, age, price) {
    Person.call(this, name, age)
    this.price = price
}
Student.prototype = new Person()
Student.prototype.constructor = Student
Student.prototype.setPrice = function(price) {
    this.price = price
}
let stu = new Student('Tom', 20, 14000)
stu.setName('Bob')
stu.setPrice(16000)
console.log(stu.name, stu.age, stu.price)

优点:解决了原型链继承和借用构造函数继承造成的影响。

缺点:无论在什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。

19.ES5和ES6实现继承的区别

(1)ES5继承是通过原型或构造函数机制来实现。

(2)ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上。

(3)ES6的继承实质上是先创建父类的实例对象this(必须先调用父类的super方法),然后再用子类的构造函数修改this。

(4)ES6通过class关键字定义类,里面有构造方法,类之间通过extends关键字实现继承。子类必须在constructor中调用super方法,否则会报错。因为子类没有自己的this对象,而是继承了父类的this对象,然后对其进行加工。如果不调用super方法,子类得不到this对象。

20.箭头函数与普通函数的区别

(1)箭头函数都是匿名函数。

(2)箭头函数不能用于构造函数,不能使用new。

(3)普通函数中this总是指向调用它的对象,如果用作构造函数,this指向创建的对象实例。箭头函数中this是静态的,指向永远是声明时所在作用域下的this值,它并没有自己的this。call、apply、bind也无法改变this的指向。

(4)箭头函数不具有prototype原型对象。

(5)箭头函数不绑定arguments,取而代之使用rest参数。

21.call、apply、bind的区别

(1)call函数

概念:call()方法调用一个对象。简单理解为调用函数的方式,但是它可以改变函数的this指向。

调用函数:fn.call('形参1','形参2',...)

改变指向:fn.call(this,'形参1','形参2',...)

应用场景:让函数立即执行,改变this的指向,实现继承。

(2)apply函数

概念:apply()方法调用一个函数。简单理解为调用函数的方式,但是它可以改变函数的this指向。传递参数的时候,程序会自动转换为相应的类型,数字转换为数字型,字符串转换为字符串类型。

调用函数:fn.apply([形参以伪数组形式])

改变指向:fn.apply(this,[形参伪数组形式])

(3)bind函数

概念:bind()方法不会调用函数,但是能改变函数内部this指向,返回的是原函数改变this之后产生的新函数。如果只是想改变this指向,并且不想调用这个函数的时候,可以使用bind。

改变指向:fn.bind(this,'形参1','形参2',...)

(4) 三者的区别

  • call和apply会调用函数并且改变函数内部this指向
  • call和apply传递的参数形式不一样, call传递aru1,aru2...形式,apply必须数组形式[arg]
  • bind不会调用函数,可以改变函数内部this指向
  • bind返回对应函数,便于稍后调用;apply、call则是立即调用

彻底弄懂bind,apply,call三者的区别 - 知乎

22.for in和for of的区别

forEach、for in 、 for of三者的区别-CSDN博客

23.对Promise的理解

ES6 Promise用法小结-CSDN博客

promise详解_土豆切成丝的博客-CSDN博客

Promise难懂?一篇文章让你轻松驾驭_promise很难理解-CSDN博客

24.async和await

Promise用then链来解决多层回调问题,但是连续调用then会使得代码很冗长,可读性不好,所以在ES7中提出了用async和await来解决这一问题。async会将其后的函数(函数表达式或Lambda)的返回值封装成一个Promise对象,而await会等待这个Promise完成,并将其结果返回出来。

理解异步函数async和await的用法_async await用法-CSDN博客

25.柯里化

概念:把一个多参数的函数转化为单参数函数的方法。

作用:惰性求值、动态生成函数。

26.判断元素是否出现在视口

(1)offsetTop - scrollTop <= 视口高度

(2)getBoundingClientRect()

(3)IntersectionObserver

前端必知:如何判断元素出现在视口内_判断元素进入视口_清颖~的博客-CSDN博客

27.浮点数精度问题

问题:在js中整数和浮点数都属于Number数据类型,所有数字都是以64位浮点数形式储存,即便整数也是如此。对于64位的浮点数在内存中的表示,最高的1位是符号位,接着的11位是指数,剩下的52位为有效数字。在测试js浮点数进行加减乘除计算时,可能会出现问题,例如0.1+0.2=0.30000000000000004。

原因:将0.1和0.2转换成二进制后,变成了一个无限循环的数字,但计算机不允许无限循环,对于无限循环的小数,计算机会进行舍入处理。双精度浮点数的小数部分最多支持52位,所以两者相加之后得到因浮点数小数位的限制而截断的二进制数字,这时候我们再把它转换为十进制,就成了0.30000000000000004。

0.1 → 0.0001 1001 1001 1001…(无限循环)

0.2 → 0.0011 0011 0011 0011…(无限循环) 

解决:

(1)Math.js是专门为js和Node.js提供的一个广泛的数学库。它具有灵活的表达式解析器,支持符号计算,配有大量内置函数和常量,并提供集成解决方案来处理不同的数据类型。

(2)toFixed()方法使用定点表示法来格式化一个数,会对结果进行四舍五入。语法为numObj.toFixed(digits),参数digits表示小数点后数字的个数,介于0到20之间,忽略该参数则默认为0。该方法返回的是一个字符串。

28.怎样理解JS是单线程的

进程:一个在内存中运行的应用程序。进程是CPU资源分配的最小单位,每个进程都有自己独立的一块内存空间,一个进程可以有多个线程,进程之间不会共享资源。

线程:进程中的一个执行任务(控制单元),负责当前进程中程序的执行。线程是CPU调度的最小单位,一个进程至少有一个线程,多个线程之间共享进程的资源。

区别:

  • 根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位。
  • 包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。
  • 内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的。
  • 影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
  • 执行过程:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行。

彻底理解js是单线程的_js线程-CSDN博客

29.正则表达式

正则表达式 – 教程 | 菜鸟教程

正则表达式test()方法-CSDN博客

30.Ajax 

AJAX 简介 | 菜鸟教程

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值