前端JavaScript疑问简答题面试题

JavaScript面试题

1. 简述同步和异步的区别

同步:浏览器访问服务器请求,用户看得到页面刷新,重新发请求,等请求完,页面刷新,新内容出现,用户看到新内容,进行下一步操作

异步:浏览器访问服务器请求,用户正常操作,浏览器后端进行请求。等请求完,页面不刷新,新内容也会出现,用户看到新内容

2. 怎么添加、移除、复制、创建、和查找节点

(1)创建新节点


createDocumentFragment()    //创建一个DOM片段

createElement()   //创建一个具体的元素

createTextNode()   //创建一个文本节点

(2)添加、移除、替换、插入


appendChild()

removeChild()

replaceChild()

insertBefore()

(3)查找


getElementsByTagName()    //通过标签名称

getElementsByName()    //通过元素的Name属性的值

getElementById()    //通过元素Id,唯一性

3. 实现一个函数clone 可以对Javascript中的五种主要数据类型(Number、string、Object、Array、Boolean)进行复制

Object.prototype.clone=function(){
​	var o= this.constructor===Array? [] : {};
​	for(var e in this){
​		o[e]= typeof this[e]==="object" ? this[e].clone() : this[e];
​	}
​	return o;};

4. 如何消除一个数组里面重复的元素

function qc(arr1){
​	let arr = [];
​	for( let i = 0; i < arr1.length; i++) {
​		if( arr.indexOf(arr1[i]) == -1) {
​			arr.push(arr1[i])
​		}
​	}
​	return arr;}
arr1 = ["1","1","3","5","2","24","4","4","a","a","b"];		
console.log(qc(arr1));  

5. 写一个返回闭包的函数

**闭包(closure)**是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。闭包就是能够读取其他函数内部变量的函数。可以把闭包简单理解成”定义在一个函数内部的函数”。

闭包有三个特性:

1.函数嵌套函数;

2.函数内部可以引用外部的参数和变量;

3.参数和变量不会被垃圾回收机制回收。

闭包就是一个函数的返回值为另外一个函数,在outer外部可以通过这个返回的函数访问outer内的局部变量.

function outer(){
​     var val = 0;
​     return function (){
​           val += 1;
​           document.write(val + "<br />");
​     };}
var outObj = outer();outObj();//1,执行val += 1后,val还在outObj();//2
outObj = null;//val 被回收
var outObj1 = outer();outObj1();//1outObj1();//2

闭包会使变量始终保存在内存中,如果不当使用会增大内存消耗(如果上例中定义很多outer(),则内存中会保存很多val变量)。

javascript的垃圾回收原理:

(1)、在javascript中,如果一个对象不再被引用,那么这个对象就会被GC回收;

(2)、如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。

那么使用闭包有什么好处呢?使用闭包的好处是:

1.希望一个变量长期驻扎在内存中

2.避免全局变量的污染

3.私有成员的存在

6. 使用递归完成1到100的累加

function sum(num) {
​	if( num==1 ){
​		return 1;
​	}
​	return num+sum(num-1);}
console.log(sum(100))

7. Javascript有哪几种数据类型

基本数据类型:

字符串 String

数字 Number

布尔 Boolean

复合数据类型:

数组 Array

对象 Object

特殊数据类型:

Null 空对象

Undefined 未定义

8. 如何判断数据类型

判断js中的数据类型的几种方法

判断js中的数据类型有一下几种方法:typeof、instanceof、 constructor、 prototype、 $.type()/jquery.type(),

接下来主要比较一下这几种方法的异同。

判断js中的数据类型的几种方法

1、最常见的判断方法:typeof

alert(typeof a)   ------------> string
alert(typeof b)   ------------> number
alert(typeof c)   ------------> object
alert(typeof d)   ------------> object
alert(typeof e)   ------------> function
alert(typeof f)   ------------> function

其中typeof返回的类型都是字符串形式,需注意,例如:

alert(typeof a == "string") -------------> true
alert(typeof a == String) ---------------> false

另外typeof 可以判断function的类型;在判断除Object类型的对象时比较方便。

2、判断已知对象类型的方法: instanceof

alert(c instanceof Array) ---------------> true
alert(d instanceof Date) 
alert(f instanceof Function) ------------> true
alert(f instanceof function) ------------> false

注意:instanceof 后面一定要是对象类型,并且大小写不能错,该方法适合一些条件选择或分支。

3、根据对象的constructor判断: constructor

alert(c.constructor === Array) ----------> true
alert(d.constructor === Date) -----------> true
alert(e.constructor === Function) -------> true

注意: constructor 在类继承时会出错

eg:

​      function A(){};
​      function B(){};
​      A.prototype = new B(); //A继承自B
​      var aObj = new A();
​      alert(aobj.constructor === B) -----------> true;
​      alert(aobj.constructor === A) -----------> false;

而instanceof方法不会出现该问题,对象直接继承和间接继承的都会报true:

​      alert(aobj instanceof B) ----------------> true;
​      alert(aobj instanceof B) ----------------> true;

言归正传,解决construtor的问题通常是让对象的constructor手动指向自己:

​      aobj.constructor = A; //将自己的类赋值给对象的constructor属性
​      alert(aobj.constructor === A) -----------> true
​      alert(aobj.constructor === B) -----------> false; //基类不会报true了;

4、通用但很繁琐的方法: prototype

alert(Object.prototype.toString.call(a) === ‘[object String]’) -------> true;
alert(Object.prototype.toString.call(b) === ‘[object Number]’) -------> true;
alert(Object.prototype.toString.call(c) === ‘[object Array]’) -------> true;
alert(Object.prototype.toString.call(d) === ‘[object Date]’) -------> true;
alert(Object.prototype.toString.call(e) === ‘[object Function]’) -------> true;
alert(Object.prototype.toString.call(f) === ‘[object Function]’) -------> true;

大小写不能写错,比较麻烦,但胜在通用。

5、万能的方法:jquery.type()

如果对象是undefined或null,则返回相应的“undefined”或“null”。

jQuery.type( undefined ) === "undefined"
jQuery.type() === "undefined"
jQuery.type( window.notDefined ) === "undefined"
jQuery.type( null ) === "null"

如果对象有一个内部的[[Class]]和一个浏览器的内置对象的 [[Class]] 相同,我们返回相应的 [[Class]] 名字。 (有关此技术的更多细节。 )

jQuery.type( true ) === "boolean"
jQuery.type( 3 ) === "number"
jQuery.type( "test" ) === "string"
jQuery.type( function(){} ) === "function"
jQuery.type( [] ) === "array"
jQuery.type( new Date() ) === "date"
jQuery.type( new Error() ) === "error" // as of jQuery 1.9
jQuery.type( /test/ ) === "regexp"

其他一切都将返回它的类型“object”。

9. console.log(1+‘2’)和console.log(1-‘2’)的打印结果

console.log(1+'2') //12
console.log(1-'2') //-1

10. Js的事件委托是什么,原理是什么

事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。

原理:事件委托是利用事件的冒泡原理来实现的,何为事件冒泡呢?就是事件从最深的节点开始,然后逐步向上传播事件。

11. 如何改变函数内部的this指针的指向

1.bind:

 fun.bind(thisArg[, arg1[, arg2[, …]]])

他是直接改变这个函数的this指向并且返回一个新的函数,之后再次调用这个函数的时候this都是指向bind绑定的第一个参数。bind传餐方式跟call方法一致。

thisArg 当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用new 操作符调用绑定函数时,该参数无效。

arg1, arg2, … 当绑定函数被调用时,这些参数将置于实参之前传递给被绑定的方法。

2.call:

 fun.call(thisArg, arg1, arg2, …)

call跟apply的用法几乎一样,唯一的不同就是传递的参数不同,call只能一个参数一个参数的传入。

thisArg: 在fun函数运行时指定的this值。需要注意的是,指定的this值并不一定是该函数执行时真正的this值,如果这个函数处于非严格模式下,则指定为null和undefined的this值会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象。

arg1, arg2, … 指定的参数列表

3.apply

apply则只支持传入一个数组,哪怕是一个参数也要是数组形式。最终调用函数时候这个数组会拆成一个个参数分别传入。

thisArg 在 fun 函数运行时指定的 this 值。需要注意的是,指定的 this 值并不一定是该函数执行时真正的 this 值,如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象。

