js面试题总结

主要内容包括:数据类型,变量和常量,数组和常用方法,本地存储,关于异步处理:Generator   async   promise  的区别,关于ES6兼容与优化

数据类型

1,介绍js的基本数据类型。    

Undefined、Null、Boolean、Number、String

2,类型判断用到哪些方法?

typeof

typeof xxx得到的值有以下几种类型:undefined boolean number string object function、symbol ,比较简单,不再一一演示了。这里需要注意的有三点:

* typeof null结果是object ,实际这是typeof的一个bug,null是原始值,非引用类型

* typeof [1, 2]结果是object,结果中没有array这一项,引用类型除了function其他的全部都是object

* typeof Symbol() 用typeof获取symbol类型的值得到的是symbol,这是 ES6 新增的知识点

instanceof

用于实例和构造函数的对应。例如判断一个变量是否是数组,使用typeof无法判断,但可以使用[1, 2] instanceof Array来判断。因为,[1, 2]是数组,它的构造函数就是Array。同理:

function Foo(name) {

   this.name = name

}

var foo = new Foo('bar’)

console.log(foo instanceof Foo) // true

3,JavaScript有几种类型的值?,你能画一下他们的内存图吗?

栈:原始数据类型(Undefined,Null,Boolean,Number、String)

堆:引用数据类型(对象、数组和函数)

两种类型的区别是:存储位置不同;

1,原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;

2,引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定,如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体

在参数传递方式上,原始类型是按值传递,引用类型是按共享传递

JS 中这种设计的原因是:按值传递的类型,复制一份存入栈内存,这类类型一般不占用太多内存,而且按值传递保证了其访问速度。按共享传递的类型,是复制其引用,而不是整个复制其值(C 语言中的指针),保证过大的对象等不会因为不停复制内容而造成内存的浪费。

4,介绍js有哪些内置对象?    

Object 是 JavaScript 中所有对象的父对象  

数据封装类对象:Object、Array、Boolean、Number 和 String    

其他对象:Function、Arguments、Math、Date、RegEx、Error

5,如何区分数组和对象?    

1、从原型入手,Array.prototype.isPrototypeOf(obj);  利用isPrototypeOf()方法,判定Array是不是在obj的原型链中,如果是,则返回true,否则false。Array.prototype.isPrototype([]) //true

2、也可以从构造函数入手,利用对向的constructor属性

3、根据对象的class属性(类属性),跨原型链调用toString()方法。Object.prototype.toString.call(Window);

4、Array.isArray()方法。

6,null,undefined 的区别?

null        表示一个对象被定义了,值为“空值”;

undefined   表示不存在这个值。

typeof undefined //"undefined"

undefined :是一个表示"无"的原始值或者说表示"缺少值",就是此处应该有一个值,但是还没有定义。当尝试读取时会返回 undefined;

例如变量被声明了,但没有赋值时,就等于undefined

typeof null //"object"

null : 是一个对象(空对象, 没有任何属性和方法);

例如作为函数的参数,表示该函数的参数不是对象;

注意:

在验证null时,一定要使用 === ,因为 == 无法分别 null 和 undefined

undefined表示"缺少值",就是此处应该有一个值,但是还没有定义。典型用法是:

1)变量被声明了,但没有赋值时,就等于undefined。

2) 调用函数时,应该提供的参数没有提供,该参数等于undefined。

3)对象没有赋值的属性,该属性的值为undefined。

4)函数没有返回值时,默认返回undefined。

null表示"没有对象",即该处不应该有值。典型用法是:

1) 作为函数的参数,表示该函数的参数不是对象。

2) 作为对象原型链的终点。

7,数组和对象有哪些原生方法,列举一下?

 数组的常用方法:

arr.push,arr.unshift,arr.pop,arr.shilt,arr.slice,arr.splice,arr.concat

 对象的常用方法

obj.assign(),

8,字符串有哪些原生方法,列举一下?

charAt() 返回在指定位置的字符。
charCodeAt() 返回在指定的位置的字符的 Unicode 编码。
concat() 连接字符串。
indexOf() 检索字符串。
match() 找到一个或多个正则表达式的匹配。
replace() 替换与正则表达式匹配的子串。
search() 检索与正则表达式相匹配的值。
slice() 提取字符串的片断,并在新的字符串中返回被提取的部分。
split() 把字符串分割为字符串数组。
toLocaleLowerCase() 把字符串转换为小写。
toLocaleUpperCase() 把字符串转换为大写。
toLowerCase() 把字符串转换为小写。
toUpperCase() 把字符串转换为大写。
substr() 从起始索引号提取字符串中指定数目的字符。
substring() 提取字符串中两个指定的索引号之间的字符。

9,声明变量和声明函数的提升有什么区别?

(1) 变量声明提升:变量申明在进入执行上下文就完成了。

