一、算法
1. 查找数组元素位置
题目描述:找出元素item在给定数组arr中的位置
输入/出描述:如果数组中存在item,则返回元素在数组中的位置,否则返回-1;
function indexOf(arr, item) {
for(let i=0; i<arr.length; i++){
if(arr[i]===item){
return i;
}
}
return -1;
}
var arr = [ 1, 2, 3, 4 ];
console.log(indexOf(arr,3)) //2
题目描述:在数组arr中,查找值与item相等的元素出现的所有位置
function findAllOccurrences(arr, target) {
var res = [];
for(let i=0; i<arr.length; i++){
if(arr[i] === target){
res.push(i)
}
}
return res;
}
var arr = ['a','b','c','d','e','f','a','b','c'];
console.log(findAllOccurrences(arr, 'a')); //[ 1, 4, 9, 16 ]
2. 数组求和
题目描述:计算给定数组arr中所有元素的总和
输入/出描述:数组中的元素均为Number类型,输出也是
function sum(arr) {
var res = 0;
for(let i=0; i<arr.length; i++){
res += arr[i];
}
return res;
}
var arr = [ 1, 2, 3, 4 ];
console.log(sum(arr)); //10
//---------------------//
function sum(arr) {
var total=arr.reduce((a,b) => a+b,0);
return total;
}
//---------------------//
function sum(arr) {
var res = 0;
arr.forEach(function(val, idx, arr) {
res += val;
}, 0);
return res;
}
3. 移除数组中的元素
题目描述:移除数组arr中的所有值与item相等的元素,不要直接修改数组arr,结果返回新的数组。
输入/出描述:数组
function remove(arr, item) {
var newArr = [];
for(let i=0; i<arr.length; i++){
if(arr[i]!== item){
newArr.push(arr[i])
}
}
return newArr;
}
var arr = [1, 2, 3, 4, 2];
console.log(remove(arr,2)) //[ 1, 3, 4 ]
//---------------------//
function remove(arr, item) {
return arr.filter(val => val !== item);
}
//---------------------//
function remove(arr, item) {
let newArr = []
arr.map((val, index) => {
if (val !== item) {
return newArr.push(val)
}
})
return newArr;
}
题目描述:移除数组arr中的所有值与item相等的元素,直接在给定的arr数组上进行操作,并将结果返回
function removeWithoutCopy(arr, item) {
for(let i=0; i<arr.length; i++){
if(arr[i]===item){
arr.splice(i,1);
i--;
}
}
return arr;
}
4. 添加元素
题目描述:在数组arr的末尾添加元素。不要直接修改数组arr,结果返回新的数组。
function append(arr, item) {
return arr.concat([item])
}
var arr = [1, 2, 3, 4];
var item = 10
console.log(append(arr,item)) //[ 1, 2, 3, 4, 10 ]
//---------------------//
function append(arr, item) {
var newArr = [...arr];
newArr.push(item);
return newArr;
}
题目描述:在数组arr的头部添加元素。不要直接修改数组arr,结果返回新的数组。
function prepend(arr, item) {
return [item].concat(arr);
}
var arr = [1, 2, 3, 4];
var item = 10
console.log(prepend(arr,item)) //[ 10, 1, 2, 3, 4 ]
题目描述:在数组 arr 的 index 处添加元素 item。不要直接修改数组 arr,结果返回新的数组
function insert(arr, item, index) {
var newArr = [];
for(let i=0; i<index; i++){
newArr.push(arr[i])
};
newArr.push(item);
for(let i=index; i<arr.length; i++){
newArr.push(arr[i])
}
return newArr;
}
var arr = [1, 2, 3, 4];
console.log(insert(arr, 'z' , 2)) //[ 1, 2, 'z', 3, 4 ]
//---------------------//
function insert(arr, item, index) {
var newArr = [];
for(let i = 0;i<arr.length;i++){
if(i == index) newArr.push(item);
newArr.push(arr[i]);
}
return newArr;
}
//---------------------//
function insert(arr, item, index) {
var newArr=arr.slice(0);
newArr.splice(index,0,item);
return newArr;
}
5. 删除数组元素
题目描述:删除数组arr最后一个元素。不要直接修改数组arr,结果返回新的数组。
function truncate(arr) {
var newArr = [];
for(let i=0; i<arr.length-1; i++){
newArr.push(arr[i])
}
return newArr;
}
var arr = [1, 2, 3, 4];
console.log(truncate(arr)); //[ 1, 2, 3 ]
function truncate(arr) {
return arr.slice(0,-1)
}
题目描述:删除数组 arr 第一个元素。不要直接修改数组 arr,结果返回新的数组
function curtail(arr) {
return arr.slice(1)
}
var arr = [1, 2, 3, 4];
console.log(curtail(arr)) //[ 2, 3, 4 ]
6. 数组合并
题目描述:合并数组 arr1 和数组 arr2。不要直接修改数组 arr,结果返回新的数组
function concat(arr1, arr2) {
return arr1.concat(arr2)
}
var arr1 = [1, 2, 3, 4];
var arr2 = ['a', 'b', 'c', 1]
console.log(concat(arr1,arr2)) //[1, 2, 3, 4, 'a', 'b', 'c', 1]
7. 计数
题目描述:统计数组arr中值等于item的元素出现的次数
function count(arr, item) {
var count = 0;
for(let i=0; i<arr.length; i++){
if(arr[i]===item){
count++;
}
}
return count;
}
var arr = [1, 2, 4, 4, 3, 4, 3];
console.log(count(arr, 4)) //3
8. 查找重复元素
题目描述:找出数组arr中重复出现过的元素
function duplicates(arr) {
var newArr = [];
for(let i=0; i<arr.length; i++){
for(let j=i+1; j<arr.length; j++){
if(arr[i]===arr[j] && newArr.indexOf(arr[i]) == -1){
newArr.push(arr[i])
}
}
}
return newArr;
}
var arr = [1, 2, 4, 4, 3, 3, 1, 5, 3];
console.log(duplicates(arr));
//---------------------//
function duplicates(arr) {
let newArr=[];
arr.sort();
for(i in arr){
if(arr[i]==arr[i-1] && newArr.indexOf(arr[i])==-1){
newArr.push(arr[i]);
}
}
return newArr;
}
9. 求二次方
题目描述:为数组arr中的每个元素求二次方,不直接修改数组arr,结果返回新数组
function square(arr) {
return arr.map(item => item*item)
}
var arr = [1, 2, 3, 4];
console.log(square(arr)); //[ 1, 4, 9, 16 ]
10. 完全等同
题目描述:判断val1和val2是否完全等同
function identity(val1, val2) {
return val1 === val2;
}
二、面经
作者:女朋友都有offer 链接:www.nowcoder.com/discuss/322… 来源:牛客网
一面:
1. 深拷贝、浅拷贝
- 在JavaScript中,变量包含两种类型的值:1.基本数据类型(存储在栈中按值访问,从a复制到b是两个完全独立的,一个变量的改变不会影响到第二个);2.引用类型(存储在堆内存中,栈内存中只存储指向堆内存的地址,复制的时候只是复制了变量地址,指向的是同一个变量,一个变动会影响到另一个);拷贝问题主要是针对引用类型数据来说;
- 拷贝分为浅拷贝和深拷贝
- 浅拷贝:创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
- 浅拷贝的常见方法:
- 数组(Array.concat();Array.slice());
- 对象(Object.assign({},Object);ES6扩展符{...Object});
- 遍历需要拷贝的对象,然后把需要拷贝对象的属性和属性值都依次放在一个新的对象中,返回。
let shallowCopy = function(obj){ if(typeof obj !== "object") return; let newObj = obj instanceof Array? []:{}; for(let key in obj){ if(obj.hasOwnProperty(key)){ newObj[key] = obj[key]; } } return newObj; }
- 深拷贝:将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象对原对象无影响。
- 深拷贝的常见方法:
- JSON.parse(JSON.stringify(Object))
- 利用浅拷贝和递归
let deepCopy = function(obj){ //只拷贝对象 if(typeof obj !== "object") return; //根据obj的类型判断是新建一个数组还是对象 let newObj = obj instanceof Array? []:{}; //遍历obj,并且判断是obj的属性才拷贝 for(let key in obj){ if(obj.hasOwnProperty(key)){ //如果obj的子属性是对象,则进行递归操作,否则直接赋值 newObj[key] = typeof obj[key] === "object"? deepCopy(obj[key]) : obj[key]; } } return newObj; }
2. 垂直居中
- 定位的三种
- 一:需要知道盒子的宽高【将盒子的位置设为绝对定位,top,left50%,margin-top设置为高度的一半。margin-left设置为宽度的一半】
.box{ width: 100px; height: 50px; line-height: normal; } body{ position: relative; } .box { position: absolute; top: 50%; left: 50%; margin-top: -25rpx; /高度的一半/ margin-left: -50rpx; /宽度的一半/ }
* 二:需要知道盒子的宽高【将盒子的位置设为绝对定位,上下左右设为0,margin自适应auto】
.box { position: absolute; top: 0; right: 0; bottom: 0; left: 0; margin: auto; }
* 三:不需要知道盒子的宽高,但是兼容性不好【将盒子的位置设为绝对定位,top,left50%,transform:translate(-50%,-50%)】
.box { position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); }
* display:flex
body{ display: flex; justify-content: center; //左右 align-items: center; //上下 }
* display:table-cell
body{ display: table-cell; vertical-align: middle; align-items: center; width: 500px; height: 500px; } .box{ display: inline-block; }
* js实现
### 3. 对闭包的理解
* 闭包是指有权访问另一个函数作用域中的变量的函数,并且在闭包内部形成一个外部无法访问的局部作用域。
* 常见的创建闭包的方式:在一个函数内部创建另一个函数。通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用域链,将函数内部的变量和方法传递到外部。
* 闭包的优点:加强封装性,可以达到对变量的保护作用。
* 闭包的缺点:如果闭包中有引用类型的数据被使,那么整个闭包都无法释放,占用内存。【内存泄漏】
* 闭包的特性:函数内再嵌套函数;内部函数可以引用外层的参数和变量;参数和变量不会被垃圾回收机制回收
### 4. ajax和fetch的区别
$.ajax({ type: 'POST', url: url, data: data, dataType: dataType, success: function () {}, error: function () {} });
* ajax
* 本身是针对MVC的编程,不符合现在前端的MVVM的浪潮
* 基于原生的XHR开发,XHR本身的架构不清晰,已经有了fetch的替代方案
* JQuery整个项目太大,单纯使用ajax却要引入整个JQuery非常的不合理
axios({ method: 'post', url: '/user/12345', data: { firstName: 'Fred', lastName: 'Flintstone' } }) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); });
* axios
* 从浏览器中创建XMLHttpRequest
* 从node.js发出http请求
* 支持Promise API
* 拦截请求和响应
* 转换请求和响应数据
* 取消请求
* 自动转换JSON数据
* 客户端防止CSRF/XSRF
try { let response = await fetch(url); let data = response.json(); console.log(data); } catch(e) { console.log("Oops, error", e); }
* fetch
* 只对网络请求报错,对400,500都当作成功的请求,需要封装去处理
* 默认不会带cookie,需要添加配置项
* 不支持abort,不支持超时控制,使用setTimeout及promise.reject的实现的超时控制并不能阻止请求过程继续在后台运行,造成了量的浪费
* 没有办法原生监测请求的进度,而XHR可以
### 5. 箭头函数和普通函数的区别
* 1.箭头函数的语法更加简洁 ()=>{}
* 如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。
* 如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。
```JavaScript
function fn(x){
return function(y){
return x + y;
}
}
// 等同于
let fn = x => y => x + y;
- 2.箭头函数中没有自己的this,this指向是固定的,从属于函数所处的上下文(使用call/apply等任何方式都无法改变this的指向)
- 3.箭头函数中没有arguments(类数组),只能基于...arg获取传递的参数集合(数组)
let fn = (...arg) => { console.log(arg); }; let fn = function() { console.log(arguments); } fn(10,20,30)
- 4.箭头函数不可以当作构造函数,不可以使用new命令,否则会抛出一个错误。(箭头函数没有this,也没有prototype)
6. ES5的继承
- 原型链继承(父类的实例作为子类的原型)
- 优点:简单易于实现,父类的新增的实例与属性,子类都能访问
- 缺点:可以在子类中增加实例属性,如果要新增加原型属性和方法,需要在new父类构造函数的后面;无法实现多继承;创建子类实例时,不能向父类构造函数中传参数
- 借用构造函数继承(伪造对象,经典继承)复制父类的实例属性给子类
- 优点:解决了子类构造函数向父类构造函数传递参数的问题,可以实现多继承(call或apply多个父类)
- 缺点:方法都在构造函数中定义,无法复用;不能继承原型属性/方法,只能继承父类的属性和方法
- 实例继承(原型式继承)
- 优点:不限制调用方法,简单易实现
- 缺点:不能多次继承
- 组合式继承(调用父类的构造函数,继承父类的属性,通过将父类实例作为子类原型,实现函数复用)
- 优点:函数可以复用,不存在引用属性问题,可以继承属性和方法,并且可以继承原型的属性和方法
- 缺点:调用了两次父类,产生了两个实例
- ES6继承
- es5继承首先是在子类中创建自己的this指向,最后将方法添加到this中,es6继承是使用关键字先创建父类的实例对象this,最后在子类class中修改this。
7. 原型链
每个对象都可以有一个原型对象,这个原型还可以有它自己的原型,以此类推,形成一个原型链。查找特定属性的时候,我们先去这个对象里去找,如果没有的话就去它的原型对象里面找,如果还是没有的话再去向原型对象的原型对象里去寻找...... 这个操作被委托在整个原型链上,这个就是我们说的原型链了。
8. http状态码
- 100-199 用于指定客户端应相应的某些动作。
- 200-299 用于表示请求成功。
- 300-399 用于已经移动的文件并且常被包含在定位头信息中指定新的地址信息。
- 400-499 用于指出客户端的错误。400 1、语义有误,当前请求无法被服务器理解。401 当前请求需要用户验证 403 服务器已经理解请求,但是拒绝执行它。
- 500-599 用于支持服务器错误。 503 – 服务不可用;
常用的
- 100 Continue 继续,一般在发送post请求时,已发送了http header之后服务端将返回此信息,表示确认,之后发送具体参数信息
- 200 OK 正常返回信息
- 201 Created 请求成功并且服务器创建了新的资源
- 202 Accepted 服务器已接受请求,但尚未处理
- 301 Moved Permanently 请求的网页已永久移动到新位置。
- 302 Found 临时性重定向。
- 303 See Other 临时性重定向,且总是使用 GET 请求新的 URI。
- 304 Not Modified 自从上次请求后,请求的网页未修改过。
- 400 Bad Request 服务器无法理解请求的格式,客户端不应当尝试再次使用相同的内容发起请求。
- 401 Unauthorized 请求未授权。
- 403 Forbidden 禁止访问。
- 404 Not Found 找不到如何与 URI 相匹配的资源。
- 500 Internal Server Error 最常见的服务器端错误。
- 503 Service Unavailable 服务器端暂时无法处理请求(可能是过载或维护)。
三、其它
- 回调函数:把一个函数B作为实参传递给另一个函数A,函数A在执行的时候,可以把传递进来的函数B去执行(执行N次,可传值,可改this,可返回值) 回调函数中的this默认指向window
function each(arr,callBack){
for(let i=0; i<arr.length; i++){
let flag = callBack.call(arr, arr[i], i);
//接受回调函数返回的结果,如果是false,则结束循环
if(flag === false){
break;
}
}
}
each([10,20,30],function(item,index){
if(index > 1){
return false;
}
});