argsArray 一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 fun 函数。如果该参数的值为null 或 undefined,则表示不需要传入任何参数。从ECMAScript 5 开始可以使用类数组对象。

四.总结

当我们使用一个函数需要改变this指向的时候才会用到callapplybind

如果你要传递的参数不多,则可以使用fn.call(thisObj, arg1, arg2 …)

如果你要传递的参数很多,则可以用数组将参数整理好调用fn.apply(thisObj, [arg1, arg2 …])

如果你想生成一个新的函数长期绑定某个函数给某个对象使用,则可以使用const newFn = fn.bind(thisObj); newFn(arg1, arg2…)

call和apply第一个参数为null/undefined,函数this指向全局对象,在浏览器中是window,在node中是global

12. 列举几种解决跨域问题的方式,且说明原理

1.jsonp

script标签是不受同源策略影响的,它可以引入来自任何地方的js文件。动态添加script

2、使用window.name来进行跨域

window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。

3、使用HTML5中新引进的window.postMessage方法来跨域传送数据

window.postMessage(message,targetOrigin) 方法是html5新引进的特性,可以使用它来向其它的window对象发送消息,无论这个window对象是属于同源或不同源,目前IE8+、FireFox、Chrome、Opera等浏览器都已经支持window.postMessage方法。

13. 谈谈垃圾回收机制的方式及内存管理

一、垃圾回收机制—GC

Javascript具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存。

原理:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存

通常情况下有两种实现方式:标记清除和引用计数

标记清除: js中最常用的垃圾回收方式就是标记清除。

当变量进入环境时,例如,在函数中声明一个变量,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为“离开环境”。

引用计数的含义是跟踪记录每个值被引用的次数。

当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则该值的引用次数加1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减1。当这个值的引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾回收器下次再运行时,它就会释放那些引用次数为0的值所占用的内存。

二、内存管理

1)、Javascript引擎基础GC方案是(simple GC):mark and sweep(标记清除),即:

(1)遍历所有可访问的对象。

(2)回收已不可访问的对象。

2)、GC的缺陷

和其他语言一样,javascript的GC策略也无法避免一个问题:GC时,停止响应其他操作,这是为了安全考虑。而Javascript的GC在100ms甚至以上,对一般的应用还好,但对于JS游戏,动画对连贯性要求比较高的应用,就麻烦了。这就是新引擎需要优化的点:避免GC造成的长时间停止响应。

3)、GC优化策略

1)分代回收(Generation GC)

这个和Java回收策略思想是一致的。目的是通过区分“临时”与“持久”对象;多回收“临时对象”区(young generation),少回收“持久对象”区(tenured generation),减少每次需遍历的对象,从而减少每次GC 的耗时

2)增量GC

这个方案的思想很简单,就是“每次处理一点,下次再处理一点,如此类推”

14. 写一个function ,清除字符串前后的空格

//重写trim方法
if(!String.prototype.trim){
​    String.prototype.trim = function(){
​       return this.replace(/^\s+/,"").replace(/\s+$/,""); 
		}
}
//写fntrim去掉左右空格
function fntrim(str){
return str.replace(/^\s+/,"").replace(/\s+$/,"");
}

15. js实现继承的方法有哪些

JS继承的实现方式

既然要实现继承,那么首先我们得有一个父类,代码如下:

// 定义一个动物类
function Animal (name) {
  // 属性
  this.name = name || 'Animal';
  // 实例方法
  this.sleep = function(){
​    console.log(this.name + '正在睡觉!');
  	}
 }
 // 原型方法
Animal.prototype.eat = function(food) {
  console.log(this.name + '正在吃:' + food);
};

1、原型链继承

核心:将父类的实例作为子类的原型

function Cat(){ }
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.eat('fish'));
console.log(cat.sleep());
console.log(cat instanceof Animal); //true 
console.log(cat instanceof Cat); //true

特点:

  1. 非常纯粹的继承关系,实例是子类的实例,也是父类的实例

  2. 父类新增原型方法/原型属性,子类都能访问到

  3. 简单,易于实现

缺点:

  1. 要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,不能放到构造器中

  2. 无法实现多继承

  3. 来自原型对象的所有属性被所有实例共享(来自原型对象的引用属性是所有实例共享的)(详细请看附录代码:[示例1](javascript:void(0); ))

  4. 创建子类实例时,无法向父类构造函数传参

2、构造继承

核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';}
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true

特点:

  1. 解决了1中,子类实例共享父类引用属性的问题

  2. 创建子类实例时,可以向父类传递参数

  3. 可以实现多继承(call多个父类对象)

缺点:

  1. 实例并不是父类的实例,只是子类的实例

  2. 只能继承父类的实例属性和方法,不能继承原型属性/方法

  3. 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

3、实例继承

核心:为父类实例添加新特性,作为子类实例返回

function Cat(name){
  var instance = new Animal();
  instance.name = name || 'Tom';
  return instance;}
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // false

特点:

  1. 不限制调用方式,不管是new 子类()还是子类(),返回的对象具有相同的效果

缺点:

  1. 实例是父类的实例,不是子类的实例

  2. 不支持多继承

4、拷贝继承

function Cat(name){
  var animal = new Animal();
  for(var p in animal){
​    Cat.prototype[p] = animal[p];
  }
  Cat.prototype.name = name || 'Tom';}
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true

特点:

  1. 支持多继承

缺点:

  1. 效率较低,内存占用高(因为要拷贝父类的属性)

  2. 无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)

5、组合继承

核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true

特点:

  1. 弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法

  2. 既是子类的实例,也是父类的实例

  3. 不存在引用属性共享问题

  4. 可传参

  5. 函数可复用

缺点:

  1. 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)

6、寄生组合继承

核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';}(function(){
 // 创建一个没有实例方法的类
  var Super = function(){};
  Super.prototype = Animal.prototype;
  //将实例作为子类的原型
  Cat.prototype = new Super();})();
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); //true
Cat.prototype.constructor = Cat; // 需要修复下构造函数

特点:

  1. 堪称完美

缺点:

  1. 实现较为复杂

16. 判断一个变量是否是数组,有哪些办法

1、在ECMAScript5标准中Array类增加了一个静态方法isArray,我们可以直接用Array.isArray来判断变量是否是数组。

Array.isArray([1,2,3]) //此处返回true

2、但是某些比较老的浏览器,比如IE8及以下,没有实现Array的isArray方法,那么就需要换一种方式来判断:

Object.prototype.toString.call([1,2,3]) //返回字符串:’[object Array]’

那么我们定义一个函数来实现数组判断

function isArray (value) {
​            if  (Object.prototype.toString.call(value) === '[object Array]') {
​                return true
​            }
​                return false
​        }

3、index of

var arr=new Array([“b”,2,“a”,4,“test”]);

17. let ,const ,var 有什么区别

  1. let 和 const 定义的变量不会出现变量提升,而 var 定义的变量会提升。

  2. let 和 const 是JS中的块级作用域

  3. let 和 const 不允许重复声明(会抛出错误)

  4. let 和 const 定义的变量在定义语句之前,如果使用会抛出错误(形成了暂时性死区),而 var 不会。

  5. const 声明一个只读的常量。一旦声明,常量的值就不能改变(如果声明是一个对象,那么不能改变的是对象的引用地址)

18. 箭头函数与普通函数有什么区别

  1. 箭头函数是匿名函数,不能作为构造函数,不能使用new

  2. 箭头函数不绑定arguments,取而代之用rest参数…解决

  3. 箭头函数不绑定this,会捕获其所在的上下文的this值,作为自己的this值

  4. 箭头函数通过 call() 或 apply() 方法调用一个函数时,只传入了一个参数,对 this 并没有影响。

  5. 箭头函数没有原型属性

  6. 箭头函数不能当做Generator函数,不能使用yield关键字

19. 随机取1-10之间的整数

Math.floor([Math.random]*10+1)

20. new操作符具体干了什么

  1. 创建一个空对象,并且this变量引用该对象,同时还继承了该函数的原型

  2. 属性和方法被加入到this引用的对象中

  3. 新创建的对象由this所引用,并且最后隐式的返回this

21. Ajax原理

Ajax的原理简单来说是在用户和服务器之间加了—个中间层(AJAX引擎),通过XmlHttpRequest对象来向服务器发异步请求,从服务器获得数据,然后用javascript来操作DOM而更新页面。使用户操作与服务器响应异步化。这其中最关键的一步就是从服务器获得请求数据