只要变量在代码中进行了声明,无论它在哪个位置上进行声明, js引擎都会将它的声明放在范围作用域的顶部;

(2) 函数声明提升:执行代码之前会先读取函数声明,意味着可以把函数申明放在调用它的语句后面。

只要函数在代码中进行了声明,无论它在哪个位置上进行声明, js引擎都会将它的声明放在范围作用域的顶部;

(3) 变量or函数声明:函数声明会覆盖变量声明,但不会覆盖变量赋值。

同一个名称标识a,即有变量声明var a,又有函数声明function a() {},不管二者声明的顺序,函数声明会覆盖变量声明,也就是说,此时a的值是声明的函数function a() {}。注意:如果在变量声明的同时初始化a,或是之后对a进行赋值,此时a的值变量的值。eg: var a; var c = 1; a = 1; function a() { return true; } console.log(a);

10,== 和 === 的区别 

 1.===:三个等号我们称为等同符,当等号两边的值为相同类型的时候,直接比较等号两边的值,值相同则返回true,若等号两边的值类型不同时直接返回false。

     例:

100===“100”   //返回false
abc===“abc”   //返回false
 ‘abc’===“abc”  //返回true
NaN===NaN   //返回false
 false===false  //返回true

2.==:两个等号我们称为等值符,当等号两边的值为相同类型时比较值是否相同,类型不同时会发生类型的自动转换,转换为相同的类型后再作比较。
类型转换规则:

1)如果等号两边是boolean、string、number三者中任意两者进行比较时,优先转换为数字进行比较。
 2)如果等号两边出现了null或undefined,null和undefined除了和自己相等,就彼此相等
     例:

 100==“100”    //返回true
 1==true          //返回true
“1”==“01”      //返回false,此处等号两边值得类型相同,不要再转换类型了!!
 NaN==NaN  //返回false,NaN和所有值包括自己都不相等。 

关于变量和常量

let

let 用来声明变量,类似于变量,但是所声明的变量,只在let命令所在的代码块内有效

需要注意的地方:

1.不存在变量提升,未声明直接报错

2.暂时性死区

3.for循环具有两个作用域,外面的变量和里面的变量互不干扰

const

用来声明一个只读的常量,一旦尚明,常量的值就不可以改变了,而且声明的时候必须赋值

需要注意的地方:

引用类型储存的是一个地址,所以用const声明的引用数据类型,只要不改变指针地址,就可以

如果为常量赋其他的值,就会报错。

关于数组

Array.from(ES6)

用于将类似于数组的对象和可遍历的对象转为真正的数组

应用场景:DOM操作返回的数组,函数内部参数的集合,

原理,只要含有length属性,就都可以转换

Array.from({length:3})//[undefined,undefined,undefined];

如何处理兼容:

Array.from?Array.from:obj=>[].slice.call(obj);

Array.of(ES6)

用于将数值,转换为数组

参数里面直接放数组就可以了。

可以用来代替Array();

如果没有参数,就返回一个空数组

arr.find(ES6)//按条件查找

用于找到第一个符合条件的数组成员,参数是一个回调函数,返回值是条件,返回第一个为true的成员,如果没有,就返回undefined

回调函数的三个参数分别为value,index,arr

arr.findIndex(ES6)//按条件查找

返回第一个符合条件的数组成员的位置,如果都不符合,返回-1,和find类似

注意:

这两个方法解决了NaN的问题

arr.fill(es6)//填充数组

使用特定值,填充数组

如果只有一个参数,参数里面的值会填充整个数组

还可以接收第二个和第三个参数,用于指定填充的开始文职和结束文职

(不是下标)

注意:如果填充类型为对象,被赋值的只是对象的地址(浅拷贝)

arr.entries(),arr.keys(),arr.values()//遍历数组ES6

分别是遍历键值对,键名,值的遍历

遍历的方式有两种for  of 和next手动遍历

[1,2,3].keys().next()

for(let index of arr.keys()){

console.log(index);

}

arr.includes()//ES6

返回一个布尔值,检测数组中有没有给定的值

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

数组的空位:

ES6会将空位转换为undefined

数组的深拷贝

const a1 = [1,2];

1.const a2 = [...a1];

2.const [...a2] = a1;

数组的合并

1.arr1.concat(arr2,arr3);

2.[...arr1,...arr2,...arr3];

需要注意的地方:

如果将扩展运算符用于数组赋值,只能放在参数的最后一位,不然会报错

arr.join()

join(separator): 将数组的元素组起一个字符串,以separator为分隔符,省略的话则用默认用逗号为分隔符,该方法只接收一个参数:即分隔符。

1

2

3

4

var arr = [1,2,3];

console.log(arr.join()); // 1,2,3

console.log(arr.join("-")); // 1-2-3

console.log(arr); // [1, 2, 3](原数组不变)

通过join()方法可以实现重复字符串,只需传入字符串以及重复的次数,就能返回重复后的字符串,函数如下:

?

1

2

3

4

5

function repeatString(str, n) {

return new Array(n + 1).join(str);

}

console.log(repeatString("abc", 3)); // abcabcabc

console.log(repeatString("Hi", 5)); // HiHiHiHiHi

arr.push()和arr.pop()

push(): 可以接收任意数量的参数,把它们逐个添加到数组末尾,并返回修改后数组的长度。 

pop():数组末尾移除最后一项,减少数组的 length 值,然后返回移除的项。

?

1

2

3

4

5

6

7

var arr = ["Lily","lucy","Tom"];

var count = arr.push("Jack","Sean");

console.log(count); // 5

console.log(arr); // ["Lily", "lucy", "Tom", "Jack", "Sean"]

var item = arr.pop();

console.log(item); // Sean

console.log(arr); // ["Lily", "lucy", "Tom", "Jack"]

arr.shift() 和 arr.unshift()

shift():删除原数组第一项,并返回删除元素的值;如果数组为空则返回undefined 。 

unshift:将参数添加到原数组开头,并返回数组的长度 。

这组方法和上面的push()和pop()方法正好对应,一个是操作数组的开头,一个是操作数组的结尾。

?

1

2

3

4

5

6

7

var arr = ["Lily","lucy","Tom"];

var count = arr.unshift("Jack","Sean");

console.log(count); // 5

console.log(arr); //["Jack", "Sean", "Lily", "lucy", "Tom"]

var item = arr.shift();

console.log(item); // Jack

console.log(arr); // ["Sean", "Lily", "lucy", "Tom"]

arr.sort()

sort():按升序排列数组项——即最小的值位于最前面,最大的值排在最后面。

在排序时,sort()方法会调用每个数组项的 toString()转型方法,然后比较得到的字符串,以确定如何排序。即使数组中的每一项都是数值, sort()方法比较的也是字符串,因此会出现以下的这种情况:

?

1

2

3

4

5

var arr1 = ["a", "d", "c", "b"];

console.log(arr1.sort()); // ["a", "b", "c", "d"]

arr2 = [13, 24, 51, 3];

console.log(arr2.sort()); // [13, 24, 3, 51]

console.log(arr2); // [13, 24, 3, 51](元数组被改变)

为了解决上述问题,sort()方法可以接收一个比较函数作为参数,以便我们指定哪个值位于哪个值的前面。比较函数接收两个参数,如果第一个参数应该位于第二个之前则返回一个负数,如果两个参数相等则返回 0,如果第一个参数应该位于第二个之后则返回一个正数。以下就是一个简单的比较函数:

?

1

2

3

4

5

6

7

8

9

10

11

function compare(value1, value2) {

if (value1 < value2) {

return -1;

} else if (value1 > value2) {

return 1;

} else {

return 0;

}

}

arr2 = [13, 24, 51, 3];

console.log(arr2.sort(compare)); // [3, 13, 24, 51]

如果需要通过比较函数产生降序排序的结果,只要交换比较函数返回的值即可:

?

1

2

3

4

5

6

7

8

9

10

11

function compare(value1, value2) {

if (value1 < value2) {

return 1;

} else if (value1 > value2) {

return -1;

} else {

return 0;

}

}

arr2 = [13, 24, 51, 3];

console.log(arr2.sort(compare)); // [51, 24, 13, 3]

arr.reverse()

reverse():反转数组项的顺序。

?

1

2

3

var arr = [13, 24, 51, 3];

console.log(arr.reverse()); //[3, 51, 24, 13]

console.log(arr); //[3, 51, 24, 13](原数组改变)

arr.concat()

concat() :将参数添加到原数组中。这个方法会先创建当前数组一个副本,然后将接收到的参数添加到这个副本的末尾,最后返回新构建的数组。在没有给 concat()方法传递参数的情况下,它只是复制当前数组并返回副本。

?

1

2

3

4

var arr = [1,3,5,7];

var arrCopy = arr.concat(9,[11,13]);

console.log(arrCopy); //[1, 3, 5, 7, 9, 11, 13]

console.log(arr); // [1, 3, 5, 7](原数组未被修改)

从上面测试结果可以发现:传入的不是数组,则直接把参数添加到数组后面,如果传入的是数组,则将数组中的各个项添加到数组中。但是如果传入的是一个二维数组呢?

?

1

2

3

var arrCopy2 = arr.concat([9,[11,13]]);

console.log(arrCopy2); //[1, 3, 5, 7, 9, Array[2]]

console.log(arrCopy2[5]); //[11, 13]

上述代码中,arrCopy2数组的第五项是一个包含两项的数组,也就是说concat方法只能将传入数组中的每一项添加到数组中,如果传入数组中有些项是数组,那么也会把这一数组项当作一项添加到arrCopy2中。

arr.slice()

