js面试题

作者: lazy_tomato | 来源:发表于2020-05-10 21:16 被阅读0次

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));  

个人总结:感觉就是,结合for循环遍历要处理的数组,用indexof,判断我返回的数组中有没有这个元素,没有就加进去。我在想印象中好像有更简单的方法

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();//1
outObj1();//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 未定义

判断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

个人总结:第一眼看晕了,其实还好,+相当于字符串拼接,-号相当于运算符,会隐式的把 '2' 转换成 2

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 有什么区别

  • let 和 const 定义的变量不会出现变量提升,而 var 定义的变量会提升。
  • let 和 const 是JS中的块级作用域
  • let 和 const 不允许重复声明(会抛出错误)
  • let 和 const 定义的变量在定义语句之前,如果使用会抛出错误(形成了暂时性死区),而 var 不会。
  • const 声明一个只读的常量。一旦声明,常量的值就不能改变(如果声明是一个对象,那么不能改变的是对象的引用地址)

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

    1. 箭头函数是匿名函数,不能作为构造函数,不能使用new
    1. 箭头函数不绑定arguments,取而代之用rest参数...解决
    1. 箭头函数不绑定this,会捕获其所在的上下文的this值,作为自己的this值
    1. 箭头函数通过 call() 或 apply() 方法调用一个函数时,只传入了一个参数,对 this 并没有影响。
    1. 箭头函数没有原型属性
    1. 箭头函数不能当做Generator函数,不能使用yield关键字

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

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

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

  • 创建一个空对象,并且this变量引用该对象,同时还继承了该函数的原型
  • 属性和方法被加入到this引用的对象中
  • 新创建的对象由this所引用,并且最后隐式的返回this

个人总结:

  1. 在内存中申请了一个空闲空间,储存创建的新对象
  2. 把this设置为当前对象
  3. 设置对象的属性和方法的值
  4. 把this对象返回

21. Ajax原理

  • Ajax的原理简单来说是在用户和服务器之间加了—个中间层(AJAX引擎),通过XmlHttpRequest对象来向服务器发异步请求,从服务器获得数据,然后用javascript来操作DOM而更新页面。使用户操作与服务器响应异步化。这其中最关键的一步就是从服务器获得请求数据
  • Ajax的过程只涉及JavaScriptXMLHttpRequestDOMXMLHttpRequestajax的核心机制
    //1. 创建连接
   var xhr = null;
   xhr = new XMLHttpRequest()
   // 2. 连接服务器
   xhr.open('get', url, true)
   // 3. 发送请求
   xhr.send(null);
   // 4. 接受请求
   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的方式有哪些

  • defer,只支持IE
  • async
  • 创建script,插入到DOM中,加载完毕后callBack

24. xml和 json的区别

  • 数据体积方面
    • JSON相对于XML来讲,数据的体积小,传递的速度更快些。
  • 数据交互方面
    • JSONJavaScript的交互更加方便,更容易解析处理,更好的数据交互
  • 数据描述方面
    • JSON对数据的描述性比XML较差
  • 传输速度方面
    • JSON的速度要远远快于XML

25. webpack 如何实现打包的

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

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

  • sql注入原理
    • 就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令
  • 总的来说有以下几点
    • 永远不要信任用户的输入,要对用户的输入进行校验,可以通过正则表达式,或限制长度,对单引号和双"-"进行转换等
    • 永远不要使用动态拼装SQL,可以使用参数化的SQL或者直接使用存储过程进行数据查询存取
    • 永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接
    • 不要把机密信息明文存放,请加密或者hash掉密码和敏感的信息

XSS原理及防范

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

XSS防范方法

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

XSS与CSRF有什么区别吗?

  • XSS是获取信息,不需要提前知道其他用户页面的代码和数据包。CSRF是代替用户完成指定的动作,需要知道其他用户页面的代码和数据包。要完成一次CSRF攻击,受害者必须依次完成两个步骤
  • 登录受信任网站A,并在本地生成Cookie
  • 在不登出A的情况下,访问危险网站B

CSRF的防御

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