Ajax的过程只涉及JavaScript、XMLHttpRequest和DOM。XMLHttpRequest是ajax的核心机制

  1. 创建连接
   var xhr = null;
   xhr = new XMLHttpRequest()
  1. 连接服务器
   xhr.open('get', url, true)
  1. 发送请求
   xhr.send(null);
  1. 接受请求
   xhr.onreadystatechange = function(){
​       if(xhr.readyState == 4){
​           if(xhr.status == 200){
​               success(xhr.responseText);
​           } else { // fail
​               fail && fail(xhr.status);
​           }
​       }
   }

22. 模块化开发怎么做

立即执行函数,不暴露私有成员

var module1 = (function(){
    var _count = 0;
    var m1 = function(){
      //...
    };
    var m2 = function(){
      //...
    };
    return {
      m1 : m1,
      m2 : m2
    };
  })();

23. 异步加载Js的方式有哪些

  1. defer,只支持IE

  2. async:

  3. 创建script,插入到DOM中,加载完毕后callBack

24. xml和 json的区别

数据体积方面

JSON相对于XML来讲,数据的体积小,传递的速度更快些。

数据交互方面

JSON与JavaScript的交互更加方便,更容易解析处理,更好的数据交互

数据描述方面

JSON对数据的描述性比XML较差

传输速度方面

JSON的速度要远远快于XML

25. webpack如何实现打包的

WebPack是一个模块打包工具,你可以使用WebPack管理你的模块依赖,并编绎输出模块们所需的静态文件。它能够很好地管理、打包Web开发中所用到的HTML、Javascript、CSS以及各种静态文件(图片、字体等),让开发过程更加高效。对于不同类型的资源,webpack有对应的模块加载器。webpack模块打包器会分析模块间的依赖关系,最后 生成了优化且合并后的静态资源。

26. 常见web安全及防护原理

sql注入原理

  1. 就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令

总的来说有以下几点

  1. 永远不要信任用户的输入,要对用户的输入进行校验,可以通过正则表达式,或限制长度,对单引号和双"-"进行转换等

  2. 永远不要使用动态拼装SQL,可以使用参数化的SQL或者直接使用存储过程进行数据查询存取

  3. 永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接

  4. 不要把机密信息明文存放,请加密或者hash掉密码和敏感的信息

XSS原理及防范

n Xss(cross-site scripting)攻击指的是攻击者往Web页面里插入恶意html标签或者javascript代码。比如:攻击者在论坛中放一个看似安全的链接,骗取用户点击后,窃取cookie中的用户私密信息;或者攻击者在论坛中加一个恶意表单,当用户提交表单的时候,却把信息传送到攻击者的服务器中,而不是用户原本以为的信任站点

XSS防范方法

n 首先代码里对用户输入的地方和变量都需要仔细检查长度和对”<”,”>”,”;”,”’”等字符做过滤;其次任何内容写到页面之前都必须加以encode,避免不小心把html tag弄出来。这一个层面做好,至少可以堵住超过一半的XSS 攻击

XSS与CSRF有什么区别吗?

  1. XSS是获取信息,不需要提前知道其他用户页面的代码和数据包。CSRF是代替用户完成指定的动作,需要知道其他用户页面的代码和数据包。要完成一次CSRF攻击,受害者必须依次完成两个步骤

  2. 登录受信任网站A,并在本地生成Cookie

  3. 在不登出A的情况下,访问危险网站B

CSRF的防御

n 服务端的CSRF方式方法很多样,但总的思想都是一致的,就是在客户端页面增加伪随机数

n 通过验证码的方法

27. 用过哪些设计模式

工厂模式:

  1. 工厂模式解决了重复实例化的问题,但还有一个问题,那就是识别问题,因为根本无法

  2. 主要好处就是可以消除对象间的耦合,通过使用工程方法而不是new关键字

构造函数模式

  1. 使用构造函数的方法,即解决了重复实例化的问题,又解决了对象识别的问题,该模式与工厂模式的不同之处在于

  2. 直接将属性和方法赋值给this对象;

28. 为什么要同源限制

同源策略指的是:协议,域名,端口相同,同源策略是一种安全协议

举例说明:比如一个黑客程序,他利用Iframe把真正的银行登录页面嵌到他的页面上,当你使用真实的用户名,密码登录时,他的页面就可以通过Javascript读取到你的表单中input中的内容,这样用户名,密码就轻松到手了。

29. offsetWidth/offsetHeight,clientWidth/clientHeight与scrollWidth/scrollHeight的区别

offsetWidth/offsetHeight返回值包含content + padding + border,效果与e.getBoundingClientRect()相同

clientWidth/clientHeight返回值只包含content + padding,如果有滚动条,也不包含滚动条

scrollWidth/scrollHeight返回值包含content + padding + 溢出内容的尺寸

30. javascript有哪些方法定义对象

对象字面量:var obj = {};

构造函数:var obj = new Object();

Object.create():var obj = Object.create(Object.prototype);

31. 说说你对promise的了解

依照Promise/A+的定义,Promise有四种状态:

  1. pending:初始状态, 非fulfilled或rejected.

  2. fulfilled:成功的操作.

  3. rejected:失败的操作.

  4. settled: Promise已被fulfilled或rejected,且不是pending

另外,fulfilled与rejected一起合称settled

Promise对象用来进行延迟(deferred) 和异步(asynchronous) 计算

Promise 的构造函数

构造一个Promise,最基本的用法如下:

var promise = new Promise(function(resolve, reject) {
​        if (...) {  // succeed
​            resolve(result);
​        } else {   // fails
​            reject(Error(errMessage));
​        }
​    });

Promise实例拥有then方法(具有then方法的对象,通常被称为thenable)。它的使用方法如下:

1promise.then(onFulfilled, onRejected)

接收两个函数作为参数,一个在fulfilled的时候被调用,一个在rejected的时候被调用,接收参数就是future,onFulfilled对应resolve,onRejected对应reject

32. 谈谈你对AMD、CMD的理解

CommonJS是服务器端模块的规范,Node.js采用了这个规范。CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。AMD规范则是非同步加载模块,允许指定回调函数

AMD推荐的风格通过返回一个对象做为模块对象,CommonJS的风格通过对module.exports或exports的属性赋值来达到暴露模块对象的目的。

33. web开发中会话跟踪的方法有哪些

  1. cookie

  2. session

  3. url重写

  4. 隐藏input

  5. ip地址

34. 介绍js有哪些内置对象?

  1. Object是JavaScript中所有对象的父对象

  2. 数据封装类对象:Object、Array、Boolean、Number和String

  3. 其他对象:Function、Arguments、Math、Date、RegExp、Error

35. 说几条写JavaScript的基本规范?

  1. 不要在同一行声明多个变量

  2. 请使用===/!==来比较true/false或者数值

  3. 使用对象字面量替代new Array这种形式

  4. 不要使用全局函数

  5. Switch语句必须带有default分支

  6. If语句必须使用大括号

  7. for-in循环中的变量 应该使用var关键字明确限定作用域,从而避免作用域污

36. javascript创建对象的几种方式?

javascript创建对象简单的说,无非就是使用内置对象或各种自定义对象,当然还可以用JSON;但写法有很多种,也能混合使用

对象字面量的方式

person={firstname:"Mark",lastname:"Yun",age:25,eyecolor:"black"};

用function来模拟无参的构造函数

function Person(){}
​    var person=new Person();//定义一个function,如果使用new"实例化",该function可以看作是一个Class
​        person.name="Mark";
​        person.age="25";
​        person.work=function(){
​        alert(person.name+" hello...");
​    }
person.work();

用function来模拟参构造函数来实现(用this关键字定义构造的上下文属性)

function Pet(name,age,hobby){
​       this.name=name;//this作用域:当前对象
​       this.age=age;
​       this.hobby=hobby;
​       this.eat=function(){
​          alert("我叫"+this.name+",我喜欢"+this.hobby+",是个程序员");
​       }
​    }
​    var maidou =new Pet("麦兜",25,"coding");//实例化、创建对象
​    maidou.eat();//调用eat方法

用工厂方式来创建(内置对象)