slice():返回从原数组中指定开始下标到结束下标之间的项组成的新数组。slice()方法可以接受一或两个参数,即要返回项的起始和结束位置。在只有一个参数的情况下, slice()方法返回从该参数指定位置开始到当前数组末尾的所有项。如果有两个参数,该方法返回起始和结束位置之间的项——但不包括结束位置的项。

?

1

2

3

4

5

6

7

8

9

10

var arr = [1,3,5,7,9,11];

var arrCopy = arr.slice(1);

var arrCopy2 = arr.slice(1,4);

var arrCopy3 = arr.slice(1,-2);

var arrCopy4 = arr.slice(-4,-1);

console.log(arr); //[1, 3, 5, 7, 9, 11](原数组没变)

console.log(arrCopy); //[3, 5, 7, 9, 11]

console.log(arrCopy2); //[3, 5, 7]

console.log(arrCopy3); //[3, 5, 7]

console.log(arrCopy4); //[5, 7, 9]

arrCopy只设置了一个参数,也就是起始下标为1,所以返回的数组为下标1(包括下标1)开始到数组最后。 

arrCopy2设置了两个参数,返回起始下标(包括1)开始到终止下标(不包括4)的子数组。 

arrCopy3设置了两个参数,终止下标为负数,当出现负数时,将负数加上数组长度的值(6)来替换该位置的数,因此就是从1开始到4(不包括)的子数组。 

arrCopy4中两个参数都是负数,所以都加上数组长度6转换成正数,因此相当于slice(2,5)。

arr.splice()

splice():很强大的数组方法,它有很多种用法,可以实现删除、插入和替换。

删除:可以删除任意数量的项,只需指定 2 个参数:要删除的第一项的位置和要删除的项数。例如, splice(0,2)会删除数组中的前两项。

插入:可以向指定位置插入任意数量的项,只需提供 3 个参数:起始位置、 0(要删除的项数)和要插入的项。例如,splice(2,0,4,6)会从当前数组的位置 2 开始插入4和6。

替换:可以向指定位置插入任意数量的项,且同时删除任意数量的项,只需指定 3 个参数:起始位置、要删除的项数和要插入的任意数量的项。插入的项数不必与删除的项数相等。例如,splice (2,1,4,6)会删除当前数组位置 2 的项,然后再从位置 2 开始插入4和6。

splice()方法始终都会返回一个数组,该数组中包含从原始数组中删除的项,如果没有删除任何项,则返回一个空数组。

?

1

2

3

4

5

6

7

8

9

10

var arr = [1,3,5,7,9,11];

var arrRemoved = arr.splice(0,2);

console.log(arr); //[5, 7, 9, 11]

console.log(arrRemoved); //[1, 3]

var arrRemoved2 = arr.splice(2,0,4,6);

console.log(arr); // [5, 7, 4, 6, 9, 11]

console.log(arrRemoved2); // []

var arrRemoved3 = arr.splice(1,1,2,4);

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

console.log(arrRemoved3); //[7]

arr.indexOf()和 arr.lastIndexOf() (ES5新增)

indexOf():接收两个参数:要查找的项和(可选的)表示查找起点位置的索引。其中, 从数组的开头(位置 0)开始向后查找。 

lastIndexOf:接收两个参数:要查找的项和(可选的)表示查找起点位置的索引。其中, 从数组的末尾开始向前查找。

这两个方法都返回要查找的项在数组中的位置,或者在没找到的情况下返回1。在比较第一个参数与数组中的每一项时,会使用全等操作符。

?

1

2

3

4

5

6

var arr = [1,3,5,7,7,5,3,1];

console.log(arr.indexOf(5)); //2

console.log(arr.lastIndexOf(5)); //5

console.log(arr.indexOf(5,2)); //2

console.log(arr.lastIndexOf(5,4)); //2

console.log(arr.indexOf("5")); //-1

arr.forEach() (ES5新增)

forEach():对数组进行遍历循环,对数组中的每一项运行给定函数。这个方法没有返回值。参数都是function类型,默认有传参,参数分别为:遍历的数组内容;第对应的数组索引,数组本身。

?

1

2

3

4

5

6

7

8

9

10

var arr = [1, 2, 3, 4, 5];

arr.forEach(function(x, index, a){

console.log(x + '|' + index + '|' + (a === arr));

});

// 输出为:

// 1|0|true

// 2|1|true

// 3|2|true

// 4|3|true

// 5|4|true

arr.map() (ES5新增)

map():指“映射”,对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。

下面代码利用map方法实现数组中每个数求平方。

?

1

2

3

4

5

var arr = [1, 2, 3, 4, 5];

var arr2 = arr.map(function(item){

return item*item;

});

console.log(arr2); //[1, 4, 9, 16, 25]

arr.filter() (ES5新增)

filter():“过滤”功能,数组中的每一项运行给定函数,返回满足过滤条件组成的数组。

?

1

2

