H5W3
当前位置:H5W3 > JavaScript > 正文

【JS】前端常见面试题总结——JavaScript部分(二)

前端常见面试题总结——JavaScript部分(二)

早饭君发布于 今天 08:08

1.如何对一个数组进行去重/排序?

去重

除了常用的双重循环,还有两种方法

方法一:遍历该数组,利用indexOf()方法判断新数组中是否存在,不存在就push到新驻足中,代码如下:


1.  var arr = ['a', 'b', 'b', 'c', 'c', 'd'];
2.  var newArr = [];
3.  for (var i = 0; i < arr.length; i++) {
4.      if (newArr.indexOf(arr[i]) == -1) {
5.          newArr.push(arr[i]);
6.      }
7.  }

方法二:通过es6中的set数据结构,对数组去重


1.  var setNum = new Set(arr);
2.  Array.from(setNum);
4.  //或者用rest参数
5.  [...setNum]

 排序

方法一:利用数组的sort()方法排序,如果调用该方法时没有使用参数,是按照字符编码的顺序进行排序,并不是数字大小排序。


1.  var arr = [5, 1, 3, 6, 8, 12];
2.  arr.sort();    //[1, 12, 3, 5, 6, 8]
4.  arr.sort(function (a, b) {
5.      return a - b;
6.  });
7.  console.log(arr);    //[1, 3, 5, 6, 8, 12]

 方法二:双重循环,冒泡排序


1.  var arr2 = [5, 1, 3, 6, 8, 2, 14, 17];
2.  for(var i = 0; i < arr2.length - 1; i++) {
3.      for(var j = 0; j < arr2.length - 1 - i; j++) {
4.          if (arr2[j] > arr2[j + 1]) {
5.              var item = arr2[j];
6.              arr2[j] = arr2[j + 1];
7.              arr2[j + 1] = item;
8.          }
9.      }
10.  }
11.  console.log(arr2);    //[1, 2, 3, 5, 6, 8, 14, 17]

 还有数组排序的其他几种算法,可以参考:https://www.cnblogs.com/real-me/p/7103375.html

2.什么是简单类型(原始类型primitive type),什么是复杂类型(合成类型complex type或引用类型)。

