前端面试题一


var a = {"x": 1};
var b = a;
a.x = 2;
b.x;

a = {"x":3};
console.log(b.x);
解析:
var a = {"x": 1}; //对象是引用类型,这里把对象{"x": 1}的引用地址赋值给变量a
var b = a; //把a所引用的对象地址赋值给b,此时a和b指向同一个对象
a.x = 2; //把a所指向对象的属性x设置为2,此时该对象为{"x": 2}
b.x; //由于a和b指向同一个对象,所以b.x = a.x = 2

a = {"x":3}; //这里重新对a进行赋值,把一个新对象{"x": 3}的引用地址赋值给变量a,此时a指向这个新对象{"x": 3},而b仍然指向原来的对象{"x": 2}
console.log(b.x); //这里输出2就顺理成章了
     

var setName=function(o){
  o.name='xiaoha';
};
var o1={};
setName(o1);
console.log(o1.name);//'xiaoha'

var setName=function(o){
  o.name='xiaoha';
  o={};
  o.name='22222';
};
var o1={};
setName(o1);
console.log(o1.name);//'xiaoha'
解析:
o=o1    o.name='xiaoha'     o={}#改变引用   o.name='22222'
参数传递分值传递和引用传递, 你代码中的是引用传递. 引用传递类似于指针.
第一段代码没问题是因为你没有改变o指向的对象(即没改变引用值);
第二段代码setName中将o指向的对象改成{}(即o不再指向o1), 在这之后的所有操作都与o1无关. 当然,在这之前的操作还是对o1有效.
  1.  
var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      return function(){
        return this.name;
     };
    }
};
alert(object.getNameFunc()()); //The Window。谁知道这个为什么会打印this window吗?对闭包不是很熟悉,请教你们一下。为什么调用的时候是window这个对象来调用的呢?
匿名函数的执行环境具有全局性,因此其this对象通常指向window。这不是闭包问题。在 JavaScript 中, 上下文对象就是 this 指针,即被调用函数所处的环境。 没做特殊处理的话,this根据当前执行环境决定执行那个对象的

object.getNameFunc()() === function(){return this.name}();

var name = "The Window";
var object = {
  name: "My Object",
  getNameFunc: function() {
  that = this;
  return function() {
  return that.name;
  };
  }
};

console.log(object.getNameFunc()());  // My Object
  1.    
为何这段代码的弹出结果是test1,test2,test4,以及出题者的意图是什么,涉及的知识面是哪些。
为何 test4会覆盖 test3
test2却不会覆盖test1
注册事件和下面的区别是啥
<div id="test">test</div>

<script type="text/javascript">
  var btn = document.getElementById("test");
  btn.addEventListener("click",function(e){
  alert("test1");
  },false);

  btn.addEventListener("click",function(e){
  alert("test2");
  },true);

  btn.οnclick=function(e){
  alert("test3");
  }

  btn.οnclick=function(e){
  alert("test4");
  }
</script>
test1 和 test2 是DOM 2级事件绑定,考察的主要有两点:
1)事件捕获和事件冒泡
2) DOM0 级只能添加一个事件处理程序,后添加的会覆盖先添加的。这也是test4会覆盖掉test3的原因。
DOM 2级规定可以为同一个元素绑定多个事件处理程序,绑定的事件处理程序会按照它们添加的先后顺序依次触发。
IE8及以前版本的IE不支持addEventListener,使用attachEvent,可以添加多个,只能通过removeEventListener来移除。

  1.  看下面代码,给出输出结果
for(var i=1;i<=3;i++){
  setTimeout(function(){
  console.log(i);
  },0);
 };    //4 4 4 
原因:Javascript事件处理器在线程空闲之前不会运行。

for(var i=1;i<=3;i++){
  setTimeout((function(a){ //改成立即执行函数
  console.log(a);
  })(i),0);
};  //1 2 3

  上边代码 //setTimout的存在一点必要都没有了,因为里面的console.log(a);会马上执行,而不是setTimout的序列等待结束之后才执行,可以把时间设成5秒,看看,是不是马上就打印了,更好的方式:
for(var i=1;i<=3;i++){
  (function(a){
  setTimeout(function(){ //改成立即执行函数
  console.log(a);
  },5000);
  })(i)
}

JS 是单线程的,而 setTimeout 注册事件是队列的,setTimeout 放到队列之后,因为之前的 for 循环并没有执行完,所以不会立即执行 setTimeout 的。这也就是为什么说 JS 时间处理在线程空闲之前不会运行 的原因了。实际上 for 循环是这么走的:
  • 循环开始 i=0
  • 定义了一个 setTimeout 1 并放到队列中
  • 上一个循环结束 i=1
  • 定义了一个 setTimeout 2并放到队列中
  • 上一个循环结束 i=2
  • 定义了一个 setTimeout 3并放到队列中
  • 上一个循环结束 i=3
  • 定义了一个 setTimeout 4并放到队列中
  • 执行 setTimeout 1
  • 执行 setTimeout 2
  • 执行 setTimeout 3
  • 执行 setTimeout 4