var wcDog =new Object();
​     wcDog.name="旺财";
​     wcDog.age=3;
​     wcDog.work=function(){
​       alert("我是"+wcDog.name+",汪汪汪......");
​     }
​     wcDog.work();

用原型方式来创建

function Dog(){
 }
 Dog.prototype.name="旺财";
 Dog.prototype.eat=function(){
 alert(this.name+"是个吃货");
 }
 var wangcai =new Dog();
 wangcai.eat();

l 用混合方式来创建

function Car(name,price){
​     this.name=name;
​     this.price=price; 
   }
​    Car.prototype.sell=function(){
​      alert("我是"+this.name+",我现在卖"+this.price+"万元");
​     }
   var camry =new Car("凯美瑞",27);
   camry.sell(); 

37. eval是做什么的?

它的功能是把对应的字符串解析成JS代码并运行

应该避免使用eval,不安全,非常耗性能(2次,一次解析成js语句,一次执行)

由JSON字符串转换为JSON对象的时候可以用eval,var obj =eval(’(’+ str +’)’)

38. null,undefined 的区别?

undefined表示不存在这个值。

undefined:是一个表示”无”的原始值或者说表示”缺少值”,就是此处应该有一个值,但是还没有定义。当尝试读取时会返回undefined

例如变量被声明了,但没有赋值时,就等于undefined

null表示一个对象被定义了,值为“空值”

null: 是一个对象(空对象, 没有任何属性和方法)

例如作为函数的参数,表示该函数的参数不是对象;

在验证null时,一定要使用 =,因为无法分别null和 undefined

39. [“1”, “2”, “3”].map(parseInt) 答案是多少?

[1, NaN, NaN]因为parseInt需要两个参数(val, radix),其中radix表示解析时用的基数。

map传了3个(element, index, array),对应的radix不合法导致解析失败。

40. javascript 代码中的”use strict”;是什么意思 ? 使用它区别是什么?

use strict是一种ECMAscript 5添加的(严格)运行模式,这种模式使得 Javascript 在更严格的条件下运行,使JS编码更加规范化的模式,消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为

41. js延迟加载的方式有哪些?

defer和async、动态创建DOM方式(用得最多)、按需异步载入js

42. defer和async

defer并行加载js文件,会按照页面上script标签的顺序执行

async并行加载js文件,下载完成立即执行,不会按照页面上script标签的顺序执行

43. 说说严格模式的限制

变量必须声明后再使用

函数的参数不能有同名属性,否则报错

不能使用with语句

禁止this指向全局对象

44. attribute和property的区别是什么?

attribute是dom元素在文档中作为html标签拥有的属性;

property就是dom元素在js中作为对象拥有的属性。

对于html的标准属性来说,attribute和property是同步的,是会自动更新的

但是对于自定义的属性来说,他们是不同步的

45. ECMAScript6 怎么写class么,为什么会出现class这种东西?

这个语法糖可以让有OOP基础的人更快上手js,至少是一个官方的实现了

但对熟悉js的人来说,这个东西没啥大影响;一个Object.creat()搞定继承,比class简洁清晰的多

46. 常见兼容性问题

png24位的图片在iE6浏览器上出现背景,解决方案是做成PNG8

浏览器默认的margin和padding不同。解决方案是加一个全局的*{margin:0;padding:0;}来统一,,但是全局效率很低,一般是如下这样解决:

IE下,event对象有x,y属性,但是没有pageX,pageY属性

Firefox下,event对象有pageX,pageY属性,但是没有x,y属性.

47. 函数防抖节流的原理

防抖和节流的作用都是防止函数多次调用。区别在于,假设一个用户一直触发这个函数,且每次触发函数的间隔小于设置的时间,防抖的情况下只会调用一次,而节流的情况会每隔一定时间调用一次函数。

v 防抖(debounce): n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间

function debounce(func, wait, immediate=true) {
​    let timeout, context, args;
​        // 延迟执行函数
​        const later = () => setTimeout(() => {
​            // 延迟函数执行完毕,清空定时器
​            timeout = null
​            // 延迟执行的情况下,函数会在延迟函数中执行
​            // 使用到之前缓存的参数和上下文
​            if (!immediate) {
​                func.apply(context, args);
​                context = args = null;
​            }
​        }, wait);
​        let debounced = function (...params) {
​            if (!timeout) {
​                timeout = later();
​                if (immediate) {
​                    //立即执行
​                    func.apply(this, params);
​                } else {
​                    //闭包
​                    context = this;
​                    args = params;
​                }
​            } else {
​                clearTimeout(timeout);
​                timeout = later();
​            }
​        }
​    debounced.cancel = function () {
​        clearTimeout(timeout);
​        timeout = null;
​    };
​    return debounced;};

防抖的应用场景:

每次 resize/scroll 触发统计事件

文本输入的验证(连续输入文字后发送 AJAX 请求进行验证,验证一次就好)

v 节流(throttle): 高频事件在规定时间内只会执行一次,执行一次后,只有大于设定的执行周期后才会执行第二次。

//underscore.jsfunction throttle(func, wait, options) {
​    var timeout, context, args, result;
​    var previous = 0;
​    if (!options) options = {};
​    var later = function () {
​        previous = options.leading === false ? 0 : Date.now() || new Date().getTime();
​        timeout = null;
​        result = func.apply(context, args);
​        if (!timeout) context = args = null;
​    };
​    var throttled = function () {
​       var now = Date.now() || new Date().getTime();
​        if (!previous && options.leading === false) previous = now;
​        var remaining = wait - (now - previous);
​        context = this;
​        args = arguments;
​        if (remaining <= 0 || remaining > wait) {
​            if (timeout) {
​                clearTimeout(timeout);
​                timeout = null;
​            }
​            previous = now;
​            result = func.apply(context, args);
​            if (!timeout) context = args = null;
​        } else if (!timeout && options.trailing !== false) {
​            // 判断是否设置了定时器和 trailing
​            timeout = setTimeout(later, remaining);
​        }
​        return result;
​    };
​    throttled.cancel = function () {
​        clearTimeout(timeout);
​        previous = 0;
​        timeout = context = args = null;
​    };
​    return throttled;};

函数节流的应用场景有:

DOM 元素的拖拽功能实现(mousemove)

射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹)

计算鼠标移动的距离(mousemove)

Canvas 模拟画板功能(mousemove)

搜索联想(keyup)

监听滚动事件判断是否到页面底部自动加载更多:给 scroll 加了 debounce 后,只有用户停止滚动后,才会判断是否到了页面底部;如果是 throttle 的话,只要页面滚动就会间隔一段时间判断一次

48. 原始类型有哪几种?null是对象吗?

.js中5种原始数据类型

number:整数/小数/NaN

string:

boolean:

null:

undefined:

Null类型是第二个只有一个值的数据类型,这个特殊的值是null,从逻辑角度来看,null值表示一个空对象指针,而这也正是使用typeof操作符检测null值会返回“object”的原因

