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

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

去重

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

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

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

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

var setNum = new Set(arr);
Array.from(setNum);

//或者用rest参数
[...setNum]

 排序

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

var arr = [5, 1, 3, 6, 8, 12];
arr.sort();    //[1, 12, 3, 5, 6, 8]

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

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

var arr2 = [5, 1, 3, 6, 8, 2, 14, 17];
for(var i = 0; i < arr2.length - 1; i++) {
    for(var j = 0; j < arr2.length - 1 - i; j++) {
        if (arr2[j] > arr2[j + 1]) {
            var item = arr2[j];
            arr2[j] = arr2[j + 1];
            arr2[j + 1] = item;
        }
    }
}
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()方法实现深拷贝

$.extend( [deep ], target, object1 [, objectN ] )

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

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

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

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

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

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

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

方法三:es6中的rest参数

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

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

function deepClone(source){    
  if(!source && typeof source !== 'object'){      
    throw new Error('error arguments', 'shallowClone');    
  }    
  var targetObj = Array.isArray(source) ? [] : {};    
  for(var keys in source){       
    if(source.hasOwnProperty(keys)){          
      if(source[keys] && typeof source[keys] === 'object'){  
        targetObj[keys] = deepClone(source[keys]);    //递归      
      }else{            
        targetObj[keys] = source[keys];         
      }       
    }    
  }    
  return targetObj; 
}

注意: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 父类),所有没有设置自己的属性的子类的实例属性都会改变。
但是如果是子类的一个实例属性重新赋值(子类的实例设置了自己的属性),则不会影响其他实例的属性。

function A(){
	this.aNum=1;
}
A.prototype.getaNum= function () {
	console.log(this.aNum);
}

B.prototype=new A();
var b=new B();
console.log(b.aNum);    //父类本身的属性和方法
b.getaNum();    //原型链上的属性和方法

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

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

function E(num){
    this.eNum = num;
    this.geteNum = function () {
        alert(this.eNum);
    }
}

function F(num){
    E.call(this, num);
}

var f=new F(5);
f.geteNum();

 方式三:组合式继承

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

function G(){
    this.gNum = 6;
}
G.prototype.getgNum = function () {
    console.log(this.gNum);
}

function H(){
    G.call(this);
}
H.prototype = new G();

var h = new H();
console.log(h.gNum);
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  用来判断一个对象是否存在于另一个对象的原型链上。

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

方法二:isArray 函数

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

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

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

猜你喜欢

转载自blog.csdn.net/sinat_36174237/article/details/82255503
今日推荐