第一篇博客

前言

记得第一次开通技术博客是在2014年的7月,当时是在91主机上买了一年的服务器,域名加服务器总共花了700+,但是呵呵,服务真是差透了。于是转战github搭建博客,免费又方便。当时主机到期时当时写的文章没有保存下来,(心好累。。--||)于是就在这里从开一个博客了。。之前的文章看情况有可能的话再写一遍。
写本博客的目的一是积累学到的技术经验,二是进行分享。在我的技术之路上有得到很多前辈同学或者网络上不认识的人的帮助,我感觉这很温暖,也很好。交流、分享、开源是我们程序员界的优良传统,我也会继承发扬下去。
想说的话就这么多。如果大家有什么疑问或者发现我哪里有错误,欢迎联系我^_^,qq:786833771 email:abbottzjz@gmail.com

一些其他博客

Scofield Blog
PPTing’s Blog
markyun
廖雪峰的官方网站
阮一峰的博客
张鑫旭-鑫空间-鑫生活
Ruby’s Louvre(司徒正美)
寒冬winter的blog
老赵的blog
轮子哥不再更新的blog。。
sadpig blog
依云’s Blog
Parry@苏州
鱼的笔记
耗子哥的blog
不给力的面条的blog
lwwmaxwell的blog

js中高级相关问题

1. es6相比es5新增了那些东西(es6中你都用到了什么)

(常用)
扩展运算符…([1,2,3,4])
Destructuring Assignment (解构赋值)
Arrow Functions in(箭头函数)
Default Parameters(默认参数)
Template Literals(模板对象)
Promises
块作用域和构造let和const
Classes (类)in ES6
ES6 module
数组和对象新增的方法和属性

// 对象新增的方法

// 它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致
Object.is()

// Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
Object.assign()

// 引入了Object.keys方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。
Object.keys()

// Object.values方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。
Object.values()

//  Object.entries()方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组

const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]


//  Object.fromEntries() 该方法的主要目的,是将键值对的数据结构还原为对象,因此特别适合将 Map结构转为对象。Object.fromEntries()方法是Object.entries()的逆操作,用于将一个键值对数组转为对象。用于将一个键值对数组转为对象。该方法的一个用处是配合URLSearchParams对象,将查询字符串转为对象

Object.fromEntries([
  ['foo', 'bar'],
  ['baz', 42]
])
// { foo: "bar", baz: 42 }

Object.fromEntries(new URLSearchParams('foo=bar&baz=qux'))
// { foo: "bar", baz: "qux" }


// 数组新增方法
1.Array.from()方法:JSON数组格式转换
let people={
    0:'zhangsan', 
    '1':24,     //key值必须是0,1,2......可以是数字或者字符串
    length:2    //必须有length这个特殊的属性
};
let trans=Array.from(people);//Array.from()方法
console.log(trans); //['zhangsan',24]
// 伪数组转化为数组
var doms = document.querySelectorAll('p');
var domsList = Array.from(doms);


2.Array.of()方法:
let arr =Array.of(3,4,5,'zhang','li');
console.log(arr);  //[3, 4, 5, "zhang","li"]


3.find( )实例方法:
所谓的实例方法就是并不是以Array对象开始的,而是必须有一个已经存在的数组,然后使用的方法,这就是实例方法(不理解请看下边的代码)。这里的find方法是从数组中查找。在find方法中我们需要传入一个匿名函数,函数需要传入三个参数:

value:表示当前查找的值。
index:表示当前查找的数组索引。
arr:表示当前数组。

let arr=[1,2,3,4,5,6,7,8,9];
console.log(arr.find(function(value,index,arr){
    return value > 5;
})) 
//输出6  注意:在函数中如果找到符合条件的数组元素就return,并停止查找 ,找不到的话就 返回的unde

4. findIndex()实例方法
let arr=[1,2,3,4,5,6,7,8,9];
console.log(arr.findIndex(function(value,index,arr){
    return value > 5;
})) 
// 结果: 5 ,如果在数组中找不到该元素 则返回  -1

5. includes() 方法 查看某个数组是否包含给定的值
参数:
第一个参数必选(待检查的给定值)
第二个参数可选,表示搜索的起始位置,默认为0,负数表示倒数的位置

[1, 2, 3].includes(2, 2);     // false
[1, 2, 3].includes(4);     // false
[1, 2, NaN].includes(NaN); // true

返回值:Boolean 
注意:和indexOf的区别,indexOf进行了运算符的强比对,会导致对NaN误判。

[1, 2, 3].includes(2);     // true
[1, 2, 3].includes(4);     // false
[1, 2, NaN].includes(NaN); // true

6. fill()函数,使用指定的元素替换原数组内容,会改变原来的数组。

fill(value, start, end)

value:替换值。
start:替换起始位置(数组的下标),可以省略。
end:替换结束位置(数组的下标),
如果省略不写就默认为数组结束。有参数时为结束位置,但不替换该位置。如果结束位置大于数组的长度,那么默认也只替换到数组的实际长度结束位置。
替换的区间为 [start,end) 。
let oldArr1 = [];
let oldArr2 = [1,2,3];
let newArr1 = oldArr1.fill(6); //当为空数组时什么都不替换
let newArr2 = oldArr2.fill(6);
console.log(newArr1); // []
console.log(newArr2); // [6, 6, 6]

7.for..of数组索引:有时候开发中是需要数组的索引的,那我们可以使用下面的代码输出数组索引。

var arr=[1,2,3,4,5];
for(a of arr){
    console.log(a);
}

8.copyWithin():
选择数组的某个下标,从该位置开始复制数组元素,默认从0开始复制。也可以指定要复制的元素范围。

arr.copyWithin(target, start, end)


const arr = [1, 2, 3, 4, 5]
console.log(arr.copyWithin(3))
 // [1,2,3,1,2] 从下标为3的元素开始,复制数组,所以4, 5被替换成1, 2
const arr1 = [1, 2, 3, 4, 5]
console.log(arr1.copyWithin(3, 1)) 
// [1,2,3,2,3] 从下标为3的元素开始,复制数组,指定复制的第一个元素下标为1,所以4, 5被替换成2, 3
const arr2 = [1, 2, 3, 4, 5]
console.log(arr2.copyWithin(3, 1, 2)) 
// [1,2,3,2,5] 从下标为3的元素开始,复制数组,指定复制的第一个元素下标为1,结束位置为2,所以4被替换成2

9.实例方法values(),keys(), entries() 

const arr = ['a', 'b', 'c'];
for(let v of arr.values()) {
  console.log(v)
}
//'a' 'b' 'c'

const arr = ['a', 'b', 'c'];
for(let v of arr.keys()) {
  console.log(v)
}
// 0 1 2

const arr = ['a', 'b', 'c'];
for(let v of arr.entries()) {
  console.log(v)
}
// [0, 'a'] [1, 'b'] [2, 'c']

2. 数组相关(从最新的开始写,那些方法改变了原数组,那些没有改变)

改变原数组的方法

fill()—用新元素替换掉数组内的元素,可以指定替换下标范围。

let oldArr2 = [1,2,3];
let newArr1 = oldArr1.fill(6); //当为空数组时什么都不替换
let newArr2 = oldArr2.fill(6);
console.log(oldArr1, oldArr2)

copyWithin()—选择数组的某个下标,从该位置开始复制数组元素,默认从0开始复制。也可以指定要复制的元素范围。

console.log(arr.copyWithin(3), arr)
//  [1, 2, 3, 1, 2]  [1, 2, 3, 1, 2]

pop()—删除数组的最后一个元素并返回删除的元素。

let popItem = arr.pop();
console.log(popItem, arr)
// 1  , [9, 2, 4, 5, 6]

push()—向数组的末尾添加一个或更多元素,并返回新的长度。

let length = arr.push(22);
console.log(length, arr)
// 7 ,  [9, 2, 4, 5, 6, 1, 22]

shift()—删除并返回数组的第一个元素。

let firstItem = arr.shift();
console.log(firstItem, arr)
// 9 , [2, 4, 5, 6, 1]

unshift()—向数组的开头添加一个或更多元素,并返回新的长度。

let newLength = arr.unshift(100, 20);
console.log(newLength, arr)
// 8 [100, 20, 9, 2, 4, 5, 6, 1]

reverse()—反转数组的元素顺序。

arr.reverse();
console.log(arr)
// [1, 6, 5, 4, 2, 9]

sort()—对数组的元素进行排序。

let arr1 = arr.sort();
console.log(arr1, arr)
// [1, 2, 4, 5, 6, 9] 
// [1, 2, 4, 5, 6, 9]

splice()—用于插入、删除或替换数组的元素。

// splice(index,howmany,item1,.....,itemX)
index    必需。整数,规定添加/删除项目的位置,使用负数可从数组结尾处规定位置。
howmany    必需。要删除的项目数量。如果设置为 0,则不会删除项目。
item1, ..., itemX    可选。向数组添加的新项目。

let arr =[9,2,4,5,6,1];
arr.splice(100, 0,123,1234)
console.log(arr)
// [9, 2, 4, 5, 6, 1, 123, 1234]
不改变原数组的方法

includes()— 判断数组中是否存在该元素,参数:查找的值、起始位置,可以替换 ES5 时代的 indexOf 判断方式。indexOf 判断元素是否为 NaN,会判断错误。

a.includes(2);
console.log(a)
//  [1, 2, 3]

keys()—返回键值对的key

let brr =  arr.keys();
console.log(arr, brr);
//  ["a", "b", "c"]  Array Iterator {}

values()— 返回迭代器:返回键值对的value

let brr =  arr.values();
console.log(arr, brr);
// ["a", "b", "c"] Array Iterator {}

entries()— 返回迭代器:返回键值对

let brr =  arr.entries();
console.log(arr, brr);
// ["a", "b", "c"] Array Iterator {}

find()—传入一个回调函数,找到数组中符合当前搜索规则的第一个元素,返回它,并且终止搜索。

let arr = [1, "2", 3, 3, "2"]
console.log(arr.find(n => typeof n === "number") , arr) 
// 1 , [1, "2", 3, 3, "2"]

findIndex()— 传入一个回调函数,找到数组中符合当前搜索规则的第一个元素,返回它的索引,并且终止搜索

console.log(arr.findIndex(n => typeof n === "number") , arr)// 0  , [1, "2", 3, 3, "2"]

concat()—连接两个或更多的数组,并返回结果。

let brr = ['foo', 'boo'];
let crr = arr.concat(brr);
console.log(crr)
// [9, 2, 4, 5, 6, 1, "foo", "boo"]

every()—检测数组元素的每个元素是否都符合条件。

let falg = arr.every((item, index)=>{
    return item > 4
})
console.log(falg);
// false

some()—检测数组元素中是否有元素符合指定条件。

let falg = arr.some((item, index)=>{
    return item > 4
})
console.log(falg);
// true

filter()—检测数组元素,并返回符合条件所有元素的数组。

let brr = arr.filter((item, index)=>{
    return item > 4
})
console.log(brr);
//  [9, 5, 6]

indexOf()—搜索数组中的元素,并返回它所在的位置。