49. 为什么console.log(0.2+0.1==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 这样的运算,操作数会先被转成二进制,然后再计算

50. 说一下JS中类型转换的规则?

转数字转字符串转布尔值
undefinedNaN“undefined”false
null0“null”false
true1“true”
false0“false”
0“0”false
-0“0”false
NaN“NaN”false
Infinity“Infinity”true
-Infinity”-Infinity”true
1(非零)“1”true
{}(任意对象)见下文见下文true
0””true
99“9”true
”a”NaN使用.join()方法true
function(){}(任意函数)NaN见下文true

Number的原始类型转换规则

数值转换后还是数值

字符串如果可以解析为数值则为数值, 空字符串为0, 无法解析的字符串为NaN

布尔转数值, true转为1, false转为0

null转换为0

原始类型转换Number

Number的对象类型转换规则

  1. 传入实例M, 先调用M的valueOf(), 如果返回值V为基本数据类型, 则直接使用Number(V), 求最终返回值

  2. 如果T不属于基本数据类型, 则调用M的toString(), 如果返回值S为基本数据类型, 则直接使用Number(S),求最后的结果, 如果S不属于基本数据类型, 则直接返回NaN

  3. 对象类型转换1

  4. 对象类型转换2

String的原始类型转换规则

数值(Number)转为相应的字符串

字符串(String) 转换后还是字符串

布尔值(Boolean)转换规则: true => ‘true’, false=> ‘false’

undefine 转换为"undefine"

null 转换为’null’

String原始类型转换

String 的对象类型转换规则

  1. 与Number的对象转换规则类似, 区别是: 先调用对象的toString(), 然后再调用valueOf()

  2. 其实正常情况下, 对象调用自身的toString()后, 对象就可以转换为string基本类型, valueOf() 没有机会被调用, 但万事有个例, 如果我们重新定义了对象的toString()方法,使其返回非基本类型的值, 那样就有机会调用对象的valueOf()方法了

String对象类型转换规则

Boolean的原始类型转换 和 对象类型转换

undefined,null,NaN,’’,-0,+0皆为false, 其余为true

隐式类型转换

四则运算+, -, *, /

隐式类型转换之四则运算

51. 深拷贝和浅拷贝的区别?如何实现

浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。浅拷贝只复制对象的第一层属性

但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。对对象的属性进行递归复制

浅拷贝实现方法

1、可以通过简单的赋值实现

类似上面的例子,当然,我们也可以封装一个简单的函数,如下:

function simpleClone(initalObj) {    
​      var obj = {};    
​      for ( var i in initalObj) {
​        obj[i] = initalObj[i];
​      }    
​      return obj;
​    }
​    var obj = {
​      a: "hello",
​      b:{
​          a: "world",
​          b: 21
​        },
​      c:["Bob", "Tom", "Jenny"],
​      d:function() {
​          alert("hello world");
​        }
​    }
​    var cloneObj = simpleClone(obj); 
​    console.log(cloneObj.b); 
​    console.log(cloneObj.c);
​    console.log(cloneObj.d);
​    cloneObj.b.a = "changed";
​    cloneObj.c = [1, 2, 3];
​    cloneObj.d = function() { alert("changed"); };
​    console.log(obj.b);
​    console.log(obj.c);
​    console.log(obj.d);

2、Object.assign()实现

Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。

var obj = { a: {a: "hello", b: 21} };
var initalObj = Object.assign({}, obj);
initalObj.a.a = "changed";
console.log(obj.a.a); //  "changed"

深拷贝的实现方式

1、对象只有一层的话可以使用上面的:Object.assign()函数

var obj1 = { a: 10, b: 20, c: 30 };
var obj2 = Object.assign({}, obj1);
obj2.b = 100;
console.log(obj1);// { a: 10, b: 20, c: 30 } <-- 沒被改到
console.log(obj2);// { a: 10, b: 100, c: 30 }

1、转成 JSON 再转回来

var obj1 = { body: { a: 10 } };
var obj2 = JSON.parse(JSON.stringify(obj1));
obj2.body.a = 20;
console.log(obj1);// { body: { a: 10 } } <-- 沒被改到
console.log(obj2);// { body: { a: 20 } }
console.log(obj1 === obj2);// false
console.log(obj1.body === obj2.body);// false

用JSON.stringify把对象转成字符串,再用JSON.parse把字符串转成新的对象。

可以封装如下函数

var cloneObj = function(obj){
​    var str, newobj = obj.constructor === Array ? [] : {};
​    if(typeof obj !== 'object'){
​        return;
​    } else if(window.JSON){
​        str = JSON.stringify(obj), //系列化对象
​        newobj = JSON.parse(str); //还原
​    } else {
​        for(var i in obj){
​            newobj[i] = typeof obj[i] === 'object' ? 
​            cloneObj(obj[i]) : obj[i]; 
​        }
​    }
return newobj;
};

2、递归拷贝

function deepClone(initalObj, finalObj) {    

  var obj = finalObj || {};    

  for (var i in initalObj) {        

​    var prop = initalObj[i];        // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况

​    if(prop === obj) {            

​      continue;

​    }        

​    if (typeof prop === 'object') {

​      obj[i] = (prop.constructor === Array) ? [] : {};            

​      arguments.callee(prop, obj[i]);

​    } else {

​      obj[i] = prop;

​    }

  }    

  return obj;}

var str = {};

var obj = { a: {a: "hello", b: 21} };deepClone(obj, str);

console.log(str.a);

3、使用Object.create()方法

直接使用var newObj = Object.create(oldObj),可以达到深拷贝的效果。

function deepClone(initalObj, finalObj) {    
  var obj = finalObj || {};    
  for (var i in initalObj) {        
​    var prop = initalObj[i];        // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况
​    if(prop === obj) {            
​      continue;
​    }        
​    if (typeof prop === 'object') {
​      obj[i] = (prop.constructor === Array) ? [] : Object.create(prop);
​    } else {
​      obj[i] = prop;
​    }
  }    
  return obj;}

4、jquery

jquery 有提供一个$.extend可以用来做 Deep Copy。

var $ = require('jquery');
var obj1 = {
​    a: 1,
​    b: { f: { g: 1 } },
​    c: [1, 2, 3]};
var obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f);// false

5、lodash

另外一个很热门的函数库lodash,也有提供_.cloneDeep用来做 Deep Copy。

var _ = require('lodash');
var obj1 = {
​    a: 1,
​    b: { f: { g: 1 } },
​    c: [1, 2, 3]};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false

52. 如何判断this?箭头函数的this是什么

  1. 谁作为拥有者调用它就指向谁
function a() { 
​    console.log(this); }
var b  = {};
b.hehe = a;
b.hehe();//这时候this指向b//常见的就是绑定事件
  1. bind谁就指向谁
function a() { 
​    console.log(this); }
var b  = {};
var c = {};
b.hehe = a.bind(c);
b.hehe();//这时候this指向c//如果你用bind的话
  1. 没有拥有者,直接调用,就指向window
function a() { 
console.log(this); 
}
a();//this指向window
  1. call谁就是谁,apply谁就是谁,其实bind就是通过call和apply实现的

箭头函数不绑定this,会捕获其所在的上下文的this值,作为自己的this值

53. == 和 ===的区别

1.===:三个等号我们称为等同符,当等号两边的值为相同类型的时候,直接比较等号两边的值,值相同则返回true,若等号两边的值类型不同时直接返回false。

例:100===“100” //返回false

abc===“abc” //返回false

‘abc’===“abc” //返回true

NaN===NaN //返回false

false===false //返回true

2.==:两个等号我们称为等值符,当等号两边的值为相同类型时比较值是否相同,类型不同时会发生类型的自动转换,转换为相同的类型后再作比较。

54. 什么是闭包

闭包就是能够读取其他函数内部变量的函数

闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域

闭包的特性:

  1. 函数内再嵌套函数

  2. 内部函数可以引用外层的参数和变量

  3. 参数和变量不会被垃圾回收机制回收

说说你对闭包的理解

使用闭包主要是为了设计私有的方法和变量。闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。在js中,函数即闭包,只有函数才会产生作用域的概念

闭包 的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中

闭包的另一个用处,是封装对象的私有属性和私有方法

好处:能够实现封装和缓存等;

坏处:就是消耗内存、不正当使用会造成内存溢出的问题

使用闭包的注意点

由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露

解决方法是,在退出函数之前,将不使用的局部变量全部删除

55.JavaScript原型**,原型链 ? 有什么特点?**

每个对象都会在其内部初始化一个属性,就是prototype(原型),当我们访问一个对象的属性时

如果这个对象内部不存在这个属性,那么他就会去prototype里找这个属性,这个prototype又会有自己的prototype,于是就这样一直找下去,也就是我们平时所说的原型链的概念

关系:instance.constructor.prototype = instance.proto

特点:

JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变

当我们需要一个属性的时,Javascript引擎会先看当前对象中是否有这个属性, 如果没有的

就会查找他的Prototype对象是否有这个属性,如此递推下去,一直检索到Object内建对象

56.typeof()和instanceof()的用法区别

typeof() 是一个一元运算,放在一个运算数之前,运算数可以是任意类型。

它返回值是一个字符串,该字符串说明运算数的类型。

instanceof() 运算符用来判断一个构造函数的prototype属性所指向的对象是否存在另外一个要检测对象的原型链上。通常来讲,使用 instanceof 就是判断一个实例是否属于某种类型。

57. 什么是变量提升

变量提升:函数声明和变量声明总是会被解释器悄悄地被"提升"到方法体的最顶部。

JavaScript 只有声明的变量会提升,初始化的不会。