原始类型就是直接存储在栈中的数据,是最基本的数据类型,不可以再分了。

  • 数值(number):整数和小数(比如13.14
  • 字符串(string):文本(比如Hello World)。
  • 布尔值(boolean):表示真伪的两个特殊值,即true(真)和false(假)
  • undefined:表示“未定义”或不存在,即由于目前没有定义,所以此处暂时没有任何值
  • null:表示空值,即此处的值为空。
  • Symbol:表示独一无二的,是es6中新增的类型

复杂类型就是指存储在堆中的数据。常用复杂类型如下:

  • 数组(Array):存储一系列的值
  • 日期(Date):用于处理日期和时间
  • 算数(Math):执行普通的算数任务
  • 正则表达式(RegExp):描述了字符的模式对象   

3.深拷贝与浅拷贝的区别,如何进行深拷贝?

深拷贝和浅拷贝都是针对复杂类型的,简单类型没有深浅拷贝之分。

对于仅仅是复制了引用(地址),换句话说,复制了之后,原来的变量和新的变量指向同一个东西,彼此之间的操作会互相影响,为 浅拷贝

而如果是在堆中重新分配内存,拥有不同的地址,但是值是一样的,复制后的对象与原来的对象是完全隔离,互不影响,为 深拷贝

深浅拷贝 的主要区别就是:复制的是引用(地址)还是复制的是实例。

那么如何进行深拷贝呢?

方法一:利用jQuery中的extend()方法实现深拷贝

deep:表示是否深度合并对象,为true是就是神拷贝

target Object类型 目标对象,其他对象的成员属性将被附加到该对象上。

object1  objectN可选。 Object类型 第一个以及第N个被合并的对象。 


1.  var obj = {name:'ming',age:19,company : { name : 'RNG', address : '北京'} };
2.  var obj_extend = $.extend(true,{}, obj);
3.  console.log(obj === obj_extend);    //false

extend()方法可以参考菜鸟教程:http://www.runoob.com/jquery/misc-extend.html

方法二:利用JSON 对象的 parse 和 stringify方法


1.  var obj = {name:'xixi',age:20,company : { name : '腾讯', address : '深圳'} };
2.  var obj_json = JSON.parse(JSON.stringify(obj));
3.  console.log(obj === obj_json);    //false

方法三:es6中的rest参数

var aa = [...array];   //es6

方法四:利用 递归 来实现深复制,对属性中所有引用类型的值,遍历到是基本类型的值为止。


1.  function deepClone(source){
2.    if(!source && typeof source !== 'object'){
3.      throw new Error('error arguments', 'shallowClone');
4.    }
5.    var targetObj = Array.isArray(source) ? [] : {};
6.    for(var keys in source){
7.      if(source.hasOwnProperty(keys)){
8.        if(source[keys] && typeof source[keys] === 'object'){
9.          targetObj[keys] = deepClone(source[keys]);    //递归
10.        }else{
11.          targetObj[keys] = source[keys];
12.        }
13.      }
14.    }
15.    return targetObj;
16.  }

注意:Array对象的slice()和concat()方法不是真正的深拷贝。

参考自知乎:https://zhuanlan.zhihu.com/p/26282765    (这里详细写了深浅拷贝的原理,此处不再赘述)

4.说几个常用的数组/字符串的原生方法

数组:

  • splice() 方法可删除从 index 处开始的零个或多个元素,并且用参数列表中声明的一个或多个值来替换那些被删除的元素。如果从 arrayObject 中删除了元素,则返回的是含有被删除的元素的数组。
  • reverse() 方法用于颠倒数组中元素的顺序。该方法会改变原有数组。
  • slice()方法 返回一个新的数组,包含从 start 到 end (不包括该元素)的 arrayObject 中的元素。
  • join() 方法用于把数组中的所有元素放入一个字符串。元素是通过指定的分隔符进行分隔的。

字符串:

  • indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置。没有则返回-1。
  • split() 方法用于把一个字符串分割成字符串数组。
  • slice() 方法可提取字符串的某个部分,并以新的字符串返回被提取的部分。
  • substring() 方法用于提取字符串中介于两个指定下标之间的字符。

5. 面向对象的继承有几种方式?

方式一:原型链继承
修改子类的prototype为父类,这样会使子类的原型对象的constructor变成了父类,也可以手动修改回来
子类.prototype=new 父类();
子类.prototype.constructor=子类;
特点:
(1)非常纯粹的继承关系,实例是子类的实例,也是父类的实例
(2)继承父类本身的属性/方法、父类原型属性/方法、父类新增原型方法/原型属性
(3)无法实现多继承
(4)创建子类实例时,无法向父类构造函数传参
(5)子类的一个实例属性值改变时,会影响所有子类的实例属性。因为所有子类的prototype指向父类(new 父类),所有没有设置自己的属性的子类的实例属性都会改变。
但是如果是子类的一个实例属性重新赋值(子类的实例设置了自己的属性),则不会影响其他实例的属性。


1.  function A(){
2.      this.aNum=1;
3.  }
4.  A.prototype.getaNum= function () {
5.      console.log(this.aNum);
6.  }
8.  B.prototype=new A();
9.  var b=new B();
10.  console.log(b.aNum);    //父类本身的属性和方法
11.  b.getaNum();    //原型链上的属性和方法

 方式二:构造函数继承(对象冒充)

子类调用父类的构造函数,并且把this传进去,
子类函数中:父类.call(this);
子类函数中:父类.apply(this);
特点:
(1)只继承父类构造函数中的属性和方法,不能继承原型属性/方法。
(2)可以实现多重继承(call/apply多个父类对象)
(3)实例只是子类的实例,不是父类的实例。
(4)创建子类实例时,可以向父类传递参数
(5)无法实现函数复用,每个子类都有父类实例函数的副本,影响性能


1.  function E(num){
2.      this.eNum = num;
3.      this.geteNum = function () {
4.          alert(this.eNum);
5.      }
6.  }
8.  function F(num){
9.      E.call(this, num);
10.  }
12.  var f=new F(5);
13.  f.geteNum();

 方式三:组合式继承

通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
特点:
(1)既可以继承父类本身的属性/方法,也可以继承原型属性/方法
(2)既是子类的实例,也是父类的实例
(3)不存在子类的一个实例属性值改变时,会影响所有子类的实例属性的问题。
每创建一个子类对象实例,就调用父类的构造函数并且将实例对象作为this值传过去,所以每个实例对象都有自己的属性值。
(4)可传参
(5)函数可复用
(6)调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)