let index = arr.indexOf(9);
console.log(index);
// 0

join()—把数组的所有元素放入一个字符串。

let str = arr.join('-');
console.log(str, arr);
// 9-2-4-5-6-1  , [9, 2, 4, 5, 6, 1]

toString()—把数组转换为字符串,并返回结果。

let str = arr.toString();
console.log(str);
// 9,2,4,5,6,1

lastIndexOf()—返回一个指定的字符串值最后出现的位置,在一个字符串中的指定位置从后向前搜索。

let index = arr.lastIndexOf(9);
console.log(index);
// 0

map()—通过指定函数处理数组的每个元素,并返回处理后的数组。

let brr = arr.map((item,index)=>{
    return {
        key: item,
        value: item+3
    }
});
console.log(brr);
//  0: {key: 9, value: 12}
    1: {key: 2, value: 5}
    2: {key: 4, value: 7}
    3: {key: 5, value: 8}
    4: {key: 6, value: 9}
    5: {key: 1, value: 4}

slice(start,end)—选取数组的的一部分,并返回一个新数组。

let brr = arr.slice(2,4);
console.log(brr);
// [4, 5]

valueOf()—返回数组对象的原始值。

let brr = arr.valueOf();
console.log(brr);
// [9, 2, 4, 5, 6, 1]

3. 对象相关(新增了那些方法,怎么使用(平常项目中使用过那些))

// 对象新增的方法

// 它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致
Object.is()

// Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
Object.assign()

// 引入了Object.keys方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。
Object.keys()

// Object.values方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。
Object.values()

//  Object.entries()方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组

const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]


//  Object.fromEntries() 该方法的主要目的,是将键值对的数据结构还原为对象,因此特别适合将 Map结构转为对象。Object.fromEntries()方法是Object.entries()的逆操作,用于将一个键值对数组转为对象。用于将一个键值对数组转为对象。该方法的一个用处是配合URLSearchParams对象,将查询字符串转为对象

Object.fromEntries([
  ['foo', 'bar'],
  ['baz', 42]
])
// { foo: "bar", baz: 42 }

Object.fromEntries(new URLSearchParams('foo=bar&baz=qux'))
// { foo: "bar", baz: "qux" }

4. 字符串相关(新增了那些方法,怎么使用(平常项目中使用过那些))

indexOf()方法和lastIndexOf()方法能接收2个参数,第一个参数为要寻找的字符串,第二个为开始位置,如果不写开始位置,会在全局找,无论从哪个位置开始找,返回的都是第一次出现的位置的下标。

console.log(str.indexOf('rf'));
console.log(str.lastIndexOf('rf'));
// 10
// 10
console.log(str.indexOf('rf', 2));
console.log(str.lastIndexOf('rf', 5));
// 10
// 10

includes()方法—同样能接收2个参数,填写一个参数在全局找,填写第二个参数,则从填写的位置往后找。如果找到返回true,没找到返回false。

console.log(str.includes('rf'));
console.log(str.includes('rf', 20));
// true  false

startsWith()方法—查询是否以什么什么开头,同样能接收2个参数,1个参数的话在全局找,2个参数的话则从填写的位置往后找,找到返回true,没找到返回false。

console.log(str.startsWith('adf'));
console.log(str.startsWith('rf', 10));
console.log(str.startsWith('rf'));
// true  true false

endsWith()方法—用法与第3个一样,如果填写第二个参数的话,则是从填写的位置往前找

console.log(str.endsWith('zle'));
console.log(str.endsWith('zl', 17));
console.log(str.endsWith('rf'));
// true true false

repeat() —收一个Number类型的数据,返回一个新的字符串,表示将原字符串重复 n 次。

padStart()方法—用于头部补全,接收2个参数,第一个参数是补全后的字符串的最大长度,第二个是要补的字符串,返回的是补全后的字符串。如果原字符串长度大于第一个参数,则会返回原字符串。如果不写第二个参数,则会用空格替补。

console.log(str.padStart(4, 'djw'));
console.log(str.padStart(10));
console.log(str.padStart(10, 'djw'));
console.log(str.padStart(10, 'djwdjw'));
// hello
//     hello
// djwdjhello
// djwdjhello

padEnd()方法—用于尾部填充,用法与上面一样。

console.log(str.padEnd(4, 'djw'));
console.log(str.padEnd(10));
console.log(str.padEnd(10, 'djw'));
console.log(str.padEnd(10, 'djwdjw'));
// hello
// hello     
// hellodjwdj
// hellodjwdj

toLowerCase()—把字符串转为小写,返回新的字符串。

var str1=str.toLowerCase();
console.log(str); //Hello World
console.log(str1); //hello world

toUpperCase()—串转为大写,返回新的字符串。

var str1=str.toUpperCase();
console.log(str); //hello world
console.log(str1); //HELLO WORLD

charAt()—返回指定下标位置的字符。如果index不在0-str.length(不包含str.length)之间,返回空字符串。

var str1=str.charAt(6);
console.log(str1); // w

charCodeAt()—返回指定下标位置的字符的unicode编码,这个返回值是 0 - 65535 之间的整数。

var str1=str.charCodeAt(1);
var str2=str.charCodeAt(-2); //NaN
console.log(str1); //101

slice(): 返回字符串中提取的子字符串。

var str1=str.slice(2); //如果只有一个参数,则提取开始下标到结尾处的所有字符串
var str2=str.slice(2,7); //两个参数,提取下标为2,到下标为7但不包含下标为7的字符串
var str3=str.slice(-7,-2); //如果是负数,-1为字符串的最后一个字符。提取从下标-7开始到下标-2但不包含下标-2的字符串。前一个数要小于后一个数,否则返回空字符串

console.log(str1); //llo World
console.log(str2); //llo W
console.log(str3); //o Wor

substring()—提取字符串中介于两个指定下标之间的字符。

var str1=str.substring(2)
var str2=str.substring(2,2);
var str3=str.substring(2,7);
console.log(str1); //llo World
console.log(str2); //如果两个参数相等,返回长度为0的空串
console.log(str3); //llo W

substr()—返回从指定下标开始指定长度的的子字符串

var str1=str.substr(1)
var str2=str.substr(1,3);
var str3=str.substr(-3,2);
console.log(str1); //ello World 
console.log(str2); //ell
console.log(str3); //rl

split()— 把字符串分割成字符串数组。

var string1="1:2:3:4:5";
var str1=str.split("");//如果把空字符串 ("")用作分割符,那么字符串的每个字符之间都会被分割
var str2=str.split(" "); //以空格为分隔符
var str3=str.split("",4); //4指定返回数组的最大长度
var str4=string1.split(":");
console.log(str1); // ["A", "A", " ", "B", "B", " ", "C", "C", " ", "D", "D"]
console.log(str2); //["AA" "BB" "CC" "DD"]
console.log(str3); //["A", "A", " ", "B"]
console.log(str4); // ["1", "2", "3", "4", "5"]

replace()—在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。

var reg=/o/ig; //o为要替换的关键字,不能加引号,否则替换不生效,i忽略大小写,g表示全局查找。
var str1=str.replace(reg,"**")
console.log(str1); //hell** W**RLD

match(): 返回所有查找的关键字内容的数组。

var reg=/to/ig;
var str1=str.match(reg);
console.log(str1); //["To", "to"]
console.log(str.match("Hello")); //null

5. 数组和对象相互转换,在那些情况下可以相互转换

Object.keys()

let obj = {
    name: 'haha', 
    age: 20,
    showName:  function () {}
}
Object.keys(obj)   //['name','age','showName']

// 组合用法
let obj = {
    name: 'haha', 
    age: 20, 
}

Object.keys(obj).map((val, index)=>{
  return obj[val] // 可以针对obj的不同属性做不同处理
}) 

Object.values();

let obj = {
    name: 'haha', 
    age: 20,
    showName:  function () {}
}
Object.values(obj)   //['haha','20', f]

Object.entries()

console.log(Object.entries(obj)); // [ ['foo', 'bar'], ['baz', 42] ]

const anObj = { 100: 'a', 2: 'b', 7: 'c' };
console.log(Object.entries(anObj)); // [ ['2', 'b'], ['7', 'c'], ['100', 'a'] ]


console.log(Object.entries('foo')); // [ ['0', 'f'], ['1', 'o'], ['2', 'o'] ]

// 更优雅的遍历对象键值:
const obj = { a: 5, b: 7, c: 9 };
for (const [key, value] of Object.entries(obj)) {
  console.log(`${key} ${value}`); 
  // "a 5", "b 7", "c 9"
}

// 或者
Object.entries(obj).forEach(([key, value]) => {
console.log(`${key} ${value}`); // "a 5", "b 7", "c 9"
});

Object.fromEntries()

const obj = Object.fromEntries(arr);
console.log(obj); // { 0: "a", 1: "b", 2: "c" }

6. var,let,const 的区别,和实现原理

7. 实现深拷贝

8. 获取页面中的全部标签,并计算个数

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div class="a">aa</div>
    <p class="b">asd</p>
    <p class="c">asd</p>
</body>
</html>
<script>
let elObj = {};
let elTotal = document.getElementsByTagName('*');
for(let i=0; i< elTotal.length; i++) {
    let lowerTag = elTotal[i].tagName.toLowerCase();
    if(elObj[lowerTag]){
        elObj[lowerTag]++
    } else {
        elObj[lowerTag] = 1;
    }
}
console.log(elObj);
</script>

9. 节流和防抖,原理以及实现

1.防抖

触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间

思路:

使用闭包,每次触发事件时都取消之前的延时调用方法

function debounce(fn) {
  let timeout = null; // 创建一个标记用来存放定时器的返回值
  return function () {
    clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
    timeout = setTimeout(() => { // 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数
      fn.apply(this, arguments);
    }, 500);
  };
}
function sayHi() {
  console.log('防抖成功');
}

var inp = document.getElementById('inp');
inp.addEventListener('input', debounce(sayHi)); // 防抖

2.节流

思路:

每次触发事件时都判断当前是否有等待执行的延时函数

function throttle(fn) {
  let canRun = true; // 通过闭包保存一个标记
  return function () {
    if (!canRun) return; // 在函数开头判断标记是否为true,不为true则return
    canRun = false; // 立即设置为false
    setTimeout(() => { // 将外部传入的函数的执行放在setTimeout中
      fn.apply(this, arguments);
      // 最后在setTimeout执行完毕后再把标记设置为true(关键)表示可以执行下一次循环了。当定时器没有执行的时候标记永远是false,在开头被return掉
      canRun = true;
    }, 500);
  };
}
function sayHi(e) {
  console.log(e.target.innerWidth, e.target.innerHeight);
}
window.addEventListener('resize', throttle(sayHi));

10. call,apply,bind的原理与实现

call,apply,bind的作用都是改变this的指向,不同的地方是call和apply是立即执行函数,而bind不是,call和apply的区别是入参不同,call是单个的入参,而apply是第二个参数是个数组,以数组形式入参

11. promise的相关概念及实现

12. 箭头函数中的this,箭头函数与function的区别

13. 介绍一下set,map,weakSet和weakMap的区别