解决方法:通过闭包保存 i ,使之与匿名函数的  i 关联。
队列:
 - i++ // 2
 - (function(a){console.log(a)})(i) // 末尾的括号中传入i,保存现在i的值 2
 - i++ // 3
 - (function(a){console.log(a)})(i) // 末尾的括号中传入i,保存现在i的值 3
 - i++ // 4
 - (function(a){console.log(a)})(i) // 末尾的括号中传入i,保存现在i的值 4
等真正执行匿名函数时,i就是每个函数保存的值,而不是for循环i的最终值。

  1.   假设需实现构造函数Scope,它的特性如下:
var scopeA = new Scope();
scopeA.title = 'My title';
var scopeB = scopeA.$clone();
//实例有$clone方法用创建一个对象克隆,表现如下
console.log(scopeB.title === 'My title' ); //输出true
scopeA.title = 'Home title' ;
console.log(scopeB.title === 'Home title' );//输出true
//但是一旦scopeB主动修改它的属性,scopeA并不受影响
scopeB.title = 'scopeB title' ;
console.log(scopeA.title === 'Home title' )//输出true
请实现满足这个条件构造函数Scope(只需实现上述描述要求即可)。

解析:这个是考查原型继承,和自有属性 覆盖原型属性…
//方法1

var Scope = function(){};

Scope.prototype.$clone = function(){
  var f = function(){};//创建一个新的构造函数

  f.prototype = this;//将它的原型指向 当前的这个Scope 实例

  return new f();//返回 新创建的这个对象的实例
}

var scopeA = new Scope();
scopeA.title = 'My title';

var scopeB = scopeA.$clone();
//实例有$clone方法用创建一个对象克隆,表现如下
console.log(scopeB.title === 'My title' ); //输出true
scopeA.title = 'Home title' ;
console.log(scopeB.title === 'Home title' );//输出true
//但是一旦scopeB主动修改它的属性,scopeA并不受影响
scopeB.title = 'scopeB title' ;
console.log(scopeA.title === 'Home title' )//输出true
console.log(scopeB.title)//输出 scopeB title

//方法2.  

var Scope = function(){};
Scope.prototype.$clone = function(){
  return Object.create(this);
}

//ECMA5对原型式继承做了规范,增加Object.create这个API,IE6,7,8不支持这个方法.

  1. 此文的目的是为了解释如下现象:
var foo = { n: 1 };
var bar = foo;
foo.x = foo = { n: 2 };
console.log(foo.x); // undefined

关键点在于它的返回值,用一个栗子来解释就是说:如果有表达式 foo.x,则它的返回值是一个指向 foo 对象 属性的引用。

那么在知道了这一点后,开始解释上面的现象:

首先是两个变量的声明和初始化,var foo = { n: 1 }; var bar = foo;,这个很好理解,就是foo和bar同时指向了一个相同的对象{ n: 1 }

接下来,对于表达式 foo.x = foo = { n: 2 };,我们都知道它实际上等于是 foo.x = (foo = { n: 2 })。我们开始应用上ECMA规范上的步骤,虽然赋值运算符具有右结合性,然而它首先做的是得到表达式foo.x的值,根据我们对Property Accessors的解释它返回一个指向对象{ n: 1}的x成员的引用,需要注意的是,这个时候foo并没有改变引用的指向。

继续,开始计算右边的结果,就是让 foo指向另外的一个对象 {n: 2},返回值就是其右边运算式(operand)的结果,即对象 {n: 2} 这个容易理解。

那么现在应该清楚了,赋值语句中 foo.x 的结果是指向对象一成员x的引用,而下面的 console.log(foo.x) 中的 foo 指向的是对象二,所以这里 foo.x 返回 undefined 就理所当然了。

所以试着输出对象一,即 bar(因为它从始至终指向的是对象一):

{ n: 1, x: { n: 2 } }

  1. 小贤是一条可爱的小狗(Dog),它的叫声很好听(wow),每次看到主人的时候就会乖乖叫一声(yelp)。从这段描述可以得到以下对象:
function Dog() {
  this.wow = function() {
  alert(’Wow’);
  }
  this.yelp = function() {
  this.wow();
  }
}
小芒和小贤一样,原来也是一条可爱的小狗,可是突然有一天疯了(MadDog),一看到人就会每隔半秒叫一声(wow)地不停叫唤(yelp)。请根据描述,按示例的形式用代码来实现(提示关键字: 继承,原型,setInterval)。

解析
按照题面要求,使用 原型继承 来实现,代码如下:
function Dog() {
  this.wow = function() {
  console.log('Wow');
  }
  this.yelp = function() {
  this.wow();
  }
}

function MadDog() {
  this.madYelp = function() {
  setInterval(this.wow, 500);
  }
}

MadDog.prototype = new Dog();
var madDog = new MadDog();
madDog.madYelp();

  1. 请写出输出结果
var name = 'global';
var obj = {
  name: 'obj',
  dose: function() {
  this.name = 'dose';
  return function() {
  return this.name;
  }
  }
}
alert(obj.dose().call(this));
解析
obj.dose().call(this) 这句表达式有点长,我们可以做个 等价变换 看起来会便于理解。