个人总结:

说实话这么多默认的单词缩写,记不住,但是我印象比较深的是,在表单输入这一块,永远要进行处理,万一别人给你提交一段代码,在后台跑起来了,那就不是开玩笑的了。

27.用过哪些设计模式

  • 工厂模式:
    • 工厂模式解决了重复实例化的问题,但还有一个问题,那就是识别问题,因为根本无法
    • 主要好处就是可以消除对象间的耦合,通过使用工程方法而不是new关键字
  • 构造函数模式
    • 使用构造函数的方法,即解决了重复实例化的问题,又解决了对象识别的问题,该模式与工厂模式的不同之处在于
    • 直接将属性和方法赋值给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有四种状态:
    • pending:初始状态, 非fulfilledrejected.
    • fulfilled:成功的操作.
    • rejected:失败的操作.
    • settled: Promise已被fulfilledrejected,且不是pending
  • 另外,fulfilledrejected一起合称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的时候被调用,接收参数就是futureonFulfilled对应resolve,onRejected对应reject

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

  • CommonJS是服务器端模块的规范,Node.js采用了这个规范。CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。AMD规范则是非同步加载模块,允许指定回调函数
  • AMD推荐的风格通过返回一个对象做为模块对象,CommonJS的风格通过对module.exportsexports的属性赋值来达到暴露模块对象的目的

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

  • cookie
  • session
  • url重写
  • 隐藏input
  • ip地址

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

  • ObjectJavaScript中所有对象的父对象
  • 数据封装类对象:ObjectArrayBooleanNumberString
  • 其他对象:FunctionArgumentsMathDateRegExpError

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

  • 不要在同一行声明多个变量
  • 请使用===/!==来比较true/false或者数值
  • 使用对象字面量替代new Array这种形式
  • 不要使用全局函数
  • Switch语句必须带有default分支
  • If语句必须使用大括号
  • 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();
  • 用混合方式来创建
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 +')')

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延迟加载的方式有哪些?

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

42. defer 和 async

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

43.说说严格模式的限制

  • 变量必须声明后再使用
  • 函数的参数不能有同名属性,否则报错
  • 不能使用with语句
  • 禁止this指向全局对象

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

  • attributedom元素在文档中作为html标签拥有的属性;
  • property就是dom元素在js中作为对象拥有的属性。
  • 对于html的标准属性来说,attributeproperty是同步的,是会自动更新的
  • 但是对于自定义的属性来说,他们是不同步的

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

  • 这个语法糖可以让有OOP基础的人更快上手js,至少是一个官方的实现了
  • 但对熟悉js的人来说,这个东西没啥大影响;一个Object.creat()搞定继承,比class简洁清晰的多

46.常见兼容性问题

  • png24位的图片在iE6浏览器上出现背景,解决方案是做成PNG8
  • 浏览器默认的marginpadding不同。解决方案是加一个全局的*{margin:0;padding:0;}来统一,,但是全局效率很低,一般是如下这样解决:
  • IE下,event对象有x,y属性,但是没有pageX,pageY属性
  • Firefox下,event对象有pageX,pageY属性,但是没有x,y属性.

47.函数防抖节流的原理

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

防抖(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 请求进行验证,验证一次就好)

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

//underscore.js
function 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中类型转换的规则?

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

Number的原始类型转换规则

  • 数值转换后还是数值
  • 字符串如果可以解析为数值则为数值, 空字符串为0, 无法解析的字符串为NaN
  • 布尔转数值, true转为1, false转为0
  • null转换为0
img

原始类型转换Number

Number的对象类型转换规则

传入实例M, 先调用M的valueOf(), 如果返回值V为基本数据类型, 则直接使用Number(V), 求最终返回值
如果T不属于基本数据类型, 则调用M的toString(), 如果返回值S为基本数据类型, 则直接使用Number(S),求最后的结果, 如果S不属于基本数据类型, 则直接返回NaN

img

对象类型转换1

img

对象类型转换2