3

4

5

var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

var arr2 = arr.filter(function(x, index) {

return index % 3 === 0 || x >= 8;

});

console.log(arr2); //[1, 4, 7, 8, 9, 10]

arr.every() (ES5新增)

every():判断数组中每一项都是否满足条件,只有所有项都满足条件,才会返回true。

?

1

2

3

4

5

6

7

8

9

var arr = [1, 2, 3, 4, 5];

var arr2 = arr.every(function(x) {

return x < 10;

});

console.log(arr2); //true

var arr3 = arr.every(function(x) {

return x < 3;

});

console.log(arr3); // false

arr.some() (ES5新增)

some():判断数组中是否存在满足条件的项,只要有一项满足条件,就会返回true。

?

1

2

3

4

5

6

7

8

9

var arr = [1, 2, 3, 4, 5];

var arr2 = arr.some(function(x) {

return x < 3;

});

console.log(arr2); //true

var arr3 = arr.some(function(x) {

return x < 1;

});

console.log(arr3); // false

arr.reduce()和 arr.reduceRight() (ES5新增)

这两个方法都会实现迭代数组的所有项,然后构建一个最终返回的值。reduce()方法从数组的第一项开始,逐个遍历到最后。而 reduceRight()则从数组的最后一项开始,向前遍历到第一项。

这两个方法都接收两个参数:一个在每一项上调用的函数和(可选的)作为归并基础的初始值。

传给 reduce()和 reduceRight()的函数接收 4 个参数:前一个值、当前值、项的索引和数组对象。这个函数返回的任何值都会作为第一个参数自动传给下一项。第一次迭代发生在数组的第二项上,因此第一个参数是数组的第一项,第二个参数就是数组的第二项。

下面代码用reduce()实现数组求和,数组一开始加了一个初始值10。

?

1

2

3

4

5

var values = [1,2,3,4,5];

var sum = values.reduceRight(function(prev, cur, index, array){

return prev + cur;

},10);

console.log(sum); //25

Array.isArray(obj)

是否属于数组,返回值是布尔值,如果是数组则返回true

可代替 obj instanceof Array

可以改变原数组的方法总共有7种:包括unshift()、shift()、push()、pop()这4种栈和队列方法,reverse()和sort()这2种数组排列方法,数组删改方法splice()

关于本地存储和会话存储

常用浏览器存储方案有,cookie,session,localstorage,sessionstroage

cookie

cookie 本身不是用来做服务器端存储的,它是设计用来在服务器和客户端进行信息传递的,因此我们的每个 HTTP 请求都带着 cookie。但是 cookie 也具备浏览器端存储的能力(例如记住用户名和密码,也就是常用的登录功能),因此就被开发者用上了。

使用起来也非常简单,document.cookie = ....即可。

cookie前端的用法,获取的时候

document.cookie

设置的时候

document.cookie = 'key' +'='+value;

封装了一个方法用来直接获取cookie的值

 function getCookie(name) { //获取指定名称的cookie值

// (^| )name=([^;]*)(;|$),match[0]为与整个正则表达式匹配的字符串,match[i]为正则表达式捕获数组相匹配的数组;

var arr = document.cookie.match(new RegExp("(^| )"+name+"=([^;]*)(;|$)"));

if(arr != null) {

  console.log(arr);

  return unescape(arr[2]);

}

return null;

}

 var cookieData=getCookie('token'); //cookie赋值给变量。

cookie后台的用法

cookies设置值的时候是res.cookie(key,value);

{credentials: 'include'}//储存的时候需要在fetch请求上加上这句话,feitch默认的是拒绝cookies

获取值的时候req.cookies.key;

但是 cookie 有它致命的缺点:

存储量太小,只有 4KB

所有 HTTP 请求都带着,会影响获取资源的效率

API 简单,需要封装才能用

所有的api请求都会携带cookie,所以cookie不太安全,使用的时候一般都需要做加密处理

session

session是服务端的会话存储技术,他的生存周期只是保持在浏览器打开,浏览器关闭这个阶段之中

在Session被创建之后,就可以调用Session相关的方法往Session中增加内容了,而这些内容只会保存在服务器中,发到客户端的只有Session id;当客户端再次发送请求的时候,会将这个Session id带上,服务器接受到请求之后就会依据Session id找到相应的Session,从而再次使用之。正式这样一个过程,用户的状态也就得以保持了。

在使用session的时候我们一般都保存一些临时数据,常见的需求一般就是获取验证码

locationStorage 和 sessionStorage

后来,HTML5 标准就带来了sessionStorage和localStorage,先拿localStorage来说,它是专门为了浏览器端缓存而设计的。其优点有:

存储量增大到 5MB

不会带到 HTTP 请求中

API 适用于数据存储 localStorage.setItem(key, value) localStorage.getItem(key)