var xxx = obj.dose();
xxx.call(this);
通过 等价变换 后,表达式的解构更下明了。
首先,要明确  obj.dose() 返回结果为:
function() {
  return this.name;
}

其次,要明确 call 函数的作用是: 改变函数体内部this的指向。
题目中 call 函数的传参this指代的是window。通过 call 函数将obj.dose()返回函数的运行环境变更为在 window 中。所以,this.name返回的就是window.name,即 'global' 。

对上述题目做一个变换。
var name = 'global';
var obj = {
  name: 'obj',
  dose: function() {
  this.name = 'dose';
  return function() {
  return this.name;
  }.bind(this)
  }
}
alert(obj.dose().call(this));
解析
由于 obj.dose() 的返回函数使用了 bind函数来固定了 this 的指向,那么外部再通过 call 函数来改变 this 指向变会 失效 。 obj.dose() 的执行环境仍为 dose 函数内。
首先, obj 对象中定义了 name 属性为 'obj';接着在 dose 方法内又改写了 name 属性为 ' dose'; 根据作用域链的就近原则,输出结果为 'dose'.


(function(){
    return typeof arguments;//"object"
})();
arguments 是一个 Array-like 对象,对应的就是传入函数的参数列表。你可以在任何函数中直接使用该变量。
typeo f操作符只会返回string类型的结果。参照如下列表可知对应不同数据,typeof返回的值都是什么:
类型 结果
undefined 'undefined'
null 'object'
Boolean 'boolean'
Number 'number'
String 'string'
Symbol (new in ECMAScript 2015) 'symbol'
Host object (provided by the JS environment) Implementation-dependent
Function object (implements [[Call]] in ECMA-262 terms) 'function'
Any other object 'object'
由此我们推断出, typeof arguments object

  1.  
var f = function g(){ return 23; };
typeof g();//报错
在 JS 里,声明函数只有 2 种方法:
第 1 种: function foo(){...} (函数声明)
第 2 种: var foo = function(){...} (等号后面必须是匿名函数,这句实质是函数表达式function expression)

除此之外,类似于 var foo = function bar(){...} 这样的东西统一按 2 方法处理,即在函数外部无法通过 bar 访问到函数,因为这已经变成了一个表达式。

但为什么不是 "undefined"?
这里如果求 typeof g ,会返回 undefined,但求的是 g(),所以会去先去调用函数 g,这里就会直接抛出异常,所以是 Error。
 
  1.  
(function(x){
  delete x;
  return x;//1
})(1);
delete操作符可以从对象中删除属性,正确用法如下:
delete object.property
delete object['property']

delete操作符只能作用在 对象的属性上,对变量和函数名无效。也就是说delete x是没有意义的。
你最好也知道,delete是不会直接释放内存的,她只是间接的中断对象引用
(delete 操作符用于删除对象的成员变量,不能删除函数的参数。)

  1.   
var y = 1, x = y = typeof x;
x;//"undefined"
我们试图分解上述代码成下面两步:
var y = 1; //step 1
var x = y = typeof x; //step 2

第一步应该没有异议,我们直接看第二步

赋值表达式从右向左执行
y 被重新赋值为 typeof x的结果,也就是“ undefined”
x 被赋值为右边表达式 (y = typeof x)的结果,也就是“ undefined”

  1.  
(function f(f){
  return typeof f();//"number"
})(function(){ return 1; });
解析:
(function f(f){
  //这里的f是传入的参数function(){ return 1; }
  //执行的结果自然是1
  return typeof f(); //所以根据问题一的表格我们知道,typeof 1结果是"number"
})(function(){ return 1; });

  1.      
var foo = {
  bar: function() { return this.baz; },
  baz: 1
};

(function(){
  return typeof arguments[0]();//"undefined"
})(foo.bar);
这里你可能会误以为最终结果是number。向函数中传递参数可以看作是一种赋值,所以arguments[0]得到是是真正的bar函数的值,而不是foo.bar这个引用,那么自然this也就不会指向foo,而是指向 arguments,经测试确实如此:
var foo = {
  bar: function() { return this; },
  baz: 1
  };
  (function(){
  return arguments[0]();
  })(foo.bar);   //[foo.bar()]
注意方括号。


var foo = {
  bar: function(){ return this.baz; },
  baz: 1
}
typeof (f = foo.bar)();//"undefined"
这和上一题是一样的问题,(f = foo.bar)返回的就是bar的值,而不是其引用,那么this也就指的不是foo了。

  1.   
var f = (function f(){ return '1'; }, function g(){ return 2; })();
typeof f;//"number"

逗号操作符 对它的每个操作对象求值(从左至右),然后返回最后一个操作对象的值

所以  (function f(){ return '1'; }, function g(){ return 2; }) 的返回值就是函数g,然后执行她,那么结果是2;
最后再 typeof 2,根据问题一的表格,结果自然是 number


  var x = 1;
  if (function f(){}) {
  x += typeof f;
  }
  x;   //1undefined
括号内的 function f(){} 不是函数声明,会被转换成 true ,因此 f 未定义。