1.  function G(){
2.      this.gNum = 6;
3.  }
4.  G.prototype.getgNum = function () {
5.      console.log(this.gNum);
6.  }
8.  function H(){
9.      G.call(this);
10.  }
11.  H.prototype = new G();
13.  var h = new H();
14.  console.log(h.gNum);
15.  h.getgNum();

6.面向对象中的this代表什么,可否举例说明?

this是一个指针,this总是指向调用方法的对象,作为方法调用,那么this就是指实例化的对象。

举例说明:jQuery中的链式调用,就是this对象的应用。

*7.说一下事件委派(事件委托)的原理*

通事件委派的原理是事件冒泡机制,通过给父标签绑定事件,然后利用事件冒泡的现象使得点击子元素的时候,可以触发事件达到给子元素绑定事件的效果。

Event对象提供了一个属性叫target,表示为当前的事件操作的dom,这个属性是有兼容性的,标准浏览器用event.target,IE浏览器用event.srcElement。

优点:1.可以大量节省内存占用,减少事件注册。比如ul上代理所有li的click事件。 

           2.可以实现当新增子对象时,无需再对其进行事件绑定,对于动态内容部分尤为合适。

8.window的ready与onload事件的区别,执行的先后顺序是什么?**

ready,表示文档结构(DOM结构)已经加载完成(不包含图片等非文字媒体文件),
onload,指示页面包含图片等文件在内的所有元素都加载完成。

可以说:ready 在onload 前加载!!!

9.如何判断一个数据是不是数组?

通过typeof检测数组,得到的结构是object,并不能区分是不是数组,我们可以通过以下几种方法来判断一个数据是不是数组。

方法一:instanceof  用来判断一个对象是否存在于另一个对象的原型链上。


1.  var str=["aa", "bb", "cc"];
2.  console.log(str instanceof Array);    //true

方法二:isArray 函数


1.  var arr=["aa", "bb", "cc"];
2.  console.log(Array.isArray(arr));    //true

方法三:constructor  属性返回对创建此对象的函数的引用,使用此属性可以检测数组类型。


1.  var arr=["aa", "bb", "cc"];
2.  if(arr.constructor===Array){
3.      console.log('array');
4.  }
5.  //array
面试javascript前端
阅读 53发布于 今天 08:08
本作品系原创,采用《署名-非商业性使用-禁止演绎 4.0 国际》许可协议
avatar

早饭君

19年之前本人技术博客传送门

113 声望
2 粉丝

0 条评论
得票时间

avatar

早饭君

19年之前本人技术博客传送门

113 声望
2 粉丝

宣传栏

1.如何对一个数组进行去重/排序?

去重

除了常用的双重循环,还有两种方法

方法一:遍历该数组,利用indexOf()方法判断新数组中是否存在,不存在就push到新驻足中,代码如下:


1.  var arr = ['a', 'b', 'b', 'c', 'c', 'd'];
2.  var newArr = [];
3.  for (var i = 0; i < arr.length; i++) {
4.      if (newArr.indexOf(arr[i]) == -1) {
5.          newArr.push(arr[i]);
6.      }
7.  }

方法二:通过es6中的set数据结构,对数组去重


1.  var setNum = new Set(arr);
2.  Array.from(setNum);
4.  //或者用rest参数
5.  [...setNum]

 排序

方法一:利用数组的sort()方法排序,如果调用该方法时没有使用参数,是按照字符编码的顺序进行排序,并不是数字大小排序。


1.  var arr = [5, 1, 3, 6, 8, 12];
2.  arr.sort();    //[1, 12, 3, 5, 6, 8]
4.  arr.sort(function (a, b) {
5.      return a - b;
6.  });
7.  console.log(arr);    //[1, 3, 5, 6, 8, 12]

 方法二:双重循环,冒泡排序


1.  var arr2 = [5, 1, 3, 6, 8, 2, 14, 17];
2.  for(var i = 0; i < arr2.length - 1; i++) {
3.      for(var j = 0; j < arr2.length - 1 - i; j++) {
4.          if (arr2[j] > arr2[j + 1]) {
5.              var item = arr2[j];
6.              arr2[j] = arr2[j + 1];
7.              arr2[j + 1] = item;
8.          }
9.      }
10.  }
11.  console.log(arr2);    //[1, 2, 3, 5, 6, 8, 14, 17]

 还有数组排序的其他几种算法,可以参考:https://www.cnblogs.com/real-me/p/7103375.html

2.什么是简单类型(原始类型primitive type),什么是复杂类型(合成类型complex type或引用类型)。