sessionStorage的区别就在于它是根据 session 过去时间而实现,而localStorage会永久有效,应用场景不同。例如,一些需要及时失效的重要信息放在sessionStorage中,一些不重要但是不经常设置的信息,放在localStorage中。

但是sessionstroage有个致命的缺点,就是无法多标签页共享,cookie可以多标签共享和设置过期时间

关于异步处理:Generator   async   promise  的区别

Generator async promise这三个api都是es6新增api,也都可以理解为异步的解决方案,但是他们的适用场景不一样

先说Generator:

简单点可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。

执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。

形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态

而Generator在适用场景上我们可以把它理解为任务的挂起,在js中我们使用没有任务挂起这个概念的,直到Generator的出现,在Generator的使用上我们通过yield定义不同的状态,执行的时候需要使用next进行不同状态的调用,但是他是从上到下的,也就是只要需要yield就会停止执行后面的语句,调用next后继续执行后面的语句,在实际项目开发中,我经常会遇到这样的需求,填写用户信息的时候一般都是分成几步,这时候我们就可以利用Generator的任务挂起特性进行,不同步骤的数据存储。

然后就是promise

promise解决了传统异步操作回调函数更加合理强大,有了promise就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。

例如我常见的一个需求

有两个接口A,B, 他们之间有依赖关系,B接口必须等到A接口的值返回成功后,拿到A接口里面的一个属性才能请求,如果利用之前的回调函数形势的话就得是层层嵌套,代码维护比较麻烦,如果利用promise的话我们可以用链式调用的形势直观的表示出这种关系

而Async是Generator函数的语法糖,Async的出现主要是为了解决另外一个问题,刚刚说了Generator是解决任务的挂起,promise是解决异步回调问题,而async就是吧异步的操作,变成队列模式,

因为async中使用await做状态的定义,调用的时候不需要next(),自动执行,并且会讲每个await中promise中resolve结果赋值给await的变量上以供后面的步骤使用,每一个await都会等到promise返回结果后才会继续自动往下执行,这样就实现了我在日常生活中排队执行的概念,将所有的异步任务以同步的方式定义,不需要担心那个快那个慢,因为她是一个一个自动向下的

关于ES6

1,说说对es6的理解(说一下es6,知道es6吗)

语法糖(箭头函数,类的定义,继承),以及一些新的扩展(数组,字符串,对象,方法等),对作用域的重新定义,以及异步编程的解决方案(promise,async,await)、解构赋值的出现

2,ES6常用特性

变量定义(let和const,可变与不可变,const定义对象的特殊情况)

解构赋值

模板字符串

数组新API(例:Array.from(),entries(),values(),keys())

箭头函数(rest参数,扩展运算符,::绑定this)

Set和Map数据结构(set实例成员值唯一存储key值,map实例存储键值对(key-value))

Promise对象(前端异步解决方案进化史,generator函数,async函数)

Class语法糖(super关键字)

                                             

3,说说你对Promise的理解

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件监听——更合理和更强大。Promise 有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。但是无法获取到pending状态,在promise中接受两个内置参数分别是resolve(成功)和reject(失败),Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。then方法可以传递两个回调函数第一个是成功,第二个是失败,失败回调也可以使用promise的catch方法回调,promise还有一个强大的功能那就是all方法可以组合多个promise实例,包装成一个新的 Promise 实例。

4,介绍一下async和await;

async 会将其后的函数(函数表达式或 Lambda)的返回值封装成一个 Promise 对象,而 await 会等待这个 Promise 完成,并将其 resolve 的结果返回出来。

async / await是ES7的重要特性之一,也是目前社区里公认的优秀异步解决方案。目前async / await 在 IE edge中已经可以直接使用了,但是chrome和Node.js还没有支持。幸运的是,babel已经支持async的transform了,所以我们使用的时候引入babel就行。在开始之前我们需要引入以下的package,preset-stage-3里就有我们需要的async/await的编译文件。

5,es6中的Module

ES6 中模块化语法更加简洁,使用export抛出,使用import from 接收,

如果只是输出一个唯一的对象,使用export default即可

// 创建 util1.js 文件,内容如

export default {

    a: 100

}

// 创建 index.js 文件,内容如

import obj from './util1.js’

如果想要输出许多个对象,就不能用default了,且import时候要加{...},代码如下

// 创建 util2.js 文件,内容如

export function fn1() {

    alert('fn1')

}

export function fn2() {

    alert('fn2')

}

// 创建 index.js 文件,内容如

import { fn1, fn2 } from './util2.js’

6,ES6 class 和普通构造函数的区别

class 其实一直是 JS 的关键字(保留字),但是一直没有正式使用,直到 ES6 。 ES6 的 class 就是取代之前构造函数初始化对象的形式,从语法上更加符合面向对象的写法

1. class 是一种新的语法形式,是class Name {...}这种形式,和函数的写法完全不一样