14. settimeout , promise, async/await的区别

15. async/await如何通过同步的方式实现异步

16. js异步解决方案的发展历程及其优缺点(amd,cmd,commonjs,es6的module)

17. for循环和foreach哪个执行效率更高,为什么

18. js判断数字,字母,中文,符号并截取16位

19. 手写实现js的多种继承方式

20. 使用settimeout实现setinterval,有什么优点

21. js闭包的概念,闭包的使用场景,以及闭包的优缺点

22. js中this的指向

23. js的原型链

24. js的内存管理机制

25. js的作用域与作用域链

26. 什么是xss和csrf,并如何防止

27. es6的class和构造函数的区别

28. js中的变量提升(和函数提升)

29. js的事件捕获和事件冒泡

30. onclick和addeventListener(第三个参数)绑定同一个元素,那个先执行,那个后执行

根据初始化函数的注册顺序,从上往下执行。
如果是以下这样

<button class="a">fdfasf</button>

document.querySelector('.a').addEventListener('click', function (params) {
    console.log('addeventListener 冒泡');
}, false);
document.querySelector('.a').onclick = function () {
        console.log('onclick1');
    }
document.querySelector('.a').addEventListener('click', function (params) {
    console.log('addeventListener 捕获');
}, true);
document.querySelector('.a').onclick = function () {
    console.log('onclick2');
}
// 打印 addeventListener 冒泡 onclick2  addeventListener 捕获

31. 两个onclick事件,是执行第一个还是执行第二个

在js中写多个onclick事件绑定相同元素,只会触发最后一个绑定的事件。

32. 怎么让函数只执行一次,addeventListener的第三个参数都有哪些

  1. 设置标志位

33. new一个对象的过程

1、创建一个空对象,并且 this 变量引用该对象,同时还继承了构造函数的原型。
2、属性和方法被加入到 this 引用的对象中。
3、新创建的对象由 this 所引用,并且最后隐式的返回 this

34. 如何判断一个js的变量是什么数据类型,有哪几种方式

type, instanceof, Object.prototype.toString.call();

type 只能判断 number,string,boolean,undefined,object和function,在es6中又新增了symbol,无法判断Array,object,和null(因为判断出来都是object)

在判断是否是数组的时候,可以用es6中新增的方法Array.isArray(),也可以用instanceof

instanceof的原理:
构造函数的prototype属性是否出现在对象的原型链中的任何位置

如果想精确判断是那种数据类型,建议使用Object.prototype.toString.call()

35. js中如何防止一个对象被修改(删除,添加)

  1. 不可扩展对象(不能再给对象添加属性和方法)

    Object.preventExtensions(person);//设置为防拓展对象
    Object.isExtensible(person);//用来确定对象是否可扩展。

  2. 密封对象(不能删除属性和方法)

    Object.seal(person);//将对象密封
    Object.isSealed(person); // 检测对象是否被密封

  3. 冻结对象(不能修改属性和方法)

    Object.freeze(man); // 冻结对象
    Object.isFrozen(man); // 检测对象是否被冻结

  4. 使用Object.defineProperty的writable

    var person = function() {

    this.name = 'a'
    

    }
    var p = new person();
    Object.defineProperty(p, ‘name’, {

    writable: false
    

    })
    p.name = ‘b’;
    console.log(p.name) // a

36. 浏览器中的event-loop

JS 在执行的过程中会产生执行环境,这些执行环境会被顺序的加入到执行栈中。如果遇到异步的代码,会被挂起并加入到 Task(有多种 task) 队列中。一旦执行栈为空,Event Loop 就会从 Task 队列中拿出需要执行的代码并放入执行栈中执行,所以本质上来说 JS 中的异步还是同步行为。

setTimeout的第二个参数不得小于 4 毫秒,不足会自动增加

同的任务源会被分配到不同的 Task 队列中,任务源可以分为 微任务(microtask) 和 宏任务(macrotask)。在 ES6 规范中,microtask 称为 jobs,macrotask 称为 task。

微任务包括 process.nextTick ,promise ,Object.observe ,MutationObserver
宏任务包括 script , setTimeout ,setInterval ,setImmediate ,I/O ,UI rendering

很多人有个误区,认为微任务快于宏任务,其实是错误的。因为宏任务中包括了 script ,浏览器会先执行一个宏任务,接下来有异步代码的话就先执行微任务。

所以正确的一次 Event loop 顺序是这样的

执行同步代码,这属于宏任务
执行栈为空,查询是否有微任务需要执行
执行所有微任务
必要的话渲染 UI
然后开始下一轮 Event loop,执行宏任务中的异步代码

37. cookie,sessionStorage,localStorage和indexDB的使用场景和优缺点

cookie:key/value 一般大小4k,每次访问都要传送cookie给服务器,可以设置过期时间,和域名绑定
localstorage:key/value 一直存储于本地硬盘(浏览器中可以删除),一般数据最大5MB(各个浏览器不一样),和域名绑定
sessionStorage:key/value 关闭页面或浏览器后被清除,最大5MB,和域名绑定
indexDB:key/object 可以存储对象,浏览器中的数据库,异步,支持事务,和域名绑定,存储空间大

总结:
LocalStorage、SessionStorage和Cookie都是通过域名进行隔离的。

全局存储:LocalStorage、Cookie
自动参与HTTP通信:Cookie
实现不同tab保存不同的数据:SessionStorage
存储大量数据:IndexDB

应用:登录相关可以考虑使用Cookie。

38. 什么是函数式编程,什么是纯函数

https://www.jianshu.com/p/01cbebd9655d

函数式编程:
通过最小化变化使得代码更易理解

纯函数定义:
对于相同的输入,永远得到相同的输出,它不依赖外部环境,也不会改变外部环境

39. 如何优化递归(尾递归)

尾调用和尾递归
递归
当一个函数在内部调用自身,就可以称为一个递归

function foo () {
    foo();
}

这里没有结束条件,是死递归,所以会报栈溢出错误的,写代码时千万注意给递归添加结束条件。

什么是尾递归
当一个函数尾调用自身,就叫做尾递归。

例子:
function foo () {
return foo();
}

好处:

40. 函数柯里化

是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
好处:

  1. 参数复用
  2. 提前确认
  3. 延迟运行

curry的一些性能问题你只要知道下面四点就差不多了:

(1) 存取arguments对象通常要比存取命名参数要慢一点
(2) 一些老版本的浏览器在arguments.length的实现上是相当慢的
(3) 使用fn.apply( … ) 和 fn.call( … )通常比直接调用fn( … ) 稍微慢点
(4) 创建大量嵌套作用域和闭包函数会带来花销,无论是在内存还是速度上

例子:

// 实现一个add方法,使计算结果能够满足如下预期:
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;

function add() {
    // 第一次执行时,定义一个数组专门用来存储所有的参数
    var _args = Array.prototype.slice.call(arguments);

    // 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
    var _adder = function() {
        _args.push(...arguments);
        return _adder;
    };

    // 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
    _adder.toString = function () {
        return _args.reduce(function (a, b) {
            return a + b;
        });
    }
    return _adder;
}

add(1)(2)(3)                // 6
add(1, 2, 3)(4)             // 10
add(1)(2)(3)(4)(5)          // 15
add(2, 6)(1)                // 9

41. 函数的arguments,arguments中都有什么属性

作用:
用于储存调用函数时的所有实参(arguments数组的个数,取决于实参列表,与形参无关)
arguments.callee()是arguments的重要属性。表示arguments所在函数的引用地址

function name() {
    console.log(arguments);
}
name(1,2,3)
// 打印出
arguments {
    0: 1
    1: 2
    2: 3
    callee: ƒ name()
    length: 3
    Symbol(Symbol.iterator): ƒ values()
    __proto__: Object
}

42. js的跨域通信

当域名不是同一协议,同一域名或者同一端口号,则会根据浏览器的默认安全策略会产生跨域的问题。

43. js如何实现懒加载

原理:
每个图片的src会有一个get请求,我们把不能看到的图片src设置为相同的图片,这些图片发一次请求即可,设置属性data-src为真正的图片路径。当图片滚动到可视区,我们就用js把data-src 赋值给 src,简单的懒加载就可以实现了。

如何判断是否在可视区

就是 图片的 offsetTop < scrollTop + clientHeigth 即可
offsetTop // 元素距离文档顶部的距离
scrollTop // 滚动的距离
clientHeigth // 窗口的高度

代码:

let imgArr = document.querySelectorAll('img');
let len = imgArr.length;
window.onscroll = function () {
    let seeHeight = document.documentElement.clientHeight;
    console.log("seeHeight ="+seeHeight);
    let scrollTop =  document.body.scrollTop || document.documentElement.scrollTop;
    console.log("scrollTop ="+scrollTop);
    for(let i=0; i<len; i++){
        console.log(imgArr[i].offsetTop);
        if(imgArr[i].offsetTop < seeHeight + scrollTop){
                if(imgArr[i].getAttribute('src')=='timg.jpg'){
                    imgArr[i].src = imgArr[i].getAttribute('data-src');
                }
        }
    }
}

优化:
1.在初始条件下,应该有图片显示,只要在加载完毕之后滚动之前执行图片的加载即可

2.函数节流,但我们在高频度的滚动时,每隔一段事件开始图片的渲染。实现原理是 加入一个开关变量, 控制每隔固定的一段时间,函数才可能被触发

优化后代码:

let imgArr = document.querySelectorAll('img');
let len = imgArr.length;
let n = 0; //记录加载图片的位置,避免从第一张开始加载
let canrun = true;
let seeHeight = document.documentElement.clientHeight;
console.log("seeHeight ="+seeHeight);

lazyLoad();
window.onscroll = function () {
    if(!canrun){
        return ;
    }
    canrun = false;
    setTimeout(function () {
        console.log('*****');
        lazyLoad();
        canrun= true;
    },1000);

}

function lazyLoad() {
    let scrollTop =  document.body.scrollTop || document.documentElement.scrollTop;
    console.log("scrollTop ="+scrollTop);
    for(let i=0; i<len; i++){
        console.log(imgArr[i].offsetTop);
        if(imgArr[i].offsetTop < seeHeight + scrollTop){
            if(imgArr[i].getAttribute('src')=='timg.jpg'){
                imgArr[i].src = imgArr[i].getAttribute('data-src');
            }
            n = i+1;
            console.log("n="+n);
        }
    }
}

44. js对象转化为字符串的步骤,数组与对象的相互转化(存疑)

js对象转化为字符串的步骤:
首先调用toString方法,只有当toString不返回一个原始值的时候,才会调用valueOf()。toString方法但是基本上所有对象都返回字符串。所以对象到字符串形式的转换基本都是使用toString方法。
俩个方法都不返回原始值时,会抛出错误。

数组转对象:

45. 什么是防御式编程

主要思想
子程序应该不因传入错误数据而被破坏,哪怕是由其他子程序产生的错误数据。

46. 有没有写过npm包,怎么设计的,用什么模块化方式(commonjs,es6的module,ump等)

47. js中有几种方法定义函数