原始类型就是直接存储在栈中的数据,是最基本的数据类型,不可以再分了。

  • 数值(number):整数和小数(比如13.14
  • 字符串(string):文本(比如Hello World)。
  • 布尔值(boolean):表示真伪的两个特殊值,即true(真)和false(假)
  • undefined:表示“未定义”或不存在,即由于目前没有定义,所以此处暂时没有任何值
  • null:表示空值,即此处的值为空。
  • Symbol:表示独一无二的,是es6中新增的类型

复杂类型就是指存储在堆中的数据。常用复杂类型如下:

  • 数组(Array):存储一系列的值
  • 日期(Date):用于处理日期和时间
  • 算数(Math):执行普通的算数任务
  • 正则表达式(RegExp):描述了字符的模式对象   

3.深拷贝与浅拷贝的区别,如何进行深拷贝?

深拷贝和浅拷贝都是针对复杂类型的,简单类型没有深浅拷贝之分。

对于仅仅是复制了引用(地址),换句话说,复制了之后,原来的变量和新的变量指向同一个东西,彼此之间的操作会互相影响,为 浅拷贝

而如果是在堆中重新分配内存,拥有不同的地址,但是值是一样的,复制后的对象与原来的对象是完全隔离,互不影响,为 深拷贝

深浅拷贝 的主要区别就是:复制的是引用(地址)还是复制的是实例。

那么如何进行深拷贝呢?

方法一:利用jQuery中的extend()方法实现深拷贝

deep:表示是否深度合并对象,为true是就是神拷贝

target Object类型 目标对象,其他对象的成员属性将被附加到该对象上。

object1  objectN可选。 Object类型 第一个以及第N个被合并的对象。 


1.  var obj = {name:'ming',age:19,company : { name : 'RNG', address : '北京'} };
2.  var obj_extend = $.extend(true,{}, obj);
3.  console.log(obj === obj_extend);    //false

extend()方法可以参考菜鸟教程:http://www.runoob.com/jquery/misc-extend.html

方法二:利用JSON 对象的 parse 和 stringify方法


1.  var obj = {name:'xixi',age:20,company : { name : '腾讯', address : '深圳'} };
2.  var obj_json = JSON.parse(JSON.stringify(obj));
3.  console.log(obj === obj_json);    //false

方法三:es6中的rest参数

var aa = [...array];   //es6

方法四:利用 递归 来实现深复制,对属性中所有引用类型的值,遍历到是基本类型的值为止。


1.  function deepClone(source){
2.    if(!source && typeof source !== 'object'){
3.      throw new Error('error arguments', 'shallowClone');
4.    }
5.    var targetObj = Array.isArray(source) ? [] : {};
6.    for(var keys in source){
7.      if(source.hasOwnProperty(keys)){
8.        if(source[keys] && typeof source[keys] === 'object'){
9.          targetObj[keys] = deepClone(source[keys]);    //递归
10.        }else{
11.          targetObj[keys] = source[keys];
12.        }
13.      }
14.    }
15.    return targetObj;
16.  }

注意:Array对象的slice()和concat()方法不是真正的深拷贝。

参考自知乎:https://zhuanlan.zhihu.com/p/26282765    (这里详细写了深浅拷贝的原理,此处不再赘述)

4.说几个常用的数组/字符串的原生方法

数组:

  • splice() 方法可删除从 index 处开始的零个或多个元素,并且用参数列表中声明的一个或多个值来替换那些被删除的元素。如果从 arrayObject 中删除了元素,则返回的是含有被删除的元素的数组。
  • reverse() 方法用于颠倒数组中元素的顺序。该方法会改变原有数组。
  • slice()方法 返回一个新的数组,包含从 start 到 end (不包括该元素)的 arrayObject 中的元素。
  • join() 方法用于把数组中的所有元素放入一个字符串。元素是通过指定的分隔符进行分隔的。

字符串:

  • indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置。没有则返回-1。
  • split() 方法用于把一个字符串分割成字符串数组。
  • slice() 方法可提取字符串的某个部分,并以新的字符串返回被提取的部分。
  • substring() 方法用于提取字符串中介于两个指定下标之间的字符。

5. 面向对象的继承有几种方式?

方式一:原型链继承
修改子类的prototype为父类,这样会使子类的原型对象的constructor变成了父类,也可以手动修改回来
子类.prototype=new 父类();
子类.prototype.constructor=子类;
特点:
(1)非常纯粹的继承关系,实例是子类的实例,也是父类的实例
(2)继承父类本身的属性/方法、父类原型属性/方法、父类新增原型方法/原型属性
(3)无法实现多继承
(4)创建子类实例时,无法向父类构造函数传参
(5)子类的一个实例属性值改变时,会影响所有子类的实例属性。因为所有子类的prototype指向父类(new 父类),所有没有设置自己的属性的子类的实例属性都会改变。
但是如果是子类的一个实例属性重新赋值(子类的实例设置了自己的属性),则不会影响其他实例的属性。


1.  function A(){
2.      this.aNum=1;
3.  }
4.  A.prototype.getaNum= function () {
5.      console.log(this.aNum);
6.  }
8.  B.prototype=new A();
9.  var b=new B();
10.  console.log(b.aNum);    //父类本身的属性和方法
11.  b.getaNum();    //原型链上的属性和方法

 方式二:构造函数继承(对象冒充)

子类调用父类的构造函数,并且把this传进去,
子类函数中:父类.call(this);
子类函数中:父类.apply(this);
特点:
(1)只继承父类构造函数中的属性和方法,不能继承原型属性/方法。
(2)可以实现多重继承(call/apply多个父类对象)
(3)实例只是子类的实例,不是父类的实例。
(4)创建子类实例时,可以向父类传递参数
(5)无法实现函数复用,每个子类都有父类实例函数的副本,影响性能


1.  function E(num){
2.      this.eNum = num;
3.      this.geteNum = function () {
4.          alert(this.eNum);
5.      }
6.  }
8.  function F(num){
9.      E.call(this, num);
10.  }
12.  var f=new F(5);
13.  f.geteNum();

 方式三:组合式继承

通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
特点:
(1)既可以继承父类本身的属性/方法,也可以继承原型属性/方法
(2)既是子类的实例,也是父类的实例
(3)不存在子类的一个实例属性值改变时,会影响所有子类的实例属性的问题。
每创建一个子类对象实例,就调用父类的构造函数并且将实例对象作为this值传过去,所以每个实例对象都有自己的属性值。
(4)可传参
(5)函数可复用
(6)调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)