2. 两者对比,构造函数函数体的内容要放在 class 中的constructor函数中,constructor即构造器,初始化实例时默认执行 


3. class 中函数的写法是add() {...}这种形式,并没有function关键字 


而且使用 class 来实现继承就更加简单了

在class中直接extends关键字就可以实现继承,而不像之前的继承实现有多种不同的实现方式,在es6中就只有一种

注意以下两点:

使用extends即可实现继承,更加符合经典面向对象语言的写法,如 Java

子类的constructor一定要执行super(),以调用父类的constructor

7,ES6 中新增的数据类型有哪些?

Set 和 Map 都是 ES6 中新增的数据结构,是对当前 JS 数组和对象这两种重要数据结构的扩展。由于是新增的数据结构

1. Set 类似于数组,但数组可以允许元素重复,Set 不允许元素重复 


2. Map 类似于对象,但普通对象的 key 必须是字符串或者数字,而 Map 的 key 可以是任何数据类型 


8,箭头函数的作用域上下文和 普通函数作用域上下文 的区别

箭头函数其实只是一个密名函数的语法糖,区别在于普通函数作用域中的this有特定的指向,一般指向window,而箭头函数中的this只有一个指向那就是指当前函数所在的对象,其实现原理其实就是类似于之前编程的时候在函数外围定义that一样,用了箭头函数就不用定义that了直接使用this

9.es6如何转为es5?

使用Babel 转码器,Babel 的配置文件是.babelrc,存放在项目的根目录下。使用 Babel 的第一步,就是配置这个文件。

兼容与优化

1,页面重构怎么操作?

网站重构:在不改变外部行为的前提下,简化结构、添加可读性,而在网站前端保持一致的行为。

也就是说是在不改变UI的情况下,对网站进行优化,在扩展的同时保持一致的UI。

对于传统的网站来说重构通常是:

表格(table)布局改为DIV+CSS

使网站前端兼容于现代浏览器(针对于不合规范的CSS、如对IE6有效的)

对于移动平台的优化

针对于SEO进行优化

深层次的网站重构应该考虑的方面

减少代码间的耦合              

让代码保持弹性

严格按规范编写代码

设计可扩展的API

代替旧有的框架、语言(如VB)

增强用户体验

通常来说对于速度的优化也包含在重构中

压缩JS、CSS、image等前端资源(通常是由服务器来解决)

程序的性能优化(如数据读写)

采用CDN来加速资源加载

对于JS DOM的优化

HTTP服务器的文件缓存

2,列举IE与其他浏览器不一样的特性?

1、事件不同之处:

   1-1,触发事件的元素被认为是目标(target)。而在 IE 中,目标包含在 event 对象的 srcElement 属性;

   1-2,获取字符代码、如果按键代表一个字符(shift、ctrl、alt除外),IE 的 keyCode 会返回字符代码(Unicode),DOM 中按键的代码和字符是分离的,要获取字符代码,需要使用 charCode 属性;

   1-3,阻止某个事件的默认行为,IE 中阻止某个事件的默认行为,必须将 returnValue 属性设置为 false,Mozilla 中,需要调用 preventDefault() 方法;

   1-4,停止事件冒泡,IE 中阻止事件进一步冒泡,需要设置 cancelBubble 为 true,Mozzilla 中,需要调用 stopPropagation();

3,什么叫优雅降级和渐进增强?

优雅降级:Web站点在所有新式浏览器中都能正常工作,如果用户使用的是老式浏览器,则代码会针对旧版本的IE进行降级处理了,使之在旧式浏览器上以某种形式降级体验却不至于完全不能用。

如:border-shadow

渐进增强:从被所有浏览器支持的基本功能开始,逐步地添加那些只有新版本浏览器才支持的功能,向页面增加不影响基础浏览器的额外样式和功能的。当浏览器支持时,它们会自动地呈现出来并发挥作用。

如:默认使用flash上传,但如果浏览器支持 HTML5 的文件上传功能,则使用HTML5实现更好的体验;

4,说说严格模式的限制

严格模式主要有以下限制:

变量必须声明后再使用

函数的参数不能有同名属性,否则报错

不能使用with语句

不能对只读属性赋值,否则报错

不能使用前缀0表示八进制数,否则报错

不能删除不可删除的属性,否则报错

不能删除变量delete prop,会报错,只能删除属性delete global[prop]

eval不会在它的外层作用域引入变量

eval和arguments不能被重新赋值

arguments不会自动反映函数参数的变化

不能使用arguments.callee

不能使用arguments.caller

禁止this指向全局对象

不能使用fn.caller和fn.arguments获取函数调用的堆栈

增加了保留字(比如protected、static和interface)

设立"严格模式"的目的,主要有以下几个:

消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;

消除代码运行的一些不安全之处,保证代码运行的安全;

提高编译器效率,增加运行速度;

为未来新版本的Javascript做好铺垫。

