前端面试题总汇

最近跑面试,于是整理下最近面试所遇到的各种形形色色的问题:

\n\n

目录:

\n\n\n\n

angular中英this 和 scope的区别

\n\n
引用方式:
\n\n
    \n
  • scope
    \n$scope 只需要在注入中声明,后面就可以直接在附加数据对象:
    \ncontroller:
    \nfunction ACtrl($scope) {\n $scope.test = \"一个例子\"; //在$scope对象中加入test\n }
    \nhtml:
    \n<div ng-controller=\"ACtrl\">\n {{test}}\n </div>
  • \n
  • this
    \nthis 则采用了controller as(需要版本为ng 1.2+)写法:
    \ncontroller:
    \nfunction BCtrl() {\n var vm = this;\n this.test = \"一个例子\"; //在this对象中加入test\n }
    \nhtml:
    \n<!-- vm为自己为当前控制器作的一个简略记号,也可以写作 BCtrl as b,\n 后面变量便可以在b中引出 如b.test -->\n <div ng-controller=\"BCtrl as vm\">\n {{vm.test}}\n </div>
  • \n
\n\n
作用范围:
\n\n
    \n
  • scope:
    \n$scope 中的变量或数据对象我们可以全部拿到,并且上级控制器中的变量也可以在下级控制器中被获取到:
    \ncontroller:
    \nfunction ParentCtrl($scope) {\n $scope.test = \"测试\";\n $scope.cover =\"覆盖测试\";\n }\n function ChildCtrl($scope) {\n $scope.cover =\"子覆盖测试\";\n var test = $scope.test; //“测试”\n }
    \nhtml:
    \n<div ng-controller=\"ParentCtrl\">\n <p>Parent-test : {{test}}</p>\n <p>Parent-cover : {{cover}}</p>\n <div ng-controller=\"ChildCtrl\">\n <p>Child-test : {{test}}</p>\n <p>Child-cover : {{cover}}</p>\n </div>\n</div>
    \n我在父控制器ParentCtrl中声明的test变量并未在子控制器ChildCtrl中做声明,而在ChildCtrl作用范围内的Child-test 中,test却输出了”测试”;基于此我再做了一次覆盖测试,检测结果显示,当父子控制器同时存在相同的变量时, 父子控制器各自范围内的值不会被覆盖;
  • \n
  • this
    \nthis 中的变量则只适用于当前控制器:
    \ncontroller:
    \nfunction ParentCtrl($scope) {\n var vm = this;\n vm.test = \"测试\";\n vm.cover =\"覆盖测试\";\n }\n function ChildCtrl($scope) {\n var vm = this;\n vm.cover =\"子覆盖测试\";\n }
    \nhtml:
    \n<div ng-controller=\"ParentCtrl as parent\">\n <p>Parent-test : {{parent.test}}</p>\n <p>Parent-cover : {{parent.cover}}</p>\n <div ng-controller=\"ChildCtrl as child\">\n <p>Child-test : {{child.test}}</p>\n <p>Child-cover : {{child.cover}}</p>\n </div>\n <div ng-controller=\"ChildCtrl as parent\">\n <p>Child-test : {{parent.test}}</p>\n <p>Child-cover : {{parent.cover}}</p>\n </div>\n </div>
    \n在使用this的时候,各层级变量的命名空间是平行的状态,模板html中只可以拿到当前控制器下声明的变量。
  • \n
\n\n
对象对比:
\n\n

controller:
\nfunction CCtrl($scope) {\n var vm = this;\n $scope.logThisAndScope = function() {\n console.log(vm === $scope)\n }\n }
\nvm与$scope实际上是不相等的,在console中我们可以看到
\n vm: Constructor;
\n $scope: $get.Scope.$new.Child;
\n 而在$scope中又包含了一个变量vm: Constructor
\n实际结构是
\n$scope: {\n ...,\n vm: Constructor,\n ...\n }

\n\n

$scope 当控制器在写法上形成父子级关系时,子级没有的变量或方法父级会自动强加在子级身上,子级可以任意获取到当前父级的变量或方法,该种形式是不可逆的,即父级无法通过$scope获取到子级的任意变量或方法。
\nthis则像一个独立的个体,所有东西都是由自己管理,也就不存在父子级变量混淆关系了。

\n\n

其实可以理解为私用跟公用的区别

\n\n

ajax返回状态

\n\n

这个说真的我并不知道如何回答,看面试官的口味了
\n比如:

\n\n
    \n
  • 成功 success
  • \n
  • 失败 error
  • \n
  • 完成 complete
  • \n
\n\n

http请求所返回状态

\n\n
    \n
  • 1字头-消息
  • \n
  • 2字头-成功
  • \n
  • 3字头-重定向
  • \n
  • 4字头-连接错误
  • \n
  • 5字头-服务器错误
  • \n
\n\n

详细内容请 移步 《面试题之http请求状态码》

\n\n

angular的优劣

\n\n
    \n
  • 优点

    \n\n
    • 模板功能强大丰富,并且是声明式的,自带了丰富的 Angular 指令
    • \n
    • 是一个比较完善的前端MV*框架,包含模板,数据双向绑定,路由,模块化,服务,依赖注入等所有功能
    • \n
    • 自定义 Directive,比 jQuery 插件还灵活,但是需要深入了解 Directive 的一些特性,简单的封装容易
    • \n
    • ng模块化能够很容易的写出可复用的代码,对于敏捷开发的团队来说非常有帮助
  • \n
  • 缺点

    \n\n
    • 验证功能错误信息显示比较薄弱
    • \n
    • 对于特别复杂的应用场景,性能上有点问题
    • \n
    • ng提倡在控制器里面不要有操作DOM的代码,对于一些JQuery 插件的使用,如果想不破坏代码的整洁性,需要写一些directive去封装一下JQ插件
    • \n
    • Angular 太重了
    • \n
    • SEO不友好
  • \n
\n\n

闭包

\n\n

闭包就是函数的局部变量集合,只是这些局部变量在函数返回后会继续存在。
\n闭包就是就是函数的“堆栈”在函数返回后并不释放,我们也可以理解为这些函数堆栈并不在栈上分配而是在堆上分配
\n当在一个函数内定义另外一个函数就会产生闭包

\n\n

简单地说,闭包就是 函数中套函数

\n\n

闭包可以用在许多地方。它的最大用处有两个

\n\n
    \n
  • 一个是前面提到的可以读取函数内部的变量,
  • \n
  • 另一个就是让这些变量的值始终保持在内存中。
  • \n
\n\n

闭包缺点:

\n\n
    \n
  • 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
  • \n
  • 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
  • \n
\n\n
JavaScript闭包例子
\n\n

function outerFun()\n{\n var a=0;\n function innerFun()\n {\n a++;\n alert(a);\n }\n return innerFun; //注意这里\n}\nvar obj=outerFun();
\nobj(); //结果为1
\nobj(); //结果为2
\nvar obj2=outerFun();
\nobj2(); //结果为1
\nobj2(); //结果为2

\n什么是闭包:\n当内部函数 在定义它的作用域 的外部 被引用时,就创建了该内部函数的闭包 ,如果内部函数引用了位于外部函数的变量,当外部函数调用完毕后,这些变量在内存不会被 释放,因为闭包需要它们.

\n\n
闭包的几种写法
\n\n
    \n
  • 第1种写法
    \nfunction Circle(r) {
    \n this.r = r;
    \n}
    \nCircle.PI = 3.14159;
    \nCircle.prototype.area = function() {
    \nreturn Circle.PI * this.r * this.r;
    \n}
    \nvar c = new Circle(1.0);
    \nalert(c.area());

    \n这种写法没什么特别的,只是给函数添加一些属性。
  • \n
  • 第2种写法
    \nvar Circle = function() {
    \nvar obj = new Object();
    \nobj.PI = 3.14159;
    \nobj.area = function( r ) {
    \n return this.PI * r * r;
    \n}
    \nreturn obj;
    \n}
    \nvar c = new Circle();
    \nalert( c.area( 1.0 ) );

    \n这种写法是声明一个变量,将一个函数当作值赋给变量。
  • \n
  • 第3种写法
    \nvar Circle = new Object();
    \nCircle.PI = 3.14159;
    \nCircle.Area = function( r ) {
    \n return this.PI * r * r;
    \n}
    \nalert( Circle.Area( 1.0 ) );

    \n这种方法最好理解,就是new 一个对象,然后给对象添加属性和方法。
  • \n
  • 第4种写法
    \nvar Circle={
    \n\"PI\":3.14159,
    \n\"area\":function(r){
    \n return this.PI * r * r;
    \n }
    \n};
    \nalert( Circle.area(1.0) );

    \n这种方法使用较多,也最为方便。var obj = {}就是声明一个空的对象。
  • \n
\n\n
优点
\n\n
    \n
  • 不增加额外的全局变量,避免全局污染
  • \n
  • 执行过程中所有变量都是在匿名函数内部,私密性
  • \n
\n\n
缺点
\n\n
    \n
  • 常驻内存,会增大内存使用量,使用不当很容易造成内存泄露
  • \n
\n\n

angular中模块与模块间数据传递

\n\n

1、event
\n这里可以有两种方式,一种是$scope.$emit,然后通过监听$rootScope的事件获取参数;另一种是$rootScope.$broadcast,通过监听$scope的事件获取参数。

\n\n

2、service
\n可以创建一个专用的事件Service,也可以按照业务逻辑切分,将数据存储在相应的Service中

\n\n

3、$rootScope
\n这个方法可能会比较dirty一点,胜在方便,也就是把数据存在$rootScope中,这样各个子$scope都可以调用,不过需要注意一下生命周期

\n\n

4、直接使用$scope.$$nextSibling及类似的属性
\n类似的还有$scope.$parent。这个方法的缺点就更多了,官方不推荐使用任何$$开头的属性,既增加了耦合,又需要处理异步的问题,而且scope的顺序也不是固定的。不推荐

\n\n
简单的说就是通过 $rootScope 来进行模块间传参
\n\n

css flex

\n\n

原型链

\n\n

在JavaScript中,一共有两种类型的值,原始值和对象值.每个对象都有一个内部属性[[prototype]],我们通常称之为原型.原型的值可以是一个对象,也可以是null.如果它的值是一个对象,则这个对象也一定有自己的原型.这样就形成了一条线性的链,我们称之为原型链.
\n由于原型对象本身也是对象,根据上边的定义,它也有自己的原型,而它自己的原型对象又可以有自己的原型,这样就组成了一条链,这个就是原型链,JavaScritp引擎在访问对象的属性时,如果在对象本身中没有找到,则会去原型链中查找,如果找到,直接返回值,如果整个链都遍历且没有找到属性,则返回undefined.原型链一般实现为一个链表,这样就可以按照一定的顺序来查找。
\n访问一个对象的原型可以使用ES5中的Object.getPrototypeOf方法,或者ES6中的proto属性.

\n\n

原型链的作用是用来实现继承,比如我们新建一个数组,数组的方法就是从数组的原型上继承而来的.

\n\n

var arr = [];\narr.map === Array.prototype.map
\n//arr.map是从arr.__proto__上继承下来的,arr.__proto__也就是Array.prototype

\n\n

angular中路由如何传参

\n\n

translate和transform

\n\n
    \n
  • translate:移动,
    \ntransform的一个方法 通过 translate() 方法,元素从其当前位置移动,根据给定的 left(x 坐标) 和 top(y 坐标) 位置参数:
    \n 用法:
    \ntransform: translate(50px, 100px);
    \n -ms-transform: translate(50px,100px);
    \n -webkit-transform: translate(50px,100px);
    \n -o-transform: translate(50px,100px);
    \n -moz-transform: translate(50px,100px);

  • \n
  • transform:变形、改变
    \n CSS3中主要包括

    \n\n
    • 旋转:rotate() 顺时针旋转给定的角度,允许负值 rotate(30deg)

    • \n
    • 扭曲:skew() 元素翻转给定的角度,根据给定的水平线(X 轴)和垂直线(Y 轴)参数:skew(30deg,20deg)

    • \n
    • 缩放:scale() 放大或缩小,根据给定的宽度(X 轴)和高度(Y 轴)参数: scale(2,4)

    • \n
    • 移动:translate() 平移,传进 x,y值,代表沿x轴和y轴平移的距离\n 所有的2D转换方法组合在一起: matrix() 旋转、缩放、移动以及倾斜元素\n matrix(scale.x ,, , scale.y , translate.x, translate.y)

  • \n
\n\n

改变起点位置 transform-origin: bottom left;

\n\n

综合起来使用:transform: 30deg 1.5 30deg 20deg 100px 200px;

\n\n
    \n
  • transition: 允许CSS属性值在一定的时间区间内平滑的过渡,
    \n 需要事件的触发,例如单击、获取焦点、失去焦点等\n transition:property duration timing-function delay;

    \n\n
    • property:CSS的属性,例如:width height 为none时停止所有的运动,可以为transform

    • \n
    • duration:持续时间

    • \n
    • timing-function:ease等

    • \n
    • delay:延迟

  • \n
\n\n

注意:当property为all的时候所有动画
\n 例如:transition:width 2s ease 0s;

\n\n

前端自动化工程化

\n\n

栅格化实现原理(为什么是分成12份)

\n\n

栅格系统为什么是12列,因为12是1,2,3,4,6的倍数,所以12列栅格系统相对较灵活,支持将一行分成1列,2列,3列,4列,6列。若是想要支持5列,那1,2,3,4,5的最小公倍数是60,而60这个数对于栅格系统来说显然太大了,所以12是最好的选择。

\n\n

js面向对象的理解

\n\n

每一个function就是一个对象
\n使用 new 新建对象
\n一个对象肯定是要赋予它属性和方法的,在JS中我们使用prototype原型关键字进行赋值

\n\n

spa最重要的核心

\n\n

前端路由

\n\n

前端路由机制

\n\n

hash机制

\n\n

js深度拷贝和浅度拷贝

\n\n

已知:
\nvar a = {c:1, b: function(){xxxx}};\nvar b = a;
\na.c = 2;

\n\n

问:
\nba分别是什么?

\n\n

答:

\n\n

b等于a等于{c:2, b: function(){xxxxx}}

\n\n

深拷贝方法:

\n\n
    \n
  • slice方法

    \n\n
    \n

    对于array对象的slice函数,\n 返回一个数组的一段。(仍为数组)\n arrayObj.slice(start, [end])
    \n 参数\n arrayObj
    \n 必选项。一个 Array 对象。\n start
    \n 必选项。arrayObj 中所指定的部分的开始元素是从零开始计算的下标。\n end
    \n 可选项。arrayObj 中所指定的部分的结束元素是从零开始计算的下标。\n 说明\n slice 方法返回一个 Array 对象,其中包含了 arrayObj 的指定部分。
    \n slice 方法一直复制到 end 所指定的元素,但是不包括该元素。如果 start 为负,将它作为 length + start处理,此处 length 为数组的长度。如果 end 为负,就将它作为 length + end 处理,此处 length 为数组的长度。如果省略 end ,那么 slice 方法将一直复制到 arrayObj 的结尾。如果 end 出现在 start 之前,不复制任何元素到新数组中。
    \n var arr = [\"One\",\"Two\",\"Three\"];\n var arrtoo = arr.slice(0);
    \n arrtoo[1] = \"set Map\";
    \n document.writeln(\"数组的原始值:\" + arr + \"<br />\");//Export:数组的原始值:One,Two,Three
    \n document.writeln(\"数组的新值:\" + arrtoo + \"<br />\");//Export:数组的新值:One,set Map,Three

    \n
  • \n
  • concat方法。

    \n\n
    \n

    concat() 方法用于连接两个或多个数组。\n 该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本。\n 语法\n arrayObject.concat(arrayX,arrayX,......,arrayX)
    \n 说明\n 返回一个新的数组。该数组是通过把所有 arrayX 参数添加到 arrayObject 中生成的。如果要进行 concat() 操作的参数是数组,那么添加的是数组中的元素,而不是数组。
    \n var arr = [\"One\",\"Two\",\"Three\"];\n var arrtooo = arr.concat();
    \n arrtooo[1] = \"set Map To\";
    \n document.writeln(\"数组的原始值:\" + arr + \"<br />\");//Export:数组的原始值:One,Two,Three
    \n document.writeln(\"数组的新值:\" + arrtooo + \"<br />\");//Export:数组的新值:One,set Map To,Three

    \n
  • \n
  • 对象的深浅拷贝
    \nvar a={name:'yy',age:26};\nvar b=new Object();
    \nb.name=a.name;
    \nb.age=a.age;
    \na.name='xx';
    \nconsole.log(b);//Object { name=\"yy\", age=26}
    \nconsole.log(a);//Object { name=\"xx\", age=26}

    \n就是把对象的属性遍历一遍,赋给一个新的对象。\nvar deepCopy= function(source) { \nvar result={};
    \nfor (var key in source) {
    \n result[key] = typeof source[key]===’object’? deepCoyp(source[key]): source[key];\n} \nreturn result; \n}
  • \n
\n\n

URL请求过程(从客户端到服务端)

\n\n

1、连接:DNS解析,找到相应的url则返回其ip,建立一个socket连接,因为socket是通过ip和端口建立的
\n2、请求:向web服务器发送GET或POST命令请求
\n3、应答:web服务器收到这个请求,进行处理。从它的文档空间中搜索目录或子目录中的文件index.html。如果找到该文件,Web服务器把该文件内容传送给相应的Web浏览器。为了告知浏览器,,Web服务器首先传送一些HTTP头信息,然后传送具体内容(即HTTP体信息),HTTP头信息和HTTP体信息之间用一个空行分开。
\n常用的HTTP头信息有:
\n  ① HTTP 1.0 200 OK  这是Web服务器应答的第一行,列出服务器正在运行的HTTP版本号和应答代码。代码\"200 OK\"表示请求完成。
\n  ② MIMEVersion:1.0 它指示MIME类型的版本。
\n  ③ content
type:类型 这个头信息非常重要,它指示HTTP体信息的MIME类型。如:contenttype:text/html指示传送的数据是HTML文档。
\n  ④ content
length:长度值 它指示HTTP体信息的长度(字节)。

\n\n

4、关闭连接:当应答结束后,Web浏览器与Web服务器必须断开,以保证其它Web浏览器能够与Web服务器建立连接。

\n\n

js的继承

\n\n

js中有三种继承方式:
\n1.js原型(prototype)实现继承
\n<script type=\"text/javascript\">
\n function Person(name,age){
\n this.name=name;
\n this.age=age;
\n }
\n Person.prototype.sayHello=function(){
\n alert(\"使用原型得到Name:\"+this.name);
\n }
\n var per=new Person(\"马小倩\",21);
\n per.sayHello(); //输出:使用原型得到Name:马小倩
\n function Student(){}
\n Student.prototype=new Person(\"洪如彤\",21);
\n var stu=new Student();
\n Student.prototype.grade=5;
\n Student.prototype.intr=function(){
\n alert(this.grade);
\n }
\n stu.sayHello();//输出:使用原型得到Name:洪如彤
\n stu.intr();//输出:5
\n</script>

\n2.构造函数实现继承
\n<script type=\"text/javascript\">
\n function Parent(name){
\n this.name=name;
\n this.sayParent=function(){
\n alert(\"Parent:\"+this.name);
\n }
\n }
\n function Child(name,age){
\n this.tempMethod=Parent;
\n this.tempMethod(name);
\n this.age=age;
\n this.sayChild=function(){
\n alert(\"Child:\"+this.name+\"age:\"+this.age);
\n }
\n }
\n var parent=new Parent(\"江剑臣\");
\n parent.sayParent(); //输出:“Parent:江剑臣”
\n var child=new Child(\"李鸣\",24); //输出:“Child:李鸣 age:24”
\n child.sayChild();
\n</script>

\n3.call , apply实现继承
\n<script type=\"text/javascript\">
\n function Person(name,age,love){
\n this.name=name;
\n this.age=age;
\n this.love=love;
\n this.say=function say(){
\n alert(\"姓名:\"+name);
\n }
\n }
\n //call方式
\n function student(name,age){
\n Person.call(this,name,age);
\n }
\n //apply方式
\n function teacher(name,love){
\n Person.apply(this,[name,love]);
\n //Person.apply(this,arguments); //跟上句一样的效果,arguments
\n }
\n //call与aplly的异同:
\n //1,第一个参数this都一样,指当前对象
\n //2,第二个参数不一样:call的是一个个的参数列表;apply的是一个数组(arguments也可以)
\n var per=new Person(\"武凤楼\",25,\"魏荧屏\"); //输出:“武凤楼”
\n per.say();
\n var stu=new student(\"曹玉\",18);//输出:“曹玉”
\n stu.say();
\n var tea=new teacher(\"秦杰\",16);//输出:“秦杰”
\n tea.say();
\n</script>

\n\n

js中的call和apply

\n\n

js中call和apply都可以实现继承,唯一的一点参数不同,func.call(func1,var1,var2,var3)对应的apply写法为:func.apply(func1,[var1,var2,var3])。

\n\n
\n

call 方法
\n 调用一个对象的一个方法,以另一个对象替换当前对象。
\n call([thisObj[,arg1[, arg2[, [,.argN]]]]])
\n 参数
\n thisObj
\n 可选项。将被用作当前对象的对象。
\n arg1, arg2, , argN
\n 可选项。将被传递方法参数序列。
\n 说明
\n call 方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。
\n 如果没有提供 thisObj 参数,那么 Global 对象被用作 thisObj。

\n
\n\n

说简单一点,这两函数的作用其实就是更改对象的内部指针,即改变对象的this指向的内容。这在面向对象的js编程过程中有时是很有用的。下面以apply为例,说说这两个函数在 js中的重要作用。
\n另外,Function.apply()在提升程序性能方面有着突出的作用

\n\n

闭包的作用域(this)

\n\n

this是基于执行环境绑定:全局函数中,this指代window;函数作为对象方法调用时,this指代那个对象;
\n要访问闭包的this,只要定义个变量缓存下来就好了。我一般喜欢var _this = this;\nvar person = {\n fullname: function{\n console.log(this);\n },\n printAge: function(){\n console.log(this);\n }\n}\nperson.fullname(); //this指向person
\nvar age = person.printAge;
\nage(); //this指向window(浏览器中)
\n
\nperson.printAge赋值给age之后,再执行age(),此时age没有显示指定调用对象则默认是window(浏览器环境)。所以this并不是声明所在的环境

\n\n

es6中 promise的优点

\n\n

(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称Fulfilled)和Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

\n\n

(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

\n\n

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

\n\n

Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

\n\n

如果某些事件不断地反复发生,一般来说,使用stream模式是比部署Promise更好的选择。

\n\n

js的预解析问题,函数外var x=1 函数中 alert(x);var x=2; alert出来的x=undefined

\n\n

函数里重新定义了x,所以函数体里的x 那个时候是未定义,主要是因为预解析的问题