1.  function G(){
2.      this.gNum = 6;
3.  }
4.  G.prototype.getgNum = function () {
5.      console.log(this.gNum);
6.  }
8.  function H(){
9.      G.call(this);
10.  }
11.  H.prototype = new G();
13.  var h = new H();
14.  console.log(h.gNum);
15.  h.getgNum();

6.面向对象中的this代表什么,可否举例说明?

this是一个指针,this总是指向调用方法的对象,作为方法调用,那么this就是指实例化的对象。

举例说明:jQuery中的链式调用,就是this对象的应用。

*7.说一下事件委派(事件委托)的原理*

通事件委派的原理是事件冒泡机制,通过给父标签绑定事件,然后利用事件冒泡的现象使得点击子元素的时候,可以触发事件达到给子元素绑定事件的效果。

Event对象提供了一个属性叫target,表示为当前的事件操作的dom,这个属性是有兼容性的,标准浏览器用event.target,IE浏览器用event.srcElement。

优点:1.可以大量节省内存占用,减少事件注册。比如ul上代理所有li的click事件。 

           2.可以实现当新增子对象时,无需再对其进行事件绑定,对于动态内容部分尤为合适。

8.window的ready与onload事件的区别,执行的先后顺序是什么?**

ready,表示文档结构(DOM结构)已经加载完成(不包含图片等非文字媒体文件),
onload,指示页面包含图片等文件在内的所有元素都加载完成。

可以说:ready 在onload 前加载!!!

9.如何判断一个数据是不是数组?

通过typeof检测数组,得到的结构是object,并不能区分是不是数组,我们可以通过以下几种方法来判断一个数据是不是数组。

方法一:instanceof  用来判断一个对象是否存在于另一个对象的原型链上。


1.  var str=["aa", "bb", "cc"];
2.  console.log(str instanceof Array);    //true

方法二:isArray 函数


1.  var arr=["aa", "bb", "cc"];
2.  console.log(Array.isArray(arr));    //true

方法三:constructor  属性返回对创建此对象的函数的引用,使用此属性可以检测数组类型。


1.  var arr=["aa", "bb", "cc"];
2.  if(arr.constructor===Array){
3.      console.log('array');
4.  }
5.  //array

本文地址:H5W3 » 【JS】前端常见面试题总结——JavaScript部分(二)

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址