前端常见面试题

前端面试过程中最容易出现的问题

本文主要转载于
haorooms博客

javascript常见题目

  • 一、您对js的原型是如何理解的?您对js的继承是如何理解的?能否举例说明js的继承?
    javascript中的this,constructor ,prototype
    this表示当前对象,如果在全局作用范围内使用this,则指代当前页面对象window; 如果在函数中使用this,则this指代什么是根据运行时此函数在什么对象上被调用。 我们还可以使用apply和call两个全局方法来改变函数中this的具体指向。
    注:apply和call两个函数的作用相同,唯一的区别是两个函数的参数定义不同。 参考收藏的博客
    prototype本质上还是一个JavaScript对象
    并且每个函数都有一个默认的prototype属性。 如果这个函数被用在创建自定义对象的场景中,我们称这个函数为构造函数
// 构造函数
        function Person(name) {
            this.name = name;
        }
        // 定义Person的原型,原型中的属性可以被自定义对象引用
        Person.prototype = {
            getName: function() {
                return this.name;
            }
        }
        var hao= new Person("haorooms");
        console.log(hao.getName());   // "haorooms"

Array添加扩展方法后的陷阱,向Array的原型中添加扩展方法后,当使用for-in循环数组时,这个扩展方法也会被循环出来

// 向JavaScript固有类型Array扩展一个获取最小值的方法
        Array.prototype.min = function() {
            var min = this[0];
            for (var i = 1; i < this.length; i++) {
                if (this[i] < min) {
                    min = this[i];
                }
            }
            return min;
        };

        // 在任意Array的实例上调用min方法
        console.log([1, 56, 34, 12].min());  // 1

var arr = [1, 56, 34, 12];
        var total = 0;
        for (var i in arr) {
            total += parseInt(arr[i], 10);
        }
        console.log(total);   // NaN

解决方法,hasOwnproperty()判断

var arr = [1, 56, 34, 12];
        var total = 0;
        for (var i in arr) {
            if (arr.hasOwnProperty(i)) {
                total += parseInt(arr[i], 10);
            }
        }
        console.log(total);   // 103
***由此可知,给引用类型扩展方法后,要记得用hasOwnproperty()判断避免同样的问题*** **constructor始终指向创建当前对象的构造函数**
// 等价于 var foo = new Array(1, 56, 34, 12);
        var arr = [1, 56, 34, 12];
        console.log(arr.constructor === Array); // true
        // 等价于 var foo = new Function();
        var Foo = function() { };
        console.log(Foo.constructor === Function); // true
        // 由构造函数实例化一个obj对象
        var obj = new Foo();
        console.log(obj.constructor === Foo); // true

        // 将上面两段代码合起来,就得到下面的结论
        console.log(obj.constructor.constructor === Function); // true

但是当constructor遇到prototype时,有趣的事情就发生了。 我们知道每个函数都有一个默认的属性prototype,而这个prototype的constructor默认指向这个函数。

function Person(name) {
            this.name = name;
        };
        Person.prototype.getName = function() {
            return this.name;
        };
        var p = new Person("haorooms");

        console.log(p.constructor === Person);  // true
        console.log(Person.prototype.constructor === Person); // true
        // 将上两行代码合并就得到如下结果
        console.log(p.constructor.prototype.constructor === Person); // true

当时当我们重新定义函数的prototype时(注意:和上例的区别,这里不是修改而是覆盖), constructor的行为就有点奇怪了,如下示例:

function Person(name) {
            this.name = name;
        };
        Person.prototype = {
            getName: function() {
                return this.name;
            }
        };
        var p = new Person("haorooms");
        console.log(p.constructor === Person);  // false
        console.log(Person.prototype.constructor === Person); // false
        console.log(p.constructor.prototype.constructor === Person); // false

为什么呢? 原来是因为覆盖Person.prototype时,等价于进行如下代码操作:

Person.prototype = new Object({
            getName: function() {
                return this.name;
            }
        });