注:经过测试IE6,7,8,9均不支持严格模式。

5,检测浏览器版本版本有哪些方式?

根据 navigator.userAgent // UA.toLowerCase().indexOf('chrome')

根据 window 对象的成员 // 'ActiveXObject' in window

6,总结前端性能优化的解决方案

优化原则和方向

性能优化的原则是以更好的用户体验为标准,具体就是实现下面的目标:

多使用内存、缓存或者其他方法

减少 CPU 和GPU 计算,更快展现

优化的方向有两个:

减少页面体积,提升网络加载

优化页面渲染

减少页面体积,提升网络加载

静态资源的压缩合并(JS 代码压缩合并、CSS 代码压缩合并、雪碧图)

静态资源缓存(资源名称加 MD5 戳)

使用 CDN 让资源加载更快

优化页面渲染

CSS 放前面,JS 放后面

懒加载(图片懒加载、下拉加载更多)

减少DOM 查询,对 DOM 查询做缓存

减少DOM 操作,多个操作尽量合并在一起执行(DocumentFragment)

事件节流

尽早执行操作(DOMContentLoaded)

使用 SSR 后端渲染,数据直接输出到 HTML 中,减少浏览器使用 JS 模板渲染页面 HTML 的时间

7,图片懒加载与预加载

图片懒加载的原理就是暂时不设置图片的src属性,而是将图片的url隐藏起来,比如先写在data-src里面,等某些事件触发的时候(比如滚动到底部,点击加载图片)再将图片真实的url放进src属性里面,从而实现图片的延迟加载

图片预加载是指在一些需要展示大量图片的网站,实现图片的提前加载。从而提升用户体验。常用的方式有两种,一种是隐藏在css的background的url属性里面,一种是通过javascript的Image对象设置实例对象的src属性实现图片的预加载。相关代码如下:

CSS预加载图片方式:

#preload-01 { background: url(http://domain.tld/image-01.png) no-repeat -9999px -9999px; }  

#preload-02 { background: url(http://domain.tld/image-02.png) no-repeat -9999px -9999px; }  

#preload-03 { background: url(http://domain.tld/image-03.png) no-repeat -9999px -9999px; }

Javascript预加载图片的方式:

function preloadImg(url) {

   var img = new Image();

   img.src = url;

   if(img.complete) {

       //接下来可以使用图片了

       //do something here

   } else {

       img.onload = function() {

           //接下来可以使用图片了

           //do something here

       };

   }

}

5,描述浏览器的渲染过程,DOM树和渲染树的区别?

浏览器的渲染过程:

解析HTML构建 DOM(DOM树),并行请求 css/image/js

CSS 文件下载完成,开始构建 CSSOM(CSS树)

CSSOM 构建结束后,和 DOM 一起生成 Render Tree(渲染树)

布局(Layout):计算出每个节点在屏幕中的位置

显示(Painting):通过显卡把页面画到屏幕上

DOM树 和 渲染树 的区别:

DOM树与HTML标签一一对应,包括head和隐藏元素

渲染树不包括head和隐藏元素,大段文本的每一个行都是独立节点,每一个节点都有对应的css属性

7,重绘和回流(重排)的区别和关系?

重绘:当渲染树中的元素外观(如:颜色)发生改变,不影响布局时,产生重绘

回流:当渲染树中的元素的布局(如:尺寸、位置、隐藏/状态状态)发生改变时,产生重绘回流

注意:JS获取Layout属性值(如:offsetLeft、scrollTop、getComputedStyle等)也会引起回流。因为浏览器需要通过回流计算最新值

回流必将引起重绘,而重绘不一定会引起回流

8,如何最小化重绘(repaint)和回流(reflow)?

需要要对元素进行复杂的操作时,可以先隐藏(display:"none"),操作完成后再显示

需要创建多个DOM节点时,使用DocumentFragment创建完后一次性的加入document

缓存Layout属性值,如:var left = elem.offsetLeft; 这样,多次使用 left 只产生一次回流

尽量避免用table布局(table元素一旦触发回流就会导致table里所有的其它元素回流)

避免使用css表达式(expression),因为每次调用都会重新计算值(包括加载页面)

尽量使用 css 属性简写,如:用 border 代替 border-width, border-style, border-color

批量修改元素样式:elem.className 和 elem.style.cssText 代替 elem.style.xxx

9,script 的位置是否会影响首屏显示时间?

在解析 HTML 生成 DOM 过程中,js 文件的下载是并行的,不需要 DOM 处理到 script 节点。因此,script的位置不影响首屏显示的开始时间。

浏览器解析 HTML 是自上而下的线性过程,script作为 HTML 的一部分同样遵循这个原则

因此,script 会延迟 DomContentLoad,只显示其上部分首屏内容,从而影响首屏显示的完成时间

猜你喜欢

转载自blog.csdn.net/m0_38077707/article/details/81635100