var x =5;// 初始化 x  
var y =7;// 初始化 y  
elem = document.getElementById("demo");// 查找元素  
elem.innerHTML = x +" "\+ y;  // 5 7
varx =5;// 初始化 x  
elem = document.getElementById("demo");// 查找元素  
elem.innerHTML = x +" "\+ y;           // 显示 x 和 y  
vary =7;// 初始化 y

输出结果:x 为:5,y 为:undefined

y 输出了undefined,这是因为变量声明 (var y) 提升了,但是初始化(y = 7) 并不会提升,所以 y 变量是一个未定义的变量。

类似下列代码

var x = 5; // 初始化 x
var y;     // 声明 y
elem = document.getElementById("demo"); // 查找元素
elem.innerHTML = x + " " + y;           // 显示 x 和 y
y = 7;    // 设置 y 为 7

1. 函数声明会被提升 对于函数表达式,是不会提升的

​ //函数声明, 形如:

​         function show(){
​             console.log( '函数声明方式' );
​         }
​         //函数表达式, 形如:
​         var show = function(){
​             console.log( '表达式方式' );
​         }
 

2. 出现同名的函数声明,变量声明的时候, 函数声明会被优先提升,变量声明会被忽略

​        show(); //你好
​        var show;
​        function show(){
​            console.log( '你好' );
​        }
​        show = function(){
​            console.log( 'hello' );
​        }

上面这段代码,结果为什么会是 ‘你好’?

当出现同名的函数声明,变量声明的时候, 函数声明会被优先提升,变量声明会被忽略。 所以经过编译之后,就变成:

​        function show(){
​            console.log( '你好' );
​        }
​        show(); //你好
​        show = function(){
​            console.log( 'hello' );
​        }

​ show();//如果这里在调用一次,就是hello, 因为show函数体在执行阶段 被 重新赋值了

3.如果有同名的函数声明,后面的会覆盖前面的

show(); //how are you
​        var show;
​        function show(){
​            console.log( 'hello' );
​        }    
​        show = function(){
​            console.log( '你好' );
​        }
​        function show(){
​            console.log( 'how are you!' );
​        }//上面的代码经过编译之后,变成如下形式:
​        function show(){
​            console.log( 'how are you!' );
​        }
​        show(); //how are you
​        show = function(){
​            console.log( '你好' );
​        }
​        show(); //如果在这里再执行一次,结果:你好

58. all、apply以及bind函数内部实现是怎么样的

call, apply, bind都是改变函数执行的上下文,说的直白点就是改变了函数this的指向。不同的是:call和apply改变了函数的this,并且执行了该函数,而bind是改变了函数的this,并返回一个函数,但不执行该函数。

看下面的例子1:

var doThu = function(a, b) {
​    console.log(this)
​    console.log(this.name)
​    console.log([a, b])}
var stu = {
​    name: 'xiaoming',
​    doThu: doThu,}
stu.doThu(1, 2) // stu对象 xiaoming [1, 2]
doThu.call(stu, 1, 2) // stu对象 xiaoming [1, 2]

由此可见,在stu上添加一个属性doThu,再执行这个函数,就将doThu的this指向了stu。而call的作用就与此相当,只不过call为stu添加了doThu方法后,执行了doThu,然后再将doThu这个方法从stu中删除。

下面来看call函数的内部实现原理:

Function.prototype.call = function(thisArg, args) {
​    // this指向调用call的对象
​    if (typeof this !== 'function') { // 调用call的若不是函数则报错
​        throw new TypeError('Error')
​    }
​    thisArg = thisArg || window
​    thisArg.fn = this   // 将调用call函数的对象添加到thisArg的属性中
​    const result = thisArg.fn(...[...arguments].slice(1)) // 执行该属性
​    delete thisArg.fn   // 删除该属性
return result
}

apply的实现原理和call一样,只不过是传入的参数不同而已。下面只给出代码,不做解释:

Function.prototype.apply = function(thisArg, args) {
​    if (typeof this !== 'function') { 
​        throw new TypeError('Error')
​    }
​    thisArg = thisArg || window
​    thisArg.fn = this
​    let result
​    if(args) {
​        result = thisArg.fn(...args)
​    } else {
​        result = thisArg.fn()
​    }
​    delete thisArg.fn
return result
}

bind的实现原理比call和apply要复杂一些,bind中需要考虑一些复杂的边界条件。bind后的函数会返回一个函数,而这个函数也可能被用来实例化:

Function.prototype.bind = function(thisArg) {
​    if(typeof this !== 'function'){
​        throw new TypeError(this + 'must be a function');
​    }
​    // 存储函数本身
​    const _this  = this;
​    // 去除thisArg的其他参数 转成数组
​    const args = [...arguments].slice(1)
​    // 返回一个函数
​    const bound = function() {
​        // 可能返回了一个构造函数,我们可以 new F(),所以需要判断
​        if (this instanceof bound) {
​            return new _this(...args, ...arguments)
​        }
​        // apply修改this指向,把两个函数的参数合并传给thisArg函数,并执行thisArg函数,返回执行结果
​        return _this.apply(thisArg, args.concat(...arguments))
​    }
return bound
}

59. 为什么会出现setTimeout倒计时误差?如何减少

<!DOCTYPE html><html>
​	<head>
	<meta charset="utf-8">
​		<title></title>
​	</head>
​	<body>
		<p id="message"></p>
​	</body>
	<script type="text/javascript">
​		var message = document.getElementById("message");
​		var count = 1000;
​		function animate() {
​			var start = +new Date();
​			message.innerHTML = count--;
​			var finish = +new Date();
​			setTimeout(animate, 1000 - (finish-start));
​		}
​		animate();
​	</script>
</html>

本例实现了倒数1000秒的功能,我们在使用setTimeout的时候如果要一个函数每每隔1秒执行一次就会这样写

setTimeout(animate, 1000);

但是这样会忽略animate方法本身的运行时间,所以我们可以在执行animate方法的时候计算这个方法主要的语句的执行时间,之后在setTimeout中减去那个由于运行语句而耽搁的时间,从而实现更加精确的计时

60. 谈谈你对JS执行上下文栈和作用域链的理解

执行上下文就是当前 JavaScript 代码被解析和执行时所在环境, JS执行上下文栈可以认为是一个存储函数调用的栈结构,遵循先进后出的原则。

JavaScript执行在单线程上,所有的代码都是排队执行。

一开始浏览器执行全局的代码时,首先创建全局的执行上下文,压入执行栈的顶部。

每当进入一个函数的执行就会创建函数的执行上下文,并且把它压入执行栈的顶部。当前函数执行-完成后,当前函数的执行上下文出栈,并等待垃圾回收。

浏览器的JS执行引擎总是访问栈顶的执行上下文。

全局上下文只有唯一的一个,它在浏览器关闭时出栈。

作用域链: 无论是 LHS 还是 RHS 查询,都会在当前的作用域开始查找,如果没有找到,就会向上级作用域继续查找目标标识符,每次上升一个作用域,一直到全局作用域为止。

61. new的原理是什么?通过new的方式创建对象和通过字面量创建有什么区别?

new:

  1. 创建一个新对象。

  2. 这个新对象会被执行[[原型]]连接。

  3. 将构造函数的作用域赋值给新对象,即this指向这个新对象.

  4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。

function new(func) {
​    lat target = {};
​    target.__proto__ = func.prototype;
​    let res = func.call(target);
​    if (typeof(res) == "object" || typeof(res) == "function") {
​        return res;
​    }
​    return target;}

字面量创建对象,不会调用 Object构造函数, 简洁且性能更好;

new Object() 方式创建对象本质上是方法调用,涉及到在proto链中遍历该方法,当找到该方法后,又会生产方法调用必须的 堆栈信息,方法调用结束后,还要释放该堆栈,性能不如字面量的方式。

通过对象字面量定义对象时,不会调用Object构造函数。

62. prototype 和 proto 区别是什么?

prototype是构造函数的属性。

__proto__是每个实例都有的属性,可以访问 [[prototype]] 属性。

实例的__proto__与其构造函数的prototype指向的是同一个对象。

function Student(name) {
​    this.name = name;}
Student.prototype.setAge = function(){
​    this.age=20;}
let Jack = new Student('jack');
console.log(Jack.__proto__);//console.log(Object.getPrototypeOf(Jack));;
console.log(Student.prototype);
console.log(Jack.__proto__ === Student.prototype);//true

63. 使用ES5实现一个继承?

组合继承(最常用的继承方式)