1、使用function关键字定义函数 – 具有优先级,优先将function关键字定义的函数优先执行

  function functionName(arg0, arg1 ,…, argN){
      statements
  }
  函数的调用:functionName()

2、使用函数表达式的形式定义函数(即将匿名函数复制给变量)

  var variable = function(arg0, arg1 ,…, argN){
    statements
   }
  console.log(typeof variable); //function
  函数调用:variable();

3、使用new Function构造函数定义函数

  var variable = new Function(‘name’,’alert(“hello,”+name)’); //最末尾的是函数体,其前面的都是参数
  console.log(typeof variable); //function
  函数调用:variable(‘world’);

注意:

(1)使用fucntion关键字定义的函数,函数一旦声明,允许任意调用(在函数定义前、函数定义后、函数内部,可以在任意位置调用)
(2)使用函数表达式、new Function构造函数定义的函数,不能在函数定义前使用

48. focus/blur与focusin/focusout的区别与联系

focus/blur不冒泡,focusin/focusout冒泡
focus/blur兼容性好,focusin/focusout在除FireFox外的浏览器下都保持良好兼容性,如需使用事件托管,可考虑在FireFox下使用事件捕获elem.addEventListener(‘focus’, handler, true)

49. 用js实现千位分隔符

function format (num) {
    var reg=/\d{1,3}(?=(\d{3})+$)/g; 
    return (num + '').replace(reg, '$&,');
}

正则表达式 \d{1,3}(?=(\d{3})+$) 表示前面有1~3个数字,后面的至少由一组3个数字结尾。

?=表示正向引用,可以作为匹配的条件,但匹配到的内容不获取,并且作为下一次查询的开始。

$& 表示与正则表达式相匹配的内容,具体的使用可以查看字符串replace()方法的

50. ie和dom事件流区别

  1. 事件流的区别

IE采用冒泡型事件 Netscape使用捕获型事件 DOM使用先捕获后冒泡型事件
示例:

复制代码代码如下:

<body> 
<div> 
<button>点击这里</button> 
</div> 
</body> 

冒泡型事件模型: button->div->body (IE事件流)

捕获型事件模型: body->div->button (Netscape事件流)

DOM事件模型: body->div->button->button->div->body (先捕获后冒泡)

  1. 事件侦听函数的区别

IE使用:

[Object].attachEvent("name_of_event_handler", fnHandler); //绑定函数 
[Object].detachEvent("name_of_event_handler", fnHandler); //移除绑定 

DOM使用:

[Object].addEventListener("name_of_event", fnHandler, bCapture); //绑定函数 
[Object].removeEventListener("name_of_event", fnHandler, bCapture); //移除绑定 

bCapture参数用于设置事件绑定的阶段,true为捕获阶段,false为冒泡阶段。

51. js怎么实现精确倒计时

http://www.xuanfengge.com/js-realizes-precise-countdown.html

52. js的数组降纬

  1. 使用flat

    [1, [2, [3]]].flat(Infinity) // 参数可以是数字,可以拉平传入数字的n+1层,不写默认两层,Infinity默认全部拉平

  2. flatMap() 只能拉平二维数组

  3. 使用递归

    let newArr = [];
    function flatArr (arr) {

    for(let i=0; i<arr.length; i++) {
        if(arr[i] instanceof Array) {
           flatArr(arr[i]);
        }else {
            newArr.push(arr[i]);
        }
    }
    

    }

  4. 使用reduce

    const flattenDeep = (arr) => Array.isArray(arr)
    ? arr.reduce( (a, b) => […a, …flattenDeep(b)] , [])
    : [arr]
    flattenDeep([1, [[2], [3, [4]], 5]])

53. script的标签上都有什么属性

type:表示编写代码使用的脚本语言的内容类型
src :表示包含要执行代码的外部文件(带有src属性的元素中,不应该包含额外的js代码,如果包含了嵌入的代码,则只会下载并执行外部脚本文件,嵌入的代码会被忽略 。)
charset:设置字符集,例如utf-8等
defer:表示脚本可以延迟到文档全部被解析和显示之后再执行(defer脚本会在文档渲染完毕后,DOMContentLoaded事件调用前执行)
async: 表示应该立即下载脚本,但不应妨碍页面中的其他操作,比如下载其他资源或等待加载其他脚本。(async的执行,并不会按着script在页面中的顺序来执行,而是谁先加载完谁执行)

defer和async的区别:
有了async属性,表示后续文档的加载和渲染与js脚本的加载和执行是并行进行的,即异步执行;
有了defer属性,加载后续文档的过程和js脚本的加载(此时仅加载不执行)是并行进行的(异步),js脚本的执行需要等到文档所有元素解析完成之后,DOMContentLoaded事件触发执行之前。

54. jquery为什么可以一直链式调用

因为在每次使用完jquery的方法之后,都会返回一个当前的jquery对象,因此jquery可以一直链式调用

55. 请简单实现一个下拉加载

56. {}+[]与[]+{}分别返回什么,为什么

57. addeventListener的第三个参数都可以是什么

58. 什么是Cookie 隔离?(或者说:请求资源的时候不要让它带cookie怎么做)

如果静态文件都放在主域名下,那静态文件请求的时候都带有的cookie的数据提交给server的,非常浪费流量,所以不如隔离开。

因为cookie有域的限制,因此不能跨域提交请求,故使用非主要域名的时候,请求头中就不会带有cookie数据,
这样可以降低请求头的大小,降低请求时间,从而达到降低整体请求延时的目的。

同时这种方式不会将cookie传入Web Server,也减少了Web Server对cookie的处理分析环节,
提高了webserver的http请求的解析速度。

算法相关

1. 实现一个快速排序(然后怎么优化)

2. 深度遍历(递归以及非递归)

3. 广度遍历(递归以及非递归)

4. 递归的优化(尾递归)

5. 数组的排序,去重

6. 实现一个js的深拷贝

7. 动态规划法,0-1背包问题

8. 红黑树,反转二叉树实现

网络基础相关

1. 简述一下http1 1.1 2 3的区别


http://www.ruanyifeng.com/blog/2016/08/http.html

http1
支持get,post,head请求
缺点:

  1. 每个tcp连接只能发送一个请求,发送完毕后,连接就关闭了,要想发送其他请求,需要再开一个tcp连接(解决方法是加一个Connection: keep-alive字段,但这不是标准字段,不同的实现行为可能不一致)

http1.1

  1. 引入持久连接,不用声明Connection: keep-alive,在客户端或者服务器一段时间发现对方没有活动,就可以主动关闭连接,不过规范做法是在最后一个请求发送Connection: close,明确要求关闭tcp连接
  2. 引入管道机制,即同一个tcp里面,客户端可以同时发送多个请求,这样进一步改进了http的效率(举例来说,客户端需要请求两个资源。以前的做法是,在同一个TCP连接里面,先发送A请求,然后等待服务器做出回应,收到后再发出B请求。管道机制则是允许浏览器同时发出A请求和B请求,但是服务器还是按照顺序,先回应A请求,完成后再回应B请求。)
  3. 新增了很多动词方法,例如put,delete,head,options,patch
  4. 客户端新增了host字段,用来指定服务器域名

但是这样做仍然有缺点

  1. 虽然是并发请求,但是响应却是要按照顺序接收,所以服务器只能处理完一个回应,才能处理下一个回应,如果当中一个回应特别慢,就会造成后面有许多回应要排队。
  2. 不会压缩请求和响应首部,从而导致不必要的网络流量

http2

  1. 二进制分帧,每个桢包括一个帧头,里面有个很小标志,来区别是属于哪个流(减少服务端的链接压力,内存占用更少,连接吞吐量更大;而且由于 TCP 连接的减少而使网络拥塞状况得以改善,同时慢启动时间的减少,使拥塞和丢包恢复速度更快。)
  2. 多路复用 ()
  3. 首部压缩(使用 HPACK 算法)
  4. 服务端推送 (服务器推送还有一个很大的优势:可以缓存!也让在遵循同源的情况下,不同页面之间可以共享缓存资源成为可能。)
    1)推送的资源可以由客户端缓存
    2)推送的资源可以在不同的页面上重复使用
    3)推送的资源可以与其他资源一起复用
    4)推送的资源可以由服务器优先
    5)推送的资源可以被客户拒绝

http3
使用QUIC代替TCP
(1)对移动端切换网络体验更好
(2)解决了tcp的队头拥塞问题
(3)缩短连接建立时间

2. tcp三次握手,四次挥手

TCP 头部协议:

| Flags:TCP 首部中有 6 个标志比特,它们中的多个可同时被设置为 1,主要是用于操控 TCP 的状态(URG,ACK,PSH,RST,SYN,FIN)

  • ACK : TCP 协议规定,只有 ACK=1 时有效,也规定连接建立后所有发送的报文的 ACK 必须为 1
  • SYN(SYNchronization) : 在连接建立时用来同步序号。当 SYN=1 而 ACK=0 时,表明这是一个连接请求报文。对方若同意建立连接,则应在响应报文中使 SYN=1 和 ACK=1. 因此, SYN 置 1 就表示这是一个连接请求或连接接受报文。
  • FIN (finis)即完,终结的意思, 用来释放一个连接。当 FIN = 1 时,表明此报文段的发送方的数据已经发送完毕,并要求释放连接。
三次握手
  • 第一次握手:建立连接。客户端发送连接请求报文段,将 SYN 位置为 1,Sequence Number 为 x;然后,客户端进入 SYN_SEND 状态,等待服务器的确认;
  • 第二次握手:服务器收到 SYN 报文段。服务器收到客户端的 SYN 报文段,需要对这个 SYN 报文段进行确认,设置 Acknowledgment Number 为 x+1(Sequence Number+1);同时,自己自己还要发送 SYN 请求信息,将 SYN 位置为 1,Sequence Number 为 y;服务器端将上述所有信息放到一个报文段(即 SYN+ACK 报文段)中,一并发送给客户端,此时服务器进入 SYN_RECV 状态;
  • 第三次握手:客户端收到服务器的 SYN+ACK 报文段。然后将 Acknowledgment Number 设置为 y+1,向服务器发送 ACK 报文段,这个报文段发送完毕以后,客户端和服务器端都进入 ESTABLISHED 状态,完成 TCP 三次握手。
  • 完成了三次握手,客户端和服务器端就可以开始传送数据。以上就是 TCP 三次握手的总体介绍。
四次分手

当客户端和服务器通过三次握手建立了 TCP 连接以后,当数据传送完毕,肯定是要断开 TCP 连接的啊。那对于 TCP 的断开连接,这里就有了神秘的“四次分手”。

  • 第一次分手:主机 1(可以使客户端,也可以是服务器端),设置 Sequence Number 和 Acknowledgment Number,向主机 2 发送一个 FIN 报文段;此时,主机 1 进入 FIN_WAIT_1 状态;这表示主机 1 没有数据要发送给主机 2 了;
  • 第二次分手:主机 2 收到了主机 1 发送的 FIN 报文段,向主机 1 回一个 ACK 报文段,Acknowledgment Number 为 Sequence Number 加 1;主机 1 进入 FIN_WAIT_2 状态;主机 2 告诉主机 1,我“同意”你的关闭请求;
  • 第三次分手:主机 2 向主机 1 发送 FIN 报文段,请求关闭连接,同时主机 2 进入 LAST_ACK 状态;
  • 第四次分手:主机 1 收到主机 2 发送的 FIN 报文段,向主机 2 发送 ACK 报文段,然后主机 1 进入 TIME_WAIT 状态;主机 2 收到主机 1 的 ACK 报文段以后,就关闭连接;此时,主机 1 等待 2MSL 后依然没有收到回复,则证明 Server 端已正常关闭,那好,主机 1 也可以关闭连接了。