而constructor始终指向创建自身的构造函数,所以此时Person.prototype.constructor === Object,即是:

function Person(name) {
            this.name = name;
        };
        Person.prototype = {
            getName: function() {
                return this.name;
            }
        };
        var p = new Person("haorooms");
        console.log(p.constructor === Object);  // true
        console.log(Person.prototype.constructor === Object); // true
        console.log(p.constructor.prototype.constructor === Object); // true

怎么修正这种问题呢?方法也很简单,重新覆盖Person.prototype.constructor即可:

function Person(name) {
            this.name = name;
        };
        Person.prototype = {
            getName: function() {
                return this.name;
            }
        };
        Person.prototype.constructor = Person;
        var p = new Person("haorooms");
        console.log(p.constructor === Person);  // true
        console.log(Person.prototype.constructor === Person); // true
        console.log(p.constructor.prototype.constructor === Person); // true

也可以这么写:

function Person(name) {
            this.name = name;
        };
        Person.prototype = {
          constructor:Person,//指定constructor
            getName: function() {
                return this.name;
            }
        };
**js的继承阅读《[JavaScript高级程序设计(第3版)]》的第六章**

继承最常用的两种方式如下:
原型链继承(对象间的继承)
类式继承(构造函数间的继承)

**原型链继承**

其实就是用prototype继承父级


function Parent(){
    this.name = 'mike';
} 
function Child(){
    this.age = 12;
}
Child.prototype = new Parent();//Child继承Parent,通过原型,形成链条

var test = new Child();
alert(test.age);
alert(test.name);//得到被继承的属性
//继续原型链继承
function Brother(){   //brother构造
    this.weight = 60;
}
Brother.prototype = new Child();//继续原型链继承
var brother = new Brother();
alert(brother.name);//继承了Parent和Child,弹出mike
alert(brother.age);//弹出12
**类式继承(借用构造函数)**

类式继承一般是通过运用call或者apply 进行子类型构造函数的内部调用超类型的构造函数

function Parent(age){
    this.name = ['mike','jack','smith'];
    this.age = age;
}

function Child(age){
    Parent.call(this,age);
}
var test = new Child(21);
alert(test.age);//21
alert(test.name);//mike,jack,smith
test.name.push('bill');
alert(test.name);//mike,jack,smith,bill
**原型式继承**

和上面讲的原形链式继承只有一字之差,但是不是同一个内容
Object.create()方式来创建新的类。因为这种方式老式浏览器不支持。因此,假如我们不用Object.create(),也可以有替代法,方式如下:

 function obj(o){
     function F(){}
     F.prototype = o;
     return new F();
 }

这个函数,就实现了我们Object.create()创建类的方式!
因此举例如下:

 function obj(o){
     function F(){}
     F.prototype = o;
     return new F();
 }
var box = {
    name : 'trigkit4',
    arr : ['brother','sister','baba']
};
var b1 = obj(box);
alert(b1.name);//trigkit4

b1.name = 'mike';
alert(b1.name);//mike

alert(b1.arr);//brother,sister,baba
b1.arr.push('parents');
alert(b1.arr);//brother,sister,baba,parents

var b2 = obj(box);
alert(b2.name);//trigkit4
alert(b2.arr);//brother,sister,baba,parents

参考资料js的继承
js继承参考资料

  • 二、js闭包?举例并说明其主要的作用!

闭包,其实是指有权访问另一个函数作用域中变量的函数
一般函数执行完后,局部活动对象就会被销毁,内存中仅保存全局对象和作用域。但是闭包函数却不相同,内部函数会把外部函数的活动对象添加到它的作用域链中,这样内部函数就可以访问外部函数的变量,而且,外部函数执行完后,作用域链会被销毁,但是活动对象不会被销毁,因为其活动对象被内部函数的作用域链引用,所以其活动对象不会被销毁,直到内部函数被销毁
闭包比其他函数更占用内存,非必要情况不建议使用
通俗地讲,JavaScript 中每个的函数都是一个闭包,但通常意义上嵌套的函数更能够体现出闭包的特性,请看下面这个例子:

var   generateClosure   =   function()   {
    var   count   =   0;
    var   get   =   function()   {
    count   ++;
    return   count;
         };
    return   get;
};
var   counter   =   generateClosure();
console.log(counter());   //   输出   1
console.log(counter());   //   输出   2
console.log(counter());   //   输出   3

当一个函数返回它内部定义的一个函数时,就产生了一个闭包,闭包不但包括被返回的函数,还包括这个函数的定义环境

var   generateClosure   =   function()   {
    var   count   =   0;
    var   get   =   function()   {
        count   ++;
        return   count;
    };
    return   get;
};

var   counter1   =   generateClosure();
var   counter2   =   generateClosure();
console.log(counter1());   //   输出   1
console.log(counter2());   //   输出   1
console.log(counter1());   //   输出   2
console.log(counter1());   //   输出   3
console.log(counter2());   //   输出   2

上面这个例子解释了闭包是如何产生的: counter1和counter2分别调用了generate-Closure() 函数,生成了两个闭包的实例,它们内部引用的 count 变量分别属于各自的运行环境。我们可以理解为,在 generateClosure() 返回 get 函数时,私下将 get 可能引用到的 generateClosure() 函数的内部变量(也就是 count 变量)也返回了,并在内存中生成了一个副本,之后 generateClosure() 返回的函数的两个实例 counter1和 counter2 就是相互独立的了。

闭包的用途
*1. 嵌套的回调函数
闭包有两个主要用途,一是实现嵌套的回调函数,二是隐藏对象的细节。让我们先看下面这段代码示例,了解嵌套的回调函数。如下代码是在 Node.js 中使用 MongoDB 实现一个简单的增加用户的功能(可以说每一个nodejs的高手,通常也是js的高手):

exports.add_user   =   function(user_info,   callback)   {
    var   uid   =   parseInt(user_info['uid']);
    mongodb.open(function(err,   db)   {
        if   (err)   {callback(err);   return;}
        db.collection('users',   function(err,   collection)   {
            if   (err)   {callback(err);   return;}
            collection.ensureIndex("uid",   function(err)   {
                if   (err)   {callback(err);   return;}
                collection.ensureIndex("username",   function(err)   {
                    if   (err)   {callback(err);   return;}
                    collection.findOne({uid:   uid},   function(err)   {
                        if   (err)   {callback(err);   return;}
                        if   (doc)   {
                            callback('occupied');
                        }   else   {
                            var   user   =   {
                                uid:   uid,
                                user:   user_info,
                            };
                            collection.insert(user,   function(err)   {
                                callback(err);
                                           })
                                     }
                            })
                        })
                   })
              })
        })
}

如果你对 Node.js 或 MongoDB 不熟悉,没关系,不需要去理解细节,只要看清楚大概的逻辑即可。这段代码中用到了闭包的层层嵌套,每一层的嵌套都是一个回调函数。回调函数不会立即执行,而是等待相应请求处理完后由请求的函数回调。我们可以看到,在嵌套的每一层中都有对 callback 的引用,而且最里层还用到了外层定义的 uid 变量。由于闭包机制的存在,即使外层函数已经执行完毕,其作用域内申请的变量也不会释放,因为里层的函数还有可能引用到这些变量,这样就完美地实现了嵌套的异步回调。

2. 实现私有成员
我们知道,JavaScript 的对象没有私有属性,也就是说对象的每一个属性都是曝露给外部的。这样可能会有安全隐患,譬如对象的使用者直接修改了某个属性,导致对象内部数据的一致性受到破坏等。JavaScript通过约定在所有私有属性前加上下划线(例如_myPrivateProp),表示这个属性是私有的,外部对象不应该直接读写它。但这只是个非正式的约定,假设对象的使用者不这么做,有没有更严格的机制呢?答案是有的,通过闭包可以实现。让我们再看看前面那个例子:

var   generateClosure   =   function()   {
        var   count   =   0;
        var   get   =   function()   {
            count   ++;
            return   count;
        };
        return   get;
    };

    var   counter   =   generateClosure();
    console.log(counter());   //   输出   1
    console.log(counter());   //   输出   2
    console.log(counter());   //   输出   3

我们可以看到,只有调用 counter() 才能访问到闭包内的 count 变量,并按照规则对其增加1,除此之外决无可能用其他方式找到 count 变量。受到这个简单例子的启发,我们可以把一个对象用闭包封装起来,只返回一个“访问器”的对象,即可实现对细节隐藏
- 三、js数组主要有哪些方法?主要参数你了解吗?

  • 四、js迭代的方法

  • 五、js数组去重和排序

  • 六、js正则表达式

  • 七、纯js的ajax请求原理

纯js的ajax其实主要原理是利用XMLHttpRequest这个对象,关于这个对象
百度百科

下面我就列举一个纯js的ajax函数吧

/**
 * 得到ajax对象
 */
function getajaxHttp() {
    var xmlHttp;
    try {
        // Firefox, Opera 8.0+, Safari
        xmlHttp = new XMLHttpRequest();
        } catch (e) {
            // Internet Explorer
            try {
                xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
            } catch (e) {
            try {
                xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
            } catch (e) {
                alert("您的浏览器不支持AJAX!");
                return false;
            }
        }
    }
    return xmlHttp;
}
/**
 * 发送ajax请求
 * url--url
 * methodtype(post/get)
 * con (true(异步)|false(同步))
 * parameter(参数)
 * functionName(回调方法名,不需要引号,这里只有成功的时候才调用)
 * (注意:这方法有二个参数,一个就是xmlhttp,一个就是要处理的对象)
 * obj需要到回调方法中处理的对象
 */
function ajaxrequest(url,methodtype,con,parameter,functionName,obj){
    var xmlhttp=getajaxHttp();
    xmlhttp.onreadystatechange=function(){
        if(xmlhttp.readyState==4){
            //HTTP响应已经完全接收才调用
            functionName(xmlhttp,obj);
        }
    };
    xmlhttp.open(methodtype,url,con);
    xmlhttp.send(parameter);
}
//这就是参数
function createxml(){
    var xml="<user><userid>haorooms 纯js ajax请求<\/userid><\/user>";//"\/"这不是大写V而是转义是左斜杠和右斜杠
    return xml;
}
//这就是参数
function createjson(){
    var json={id:0,username:"haorooms"};
    return json;
}
function c(){
    alert("");
}
//测试
ajaxrequest("http://www.haorooms.com","post",true,createxml(),c,document);
  • 八、js模块化

一、为什么要用require.js?

最早的时候,所有Javascript代码都写在一个文件里面,只要加载这一个文件就够了。后来,代码越来越多,一个文件不够了,必须分成多个文件,依次加载。下面的网页代码,相信很多人都见过。

<script src="1.js"></script>
<script src="2.js"></script>
<script src="3.js"></script>
<script src="4.js"></script>
<script src="5.js"></script>
<script src="6.js"></script>

这段代码依次加载多个js文件。
这样的写法有很大的缺点。首先,加载的时候,浏览器会停止网页渲染,加载文件越多,网页失去响应的时间就会越长;其次,由于js文件之间存在依赖关系,因此必须严格保证加载顺序(比如上例的1.js要在2.js的前面),依赖性最大的模块一定要放到最后加载,当依赖关系很复杂的时候,代码的编写和维护都会变得困难。

require.js的诞生,就是为了解决这两个问题:

(1)实现js文件的异步加载,避免网页失去响应;
(2)管理模块之间的依赖性,便于代码的编写和维护。

二、require.js的加载