var x = [typeof x, typeof y][1];
typeof typeof x;//"string"
因为没有声明过变量y,所以typeof y返回"undefined"
将typeof y的结果赋值给x,也就是说x现在是"undefined"
然后typeof x当然是"string"
最后typeof "string"的结果自然还是"string"


(function(foo){
  return typeof foo.bar;//"undefined"
})({ foo: { bar: 1 } });

这是个纯粹的视觉诡计,上注释
(function(foo){
  //这里的foo,是{ foo: { bar: 1 } },并没有bar属性哦。
  //bar属性是在foo.foo下面
  //所以这里结果是"undefined"
  return typeof foo.bar;
})({ foo: { bar: 1 } });


(function f(){
  function f(){ return 1; }
  return f();//2
  function f(){ return 2; }
})();
通过function declaration声明的函数甚至可以在声明之前使用,这种特性我们称之为hoisting。于是上述代码其实是这样被运行环境解释的:
(function f(){
  function f(){ return 1; }
  function f(){ return 2; }
  return f();
})();


function f(){ return f; }
new f() instanceof f;//false
简单地说,使用new操作符时,若 调用的函数返回的是一个对象,则相当于这个new操作符一点用也没有。函数f返回的是自身,即一个对象,因此代码相当于
function f(){ return f; }
 f instanceof f
instanceof操作符根据原型链判断一个对象是否是一个构造函数的实例。显然一个函数不可能是自己的实例。所以答案是false

  1. 简答题:什么是闭包,闭包有什么用?请举例说明。

闭包:这个术语非常古老,是指函数变量可以被隐藏于作用域链之内,因此看起来是函数将变量“包裹”起来了。

定义大多数函数时的作用域链在调用函数时依然有效,但这并不影响闭包。当调用函数时闭包所指向的作用域链,和定义函数时的作用域链不是同一个作用域链时,事情就变得非常微妙。当一个函数嵌套另一个函数,外部函数将嵌套的函数对象作为返回值返回的时候往往会发生这种事情。

function f1(){
   var n=1024;
   function f2(){
      console.log(n)
   }
   return f2;
}
var foo =f1();
foo(); //1024
//以上函数f2()就是闭包
闭包就是能够读取其他函数内部变量的函数。
function f1(){
  var a=1;
  function f2(){
  alert(a);
  };
  return f2
};

console.log(f1());//function f2(){alert(a);};
console.log(f1()());//弹出1

所以一般可以这样写:
function f1(){
  var a=1;
  function f2(){
  alert(a);
  };
  return f2
};

var result=f1();
console.log(result());//弹出1

关于闭包的概念
闭包是指有权访问另外一个函数作用域中的变量的函数.这概念有点绕,拆分一下。从概念上说,闭包有两个特点:

1、函数
2、能访问另外一个函数作用域中的变量

在ES 6之前,Javascript只有函数作用域的概念,没有块级作用域(但catch捕获的异常 只能在catch块中访问)的概念(IIFE可以创建局部作用域)。每个函数作用域都是封闭的,即外部是访问不到函数作用域中的变量。
function getName() {
  var name = "美女的名字";
  console.log(name); //"美女的名字"
}
function displayName() {
  console.log(name); //报错
}
但是为了得到美女的名字,不死心的单身汪把代码改成了这样:
function getName() {
  var name = "美女的名字";
  function displayName() {
  console.log(name);
  }
  return displayName;
}
var 美女 = getName();
美女() //"美女的名字"
这下,美女是一个闭包了,单身汪想怎么玩就怎么玩了。(但并不推荐单身汪用中文做变量名的写法,大家不要学)。

关于闭包呢,还想再说三点:
1、闭包可以访问当前函数以外的变量
function getOuter(){
  var date = '815';
  function getDate(str){
  console.log(str + date); //访问外部的date
  }
  return getDate('今天是:'); //"今天是:815"
}
getOuter();
getDate是一个闭包,该函数执行时,会形成一个作用域A,A中并没有定义变量date,但它能在父一级作用域中找到该变量的定义。
2、即使外部函数已经返回,闭包仍能访问外部函数定义的变量
function getOuter(){
  var date = '815';
  function getDate(str){
  console.log(str + date); //访问外部的date
  }
  return getDate; //外部函数返回
}
var today = getOuter();
today('今天是:'); //"今天是:815"
today('明天不是:'); //"明天不是:815"
3、闭包可以更新外部变量的值
function updateCount(){
  var count = 0;
  function getCount(val){
  count = val;
  console.log(count);
  }
  return getCount; //外部函数返回
}
var count = updateCount();
count(815); //815
count(816); //816
  1. apply 和 call 的用法和区别。

两者的作用都是将函数绑定到另外一个对象上面去,两者仅仅在定义参数方式有所区别。
apply(thisArg,argArray); //多个参数合并在一个数组
call(thisArg[,arg1,arg2…] ]); //多个参数分开传

  1. callee和caller属性
在ECMAScript 5严格模式种,对这两个属性的读写操作都会产生一个类型错误。
而在非严格模式下,ECMAScript标准规范规定