String的原始类型转换规则

  • 数值(Number)转为相应的字符串
  • 字符串(String) 转换后还是字符串
  • 布尔值(Boolean)转换规则: true => 'true', false=> 'false'
  • undefine 转换为"undefine"
  • null 转换为'null'
img

String原始类型转换

String 的对象类型转换规则

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

img

String对象类型转换规则

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

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

隐式类型转换

四则运算+, -, *, /

img

隐式类型转换之四则运算

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"

注意:当object只有一层的时候,是深拷贝,例如如下:

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、对象只有一层的话可以使用上面的:Object.assign()函数

2、转成 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;
};

3、递归拷贝

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);

4、使用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;
}

5、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

6、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的话

3.没有拥有者,直接调用,就指向window

function a() { 
    console.log(this); 
}
a();
//this指向window

4.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.什么是闭包

  • 闭包就是能够读取其他函数内部变量的函数
  • 闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域
  • 闭包的特性:
    • 函数内再嵌套函数
    • 内部函数可以引用外层的参数和变量
    • 参数和变量不会被垃圾回收机制回收

说说你对闭包的理解

  • 使用闭包主要是为了设计私有的方法和变量。闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。在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.函数声明会被提升 对于函数表达式,是不会提升的

1         //函数声明, 形如:
2         function show(){
3             console.log( '函数声明方式' );
4         }
5 
6         //函数表达式, 形如:
7         var show = function(){
8             console.log( '表达式方式' );
9         }

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 的优点:

  1. 一旦状态改变,就不会再变,任何时候都可以得到这个结果
  2. 可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数

Promise 的缺点:

  1. 无法取消 Promise
  2. 当处于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遍历方法后面使用,即(“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中.get()提交和.post()提交有区别吗?

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

异同点:

1、请求方式不同:.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.手写一个类的继承

继承的形式有很多中,js高程里面归纳了其中,我简单说一下前三种。
1.原型继承

function Parent(){
    this.name = "parent";
}
Parent.prototype.getName = function(){
    return this.name;
}

function Child(){
    this.name = "child";
}

//继承parent
Child.prototype = new Parent();

2.构造函数继承

function Animal(name){
    this.name = name;
    this.eat = function(){
        consoel.log(this.name + "吃饭");
    }
}
var cat = new Animal("maomi");
cat.name;
cat.eat();

3.组合继承

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

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

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

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

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

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

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

相关文章

  • js基础面试题

    面试题:延迟加载JS有哪些方式? 面试题:JS数据类型有哪些? 面试题:JS数据类型考题 考题一: 考题二: 面试...

  • 前端相关大杂烩

    前端基础面试题(JS部分) 前端基础面试题(JS部分)学习 React.js 比你想象的要简单 原文地址:Lear...

  • 前端JavaScript面试技巧

    1-1 课程概述 要做什么?——讲解前端 JS 基础面试题 哪些部分?——JS 基础,JS-WEB-API,JS ...

  • 2021 web高频面试题新人可看

    当日目标 1 常见面试题分布情况? 2 高频面试题 1 常见面试题分布情况 公司面试题一般分为js,vue,小程序...

  • js 基础面试题

    js 基础面试题 1 js中使用typeof能得到哪些类型? undefined、string、number、bo...

  • 99%的人都会答错的js面试题(你会吗?)(转)

    99%的人都会答错的js面试题(你会吗?)

  • js数组题目

    js面试题 js数组 一、按要求分割数组 将"js,数组,分类"字符串数组以/分割 for循环累加 join()把...

  • 前端JavaScript面试技巧

    先看几道面试题: 1、JS中使用typeof能得到哪些类型。//JS变量类型2、何时使用“==”、“===”。//...

  • js运行机制

    js运行机制 在日常面试求职中,不免会做几道面试题,这面试题中往往会遇到js执行输出顺序,而这运行机制是我们每个前...

  • web前端入门到实战:32道常见的js面试题

    JS面试题: 【点击这里领取面试题资料】[https://jq.qq.com/?_wv=1027&k=fBr6dx...

网友评论

    本文标题:js面试题

    本文链接:https://www.haomeiwen.com/subject/tkvjnhtx.html