3. https的实现方式(详细过程),里面涉及到的加密算法都有哪些

HTTP传输协议的缺点

(1) 窃听风险(eavesdropping):第三方可以获知通信内容。
(2) 篡改风险(tampering):第三方可以修改通信内容。
(3) 冒充风险(pretending):第三方可以冒充他人身份参与通信。
因此需要加密传输避免以上缺点

https使用SSL/TLS

目前,应用最广泛的是TLS 1.0,接下来是SSL 3.0。但是,主流浏览器都已经实现了TLS 1.2的支持。
TLS 1.0通常被标示为SSL 3.1,TLS 1.1为SSL 3.2,TLS 1.2为SSL 3.3。

HTTP协议和SLL/TLS协议是如何结合使用的

4. OSI七层模型

  • 应用层(Application) 提供网络与用户应用软件之间的接口服务
  • 表示层(Presentation) 提供格式化的表示和转换数据服务,如加密和压缩
  • 会话层(Session) 提供包括访问验证和会话管理在内的建立和维护应用之间通信的机制
  • 传输层(Transimission) 提供建立、维护和取消传输连接功能,负责可靠地传输数据(PC)
  • 网络层(Network) 处理网络间路由,确保数据及时传送(路由器)
  • 数据链路层(DataLink) 负责无错传输数据,确认帧、发错重传等(交换机)
  • 物理层(Physics) 提供机械、电气、功能和过程特性(网卡、网线、双绞线、同轴电缆、中继器)

5. DNS请求路径

6. TCP/UDP的区别,怎么实现可靠UDP

  • TCP 是面向连接(Connection oriented)的协议,UDP 是无连接(Connection less)协议;TCP 用三次握手建立连接:1) Client 向 server 发送 SYN;2) Server 接收到 SYN,回复 Client 一个 SYN-ACK;3) Client 接收到 SYN_ACK,回复 Server 一个 ACK。到此,连接建成。UDP 发送数据前不需要建立连接。
  • TCP 可靠,UDP 不可靠;TCP 丢包会自动重传,UDP 不会。
  • TCP 有序,UDP 无序;消息在传输过程中可能会乱序,后发送的消息可能会先到达,TCP 会对其进行重排序,UDP 不会。
  • TCP 无界,UDP 有界;TCP 通过字节流传输,UDP 中每一个包都是单独的。
  • TCP 有流量控制(拥塞控制),UDP 没有;主要靠三次握手实现。
  • TCP 传输慢,UDP 传输快;因为 TCP 需要建立连接、保证可靠性和有序性,所以比较耗时。这就是为什么视频流、广播电视、在线多媒体游戏等选择使用 UDP。
  • TCP 是重量级的,UDP 是轻量级的;TCP 要建立连接、保证可靠性和有序性,就会传输更多的信息,如 TCP 的包头比较大。
  • TCP 的头部比 UDP 大;TCP 头部需要 20 字节,UDP 头部只要 8 个字节

7. web开发中会话的跟踪方法有哪些

当用户发出请求时,服务器就会做出响应,客户端与服务器之间的联系是离散的、非连续的。当用户在同一网站的多个页面之间转换时,根本无法确定是否是同一个客户,会话跟踪技术就可以解决这个问题。当一个客户在多个页面间切换时,服务器会保存该用户的信息。

1.Cookie:
可以使用 cookie 存储购物会话的 ID;在后续连接中,取出当前的会话 ID,并使用这个 ID 从服务器上的查找表(lookup table)中提取出会话的相关信息。 以这种方式使用 cookie 是一种绝佳的解决方案,也是在处理会话时最常使用的方式。但是,sevlet 中最好有一种高级的 API 来处理所有这些任务,以及下面这些冗长乏味的任务:从众多的其他cookie中(毕竟可能会存在许多cookie)提取出存储会话标识符的 cookie;确定空闲会话什么时候过期,并回收它们;将散列表与每个请求关联起来;生成惟一的会话标识符.

2.URL 重写:
采用这种方式时,客户程序在每个URL的尾部添加一些额外数据。这些数据标识当前的会话,服务器将这个标识符与它存储的用户相关数据关联起来。 URL重写是比较不错的会话跟踪解决方案,即使浏览器不支持 cookie 或在用户禁用 cookie 的情况下,这种方案也能够工作。
URL 重写具有 cookie 所具有的同样缺点,也就是说,服务器端程序要做许多简单但是冗长乏味的处理任务。即使有高层的 API 可以处理大部分的细节,仍须十分小心每个引用你的站点的 URL ,以及那些返回给用户的 URL。即使通过间接手段,比如服务器重定向中的 Location 字段,都要添加额外的信息。这种限制意味着,在你的站点上不能有任何静态 HTML 页面(至少静态页面中不能有任何链接到站点动态页面的链接)。因此,每个页面都必须使用 servlet 或 JSP 动态生成。即使所有的页面都动态生成,如果用户离开了会话并通过书签或链接再次回来,会话的信息也会丢失,因为存储下来的链接含有错误的标识信息。

3.隐藏的表单域:
HTML 表单中可以含有如下的条目:
这个条目的意思是:在提交表单时,要将指定的名称和值自动包括在 GET 或 POST 数据中。这个隐藏域可以用来存储有关会话的信息,但它的主要缺点是:仅当每个页面都是由表单提交而动态生成时,才能使用这种方法。单击常规的超文本链接并不产生表单提交,因此隐藏的表单域不能支持通常的会话跟踪,只能用于一系列特定的操作中,比如在线商店的结账过程。

4.session:
信息保存在服务器端
使用 setAttribute(String str,Object obj)方法将对象捆绑到一个会话

8. http method

1 GET 请求指定的页面信息,并返回实体主体。
2 HEAD 类似于 GET 请求,只不过返回的响应中没有具体的内容,用于获取报头
3 POST 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST 请求可能会导致新的资源的建立和/或已有资源的修改。
4 PUT 从客户端向服务器传送的数据取代指定的文档的内容。
5 DELETE 请求服务器删除指定的页面。
6 CONNECT HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。
7 OPTIONS 允许客户端查看服务器的性能。
8 TRACE 回显服务器收到的请求,主要用于测试或诊断。
9 PATCH 是对 PUT 方法的补充,用来对已知资源进行局部更新 。

9. js跨域通信(跨域产生的原因,常见解决方法及原理,手动实现个jsonp)

function jsonp(options) {
  // 设置默认初始值
  var defaultObj = {
      url: '',
      jsonpCallback: 'jsonpCallback',
      data: {},
      success: function (data) { }
  }
  // 传入参数的拼接
  var dataStr = '';
  // 合并传入新值
  for (var attr in options) {
      defaultObj[attr] = options[attr];
  }
  // 拼接传入的参数
  for (var key in defaultObj.data) {
      dataStr += `key=${defaultObj.data[key]}&`
  }
  window[defaultObj.jsonpCallback] = function (data) {
      defaultObj.success(data);
  }
  // 生成script,回调执行callback
  var script = document.createElement('script');
  script.src = `${defaultObj.url}?${dataStr}&${defaultObj.jsonpCallback}=${defaultObj.jsonpCallback}`;
  // 获取head元素,把生成的script标签放到script后面
  var head = document.getElementsByTagName('head')[0];
  head.appendChild(script);

}

jsonp({
url: ‘http://libpre.cnsuning.com/api/jsonp/snjw.jsonp‘,
jsonpCallback: ‘cmsCallback’,
data: {
a: 1,
b: 2
},
success: function (data) {
console.log(112233, data);
}
})

10. http常用状态码 101,200,301,302,304,401,403,404,500,502等

  • 1xx Informational(信息性状态码) 接受的请求正在处理
  • 2xx Success(成功状态码) 请求正常处理完毕
  • 3xx Redirection(重定向状态码) 需要进行附加操作一完成请求
  • 4xx Client Error (客户端错误状态码) 服务器无法处理请求
  • 5xx Server Error(服务器错误状态码) 服务器处理请求出错

200 OK,表示从客户端发来的请求在服务器端被正确处理
301 moved permanently,永久性重定向,表示资源已被分配了新的 URL
302 found,临时性重定向,表示资源临时被分配了新的 URL
304 not modified,表示服务器允许访问资源,但因发生请求未满足条件的情况
401 unauthorized,表示发送的请求需要有通过 HTTP 认证的认证信息
403 forbidden,表示对请求资源的访问被服务器拒绝
404 not found,表示在服务器上没有找到请求的资源
500 internal sever error,表示服务器端在执行请求时发生了错误

11. restful是什么,怎么使用

Restful是一种架构设计风格,提供了设计原则和约束条件,而不是架构,而满足这些约束条件和原则的应用程序或设计就是 Restful架构或服务。
主要的设计原则:
资源与URI:网络上所有的资源都有一个资源标志符(URI)。URI的设计应该遵循可寻址性原则,具有自描述性,需要在形式上给人以直觉上的关联
统一资源接口(HTTP方法如GET,PUT和POST):不论什么样的资源,都是通过使用相同的接口进行资源的访问
资源的表述:通过HTTP内容协商,客户端可以通过Accept头请求一种特定格式的表述,服务端则通过Content-Type告诉客户端资源的表述形式。
资源的链接:
状态的转移:
RESTful的核心就是后端将资源发布为URI,前端通过URI访问资源,并通过HTTP动词表示要对资源进行的操作

12. 从浏览器输入一个url,到页面完成的加载的过程(尽可能详细的描述)

  • 首先做 DNS 查询,如果这一步做了智能 DNS 解析的话,会提供访问速度最快的 IP 地址回来
  • 接下来是 TCP 握手,应用层会下发数据给传输层,这里 TCP 协议会指明两端的端口号,然后下发给网络层。网络层中的 IP 协议会确定 IP 地址,并且指示了数据传输中如何跳转路由器。然后包会再被封装到数据链路层的数据帧结构中,最后就是物理层面的传输了
  • TCP 握手结束后会进行 TLS 握手,然后就开始正式的传输数据
  • 数据在进入服务端之前,可能还会先经过负责负载均衡的服务器,它的作用就是将请求合理的分发到多台服务器上,这时假设服务端会响应一个 HTML 文件
  • 首先浏览器会判断状态码是什么,如果是 200 那就继续解析,如果 400 或 500 的话就会报错,如果 300 的话会进行重定向,这里会有个重定向计数器,避免过多次的重定向,超过次数也会报错
  • 浏览器开始解析文件,如果是 gzip 格式的话会先解压一下,然后通过文件的编码格式知道该如何去解码文件
  • 文件解码成功后会正式开始渲染流程,先会根据 HTML 构建 DOM 树,有 CSS 的话会去构建 CSSOM 树。如果遇到 script 标签的话,会判断是否存在 async 或者 defer ,前者会并行进行下载并执行 JS,后者会先下载文件,然后等待 HTML 解析完成后顺序执行,如果以上都没有,就会阻塞住渲染流程直到 JS 执行完毕。遇到文件下载的会去下载文件,这里如果使用 HTTP 2.0 协议的话会极大的提高多图的下载效率。
  • 初始的 HTML 被完全加载和解析后会触发 DOMContentLoaded 事件
  • CSSOM 树和 DOM 树构建完成后会开始生成 Render 树,这一步就是确定页面元素的布局、样式等等诸多方面的东西
  • 在生成 Render 树的过程中,浏览器就开始调用 GPU 绘制,合成图层,将内容显示在屏幕上了

