欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-06-09 19:11:15
...

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