function SuperType() {
​    this.name = name;
​    this.colors = ['red', 'blue', 'green'];}
SuperType.prototype.sayName = function() {
​    console.log(this.name);}
function SubType(name, age) {
​    SuperType.call(this, name);
​    this.age = age;}
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
console.log(this.age);
}

64. 取数组的最大值(ES5、ES6)

// ES5 的写法
Math.max.apply(null, [14, 3, 77, 30]);
// ES6 的写法
Math.max(...[14, 3, 77, 30]);
// reduce
[14,3,77,30].reduce((accumulator, currentValue)=>{
return accumulator = accumulator > currentValue ? accumulator : currentValue
});

65. ES6新的特性有哪些?

  1. 新增了块级作用域(let,const)

  2. 提供了定义类的语法糖(class)

  3. 新增了一种基本数据类型(Symbol)

  4. 新增了变量的解构赋值

  5. 函数参数允许设置默认值,引入了rest参数,新增了箭头函数

  6. 数组新增了一些API,如 isArray / from / of 方法;数组实例新增了 entries(),keys() 和 values() 等方法

  7. 对象和数组新增了扩展运算符

  8. ES6 新增了模块化(import/export)

  9. ES6 新增了 Set 和 Map 数据结构

  10. ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例

  11. ES6 新增了生成器(Generator)和遍历器(Iterator)

66. promise 有几种状态, Promise 有什么优缺点 ?

promise有三种状态: fulfilled, rejected, pending.

Promise 的优点:

一旦状态改变,就不会再变,任何时候都可以得到这个结果

可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数

Promise 的缺点:

无法取消 Promise

当处于pending状态时,无法得知目前进展到哪一个阶段

67. Promise构造函数是同步还是异步执行,then呢 ?promise如何实现then处理 ?

Promise的构造函数是同步执行的。then 是异步执行的。

68. Promise和setTimeout的区别 ?

Promise 是微任务,setTimeout 是宏任务,同一个事件循环中,promise.then总是先于 setTimeout 执行。

69. 如何实现 Promise.all ?

要实现 Promise.all,首先我们需要知道 Promise.all 的功能:

  1. 如果传入的参数是一个空的可迭代对象,那么此promise对象回调完成(resolve),只有此情况,是同步执行的,其它都是异步返回的。

  2. 如果传入的参数不包含任何 promise,则返回一个异步完成.

promises 中所有的promise都“完成”时或参数中不包含 promise 时回调完成。

  1. 如果参数中有一个promise失败,那么Promise.all返回的promise对象失败

  2. 在任何情况下,Promise.all 返回的 promise 的完成状态的结果都是一个数组

Promise.all = function (promises) {
​    return new Promise((resolve, reject) => {
​        let index = 0;
​        let result = [];
​        if (promises.length === 0) {
​            resolve(result);
​        } else {
​            setTimeout(() => {
​                function processValue(i, data) {
​                    result[i] = data;
​                    if (++index === promises.length) {
​                        resolve(result);
​                    }
​                }
​                for (let i = 0; i < promises.length; i++) {
​                    //promises[i] 可能是普通值
​                    Promise.resolve(promises[i]).then((data) => {
​                        processValue(i, data);
​                    }, (err) => {
​                        reject(err);
​                        return;
​                    });
​                }
​            })
​        }
	});
}

70. 如何实现 Promise.finally ?

不管成功还是失败,都会走到finally中,并且finally之后,还可以继续then。并且会将值原封不动的传递给后面的then.

Promise.prototype.finally = function (callback) {
​    return this.then((value) => {
​        return Promise.resolve(callback()).then(() => {
​            return value;
​        });
​    }, (err) => {
​        return Promise.resolve(callback()).then(() => {
​            throw err;
​        });
	});
}

71. 如何判断img加载完成

一、load事件

<!DOCTYPE HTML>
<html> 
<head> 
    <meta charset="utf-8">
​    <title>img - load event</title>
</head> 
<body>
    <img id="img1" src="http://pic1.win4000.com/wallpaper/f/51c3bb99a21ea.jpg">
    <p id="p1">loading...</p>
    <script type="text/javascript">
​        img1.onload = function() {
​            p1.innerHTML = 'loaded'
​        }
​    </script>
</body>
</html> 

测试,所有浏览器都显示出了“loaded”,说明所有浏览器都支持img的load事件。

二、readystatechange事件

<!DOCTYPE HTML>
<html> 
<head> 
    <meta charset="utf-8">
​    <title>img - readystatechange event</title>
</head> 
<body>
    <img id="img1" src="http://pic1.win4000.com/wallpaper/f/51c3bb99a21ea.jpg">
    <p id="p1">loading...</p>
    <script type="text/javascript">
​        img1.onreadystatechange = function() {
​            if(img1.readyState=="complete"||img1.readyState=="loaded"){ 
​                p1.innerHTML = 'readystatechange:loaded'
​            }
​        }
​    </script>
</body>
</html> 

readyState为complete和loaded则表明图片已经加载完毕。测试IE6-IE10支持该事件,其它浏览器不支持。

**二、**img的complete属性

<!DOCTYPE HTML>
<html> 
<head> 
    <meta charset="utf-8">
​    <title>img - complete attribute</title>
</head> 
<body>
    <img id="img1" src="http://pic1.win4000.com/wallpaper/f/51c3bb99a21ea.jpg">
    <p id="p1">loading...</p>
    <script type="text/javascript">
​        function imgLoad(img, callback) {
​            var timer = setInterval(function() {
​                if (img.complete) {
​                    callback(img)
​                    clearInterval(timer)
​                }
​            }, 50)
​        }
​        imgLoad(img1, function() {
​            p1.innerHTML('加载完毕')
​        })
​    </script>
</body>
</html> 

轮询不断监测img的complete属性,如果为true则表明图片已经加载完毕,停止轮询。该属性所有浏览器都支持。

72. 如何阻止冒泡?

冒泡型事件:事件按照从最特定的事件目标到最不特定的事件目标(document对象)的顺序触发。

w3c的方法是e.stopPropagation(),IE则是使用e.cancelBubble = true。

//阻止冒泡行为

function stopBubble(e) {

//如果提供了事件对象,则这是一个非IE浏览器

if ( e && e.stopPropagation )

​ //因此它支持W3C的stopPropagation()方法

​ e.stopPropagation(); else

​ //否则,我们需要使用IE的方式来取消事件冒泡

window.event.cancelBubble = true;

}

73. 如何阻止默认事件?

w3c的方法是e.preventDefault(),IE则是使用e.returnValue = false

//阻止浏览器的默认行为 
function stopDefault( e ) { 
​    //阻止默认浏览器动作(W3C) 
​    if ( e && e.preventDefault ) 
​        e.preventDefault(); 
​    //IE中阻止函数器默认动作的方式 
​    else 
​        window.event.returnValue = false; 
return false; 
}

74. ajax请求时,如何解释json数据

使用eval parse,但是鉴于安全性考虑 使用parse更靠谱;

75. json和jsonp的区别**?**

json返回的是一串json格式数据;而jsonp返回的是脚本代码(包含一个函数调用)

jsonp的全名叫做json with padding,就是把json对象用符合js语法的形式包裹起来以使其他的网站可以请求到,也就是将json封装成js文件传过去。

76. 如何用原生js给一个按钮绑定两个onclick事件?

Var  btn=document.getElementById(‘btn’);

//事件监听 绑定多个事件

var btn4 = document.getElementById("btn4");
btn4.addEventListener("click",hello1);
btn4.addEventListener("click",hello2);
function hello1(){
 alert("hello 1");
}
function hello2(){
 alert("hello 2");
}

77. 拖拽会用到哪些事件

· dragstart:拖拽开始时在被拖拽元素上触发此事件,监听器需要设置拖拽所需数据,从操作系统拖拽文件到浏览器时不触发此事件.

· dragenter:拖拽鼠标进入元素时在该元素上触发,用于给拖放元素设置视觉反馈,如高亮

· dragover:拖拽时鼠标在目标元素上移动时触发.监听器通过阻止浏览器默认行为设置元素为可拖放元素.

· dragleave:拖拽时鼠标移出目标元素时在目标元素上触发.此时监听器可以取消掉前面设置的视觉效果.

· drag:拖拽期间在被拖拽元素上连续触发