13. 正向代理,反向代理,透明代理

14. 请求时浏览器缓存 from memory cache 和 from disk cache 的依据是什么,哪些数据什么时候存放在 Memory Cache 和 Disk Cache中

  • 200 form memory cache 不访问服务器,一般已经加载过该资源且缓存在了内存当中,直接从内存中读取缓存。浏览器关闭后,数据将不存在(资源被释放掉了),再次打开相同的页面时,不会出现from memory cache。(脚本、字体、图片)
  • 200 from disk cache 不访问服务器,已经在之前的某个时间加载过该资源,直接从硬盘中读取缓存,关闭浏览器后,数据依然存在,此资源不会随着该页面的关闭而释放掉下次打开仍然会是from disk cache。(非脚本,如css)

一般样式表会缓存在磁盘中,不会缓存到内存中,因为css样式加载一次即可渲染出页面。但是脚本可能会随时执行,如果把脚本存在磁盘中,在执行时会把该脚本从磁盘中提取到缓存中来,这样的IO开销比较大,有可能会导致浏览器失去响应。

例如:图片
访问-> 200 -> 退出浏览器
再进来-> 200(from disk cache) -> 刷新 -> 200(from memory cache)

深入理解浏览器的缓存机制

node相关试题

1. node什么时候内存溢出(泄露),怎么避免(解决)

原因:
在 Node 中,V8引擎有默认限制内存大小,通过 JavaScript 使用内存时只能使用部分内存(64位系统下约为1.4 GB,32位系统下约为0.7 GB),如果项目过于庞大,就会造成内存溢出。
解决方法:
(1)可以在Node启动的时候,传递–max-old-space-size或–max-new-space-size来调整内存大小的使用限制。

node --max-old-space-size=1700 test.js // 单位为MB
// 或者
node --max-new-space-size=1024 test.js // 单位为KB

上述参数在V8初始化时生效,一旦生效就不能再动态改变。如果遇到 Node 无法分配足够内存给 JavaScript 的情况,可以用这个办法来放宽V8默认的内存限制

2. node中间件(中间件是什么(定义),中间件原理是什么),为什么引入中间件,有没有写过或者使用过中间件,它解决了什么问题

中间件定义(是什么):

中间件就是请求req和响应res之间的一个应用,本质就是一个函数

详细描述:
请求浏览器向服务器发送一个请求后,服务器直接通过request定位属性的方式得到通过request携带过去的数据,就是用户输入的数据和浏览器本身的数据信息,这中间就一定有一个函数将这些数据分类做了处理,最后让request(是不是response存疑)对象调用使用,这个处理函数就是我们所所得中间插件

中间件原理(app.use的原理):

作用就是把我们用app.use注册的所有中间件和路由方法交给Router类来处理。

中间件分类

应用级中间件 ()
路由级中间件 (router.use, router.get, router.post等)
错误处理中间件 ()
内置中间件 (app.static)
第三方中间件

写过(或者使用过)什么中间件,解决了什么问题

写过也用过一些中间件,例如最常用的路由级中间件,首先根据页面url的不同,走到了不同的路由中间件中;在单页面应用中,还解决了单页面应用刷新时使用history下的rewrites进行重定向的问题。
再比如其他中间件,还用到过解析cookie,记录日志等中间件
自己也写过设置缓存的中间件等

res.setHeader("Expires", nowTime.toUTCString());
res.setHeader("Cache-Control", "max-age=" + maxAge);

3. node的event-loop()

Node 中的 Event loop 和浏览器中的不相同。

Node 的 Event loop 分为6个阶段,它们会按照顺序反复运行

┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<──connections─── │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘

timer
timers 阶段会执行 setTimeout 和 setInterval
一个 timer 指定的时间并不是准确时间,而是在达到这个时间后尽快执行回调,可能会因为系统正在执行别的事务而延迟。
下限的时间有一个范围:[1, 2147483647] ,如果设定的时间不在这个范围,将被设置为1。
I/O
I/O 阶段会执行除了 close 事件,定时器和 setImmediate 的回调
idle, prepare
idle, prepare 阶段内部实现
poll
poll 阶段很重要,这一阶段中,系统会做两件事情

执行到点的定时器
执行 poll 队列中的事件

并且当 poll 中没有定时器的情况下,会发现以下两件事情

如果 poll 队列不为空,会遍历回调队列并同步执行,直到队列为空或者系统限制
如果 poll 队列为空,会有两件事发生

如果有 setImmediate 需要执行,poll 阶段会停止并且进入到 check 阶段执行 setImmediate
如果没有 setImmediate 需要执行,会等待回调被加入到队列中并立即执行回调

如果有别的定时器需要被执行,会回到 timer 阶段执行回调。
check
check 阶段执行 setImmediate
close callbacks
close callbacks 阶段执行 close 事件
并且在 Node 中,有些情况下的定时器执行顺序是随机的

setTimeout(() => {
    console.log('setTimeout');
}, 0);
setImmediate(() => {
    console.log('setImmediate');
})
// 这里可能会输出 setTimeout,setImmediate
// 可能也会相反的输出,这取决于性能
// 因为可能进入 event loop 用了不到 1 毫秒,这时候会执行 setImmediate
// 否则会执行 setTimeout

复制代码当然在这种情况下,执行顺序是相同的

var fs = require('fs')

fs.readFile(__filename, () => {
    setTimeout(() => {
        console.log('timeout');
    }, 0);
    setImmediate(() => {
        console.log('immediate');
    });
});
// 因为 readFile 的回调在 poll 中执行
// 发现有 setImmediate ,所以会立即跳到 check 阶段执行回调
// 再去 timer 阶段执行 setTimeout
// 所以以上输出一定是 setImmediate,setTimeout

复制代码上面介绍的都是 macrotask 的执行情况,microtask 会在以上每个阶段完成后立即执行。

setTimeout(()=>{
    console.log('timer1')

    Promise.resolve().then(function() {
        console.log('promise1')
    })
}, 0)

setTimeout(()=>{
    console.log('timer2')

    Promise.resolve().then(function() {
        console.log('promise2')
    })
}, 0)

// 以上代码在浏览器和 node 中打印情况是不同的
// 浏览器中一定打印 timer1, promise1, timer2, promise2
// node 中可能打印 timer1, timer2, promise1, promise2
// 也可能打印 timer1, promise1, timer2, promise2

复制代码Node 中的 process.nextTick 会先于其他 microtask 执行。

setTimeout(() => {
console.log("timer1");

Promise.resolve().then(function() {
    console.log("promise1");
});
}, 0);

process.nextTick(() => {
console.log("nextTick");
});
// nextTick, timer1, promise1

4. express和koa的对比,各自的优缺点

  1. 体积不同
    Express较大而全,主要基于Connect中间件框架,功能丰富,随取随用,并且框架自身封装了大量便利的功能,比如路由、视图处理等等
    koa体积更小,主要基于co中间件框架,框架自身并没集成太多功能,大部分功能需要用户自行require中间件去解决。

  2. 中间件不同
    (1) 调用方式不同
    express是使用了callback进行回调,koa使用了ES6 generator特性,co框架会把所有generator的返回封装成为Promise对象
    koa的中间件模式与express的是不一样的,koa是洋葱型,express是直线型存疑

5. 洋葱模型

const Koa = require('koa');
const app = new Koa();

// logger

app.use(async (ctx, next) => {
    console.log(1);
    await next();
    console.log(2);
    const rt = ctx.response.get('X-Response-Time');
    console.log(`${ctx.method} ${ctx.url} - ${rt}`);
});

// x-response-time

app.use(async (ctx, next) => {
    console.log(3);
    const start = Date.now();
    await next();
    console.log(4);
    const ms = Date.now() - start;
    ctx.set('X-Response-Time', `${ms}ms`);
});

// response

app.use(async ctx => {
    console.log(5);
    ctx.body = 'Hello World';
});

app.listen(3000);  // 访问localhost:3000 得到 1 3 5 4 2

koa的洋葱模型,在遇到next的时候会把当前中间件压栈,执行下一个中间件,一直到没有next或者next为空,然后在一个个按照先进后出的原则出栈

6. v8引擎相关原理与知识

7. 有过哪些node的原生api,是怎么和php以及java系统交互的

fs、path、http(我们内部封装了h-request的npm包,在请求不同系统时,附带不同的参数)

8. node的守护进程,(node集群是怎么管理的,怎么收集日志的)

node的守护进程
pm2
node集群管理
上线代码会分别在线上node机器上全部上去,然后根据nginx做负载均衡,如果其中一台机器一直重启,在重启到一定次数后会终止重启,然后发邮件以及短信通知。
日志收集
从三个纬度收集日志

  1. 首先用leo收集打到nginx的url,用来收集一些例如url,cookie,referrer等信息,已复现用户的操作路径
  2. 在编写代码时,使用try chach或者if代码块,如果走入异常情况,发送sentry记录日志
  3. node代码中,在异常情况下,同时可以记录更为详细的log在node工程的同级目录下

    9. node怎么上线(上线流程),怎么监控

10. npm install后是怎么执行的

首先根据package.json的dependencies和devDependencies去下载引用的包放在package.json的同级目录node_modules下。如果你没有设置npm的源,那默认到npm的官方网址下载,如果有设置其他源,例如淘宝源或者公司内部源,那就会去设置的源去下载。
npm install -g 下载到全局
npm install –save 保存到dependencies
npm install –save-dev 保存到devDependencies

11. node有哪些定时功能

setTimeout/clearTimeout, setInterval/clearInterval, setImmediate/clearImmediate, process.nextTick

12. fs.watch和fs.watchFile有什么区别,怎么应用?

二者主要用来监听文件变动.fs.watch利用操作系统原生机制来监听,可能不适用网络文件系统; fs.watchFile则是定期检查文件状态变更,适用于网络文件系统,但是相比fs.watch有些慢,因为不是实时机制.

13. 实现一个简单的http服务器

var http = require('http'); // 加载http模块
http.createServer(function(req, res) {
    res.writeHead(200, {'Content-Type': 'text/html'}); // 200代表状态成功, 文档类型是给浏览器识别用的
    res.write('<meta charset="UTF-8"> <h1>我是标题啊!</h1> <font color="red">这么原生,初级的服务器,下辈子能用着吗?!</font>'); // 返回给客户端的html数据
    res.end(); // 结束输出流
}).listen(3000); // 绑定3ooo, 查看效果请访问 http://localhost:3000