callee属性指代当前正在执行的函数
caller是非标准的,但大多数浏览器都实现了这个属性,它指代调用当前正在执行函数的函数。通过caller属性可以访问调用栈。
callee属性在某些时候会非常有用,比如在匿名函数中通过callee来递归调用自身。
var factorial = function(x) {
  if(x <= 1) { return 1;}
  return x * arguments.callee(x-1);
};

  1. 下面这个JS程序的输出是什么:
function Foo() {
  var i = 0;
  return function() {
  console.log(i++);
  }
}

var f1 = Foo(),
  f2 = Foo();
f1();
f1();
f2();  //0 1 0

  1. 请给出这段代码的运行结果( )
var bb = 1;
function aa(bb) {
  bb = 2;
  alert(bb);
};
aa(bb);
alert(bb);  //2 1
函数体内,bb并没有使用var来定义,按理说这个bb在预处理的时候应该是window的属性。但在这里,函数声明的时候,带了一个参数bb,也就是相当于在函数体内声明了var bb。所以,函数里的bb就是函数活动对象的属性。所以函数执行时会输出2。函数执行完后,函数的活动对象被销毁,也就是局部的这个bb被删除了,执行流进入到window,再输出bb,值就是1了。如果声明函数时,把参数那里的bb去掉,这段代码执行起来,结果就是弹出 2 2

  1. 请使用闭包的方式,写一段 JS 程序实现如下功能:函数每调用一次则该函数的返回值加 1。
function A() {
  var count = 0;
  function B() {
  count++;
  return count;
  }
  return B;
}

var plus = A();
//A中的count会一直停留在内存中。。。
plus();// 0
plus(); // 1
plus(); // 2

  1. 为什么MVC
1.代码规模越来越大,切分职责是大势所需
2.为了复用:很多逻辑是一样的
3.为了后期维护方便:修改一处功能不影响别处
MVC只是手段,终极目标还是模块化和复用

angularjs的MVC是借助$scope来实现的。

  1.  使用 typeof bar === "object" 判断 bar 是不是一个对象有神马潜在的弊端?如何避免这种弊端?

使用 typeof 的弊端是显而易见的(这种弊端同使用 instanceof):
let obj = {};
let arr = [];
console.log(typeof obj === 'object'); //true
console.log(typeof arr === 'object'); //true
console.log(typeof null === 'object'); //true
从上面的输出结果可知, typeof bar === "object" 并不能准确判断 bar 就是一个 Object。可以通过 Object.prototype.toString.call(bar) === "[object Object]" 来避免这种弊端:
let obj = {};
let arr = [];
console.log(Object.prototype.toString.call(obj)); //[object Object]
console.log(Object.prototype.toString.call(arr)); //[object Array]
console.log(Object.prototype.toString.call(null)); //[object Null]
另外,为了珍爱生命,请远离 ==:

而 [] === false 是返回 false 的。

  1. 下面的代码会在 console 输出神马?为什么?
(function(){
  var a = b = 3;
})();

console.log("a defined? " + (typeof a !== 'undefined'));
console.log("b defined? " + (typeof b !== 'undefined'));
这跟变量作用域有关,输出换成下面的:
console.log(b); //3
console,log(typeof a); //undefined
拆解一下自执行函数中的变量赋值:
b = 3;
var a = b;
所以 b 成了全局变量,而 a 是自执行函数的一个局部变量。

  1. 下面的代码会在 console 输出神马?为什么?
var myObject = {
  foo: "bar",
  func: function() {
  var self = this;
  console.log("outer func: this.foo = " + this.foo);
  console.log("outer func: self.foo = " + self.foo);
  (function() {
  console.log("inner func: this.foo = " + this.foo);
  console.log("inner func: self.foo = " + self.foo);
  }());
  }
};
myObject.func();

第一个和第二个的输出不难判断,在 ES6 之前,JavaScript 只有函数作用域,所以 func 中的 IIFE 有自己的独立作用域,并且它能访问到外部作用域中的 self,所以第三个输出会报错,因为 this 在可访问到的作用域内是 undefined,第四个输出是 bar。如果你知道闭包,也很容易解决的:
(function(test) {
  console.log("inner func: this.foo = " + test.foo); //'bar'
  console.log("inner func: self.foo = " + self.foo);
}(self));

  1. 将 JavaScript 代码包含在一个函数块中有神马意思呢?为什么要这么做?
换句话说,为什么要用立即执行函数表达式(Immediately-Invoked Function Expression)。

IIFE 有两个比较经典的使用场景,一是类似于在循环中定时输出数据项,二是类似于 JQuery/Node 的插件和模块开发。
for(var i = 0; i < 5; i++) {
  setTimeout(function() {
  console.log(i);
  }, 1000);
}
上面的输出并不是你以为的0,1,2,3,4,而输出的全部是5,这时 IIFE 就能有用了:
for(var i = 0; i < 5; i++) {
  (function(i) {
  setTimeout(function() {
  console.log(i);
  }, 1000);
  })(i)
}
而在 JQuery/Node 的插件和模块开发中,为避免变量污染,也是一个大大的 IIFE:
(function($) {
  //代码
 } )(jQuery);

  1. 在严格模式('use strict')下进行 JavaScript 开发有神马好处?
  • 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;
  • 消除代码运行的一些不安全之处,保证代码运行的安全;
  • 提高编译器效率,增加运行速度;
  • 为未来新版本的Javascript做好铺垫。

  1. 下面两个函数的返回值是一样的吗?为什么?