· drop:鼠标在拖放目标上释放时,在拖放目标上触发.此时监听器需要收集数据并且执行所需操作.如果是从操作系统拖放文件到浏览器,需要取消浏览器默认行为.

· dragend:鼠标在拖放目标上释放时,在拖拽元素上触发.将元素从浏览器拖放到操作系统时不会触发此事件.

78. document.write和innerHTML的区别

document.write是直接写入到页面的内容流,如果在写之前没有调用document.open, 浏览器会自动调用open。每次写完关闭之后重新调用该函数,会导致页面被重写。

innerHTML则是DOM页面元素的一个属性,代表该元素的html内容。你可以精确到某一个具体的元素来进行更改。如果想修改document的内容,则需要修改document.documentElement.innerElement。

innerHTML将内容写入某个DOM节点,不会导致页面全部重绘

innerHTML很多情况下都优于document.write,其原因在于其允许更精确的控制要刷新页面的那一个部分。

79. jQuery的事件委托方法bind 、live、delegate、on之间有什么区别?

(1)、bind 【jQuery 1.3之前】

定义和用法:主要用于给选择到的元素上绑定特定事件类型的监听函数;

语法:bind(type,[data],function(eventObject));

特点:

(1)、适用于页面元素静态绑定。只能给调用它的时候已经存在的元素绑定事件,不能给未来新增的元素绑定事件。

(2)、当页面加载完的时候,你才可以进行bind(),所以可能产生效率问题。

实例如下:$( “#members li a” ).bind( “click”, function( e ) {} );

(2)、live 【jQuery 1.3之后】

定义和用法:主要用于给选择到的元素上绑定特定事件类型的监听函数;

语法:live(type, [data], fn);

特点:

(1)、live方法并没有将监听器绑定到自己(this)身上,而是绑定到了this.context上了。

(2)、live正是利用了事件委托机制来完成事件的监听处理,把节点的处理委托给了document,新添加的元素不必再绑定一次监听器。

(3)、使用live()方法但却只能放在直接选择的元素后面,不能在层级比较深,连缀的DOM遍历方法后面使用,即 ( “ u l ” &quot; ) . l i v e . . . 可 以 , 但 (“ul”&quot;).live...可以,但 (ul").live...(“body”).find(“ul”).live…不行;

实例如下:$( document ).on( “click”, “#members li a”, function( e ) {} );

(3)、delegate 【jQuery 1.4.2中引入】

定义和用法:将监听事件绑定在就近的父级元素上

语法:delegate(selector,type,[data],fn)

特点:

(1)、选择就近的父级元素,因为事件可以更快的冒泡上去,能够在第一时间进行处理。

(2)、更精确的小范围使用事件代理,性能优于.live()。可以用在动态添加的元素上。

实例如下:

$("#info_table").delegate(“td”,“click”,function(){/显示更多信息/});

$(“table”).find("#info").delegate(“td”,“click”,function(){/显示更多信息/});

(4)、on 【1.7版本整合了之前的三种方式的新事件绑定机制】

定义和用法:将监听事件绑定到指定元素上。

语法:on(type,[selector],[data],fn)

实例如下:$("#info_table").on(“click”,“td”,function(){/显示更多信息/});参数的位置写法与delegate不一样。

说明:on方法是当前JQuery推荐使用的事件绑定方法,附加只运行一次就删除函数的方法是one()。

总结 :.bind(), .live(), .delegate(),.on()分别对应的相反事件为:.unbind(),.die(), .undelegate(),.off()

80. 浏览器是如何渲染页面的?

渲染的流程如下:

1.解析HTML文件,创建DOM树。

自上而下,遇到任何样式(link、style)与脚本(script)都会阻塞(外部样式不阻塞后续外部脚本的加载)。

2.解析CSS。优先级:浏览器默认设置<用户设置<外部样式<内联样式<HTML中的style样式;

3.将CSS与DOM合并,构建渲染树(Render Tree)

4.布局和绘制,重绘(repaint)和重排(reflow)

81. $(document).ready()方法和window.onload有什么区别?

(1)、window.onload方法是在网页中所有的元素(包括元素的所有关联文件)完全加载到浏览器后才执行的。

(2)、$(document).ready() 方法可以在DOM载入就绪时就对其进行操纵,并调用执行绑定的函数。

82. jquery中 . g e t ( ) 提 交 和 .get()提交和 .get().post()提交有区别吗?

相同点:都是异步请求的方式来获取服务端的数据;

异同点:

1、请求方式不同: . g e t ( ) 方 法 使 用 G E T 方 法 来 进 行 异 步 请 求 的 。 .get() 方法使用GET方法来进行异步请求的。 .get()使GET.post() 方法使用POST方法来进行异步请求的。

2、参数传递方式不同:get请求会将参数跟在URL后进行传递,而POST请求则是作为HTTP消息的实体内容发送给Web服务器的,这种传递是对用户不可见的。

3、数据传输大小不同:get方式传输的数据大小不能超过2KB 而POST要大的多

4、安全问题: GET 方式请求的数据会被浏览器缓存起来,因此有安全问题。

83. 对前端路由的理解?前后端路由的区别?

前端的路由和后端的路由在实现技术上不一样,但是原理都是一样的。在 HTML5 的 history API 出现之前,前端的路由都是通过 hash 来实现的,hash 能兼容低版本的浏览器。

http://10.0.0.1/
http://10.0.0.1/#/about
http://10.0.0.1/#/concat

服务端路由:每跳转到不同的URL,都是重新访问服务端,然后服务端返回页面,页面也可以是服务端获取数据,然后和模板组合,返回HTML,也可以是直接返回模板HTML,然后由前端JS再去请求数据,使用前端模板和数据进行组合,生成想要的HTML。

前端路由:每跳转到不同的URL都是使用前端的锚点路由,实际上只是JS根据URL来操作DOM元素,根据每个页面需要的去服务端请求数据,返回数据后和模板进行组合,当然模板有可能是请求服务端返回的,这就是 SPA 单页程序。

在js可以通过window.location.hash读取到路径加以解析之后就可以响应不同路径的逻辑处理。

history 是 HTML5 才有的新 API,可以用来操作浏览器的 session history (会话历史)。基于 history 来实现的路由可以和最初的例子中提到的路径规则一样。

H5还新增了一个hashchange事件,也是很有用途的一个新事件:

当页面hash(#)变化时,即会触发hashchange。锚点Hash起到引导浏览器将这次记录推入历史记录栈顶的作用,window.location对象处理“#”的改变并不会重新加载页面,而是将之当成新页面,放入历史栈里。并且,当前进或者后退或者触发hashchange事件时,我们可以在对应的事件处理函数中注册ajax等操作!

但是hashchange这个事件不是每个浏览器都有,低级浏览器需要用轮询检测URL是否在变化,来检测锚点的变化。当锚点内容(location.hash)被操作时,如果锚点内容发生改变浏览器才会将其放入历史栈中,如果锚点内容没发生变化,历史栈并不会增加,并且也不会触发hashchange事件。

84. 手写一个类的继承

  1. 原型继承
function Parent(){
​    this.name = "parent";}
Parent.prototype.getName = function(){
​    return this.name;}
function Child(){
​    this.name = "child";}
//继承parent
Child.prototype = new Parent();
  1. 构造函数继承
function Animal(name){
​    this.name = name;
​    this.eat = function(){
​        consoel.log(this.name + "吃饭");
​    }}
var cat = new Animal("maomi");
cat.name;
cat.eat();

85. XMLHttpRequest:XMLHttpRequest.readyState;状态码的意思

XmlHttpRequest(Ajax)状态码readyState

当一个XMLHttpRequest初次创建时,这个属性的值是从0开始,知道接收完整的HTTP响应,这个值增加到4。有五种状态:

状态0 (请求未初始化): (XMLHttpRequest)对象已经创建或已被abort()方法重置,但还没有调用open()方法;

状态1 (载入服务器连接已建立):已经调用open() 方法,但是send()方法未调用,尚未发送请求;

状态2 (载入完成,请求已接收): send()方法已调用,HTTP请求已发送到web服务器,请求已经发送完成,未接收到响应;

状态3 (交互,请求处理中):所有响应头部都已经接收到。响应体开始接收但未完成,即可以接收到部分响应数据;

状态4 (请求完成,且相应已就绪):已经接收到了全部数据,并且连接已经关闭。

  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jay·Yuen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值