14. child-process相关

  1. 为什么需要child-process?
    参考答案: node是异步非阻塞的,这对高并发非常有效.可是我们还有其它一些常用需求,比如和操作系统shell命令交互,调用可执行文件,创建子进程进行阻塞式访问或高CPU计算等,child-process就是为满足这些需求而生的.child-process顾名思义,就是把node阻塞的工作交给子进程去做.
  2. exec,execFile,spawn和fork都是做什么用的?
    参考答案: exec可以用操作系统原生的方式执行各种命令,如管道 cat ab.txt | grep hello; execFile是执行一个文件; spawn是流式和操作系统进行交互; fork是两个node程序(javascript)之间时行交互.
  3. 实现一个简单的命令行交互程序?
    参考答案: 那就用spawn吧.

15. 怎样充分利用多个CPU?

每个进程各使用一个CPU,,以此实现多核CPU的利用。Node提供了child_process模块,并且也提供了fork()方法来实现进程的复制(Node复制进程需要不小于10M的内存和不小于30ms的时间)。
解决方案就是Master-Worker模式(又称为主从模式),通过IPC通道实现主从进程间的通信,通信的目的是为了让不同的进程能够互相访问资源并进行协调工作

16. 程序总是崩溃,怎样找出问题在哪里?

参考答案: 1) node –prof 查看哪些函数调用次数多 2) memwatch和heapdump获得内存快照进行对比,查找内存溢出

17. 有哪些常用方法可以防止程序崩溃?

1) try-catch-finally
2) EventEmitter/Stream error事件处理
3) domain统一控制
4) jshint静态检查
5) jasmine/mocha进行单元测试

18. 怎样调试node程序

node –debug app.js 和node-inspector

19. typeScript的优点(具体举例)

两大特性:

  1. 给JavaScript加上可选的类型系统,很多事情是只有静态类型才能做的,给JavaScript加上静态类型后,就能将调试从运行期提前到编码期,诸如类型检查、越界检查这样的功能才能真正发挥作用。TypeScript的开发体验远远超过以往纯JavaScript的开发体验,无需运行程序即可修复潜在bug。
  2. 另一个特性是支持未来的ES 6甚至ES 7,最近的更新都与此有关。在TypeScript中,你可以直接使用ES 6的最新特性,在编译时它会自动编译到ES 3或ES 5。

优点细节浏览:

  1. TS是一个应用程序级的JavaScript开发语言。
  2. TS是JavaScript的超集,可以编译成纯JavaScript。
  3. TS跨浏览器、跨操作系统、跨主机,开源。
  4. TS始于JS,终于JS。遵循JavaScript的语法和语义,方便了无数的JavaScript开发者。
  5. TS可以重用现有的JavaScript代码,调用流行的JavaScript库。
  6. TS可以编译成简洁、简单的JavaScript代码,在任意浏览器、Node.js或任何兼容ES3的环境上运行。
  7. TypeScript比JavaScript更具开发效率,包括:静态类型检查、基于符号的导航、语句自动完成、代码重构等。
  8. TS提供了类、模块和接口,更易于构建组件。

20. 介绍一下对nodejs的异步IO原理,以及内部线程池相关内容

21. nodejs的进程维护有了解过么

22. nodejs的0秒重载

23. express运行流程与原理

24. 模版引擎是怎么渲染到页面上的

25. express中的路由规则

26. node性能优化

27. 说一下node里对Buffer数据类型的认识,对于初始化的Buffer,可以实现增加长度吗

vue相关试题

1. vue的生命周期及相关概念(哪个周期在干什么)

new vue()

创建vue实例
初始化event和lifecycle

beforeCreate

完成实例初始化,初始化非响应式变量
this指向创建的实例;
可以在这加个loading事件;
data computed watch methods上的方法和数据均不能访问

created

实例创建完成
完成数据(data props computed)的初始化 导入依赖项。
可访问data computed watch methods上的方法和数据
未挂载DOM,不能访问$el,$ref为空数组
可在这结束loading,还做一些初始化,实现函数自执行,
可以对data数据进行操作,可进行一些请求,请求不易过多,避免白屏时间太长。
若在此阶段进行的 DOM 操作一定要放在 Vue.nextTick() 的回调函数中

beforeMount

有了el,编译了template|/outerHTML
能找到对应的template,并编译成render函数

mounted

完成创建vm.$el,和双向绑定,
完成挂载DOM 和渲染;可在mounted钩子对挂载的dom进行操作
即有了DOM 且完成了双向绑定 可访问DOM节点,$ref
可在这发起后端请求,拿回数据,配合路由钩子做一些事情;
可对DOM 进行操作

beforeUpdate

数据更新之前
可在更新前访问现有的DOM,如手动移除添加的事件监听器;

updated

完成虚拟DOM的重新渲染和打补丁;
组件DOM 已完成更新;
可执行依赖的dom 操作
注意:不要在此函数中操作数据,会陷入死循环的。

beforeDistory

在执行app.$destroy()之前
可做一些删除提示,如:你确认删除XX吗?
可用于销毁定时器,解绑全局时间 销毁插件对象

destroyed

当前组件已被删除,销毁监听事件 组件 事件 子实例也被销毁
这时组件已经没有了,你无法操作里面的任何东西了。

keep alive组件中,还有一下两个周期
activated

在使用vue-router时有时需要使用来缓存组件状态,这个时候created钩子就不会被重复调用了,
如果我们的子组件需要在每次加载的时候进行某些操作,可以使用activated钩子触发

deactivated

keep-alive 组件被移除时使用

2. vue-model 怎么实现双向数据绑定

Vue数据双向绑定(即数据响应式),是利用了Object.defineProperty()这个方法重新定义了对象获取属性值get和设置属性值set的操作来实现的。
数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发生变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。

3. 组件之间的通信(传值)有几种实现方式

  1. 父组件向子组件传值props,子组件向父组件传值$emit
  2. $emit/$on
  3. vuex
  4. $attrs/$listeners
  5. provide/inject
  6. $parent / $children与 ref

4. vue和其他框架相比,有什么优缺点

5. vue3.0(相关)和2.0比较,有什么优缺点

Evan You(尤雨溪)在2018年11月16日早上在 Vue Toronto 的主题演讲中预演了 Vue 3.0的新特性 。利用现代浏览器支持的新功能,Vue 3 将成为我们已经了解和喜爱的 Vue.js 强大的的改进版本。

大概可以分为:

  1. 更快
  2. 更小
  3. 更易于维护
  4. 更多的原生支持
  5. 更易于开发使用

1、虚拟 DOM 重写,mounting和patching的速度提高100%
2、更多的编译时的提示来减少运行时的开销
3、组件快速路径+单个调用+子类型检测

  • 跳过不必要的条件分支
  • JS引擎更容易优化
    4、优化插槽的生成
  • 确保实例正确的跟踪依赖关系
  • 避免不必要的父子组件重新渲染
    5、静态树提升
  • 跳过修补整棵树,从而降低渲染成本
  • 即使多次出现也能正常工作
    6、静态属性提升
  • 跳过不会改变节点的修补过程,但是它的子组件会保持修补过程
    7、内联的事件提升
  • 避免因为不同的内联函数标识而导致的不必要的重新渲染
    8、基于Proxy的观察者机制,全语言覆盖+更好的性能
  • 目前vue使用的是Object.defineProperty 的 getter 和 setter
  • 组件实例初始化的速度提高100%
  • 使用Proxy节省以前一半的内存开销,加快速度,但是存在低浏览器版本的不兼容
  • 为了继续支持 IE11,Vue 3 将发布一个支持旧观察者机制和新 Proxy 版本的构建
更小

更友好的tree-shaking
新的core runtime 压缩后大概 10kb

更加可维护

Flow -> TypeScript
包的解耦
编译器重写

- 可插拔的架构
- 提供更强大的IDE支持来作为基础设施
提供更方便的原生支持

运行时内核也将与平台无关,使得 Vue 可以更容易地与任何平台(例如Web,iOS或Android)一起使用

更方便的开发

暴露响应式的api

6. vue里this的指向

在根组件中
this 指向 vue实例
this.$el 指向绑定的根元素

在子组件中
this 指向 Vue 子组件的实例
this.$el 指向 当前组件的DOM元素

7. v-for的时候为什么要使用key,优缺点什么

vue和react都是采用diff算法来对比新旧虚拟节点,从而更新节点。在vue的diff函数中(建议先了解一下diff算法过程)。
在交叉对比中,当新节点跟旧节点头尾交叉对比没有结果时,会根据新节点的key去对比旧节点数组中的key,从而找到相应旧节点(这里对应的是一个key => index 的map映射)。如果没找到就认为是一个新增节点。而如果没有key,那么就会采用遍历查找的方式去找到对应的旧节点。一种一个map映射,另一种是遍历查找。相比而言。map映射的速度更快。

为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性。理想的 key 值是每项都有唯一 id

key只在查找复用节点的时候起到了查找作用

key 的特殊属性主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试修复/再利用相同类型元素的算法。使用 key,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。

8. vue的diff算法

9. vue怎么更新的视图,视图怎么更新vue

10. vue中的on可以监听多个事件嘛

可以。
vm.$on( event, callback )
用法:
监听当前实例上的自定义事件。事件可以由vm.$emit触发。回调函数会接收所有传入事件触发函数的额外参数。($emit和$on必须在一个公共实例上才能触发)

11. vuex相关,(action和mutations有什么区别)

mutations 同步更新store里的state
action 异步更新store里的state

12. vue-router相关(vue-router的生命周期,动态加载,传值,实现原理,全局守卫 https://router.vuejs.org/zh/ https://blog.csdn.net/yelin042/article/details/79932606)

13. keep-alive相关

14. vue的渲染过程,vue组件的渲染过程

15. vue中的data可以是个对象吗,为什么

16. vue里的$this.nextTick();

nextTick 可以让我们在下次 DOM 更新循环结束之后执行延迟回调,用于获得更新后的 DOM。
在 Vue 2.4 之前都是使用的 microtasks,但是 microtasks 的优先级过高,在某些情况下可能会出现比事件冒泡更快的情况,但如果都使用 macrotasks 又可能会出现渲染的性能问题。所以在新版本中,会默认使用 microtasks,但在特殊情况下会使用 macrotasks,比如 v-on。
对于实现 macrotasks ,会先判断是否能使用 setImmediate ,不能的话降级为 MessageChannel ,以上都不行的话就使用 setTimeout

if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
    macroTimerFunc = () => {
        setImmediate(flushCallbacks)
    }
    } else if (
    typeof MessageChannel !== 'undefined' &&
    (isNative(MessageChannel) ||
        // PhantomJS
        MessageChannel.toString() === '[object MessageChannelConstructor]')
    ) {
    const channel = new MessageChannel()
    const port = channel.port2
    channel.port1.onmessage = flushCallbacks
    macroTimerFunc = () => {
        port.postMessage(1)
    }
    } else {
    /* istanbul ignore next */
    macroTimerFunc = () => {
        setTimeout(flushCallbacks, 0)
    }
}