function foo1()
{
  return {
  bar: "hello"
  };
}

function foo2()
{
  return
  {
  bar: "hello"
  };
}
在编程语言中,基本都是使用分号(;)将语句分隔开,这可以增加代码的可读性和整洁性。而在JS中,如若语句各占独立一行,通常可以省略语句间的分号(;),JS 解析器会根据能否正常编译来决定是否自动填充分号:
var test = 1 +
2
console.log(test); //3
在上述情况下,为了正确解析代码,就不会自动填充分号了,但是对于 return 、break、continue 等语句,如果后面紧跟换行,解析器一定会自动在后面填充分号(;),所以上面的第二个函数就变成了这样:
function foo2()
{
  return;
  {
  bar: "hello"
  };
}
所以第二个函数是返回 undefined。

  1. 神马是 NaN,它的类型是神马?怎么测试一个值是否等于 NaN?
NaN 是 Not a Number 的缩写,JavaScript 的一种特殊数值,其类型是 Number,可以通过 isNaN(param) 来判断一个值是否是 NaN:
console.log(isNaN(NaN)); //true
console.log(isNaN(23)); //false
console.log(isNaN('ds')); //true
console.log(isNaN('32131sdasd')); //true
console.log(NaN === NaN); //false
console.log(NaN === undefined); //false
console.log(undefined === undefined); //false
console.log(typeof NaN); //number
console.log(Object.prototype.toString.call(NaN)); //[object Number]
ES6 中,isNaN() 成为了 Number 的静态方法:Number.isNaN().

  1. 解释一下下面代码的输出
console.log(0.1 + 0.2); //0.30000000000000004
console.log(0.1 + 0.2 == 0.3); //false
JavaScript 中的 number 类型就是浮点型,JavaScript 中的浮点数采用IEEE-754 格式的规定,这是一种二进制表示法,可以精确地表示分数,比如1/2,1/8,1/1024,每个浮点数占64位。但是,二进制浮点数表示法并不能精确的表示类似0.1这样 的简单的数字,会有舍入误差。

由于采用二进制,JavaScript 也不能有限表示 1/10、1/2 等这样的分数。在二进制中,1/10(0.1)被表示为 0.00110011001100110011…… 注意 0011 是无限重复的,这是舍入误差造成的,所以对于 0.1 + 0.2 这样的运算,操作数会先被转成二进制,然后再计算:
0.1 => 0.0001 1001 1001 1001…(无限循环)
0.2 => 0.0011 0011 0011 0011…(无限循环)
双精度浮点数的小数部分最多支持 52 位,所以两者相加之后得到这么一串 0.0100110011001100110011001100110011001100...因浮点数小数位的限制而截断的二进制数字,这时候,再把它转换为十进制,就成了 0.30000000000000004。

对于保证浮点数计算的正确性,有两种常见方式。

一是先升幂再降幂:
function add(num1, num2){
  let r1, r2, m;
  r1 = (''+num1).split('.')[1].length;
  r2 = (''+num2).split('.')[1].length;

  m = Math.pow(10,Math.max(r1,r2));
  return (num1 * m + num2 * m) / m;
}
console.log(add(0.1,0.2)); //0.3
console.log(add(0.15,0.2256)); //0.3756
二是是使用内置的 toPrecision() toFixed() 方法,注意,方法的返回值字符串。
function add(x, y) {
  return x.toPrecision() + y.toPrecision()
}
console.log(add(0.1,0.2)); //"0.10.2"

  1. 实现函数 isInteger(x) 来判断 x 是否是整数
可以将 x 转换成10进制,判断和本身是不是相等即可:
function isInteger(x) {
  return parseInt(x, 10) === x;
}
ES6 对数值进行了扩展,提供了静态方法 isInteger() 来判断参数是否是整数:
Number.isInteger(25) // true
Number.isInteger(25.0) // true
Number.isInteger(25.1) // false
Number.isInteger("15") // false
Number.isInteger(true) // false
JavaScript能够准确表示的整数范围在 -2^53 到 2^53 之间(不含两个端点),超过这个范围,无法精确表示这个值。ES6 引入了Number.MAX_SAFE_INTEGER 和 Number.MIN_SAFE_INTEGER这两个常量,用来表示这个范围的上下限,并提供了 Number.isSafeInteger() 来判断整数是否是安全型整数。

  1. 在下面的代码中,数字 1-4 会以什么顺序输出?为什么会这样输出?
(function() {
  console.log(1);
  setTimeout(function(){console.log(2)}, 1000);
  setTimeout(function(){console.log(3)}, 0);
  console.log(4);
})();   //1  4  3  2
主要是 JavaScript 的定时机制和时间循环,不要忘了,JavaScript 是单线程的。详解可以参考 从setTimeout谈JavaScript运行机制。

