赞
踩
目录
8, slice是干嘛的,splice是否会改变原数组,二者区别?
一般比较大一点的公司面试或笔试问题的重点都侧重于js这块,考验你js的功底扎不扎实。所以在准备面试的时候,js一定要复习好,而且要过好几遍,直至熟练回答为止,有的甚至需要手写几遍。毕竟机会都是给有准备的人的。下面给大家整理了一下面试中常问的js问题。大家可以查缺补漏。
基本数据类型 Number、String、Boolean、Null、Undefined、Symbol
引用数据类型 Object、Array、Date、Function、RegExp
这里面可以拓展的问题很多,一定要多去思考,比如:
(1)设计历史:
之前看过相关文献,作者在设计js的时候,先有null后有undefined,因为借鉴了java的语言,null的数据类型时object,会被隐式转换成0,不容易发现错误;所以就设计了undefined,它是一个基本数据类型,转成数值为NaN;
(2)数据:
null已经声明赋值为空;引用数据类型;数值转换为0;
undefined已经声明未赋值;基本数据类型;数值转换为NaN(数值类型,不表示数字);
symbol 是ES6引入了一种新的基本数据类型(原始数据类型),表示独一无二的值。
(1)Symbol的值是唯一的,用来解决命名冲突的问题;
(2)Symbol值不能与其他数据类型进行运算;
(3)Symbol定义得的对象的属性不能使用for…in 循环遍历,但是可以使用Reflect.ownKeys来获取对象的所有键名;
基本数据类型体积小,存放在栈里面;
引用数据类型体积大,存放在堆里面;
引用数据类型会有个指针放在栈里面,定位堆里面存放的引用数据;
(1)typeof:查找基本数据类型,引用数据类型除了function,其他都为object;
(2)instanceof:查找引用数据类型;
原理:instanceof 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype, 返回true,不是返回false;
(3)Object.prototype.toString().call():所有数据类型;
原理:原型链和原型有关,首先toString()这个方法本来应该返回string的字符串形式,但是大多数情况会返回[object,xxxx]形式;因为js重写了某些数据类型的toString()方法;所以这时候我们找到原型上最原始的toSting()方法;call的作用就是转变this的指向;
(1)转为字符串:toString() / String() 不能用于null和undefined
(2)转为数值:Number() / parseInt() 转为整数,parseFloat 转为浮点数
(3)隐式转换:js是一门弱语言,运行期间,变量会根据运行环境自动类型转换;
举例:字符串数据+0/*1可以转换成数值;字符串+数值=字符串
(4)转为布尔值:false(0,null,undefined,'',NaN)
==比较值
string == number || boolean || number都会隐式转换,通过valueOf()方法通常由js在后台自动转换
===比较值和数据类型,除了比较值,还比较类型
(1)首先得解释一下JS的单线程:
js是单线程的语言,用途(浏览器的脚本语言,主要负责交互,动画,效果)决定了它必须是单线程的语言。单线程是指同一时间只能做一件事。如果能做多件事情的话,假如用户同时有两个操作,一个是删除节点,一个是新增节点,那到底应该选择是新增还是删除呢?所以js在设计的时候就必须是单线程的。
(2)其次解释一下JS代码的执行流程:
同步执行完==》事件循环;同步的任务都执行完了,才会执行事件循环的内容,进入事件循环有哪些:请求、定时器、事件。
(3)然后解释一下事件循环:
进入事件循环也得分哪个先哪个后呀?所以事件循环里面又分微任务和宏任务。
微任务:promise.then();
宏任务:setTimeOut;
执行顺序为:先执行微任务,在执行宏任务,宏任务里面还有微任务,先执行微任务,以此循环下去;记住关键,要执行宏任务的前提是清空了所有的微任务。
(4)最后总结一下代码的执行流程:
同步=》事件循环【微任务,宏任务】=》微任务=》宏任务=》微任务......;
srcipt标签引入
iframe
后端
配置请求头
可能会有拓展问题:
同源策略:域名,协议,端口号相同。
为什么限制:为了防止恶意ajax请求,修改DOM页面,随意获取cookie隐私数据等;
域名:www.baidu.com
端口:8080,3000
协议:https/http
全局作用域:代码在程序的任何地方都能被访问,window对象的内置属性都拥有全局作用域;
函数作用域:只有在固定的代码片段才能被访问;
好处:隔离变量,不同作用域下同名变量不会有冲突。
作用域链:一般情况下,变量取值会在创建这个变量的函数作用域中取值,如果没找到,就会像上级作用域查找,知道查到全局作用域,这个过程就叫作用域链;
除了函数外,js没有块级作用域;
比如:在函数里定义一个变量,在函数外打印这个变量,即使函数调用了,也没办法打印;
如果有笔试题,或者现场出题的话,这个就是高频题了,就需要重点理解了。
注意:
(1)声明的变量是用var还是没有写(就是window);
(2)js变量提升的机制【变量悬挂声明】;
(3)注意本层作用域有没有变量提升;
构造函数:this指向的是实例对象,
全局函数:this指向window;
use strict:指向的是undefined;
绑定事件:this指的是被绑定事件的元素;
箭头函数:this和父级this指向相同;
考题一:
- function foo(){
- getName = function(){
- console.log(1)
- };
- return this;
- }
- Foo.getName = function(){
- console.log(2)
- }
- Foo.prototype.getName = function(){
- console.log(3)
- }
- var getName = function(){
- console.log(4)
- }
- function getName(){
- console.log(5)
- }
- Foo.getName() //2
- getName() //4
- Foo().getName() //1
- getName() //1
- new Foo().getName() //3
考题二:
- var o = {
- a:10,
- b:{
- fn:function(){
- console.log(this.a)
- console.log(this)
- }
- }
- }
- o.b.fn() //undefined b函数
考题三:
- window.name = 'ByteDance'
- function A(){
- this.name = 123;
- }
- A.prototype.getA = function(){
- console.log(this);
- return this.name+1;
- }
- let a = new A()
- let funcA = function(){
- console.log(this)
- return this.name+1
- }
- funcA() //this指的是window; 'ByteDance1'
(1) Array.isArray(arr)
(2) arr instanceof Array【可写可不写,因为有bug】
(3) Object.prototype.toString.call(arr)
(4) Array.prototype.isPrototype(arr)
(5) arr.constructor.toString()
slice:选择截取部分内容,参数可以一个,两个,也可以是负数,返回新数组;
splice:会改变原数组;插入、删除、替换;返回删除的元素;
扩展运算符 + new set
循环
sort排序
代码演示:
- (1) new Set
- //Array.from(new set(arr))
- //...new Set(arr)
- var arr1 = [1,2,3,2,4,1];
- function unique(arr){
- return [...new Set(arr)]
- }
- console.log(unique(arr1))
-
- (2) 循环
- function unique3(arr){
- var brr = []
- for(var i=0; i<arr.length; i++){
- if(brr.indexOf(arr[i]) == -1){
- brr.push(arr[i])
- }
- }
- return brr
- }
- console.log(unique3(arr));
-
- (3) sort
- function unique4(arr){
- arr = arr.sort()
- var brr = []
- for(var i=0;i<arr.length;i++){
- if(arr[1] !== arr[i-1]){
- brr.push(arr[i])
- }
- }
- return brr
- }
- console.log(unique4(arr));
Math.max(..item)
代码演示:
- var arr = [
- [1,3,7,9],
- [22,77,90,78],
- [123,567,890,345],
- [1002,3004,3009,2890]
- ]
- function fn(arr){
- var brr = []
- arr.forEach((item,index)=>{
- brr.push(Math.max(...item))
- })
- return brr
- }
- console.log(fn(arr));
11,给字符串新增方法实现功能?
addPrefix() 添加前缀
代码演示:
- String.prototype.addPrefix = function (str){
- return str + this
- }
- console.log('Bryan'.addPrefix('Kobe'));
- var str = 'aaaabbbbssssxcccddddsdfscxxasa'
- var obj = {};
- for(var i=0; i<str.length; i++){
- var char = str.charAt(i)
- if(obj[char]){
- obj[char]++;
- }else{
- obj[char] = 1;
- }
- }
- console.log(obj);
- //统计出来最大值
- var max = 0;
- for(var key in obj){
- if(max < obj[key]){
- max = obj[key]
- }
- }
- //拿最大值去对比
- for(var key in obj){
- if(obj[key] == max){
- console.log('最多的字符是'+key);
- console.log('出现的次数是'+max);
- }
- }
方法 | 功能 |
---|---|
charAt() | 返回指定索引位置的字符 |
indexOf() | 返回字符串中检索指定字符第一次出现的位置 |
replace() | 替换与正则表达式匹配的字串 |
slice() | 提取字符串的片段,返回新的字符串 |
split() | 把字符串分隔为子字符串数组 |
substr() | 从起始索引号提取字符串中指定数目的字符 |
subString() | 提取字符串中两个指定的索引号之间的字符 |
toLowerCase() | 变小写 |
toUpperCase() | 变大写 |
toString() | 返回字符串对象值 |
trim() | 移除字符串首尾空白 |
valueOf() | 返回某个字符串对象的原始值 |
(1)创建了一个空对象;
(2)将空对象的原型,指向于构造函数的原型;
(3)将构造函数里的this指向空对象;
(4)将构造函数的属性和方法赋值给空对象;
(5)返回这个对象;
代码演示:
- function Fun( name, age ){
- this.name = name;
- this.age = age;
- }
- function create( fn,...args ){
- //创建一个新对象
- var obj = {}
- //把对象的原型指向构造函数的原型
- Object.setPrototypeOf(obj,fn.prototype)
- //改变this的指向
- var result = fn.apply(obj.args)
- //对构造函数有返回值的处理判断
- return result instanceof Object ? result : obj;
- }
- console.log(create(Fun,18,'张三'));
(1)是什么:
闭包是一个函数内返回一个函数,外部函数能读取内部函数的变量;
(2)优点:
内部可以读取外部函数的变量,可以封装独有的方法和属性,保持内部的独立性;
(3)可以解决问题:
代码演示:
- var lis = document.querySelectorAll('li');
- for(var i=0; i<lis.length; i++){
- (function(i){
- lis[i].onclick = function(){
- alert(i)
- }
- lis[i]=null
- })(i)
- }
(4)缺点:
变量会驻留在内存中,造成内存损耗问题;内存泄漏(IE)
解决:把闭包的函数设置为null;
这个问题经常会问,但千篇一律肯定没办法加分,所以需要说清楚,为什么会有原型,为什么会有原型链,在什么场景会用,方法是什么,原理是什么,按照这样的思路去说基本没问题。
我们知道创建对象的方式有三种,字面量,new,构造函数。前两个只能创建单个的对象,但构造函数可以把不同对象的共同属性和方法抽离出来,通过new实例化创建拥有共同属性和方法的不同对象。但是想一想,会出现什么问题呢?
由于js存储数据的方式是引用数据类型存储在堆里面,我们new出来的不同对象都拥有这一个相同的方法,但是都要在堆里面去申请不同的空间去存储,这样就会极大的浪费我们的内容空间。
所以这个时候我们就有了原型prototype。这样不同的对象可以通过原型去查找,节省内存空间,how nice!
也就是说原型可以解决:对象共享属性和共享方法。
总结简洁版(思路如下,自己整理能说明白即可):
(1)原型可以解决什么问题:对象共享属性和共享方法;
(2)谁有原型?函数拥有prototype,对象拥有:`__proto__`
(3)对象查找属性或者方法的顺序:
先在对象本身查找-->构造函数中查找-->对象的原型-->构造函数的原型中-->当前原型的原型;
(4)原型链的最顶端是Null;
不需要每个都讲,只需要讲到重点常用的几个即可;
(1)es6
- class father{
- constructor(){
- this.age = 18
- }
- }
- class child extends father{
- constructor(){
- super()
- this.name = '张三'
- }
- }
- let Child = new child()
- console.log(Child,Child.name,Child.age)
(2)原型链继承
- child.prototype = new parent()
- let Child = new child()
- console.log(Child, Child.name, Child.age)
- //继承的属性和方法在原型上
(3)构造函数继承:
- function Father(){
- this.name = '张三'
- }
- function Son(){
- this.age = 20
- Father.call(this)
- }
- let o3 = new Son()
- console.log(o3);
(4)组合继承
- function Father(name,age){
- this.name = name
- this.age = age
- }
- function Son(name,age){
- Father.call(this,name,age)
- }
- const son = new Son('刘德华', 20)
- console.log(son);
(1)相同点:功能一致,都可以改变this指向;
(2)语法:函数.call(fn,name),函数.apply(fn,[...args]),函数.bind();
(3)区别:
(4)使用场景:
- var arr = [1,2,4,5,78,21]
- console.log(Math.max.apply(null,arr1))
- var btn = document.querySelector('btn')
- var h1s = document.querySelector('h1s')
- btn.onclick = function(){
- console.log(this.id)
- }.bind(h1s) //h1s
之前的版本是插入和快排,现在是冒泡;
使用场景:
(1)升序:
- var arr = [1,2,23,21,222,778]
- let res = arr.sort(function(a,b){
- return a-b
- })
- console.log(res)
(2)降序:
- var arr = [1,2,23,21,222,778]
- let res = arr.sort(function(a,b){
- return b-a
- })
- console.log(res)
(3)排序对象中某属性值的顺序:
- var obj = [
- {name:'张三',age:2},
- {name:'李四',age:29},
- {name:'王五',age:70}
- ]
- function compare(age){
- return function (a,b){
- var val1 = a[age]
- var val2 = b[age]
- return val1-val2
- }
- }
- var arr = obj.sort(compare('age'))
- console.log(arr)
首先什么情况会出现深浅拷贝呢?
假设我们有一个已经定义了属性和方法的对象,我们再创建一个新对象,然后用=号把已定义的对象赋值给新创建的空对象;
如果我们修改新对象里的某个属性值,打印原来已定义的对象时,你会发现它的值也发生变化了;
这种情况就是发生了浅拷贝。为什么呢?
这是因为js的数据存储方式是基本数据类型再栈里面,引用数据类型在堆里面,但引用数据的指针,也就是房间号在栈里面存着。如果我们用=号去赋值,就相当于把指针赋值给了另一个对象,也就是说这两个对象的指针都是一样的,所以你修改了任意一个对象的值另一个也会发生变化。
那如果不想出现上面的情况,我们就需要进行深拷贝了,也就是说要在堆里面重新申请一个新空间,指针也是不同的,这样就不会发生连锁反应了。
简洁版思路:
共同点:复制
(1)浅拷贝:只复制引用地址,而未复制真正的值;object.assign()
(2)深拷贝:是复制真正的值(不同的引用)
(3)方式:JSON.parse(JSON.Stringify) 递归形式;
(1)数据存放有效期:
localStorage:始终有效,持久存储;
sessionStorage:浏览器关闭就没了;
cookie:只在设置的cookie过期时间之前有效,即使窗口或者浏览器关闭也有效,其他不可以设置时间;
(2)存储大小的限制:
cookie存储量不能超过4k;
localStorage、sessionStorage不能超过5M;
(1)区别一:filter返回新数组,find返回具体的内容;
(2)区别二:find匹配到第一个即返回,filter只要满足条件push到新数组里,返回新数组;
some是只要满足一个条件就返回true;
every是都满足返回为true;
(1)方法一:Object.assign(a,b)
(2)方法二:obj = {...a,...b}
(3)方法三:循环
- function extend(target,source){
- for(var obj in source){
- target[obj] = source[obj]
- }
- return target
- }
- console.log(extend(a,b))
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。