nextTick 同时也支持 Promise 的使用,会判断是否实现了 Promise

export function nextTick(cb?: Function, ctx?: Object) {
    let _resolve
    // 将回调函数整合进一个数组中
    callbacks.push(() => {
        if (cb) {
        try {
            cb.call(ctx)
        } catch (e) {
            handleError(e, ctx, 'nextTick')
        }
        } else if (_resolve) {
        _resolve(ctx)
        }
    })
    if (!pending) {
        pending = true
        if (useMacroTask) {
        macroTimerFunc()
        } else {
        microTimerFunc()
        }
    }
    // 判断是否可以使用 Promise 
    // 可以的话给 _resolve 赋值
    // 这样回调函数就能以 promise 的方式调用
    if (!cb && typeof Promise !== 'undefined') {
        return new Promise(resolve => {
        _resolve = resolve
        })
    }
}

13道可以举一反三的Vue面试题

设计模式

一、创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

二、结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

三、行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

1、工厂方法模式:

定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method 使一个类的实例化延迟到其子类。

工厂模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修改,这违背了闭包原则,所以,从设计角度考虑,有一定的问题,这就用到工厂方法模式。

创建一个工厂接口和创建多个工厂实现类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。

2、抽象工厂模式:

提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。抽象工厂需要创建一些列产品,着重点在于”创建哪些”产品上,也就是说,如果你开发,你的主要任务是划分不同差异的产品线,并且尽量保持每条产品线接口一致,从而可以从同一个抽象工厂继承。
3、单例模式:

单例对象(Singleton)是一种常用的设计模式。在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。这样的模式有几个好处:

(1)某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。

(2)省去了new操作符,降低了系统内存的使用频率,减轻GC压力。

(3)有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。

4、建造者模式:

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

5、原型模式:

原型模式虽然是创建型的模式,但是与工程模式没有关系,从名字即可看出,该模式的思想就是将一个对象作为原型,对其进行复制、克隆,产生一个和原对象类似的新对象。本小结会通过对象的复制,进行讲解。在Java中,复制对象是通过clone()实现的,先创建一个原型类。

6、适配器模式:

适配器模式将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题。主要分为三类:类的适配器模式、对象的适配器模式、接口的适配器模式。

7、装饰器模式:

顾名思义,装饰模式就是给一个对象增加一些新的功能,而且是动态的,要求装饰对象和被装饰对象实现同一个接口,装饰对象持有被装饰对象的实例。

8、代理模式:

代理模式就是多一个代理类出来,替原对象进行一些操作,比如我们在租房子的时候回去找中介,为什么呢?因为你对该地区房屋的信息掌握的不够全面,希望找一个更熟悉的人去帮你做,此处的代理就是这个意思。

9、外观模式:

外观模式是为了解决类与类之家的依赖关系的,像spring一样,可以将类和类之间的关系配置到配置文件中,而外观模式就是将他们的关系放在一个Facade类中,降低了类类之间的耦合度,该模式中没有涉及到接口。

10、桥接模式:

桥接模式就是把事物和其具体实现分开,使他们可以各自独立的变化。桥接的用意是:将抽象化与实现化解耦,使得二者可以独立变化,像我们常用的JDBC桥DriverManager一样。

JDBC进行连接数据库的时候,在各个数据库之间进行切换,基本不需要动太多的代码,甚至丝毫不用动,原因就是JDBC提供统一接口,每个数据库提供各自的实现,用一个叫做数据库驱动的程序来桥接就行了。

11、组合模式:

组合模式有时又叫部分-整体模式在处理类似树形结构的问题时比较方便。使用场景:将多个对象组合在一起进行操作,常用于表示树形结构中,例如二叉树,数等。

12、享元模式:

享元模式的主要目的是实现对象的共享,即共享池,当系统中对象多的时候可以减少内存的开销,通常与工厂模式一起使用。

13、策略模式:

策略模式定义了一系列算法,并将每个算法封装起来,使其可以相互替换,且算法的变化不会影响到使用算法的客户。需要设计一个接口,为一系列实现类提供统一的方法,多个实现类实现该接口,设计一个抽象类(可有可无,属于辅助类),提供辅助函数。

14、模板方法模式:

一个抽象类中,有一个主方法,再定义1…n个方法,可以是抽象的,也可以是实际的方法,定义一个类,继承该抽象类,重写抽象方法,通过调用抽象类,实现对子类的调用。

15、观察者模式:
观察者模式很好理解,类似于邮件订阅和RSS订阅,当我们浏览一些博客或wiki时,经常会看到RSS图标,就这的意思是,当你订阅了该文章,如果后续有更新,会及时通知你。

其实,简单来讲就一句话:当一个对象变化时,其它依赖该对象的对象都会收到通知,并且随着变化!对象之间是一种一对多的关系。

16、迭代子模式:

顾名思义,迭代器模式就是顺序访问聚集中的对象,一般来说,集合中非常常见,如果对集合类比较熟悉的话,理解本模式会十分轻松。这句话包含两层意思:一是需要遍历的对象,即聚集对象,二是迭代器对象,用于对聚集对象进行遍历访问。

17、责任链模式:

责任链模式,有多个对象,每个对象持有对下一个对象的引用,这样就会形成一条链,请求在这条链上传递,直到某一对象决定处理该请求。但是发出者并不清楚到底最终那个对象会处理该请求,所以,责任链模式可以实现,在隐瞒客户端的情况下,对系统进行动态的调整。

18、命令模式:

命令模式的目的就是达到命令的发出者和执行者之间解耦,实现请求和执行分开。

19、备忘录模式:

主要目的是保存一个对象的某个状态,以便在适当的时候恢复对象,个人觉得叫备份模式更形象些,通俗的讲下:假设有原始类A,A中有各种属性,A可以决定需要备份的属性,备忘录类B是用来存储A的一些内部状态,类C呢,就是一个用来存储备忘录的,且只能存储,不能修改等操作。

20、状态模式:

状态模式在日常开发中用的挺多的,尤其是做网站的时候,我们有时希望根据对象的某一属性,区别开他们的一些功能,比如说简单的权限控制等。

21、访问者模式:

访问者模式把数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由地演化。访问者模式适用于数据结构相对稳定算法又易变化的系统。因为访问者模式使得算法操作增加变得容易。

若系统数据结构对象易于变化,经常有新的数据对象增加进来,则不适合使用访问者模式。访问者模式的优点是增加操作很容易,因为增加操作意味着增加新的访问者。访问者模式将有关行为集中到一个访问者对象中,其改变不影响系统数据结构。其缺点就是增加新的数据结构很困难。

22、中介者模式:

中介者模式也是用来降低类类之间的耦合的,因为如果类类之间有依赖关系的话,不利于功能的拓展和维护,因为只要修改一个对象,其它关联的对象都得进行修改。

如果使用中介者模式,只需关心和Mediator类的关系,具体类类之间的关系及调度交给Mediator就行,这有点像spring容器的作用。

23、解释器模式:

解释器模式一般主要应用在OOP开发中的编译器的开发中,所以适用面比较窄。

flex指南

flex,即‘弹性布局’,任何盒模型都可以用

.box {
    display: flex;
}

行内元素也可以使用 Flex 布局

.box {
    display: inline-flex;
}

Webkit 内核的浏览器,需要加上-webkit(兼容ios8)

.box{
    display: -webkit-flex; /* Safari */
    display: flex;
}

flex有两条轴,分别是主轴(水平轴 main axis)和交叉轴(竖直轴 cross axis);

容器的属性

1) flex-direction 决定主轴的方向

  • row // 主轴为水平方向,起点在左端
  • row-reverse // 主轴为水平方向,起点在右端
  • column // 主轴为垂直方向,起点在上沿
  • column-reverse // 主轴为垂直方向,起点在下沿

2) flex-warp 如果一条轴线排不下,如何换行

  • nowrap // 不换行(默认)
  • wrap // 换行,第一行在上方
  • wrap-reverse // 换行,第一行在下方

3) flex-flow 属性是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap

.box {
    flex-flow: <flex-direction> || <flex-wrap>;
}

4) justify-content 属性定义了项目在主轴上的对齐方式

  • flex-start 靠左对齐
  • flex-end 靠右对齐
  • center 居中
  • space-between 均匀的间隔,(两头不留空隙)
  • space-around 均匀的间隔,两头留空隙,项目之间的间隔比项目与边框的间隔大一倍

5) align-items 交叉轴上如何对齐

  • flex-start 靠上对齐
  • flex-end 靠下对齐
  • center 居中
  • baseline 项目的第一行文字的基线对齐
  • stretch (默认值):如果项目未设置高度或设为auto,将占满整个容器的高度。

6) align-content 属性定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。

  • flex-start 与交叉轴的起点对齐
  • flex-end 与交叉轴的终点对齐
  • center 与交叉轴的中点对齐
  • space-between 与交叉轴两端对齐,轴线之间的间隔平均分布
  • space-around 每根轴线两侧的间隔都相等。所以,轴线之间的间隔比轴线与边框的间隔大一倍。
  • stretch (默认值):轴线占满整个交叉轴。

项目的属性(里面的item)

1) order 属性定义项目的排列顺序。数值越小,排列越靠前,默认为0

.item {
    order: <integer>;
}

2) flex-grow 属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。

3) flex-shrink 属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小 (如果一个项目的flex-shrink属性为0,其他项目都为1,则空间不足时,前者不缩小。)
4) flex-basis 属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小
5) flex 属性是flex-grow(元素水平方向在哪,越小越靠前), flex-shrink (元素等比缩小)和 flex-basis(元素的宽度)的简写,默认值为0 1 auto。后两个属性可选。
6) align-self 属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。

es7,es8,es9,es10新特性

es7的新特性:

新增特性:

Array.prototype.includes()
**

1) Array.prototype.includes()

用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。

参数

接收两个参数

1.要搜索的值
2.搜索的开始索引

与indexof的区别

1.返回值不同,indexof返回是值型的,includes返回值是布尔型的
2.NaN的判断,如果数组中有NaN,indexof无法判断出来,但是includes可以
3.当数组的有空的值的时候,includes会认为空的值是undefined,而indexOf不会

2) ** 求幂运算符

可以使用**来替代Math.pow。

4 ** 3  // 64
Math.pow(4,3) // 64

但是,**还支持以下操作

let n = 4;
n **= 3;
// 64

es8 新特性

主要新功能:

新增特性

主要特性

异步函数 Async Functions
共享内存和Atomics

次要特性

Object.values / Object.entries
String padding
Object.getOwnPropertyDescriptors()
函数参数列表和调用中的尾逗号

es9 新特性

新增特性

主要特性

异步迭代
Rest/Spread 属性

新的正则表达式功能

RegExp named capture groups
RegExp Unicode Property Escapes
RegExp Lookbehind Assertions
s (dotAll) flag for regular expressions

其他新功能

Promise.prototype.finally()
模板字符串修改