JavaScript是单线程的编程,什么是单线程,就是说同一时间JavaScript只能执行一段代码,如果这段代码要执行很长时间,那么之后的代码只能尽情地等待它执行完才能有机会执行,单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。JavaScript单线程机制也是迫不得已,假设有多个线程,同时修改某个dom元素,那么到底是听哪个线程的呢?
如果排队是因为计算量大,CPU忙不过来,倒也算了,但是很多时候CPU是闲着的,因为IO设备(输入输出设备)很慢(比如Ajax操作从网络读取数据),不得不等着结果出来,再往下执行。
JavaScript语言的设计者意识到,这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。
于是,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

  1. 写一个按照下面方式调用都能正常工作的 sum 方法
console.log(sum(2,3)); // Outputs 5
console.log(sum(2)(3)); // Outputs 5
针对这个题,可以判断参数个数来实现:
function sum() {
  var fir = arguments[0];
  if(arguments.length === 2) {
     return arguments[0] + arguments[1]
  } else {
      return function(sec) {
        return fir + sec;
      }
    }
  }

  1. 根据下面的代码片段回答后面的问题
for (var i = 0; i < 5; i++) {
  var btn = document.createElement('button');
  btn.appendChild(document.createTextNode('Button ' + i));
  btn.addEventListener('click', function(){ console.log(i); });
  document.body.appendChild(btn);
}
1、点击 Button 4 ,会在控制台输出什么?// 点击5个按钮中的任意一个,都是输出5
2、给出一种符合预期的实现方式
for (var i = 0; i < 5; i++) {
  var btn = document.createElement('button');
  btn.appendChild(document.createTextNode('Button ' + i));
  (function(j){
     btn.addEventListener('click', function(){
           console.log(j);
          })
     })(i)
  document.body.appendChild(btn);
}

  1. 下面的代码会输出什么?为什么?
var arr1 = "john".split(''); j o h n
var arr2 = arr1.reverse(); n h o j
var arr3 = "jones".split(''); j o n e s
arr2.push(arr3);
console.log("array 1: length=" + arr1.length + " last=" + arr1.slice(-1));
console.log("array 2: length=" + arr2.length + " last=" + arr2.slice(-1));

//array 1: length=5 last=j,o,n,e,s
//array 2: length=5 last=j,o,n,e,s
//arr1  [n,h,o,j,[j,o,n,e,s]]
//arr2  [n,h,o,j,[j,o,n,e,s]]

split是字符串方法,返回一个数组
reverse是数组的方法,会改变原数组,题中 arr1和arr2同时指向一个地址,也就是说存储的内容是一样的
  1. 下面的代码会输出什么?为什么?
console.log(1 + "2" + "2");//122
console.log(1 + +"2" + "2");//32
console.log(1 + -"1" + "2");//02
console.log(+"1" + "1" + "2");//112
console.log( "A" - "B" + "2");//NaN
console.log( "A" - "B" + 2);//NaN
输出什么,自己去运行吧,需要注意三个点:
多个数字和数字字符串混合运算时,跟操作数的位置有关
console.log(2 + 1 + '3'); / /‘33’
console.log('3' + 2 + 1); //'321'
数字字符串之前存在数字中的正负号(+/-)时,会被转换成数字
console.log(typeof '3'); // string
console.log(typeof +'3'); //number
同样,可以在数字前添加 '',将数字转为字符串
console.log(typeof 3); // number
console.log(typeof (''+3)); //string
对于运算结果不能转换成数字的,将返回 NaN
console.log('a' * 'sd'); //NaN
console.log('A' - 'B'); // NaN

  1. 如果 list 很大,下面的这段递归代码会造成堆栈溢出。如果在不改变递归模式的前提下修善这段代码?

var list = readHugeList();

var nextListItem = function() {
  var item = list.pop();

  if (item) {
  // process the list item...
  nextListItem();
  }
};

原文上的解决方式是加个定时器:
var list = readHugeList();