使用require.js的第一步,是先去官方网站下载最新版本。
下载后,假定把它放在js子目录下面,就可以加载了。

<script src="js/require.js"></script>

有人可能会想到,加载这个文件,也可能造成网页失去响应。解决办法有两个,一个是把它放在网页底部加载,另一个是写成下面这样:

<script src="js/require.js" defer async="true" ></script>

async属性表明这个文件需要异步加载,避免网页失去响应。IE不支持这个属性,只支持defer,所以把defer也写上。我之前的一篇文章,关于前端性能优化的,提及过defer ,有兴趣的可以去看看!http://www.haorooms.com/post/web_xnyh_jscss 加载require.js以后,下一步就要加载我们自己的代码了。假定我们自己的代码文件是main.js,也放在js目录下面。那么,只需要写成下面这样就行了:

<script src="js/require.js" data-main="js/main"></script>

data-main属性的作用是,指定网页程序的主模块。在上例中,就是js目录下面的main.js,这个文件会第一个被require.js加载。由于require.js默认的文件后缀名是js,所以可以把main.js简写成main。

三、主模块的写法

主模块依赖于其他模块,这时就要使用AMD规范定义的的require()函数。

// main.js
  require(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){
    // some code here
  });

require()函数接受两个参数。第一个参数是一个数组,表示所依赖的模块,上例就是[‘moduleA’, ‘moduleB’, ‘moduleC’],即主模块依赖这三个模块;第二个参数是一个回调函数,当前面指定的模块都加载成功后,它将被调用。加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块。
require()异步加载moduleA,moduleB和moduleC,浏览器不会失去响应;它指定的回调函数,只有前面的模块都加载成功后,才会运行,解决了依赖性的问题。
假定主模块依赖jquery、underscore和backbone这三个模块,main.js就可以这样写:

require(['jquery', 'underscore', 'backbone'], function ($, _, Backbone){
    // some code here
});

require.js会先加载jQuery、underscore和backbone,然后再运行回调函数。主模块的代码就写在回调函数中。
四、模块的加载
上一节最后的示例中,主模块的依赖模块是[‘jquery’, ‘underscore’, ‘backbone’]。默认情况下,require.js假定这三个模块与main.js在同一个目录,文件名分别为jquery.js,underscore.js和backbone.js,然后自动加载。
使用require.config()方法,我们可以对模块的加载行为进行自定义。require.config()就写在主模块(main.js)的头部。参数就是一个对象,这个对象的paths属性指定各个模块的加载路径。

require.config({
    paths: {
      "jquery": "jquery.min",
      "underscore": "underscore.min",
      "backbone": "backbone.min"
    }
});

上面的代码给出了三个模块的文件名,路径默认与main.js在同一个目录(js子目录)。如果这些模块在其他目录,比如js/lib目录,则有两种写法。一种是逐一指定路径。

require.config({
    paths: {
      "jquery": "lib/jquery.min",
      "underscore": "lib/underscore.min",
      "backbone": "lib/backbone.min"
    }
});

另一种则是直接改变基目录(baseUrl)。

require.config({
    baseUrl: "js/lib",
    paths: {
      "jquery": "jquery.min",
      "underscore": "underscore.min",
      "backbone": "backbone.min"
    }
});

如果某个模块在另一台主机上,也可以直接指定它的网址,比如:

require.config({
    paths: {
      "jquery": "https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min"
    }
});

require.js要求,每个模块是一个单独的js文件。这样的话,如果加载多个模块,就会发出多次HTTP请求,会影响网页的加载速度。因此,require.js提供了OPTIMIZER这样一个优化工具,当模块部署完毕以后,可以用这个工具将多个模块合并在一个文件中,减少HTTP请求数。工具地址:http://requirejs.org/docs/optimization.html 此工具要基于node,还需要https://github.com/jrburke/r.js 这个js。
五、AMD模块的写法
require.js加载的模块,采用AMD规范。也就是说,模块必须按照AMD的规定来写。
具体来说,就是模块必须采用特定的define()函数来定义。如果一个模块不依赖其他模块,那么可以直接定义在define()函数之中。
假定现在有一个math.js文件,它定义了一个math模块。那么,math.js就要这样写:

// math.js
define(function (){
    var add = function (x,y){
      return x+y;
    };
    return {
      add: add
    };
});

加载方法如下:

// main.js
  require(['math'], function (math){
    alert(math.add(1,1));
  });

如果这个模块还依赖其他模块,那么define()函数的第一个参数,必须是一个数组,指明该模块的依赖性。

define(['myLib'], function(myLib){
    function foo(){
      myLib.doSomething();
    }
    return {
      foo : foo
    };
});

当require()函数加载上面这个模块的时候,就会先加载myLib.js文件。
六、加载非规范的模块
理论上,require.js加载的模块,必须是按照AMD规范、用define()函数定义的模块。但是实际上,虽然已经有一部分流行的函数库(比如jQuery)符合AMD规范,更多的库并不符合。那么,require.js是否能够加载非规范的模块呢?

回答是可以的。

这样的模块在用require()加载之前,要先用require.config()方法,定义它们的一些特征。

举例来说,underscore和backbone这两个库,都没有采用AMD规范编写。如果要加载它们的话,必须先定义它们的特征。

require.config({
    shim: {

      'underscore':{
        exports: '_'
      },
      'backbone': {
        deps: ['underscore', 'jquery'],
        exports: 'Backbone'
      }
    }
});

require.config()接受一个配置对象,这个对象除了有前面说过的paths属性之外,还有一个shim属性,专门用来配置不兼容的模块。具体来说,每个模块要定义(1)exports值(输出的变量名),表明这个模块外部调用时的名称;(2)deps数组,表明该模块的依赖性。

比如,jQuery的插件可以这样定义:

shim: {
    'jquery.scroll': {
      deps: ['jquery'],
      exports: 'jQuery.fn.scroll'
    }
}

七、require.js插件

require.js还提供一系列插件,实现一些特定的功能。

domready插件,可以让回调函数在页面DOM结构加载完成后再运行。

require(['domready!'], function (doc){
    // called once the DOM is ready
  });

text和image插件,则是允许require.js加载文本和图片文件。

define([
    'text!review.txt',
    'image!cat.jpg'
    ],

    function(review,cat){
      console.log(review);
      document.body.appendChild(cat);
    }
 );

类似的插件还有json和mdown,用于加载json文件和markdown文件。
- 九、双等号隐性转换
隐性类型转换步骤
一、首先看双等号前后有没有NaN,如果存在NaN,一律返回false。

二、再看双等号前后有没有布尔,有布尔就将布尔转换为数字。(false是0,true是1)

三、接着看双等号前后有没有字符串, 有三种情况:

1、对方是对象,对象使用toString()或者valueOf()进行转换;
2、对方是数字,字符串转数字;(前面已经举例)
3、对方是字符串,直接比较;
4、其他返回false

四、如果是数字,对方是对象,对象取valueOf()或者toString()进行比较, 其他一律返回false

五、null, undefined不会进行类型转换, 但它们俩相等

上面的转换顺序一定要牢记,面试的时候,经常会出现类型的问题。

大多数对象隐式转换为值类型都是首先尝试调用valueOf()方法,再调用toString()方法。但是Date对象是个例外,此对象的valueOf()和toString()方法都经过精心重写,默认是调用toString()方法,比如使用+运算符,如果在其他算数运算环境中,则会转而调用valueOf()方法。

var date = new Date();
console.log(date + "1"); //Sun Apr 17 2014 17:54:48 GMT+0800 (CST)1
console.log(date + 1);//Sun Apr 17 2014 17:54:48 GMT+0800 (CST)1
console.log(date - 1);//1460886888556
console.log(date * 1);//1460886888557

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值