var nextListItem = function() {
  var item = list.pop();

  if (item) {
  // process the list item...
  setTimeout( nextListItem, 0);
  }

  1. 解释下列代码的输出
console.log("0 || 1 = "+(0 || 1));
console.log("1 || 2 = "+(1 || 2));
console.log("0 && 1 = "+(0 && 1));
console.log("1 && 2 = "+(1 && 2));
逻辑与和逻辑或运算符会返回一个值,并且二者都是短路运算符:

逻辑与返回第一个是 false 的操作数 或者 最后一个是 true的操作数
console.log(1 && 2 && 0); //0
console.log(1 && 0 && 1); //0
console.log(1 && 2 && 3); //3
如果某个操作数为 false,则该操作数之后的操作数都不会被计算

逻辑或返回第一个是 true 的操作数 或者 最后一个是 false的操作数
console.log(1 || 2 || 0); //1
console.log(0 || 2 || 1); //2
console.log(0 || 0 || false); //false
如果某个操作数为 true,则该操作数之后的操作数都不会被计算

如果逻辑与和逻辑或作混合运算,则逻辑与的优先级高:
console.log(1 && 2 || 0); //2
console.log(0 || 2 && 1); //1
console.log(0 && 2 || 1); //1
在 JavaScript,常见的 false 值:
0, '0', +0, -0, false, '',null,undefined,null,NaN
要注意空数组([])和空对象({}):
console.log([] == false) //true
console.log({} == false) //false
console.log(Boolean([])) //true
console.log(Boolean({})) //true
所以在 if 中,[] 和 {} 都表现为 true
console.log(false == '0')// true
console.log(false === '0')// false

  1. 解释下面代码的输出
var a={},
  b={key:'b'},
  c={key:'c'};

a[b]=123;
a[c]=456;

console.log(a[b]);//456
我理解是a[b]这种方式处理的时候,[]在解析内容时调用了Object.prototype.toString()这个方法。
例如a[b]中a是一个object,b是一个定义了的object,采用a[b]这种方式调用的时候,中括号中的对象b会被转成[object Object]字符串作为字段,所以调用a[c]的时候,也是将之前定义的对象c转成了[object Object]字段,因此两次赋值其实是对同一个字段的修改;
如果采用a.b和a.c的方式赋值,会把b和c这两个字符作为字段进行处理。

  1. 解释下面代码的输出
console.log((function f(n){return ((n > 1) ? n * f(n-1) : n)})(10));
结果是10的阶乘,这是一个递归调用.

  1. 解释下面代码的输出
(function(x) {
  return (function(y) {
  console.log(x);
  })(2)
})(1);
输出1,闭包能够访问外部作用域的变量或参数。

  1. 给你一个 DOM 元素,创建一个能访问该元素所有子元素的函数,并且要将每个子元素传递给指定的回调函数。
函数接受两个参数:
  • DOM
  • 指定的回调函数
function Traverse(p_element,p_callback) {
  p_callback(p_element);
  var list = p_element.children;
  for (var i = 0; i < list.length; i++) {
  Traverse(list[i],p_callback); // recursive call
  }
}

  1. 讲解原生Js实现ajax的原理。
  Ajax 的全称是Asynchronous JavaScript and XML,其中,Asynchronous 是异步的意思,它有别于传统web开发中采用的同步的方式。

  Ajax的原理简单来说通过XmlHttpRequest对象来向服务器发异步请求,从服务器获得数据,然后用javascript来操作DOM而更新页面。

  XMLHttpRequest是ajax的核心机制,它是在IE5中首先引入的,是一种支持异步请求的技术。简单的说,也就是javascript可以及时向服务器提出请求和处理响应,而不阻塞用户。达到无刷新的效果。

  XMLHttpRequest这个对象的属性有:

onreadystatechange 每次状态改变所触发事件的事件处理程序。
responseText 从服务器进程返回数据的字符串形式。
responseXML 从服务器进程返回的DOM兼容的文档数据对象。
status 从服务器返回的数字代码,比如常见的404(未找到)和200(已就绪)
status Text 伴随状态码的字符串信息
readyState 对象状态值
0 (未初始化) 对象已建立,但是尚未初始化(尚未调用open方法)
1 (初始化) 对象已建立,尚未调用send方法

2 (发送数据) send方法已调用,但是当前的状态及http头未知

3 (数据传送中) 已接收部分数据,因为响应及http头不全,这时通过responseBody和responseText获取部分
数据会出现错误,

4 (完成) 数据接收完毕,此时可以通过通过responseXml和responseText获取完整的回应数据
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
很抱歉,我无法提供具体的2024年前端面试题一百道,因为面试题的内容和形式可能会因公司、职位和行业而异。不过,我可以提供一些通用的前端面试题,以帮助您准备面试。 1. 请简述前端开发的主要技术栈包括哪些? 2. 请描述一下什么是HTML、CSS和JavaScript? 3. 请解释一下什么是响应式设计?如何在前端开发中实现响应式设计? 4. 请简述一下什么是前端框架,并列举几个常用的前端框架。 5. 请解释一下什么是Vue.js,并简述其核心概念和用法。 6. 请解释一下什么是React.js,并简述其核心概念和用法。 7. 请简述一下什么是Webpack,并解释其作用和用法。 8. 请解释一下什么是ES6,并列举一些ES6的新特性。 9. 请简述一下什么是前端性能优化,并列举一些优化技巧。 10. 请解释一下什么是HTTP/2,并简述其优点和缺点。 除了以上问题,您还可以准备一些更具体的问题,例如: 1. 请解释一下如何使用CSS选择器选择元素? 2. 请解释一下如何使用JavaScript操作DOM? 3. 请描述一下如何使用Vue.js实现一个简单的计数器组件。 4. 请解释一下如何使用React.js实现一个简单的表单组件。 5. 请描述一下如何使用Webpack进行代码拆分和优化。 6. 请解释一下什么是跨域问题,并简述如何解决跨域问题。 7. 请描述一下如何使用JavaScript进行异步编程,例如使用Promise和async/await。 8. 请解释一下什么是前端安全,并列举一些常见的安全问题及其解决方法。 希望以上信息对您有所帮助,祝面试成功!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值