史上最全js面试题

js相关面试题

  • 1.简述同步和异步的区别
  • 2.怎么添加、移除、复制、创建、和查找节点
  • 3.实现一个函数clone 可以对Javascript中的五种主要数据类型(Number、string、Object、Array、Boolean)进行复制
  • 4.如何消除一个数组里面重复的元素
  • 5. 写一个返回闭包的函数
  • 6.使用递归完成1到100的累加
  • 7.Javascript有哪几种数据类型
  • 8.如何判断数据类型
  • 9.console.log(1+'2')和console.log(1-'2')的打印结果
  • 10.Js的事件委托是什么,原理是什么
  • 11.如何改变函数内部的this指针的指向
  • 12.列举几种解决跨域问题的方式,且说明原理
  • 13.谈谈垃圾回收机制的方式及内存管理
    • 垃圾回收机制—GC
    • 内存管理
  • 14.写一个function ,清除字符串前后的空格
  • 15.js实现继承的方法有哪些
    • 原型链继承
    • 构造继承
    • 实例继承
    • 拷贝继承
    • 组合继承
    • 寄生组合继承
  • 16.判断一个变量是否是数组,有哪些办法
  • 17.let ,const ,var 有什么区别
  • 18.箭头函数与普通函数有什么区别
  • 19.随机取1-10之间的整数
  • 20.new操作符具体干了什么
  • 21.Ajax原理
  • 22.模块化开发怎么做
  • 23.异步加载Js的方式有哪些
  • 24.xml和 json的区别
  • 25.webpack如何实现打包的
  • 26.常见web安全及防护原理
  • 27.用过哪些设计模式
  • 28.为什么要同源限制
  • 29.offsetWidth/offsetHeight,clientWidth/clientHeight与scrollWidth/scrollHeight的区别
  • 30.javascript有哪些方法定义对象
  • 31.说说你对promise的了解
  • 32.谈谈你对AMD、CMD的理解
  • 33.web开发中会话跟踪的方法有哪些
  • 34.介绍js有哪些内置对象?
  • 35.说几条写JavaScript的基本规范?
  • 36.javascript创建对象的几种方式?
  • 37.eval是做什么的?
  • 38.null,undefined 的区别?
  • 39.[“1”, “2”, “3”].map(parseInt) 答案是多少?
  • 40.javascript 代码中的”use strict”;是什么意思 ? 使用它区别是什么?
  • 41.js延迟加载的方式有哪些?
  • 42.defer和async
  • 43.说说严格模式的限制
  • 44.attribute和property的区别是什么?
  • 45.ECMAScript6 怎么写class么,为什么会出现class这种东西?
  • 46.常见兼容性问题
  • 47.函数防抖节流的原理
  • 48.原始类型有哪几种?null是对象吗?
  • 49.为什么console.log(0.2+0.1==0.3) //false
  • 51.深拷贝和浅拷贝的区别?如何实现
  • 52.如何判断this?箭头函数的this是什么
  • 53.== 和 ===的区别
  • 54.什么是闭包
  • 55.JavaScript原型,原型链 ? 有什么特点?
  • 56.typeof()和instanceof()的用法区别
  • 57.什么是变量提升
  • 58.all、apply以及bind函数内部实现是怎么样的
  • 59.为什么会出现setTimeout倒计时误差?如何减少
  • 60.谈谈你对JS执行上下文栈和作用域链的理解
  • 61.new的原理是什么?通过new的方式创建对象和通过字面量创建有什么区别?
  • 62.prototype 和 proto 区别是什么?
  • 63.使用ES5实现一个继承?
  • 64.取数组的最大值(ES5、ES6)
  • 65.ES6新的特性有哪些?
  • 66.promise 有几种状态, Promise 有什么优缺点 ?
  • 67.Promise构造函数是同步还是异步执行,then呢 ?promise如何实现then处理 ?
  • 68.Promise和setTimeout的区别 ?
  • 69.如何实现 Promise.all ?
  • 70.如何实现 Promise.finally ?
  • 71.如何判断img加载完成
  • 72.如何阻止冒泡?
  • 73.如何阻止默认事件?
  • 74.ajax请求时,如何解释json数据
  • 75.json和jsonp的区别?
  • 76.如何用原生js给一个按钮绑定两个onclick事件?
  • 77.拖拽会用到哪些事件
  • 78.document.write和innerHTML的区别
  • 80.浏览器是如何渲染页面的?
  • 81.$(document).ready()方法和window.onload有什么区别?
  • 82. jquery中`$.get()`提交和`$.post()`提交有区别吗?
  • 83.对前端路由的理解?前后端路由的区别?
  • 85.XMLHttpRequest:XMLHttpRequest.readyState;状态码的意思
  • 88.请重写Array 原型的map方法
  • 1.let const var比较
  • 2.反引号(`)标识
  • 3.函数默认参数
  • 4.箭头函数
  • 5.属性简写
  • 6.方法简写
  • 7.Object.keys()方法,获取对象的所有属性名或方法名
  • 8.Object.assign ()原对象的属性和方法都合并到了目标对象
  • 9.for...of 循环
  • 10.import和export
  • 11.Promise对象
  • 12.解构赋值

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

同步和异步是在编程中用于描述任务执行方式的两个概念。

  • 同步(Synchronous)任务按照顺序依次执行,每个任务必须等待前一个任务完成后才能开始执行。执行任务时会阻塞程序的运行,只有当前任务执行完成后才能继续执行下一个任务。如果某个任务执行时间过长,会导致整个程序响应变慢或停滞。

  • 异步(Asynchronous)任务不需要等待前一个任务完成,可以并发地执行多个任务。当遇到耗时操作时,不会阻塞线程或程序的执行,而是继续执行后续任务。在异步任务执行过程中,程序可以继续执行其他任务或进行其他操作。异步任务通常使用回调函数、Promise、async/await 等机制来处理任务的完成和结果返回。

区别:

  • 执行方式:同步任务按照顺序依次执行,而异步任务可以并发执行。
  • 阻塞:同步任务会阻塞程序的执行,而异步任务不会阻塞程序的执行。
  • 处理机制:同步任务没有特定的处理机制,而异步任务通常使用回调函数、Promise、async/await 等机制处理任务的完成和结果返回。

优缺点:
同步编程的优点是代码逻辑简单,易于理解和调试;但缺点是执行时间较长的任务会阻塞程序执行,导致程序响应性较差。
异步编程的优点是能够提高程序执行效率和响应速度,对于耗时操作不会阻塞程序执行;但缺点是代码可读性较差,需要处理回调函数或使用其他异步编程机制,增加了代码复杂性。

在实际开发中,根据具体需求和场景来选择同步或异步编程方式,以获得更好的性能和用户体验。

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 + “
”);
};
}
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有哪几种数据类型

  1. 基本数据类型:
  • 字符串 String
  • 数字 Number
  • 布尔 Boolean
  1. 复合数据类型:
  • 数组 Array
  • 对象 Object
  1. 特殊数据类型:
  • 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指针的指向

  • 当我们使用一个函数需要改变this指向的时候才会用到call、apply、bind
  • 如果你要传递的参数不多,则可以使用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

  1. 使用window.name来进行跨域

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

  1. 使用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实现继承的方法有哪些

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

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

原型链继承

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

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. 创建子类实例时,无法向父类构造函数传参

构造继承

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

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. 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

实例继承

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

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. 不支持多继承

拷贝继承

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. 效率较低,内存占用高(因为要拷贝父类的属性)
  2. 无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)

组合继承

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

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. 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)

寄生组合继承

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

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; // 需要修复下构造函数

特点:堪称完美
缺点:实现较为复杂

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.箭头函数与普通函数有什么区别

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

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

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

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

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

21.Ajax原理

  • Ajax的原理简单来说是在用户和服务器之间加了—个中间层(AJAX引擎),通过XmlHttpRequest对象来向服务器发异步请求,从服务器获得数据,然后用javascript来操作DOM而更新页面。使用户操作与服务器响应异步化。这其中最关键的一步就是从服务器获得请求数据
  • Ajax的过程只涉及JavaScript、XMLHttpRequest和DOM。XMLHttpRequest是ajax的核心机制
 //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
    };
  })();

模块化开发是一种将程序拆分为独立的、可复用的模块来进行开发的方法。下面是一些指导步骤,帮助你进行模块化开发:

  1. 模块划分:根据功能和责任将程序拆分成多个模块。每个模块应专注于完成一个具体的任务或提供一个特定的功能。合理的模块划分可以提高代码的可维护性和复用性。

  2. 接口定义:对每个模块明确定义接口,包括输入、输出和对外提供的函数或方法。接口定义应尽量简单明确,遵循单一职责原则,减少模块之间的依赖。

  3. 模块实现:根据接口定义,实现每个模块的具体功能。在实现过程中,尽量保持模块内部的独立性,避免与其他模块产生不必要的耦合。

  4. 模块依赖管理:如果模块之间存在依赖关系,需要进行模块依赖管理。可以使用依赖注入、依赖查找等方式来解决模块之间的依赖关系,降低模块之间的耦合性。

  5. 测试和调试:针对每个模块编写相应的测试用例,确保模块能够正确地完成其功能。通过测试和调试,及时发现和解决模块内部的问题,提高整体代码质量。

  6. 模块集成:将各个独立的模块进行集成,并测试整体系统的功能和性能。在模块集成过程中,需要注意模块之间的接口兼容性和数据传递的正确性。

  7. 文档和维护:对每个模块编写清晰的文档,包括接口说明、使用示例和注意事项等。同时,定期检查和维护模块,确保其稳定性和可用性。

值得一提的是,模块化开发通常与合适的开发框架和设计模式结合使用,可以进一步提高开发效率和代码质量。选择适合项目需求和团队技术栈的框架和设计模式,有助于更好地实现模块化开发的目标。

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

异步加载 JavaScript 是一种优化网页性能和用户体验的方法,可以提高页面加载速度。以下是几种常见的异步加载 JavaScript 的方式:

使用

<script src="your-script.js" async></script>

使用

<script src="your-script.js" defer></script>

动态创建

javascript

var script = document.createElement('script');
script.src = 'your-script.js';
document.head.appendChild(script);

使用异步模块加载器:例如,使用RequireJS、Webpack的import()函数或ES6的import语句来实现模块化的异步加载。

import('your-module')
  .then(module => {
    // 模块加载成功后的回调函数
  })
  .catch(error => {
    // 模块加载失败后的回调函数
  });

这些异步加载 JavaScript 的方式可以根据项目需求和具体场景选择适合的方法,以提升页面加载速度和用户体验。

24.xml和 json的区别

  • 数据体积方面

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

  • 数据交互方面

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

  • 数据描述方面

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

  • 传输速度方面

JSON的速度要远远快于XML

25.webpack如何实现打包的

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

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

  1. 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:初始状态, 非fulfilled或rejected.

  • fulfilled:成功的操作.

  • rejected:失败的操作.

  • 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开发中会话跟踪的方法有哪些

cookie
session
url重写
隐藏input
ip地址

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

Object是JavaScript中所有对象的父对象
数据封装类对象:Object、Array、Boolean、Number和String
其他对象:Function、Arguments、Math、Date、RegExp、Error

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 +‘)’)

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.函数防抖节流的原理

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

防抖(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 这样的运算,操作数会先被转成二进制,然后再计算

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

浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。浅拷贝只复制对象的第一层属性
但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。对对象的属性进行递归复制

  • 浅拷贝 实现方法
    1、可以通过简单的赋值实现
    2、Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。

  • 深拷贝的实现方式
    1、对象只有一层的话可以使用上面的:Object.assign()函数
    2、转成 JSON 再转回来
    3、递归拷贝
    4、使用Object.create()方法,直接使用var newObj = Object.create(oldObj),可以达到深拷贝的效果。
    5、jquery,jquery 有提供一个$.extend可以用来做 Deep Copy。
    6、lodash,另外一个很热门的函数库lodash,也有提供_.cloneDeep用来做 Deep Copy。

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
  1. ==:两个等号我们称为等值符,当等号两边的值为相同类型时比较值是否相同,类型不同时会发生类型的自动转换,转换为相同的类型后再作比较。

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.什么是变量提升

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

1.函数声明会被提升 对于函数表达式,是不会提升的
2.出现同名的函数声明,变量声明的时候, 函数声明会被优先提升,变量声明会被忽略
3.如果有同名的函数声明,后面的会覆盖前面的

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

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

创建一个新对象。
这个新对象会被执行[[原型]]连接。
将构造函数的作用域赋值给新对象,即this指向这个新对象.
如果函数没有返回其他对象,那么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新的特性有哪些?

  • 新增了块级作用域(let,const)
  • 提供了定义类的语法糖(class)
  • 新增了一种基本数据类型(Symbol)
  • 新增了变量的解构赋值
  • 函数参数允许设置默认值,引入了rest参数,新增了箭头函数
  • 数组新增了一些API,如 isArray / from / of 方法;数组实例新增了 entries(),keys() 和 values() 等方法
  • 对象和数组新增了扩展运算符
  • ES6 新增了模块化(import/export)
  • ES6 新增了 Set 和 Map 数据结构
  • ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例
  • 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 执行。

  • 宏任务(Macrotask)和微任务(Microtask)是 JavaScript 中用于处理异步操作的两种不同的任务队列,它们有一些区别和适用场景。

  • 宏任务是由宿主环境(浏览器或Node.js)提供的任务队列,包括整体代码块、setTimeout、setInterval、I/O 操作等。宏任务的执行时机是在当前执行栈为空时,从宏任务队列中取出一个任务执行。通常情况下,每个宏任务会执行完毕后更新渲染,并且可能导致 UI 的重绘和重排。所以宏任务比较适合处理一些耗时较长、不需要立即执行且与用户交互无关的任务。

  • 微任务是位于宏任务之内的任务队列,又被称为 Promise 队列、Job 队列或微任务队列。它包括Promise回调、MutationObserver 和 process.nextTick 等。微任务的执行时机是在当前宏任务执行结束后,在渲染前执行。微任务具有高优先级,会在下一个宏任务之前执行完毕。微任务的执行不会导致 UI 的重绘和重排,因此适用于需要尽快更新 UI 的任务,例如对DOM进行操作后立即需要获取计算结果。

使用场景:

  • 宏任务适合用于需要较长时间处理、文件读写、网络请求等耗时操作。
  • 微任务适合用于需要及时响应、执行顺序敏感或需要访问 DOM 后立即获取结果的场景,例如 Promise 的链式调用、DOM 变动后读取布局信息等。

需要注意的是,在同一个宏任务中可能存在多个微任务,而且微任务中再触发的微任务会被添加到当前微任务队列的末尾,因此在处理异步操作时要注意避免过深的

69.如何实现 Promise.all ?

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

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

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

  1. 如果参数中有一个promise失败,那么Promise.all返回的promise对象失败
  2. 在任何情况下,Promise.all 返回的 promise 的完成状态的结果都是一个数组

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加载完成

  1. 一、load事件,测试,所有浏览器都显示出了“loaded”,说明所有浏览器都支持img的load事件。
  2. 二、readystatechange事件,readyState为complete和loaded则表明图片已经加载完毕。测试IE6-IE10支持该事件,其它浏览器不支持。
  3. 三、img的complete属性,轮询不断监测img的complete属性,如果为true则表明图片已经加载完毕,停止轮询。该属性所有浏览器都支持。

72.如何阻止冒泡?

事件流(Event flow)是指描述事件在 HTML 文档结构中传播和触发的顺序。

事件流分为两种模型:冒泡(Bubbling)模型捕获(Capturing)模型

  • 冒泡模型(Bubbling):

在冒泡模型中,事件首先在触发事件的元素上被触发,然后逐级向上冒泡,直到达到文档根节点。即事件先由最具体的元素接收,然后逐级向上传播到较为不具体的元素。

  • 捕获模型(Capturing):

在捕获模型中,事件首先从文档根节点开始捕获,接着逐级向下传播,直到达到触发事件的元素。即事件先由最不具体的元素(文档根节点)捕获,然后逐级向下传播到最具体的元素。

大部分浏览器默认采用冒泡模型来处理事件流,但也可以通过在事件处理函数中通过 addEventListener() 方法的第三个参数来指定使用捕获模型。例如:

element.addEventListener(事件类型, 事件处理函数, 是否在捕获阶段);

冒泡型事件:事件按照从最特定的事件目标到最不特定的事件目标(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,其原因在于其允许更精确的控制要刷新页面的那一个部分。

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

渲染的流程如下:

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

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

  1. 解析CSS。优先级:浏览器默认设置<用户设置<外部样式<内联样式

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

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

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

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

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

82. jquery中$.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事件。

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

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

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

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

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

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

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

88.请重写Array 原型的map方法

先看一下map的用法

//语法:array.map(function(currentValue,index,arr), thisValue)
//用法:
var arr = [1,2,3,4];
arr.map((item,index,arr) => {
    return item*10 //新数组为10,20,30,40
})
//map遍历数组,返回一个新数组,不改变原数组的值。

用js实现

Array.prototype.fakeMap = function(fn,context) {
	let arr = this;
	let temp = [];
	for(let i=0;i<arr.length;i++){
		let result = fn.call(context,arr[i],i,arr);
		temp.push(result);
	}
	return temp;
}

1.let const var比较

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

2.反引号(`)标识

用一对反引号(`)标识,它可以当作普通字符串使用,也可以用来定义多行字符串,也可以在字符串中嵌入变量,js表达式或函数,变量、js表达式或函数需要写在${ }中。

var str = `abc
def
gh`;
console.log(str);
let name = "小明";
function a() {
    return "ming";
}
console.log(`我的名字叫做${name},年龄${17+2}岁,性别${'男'},游戏ID:${a()}`);

3.函数默认参数

ES6为参数提供了默认值。在定义函数时便初始化了这个参数,以便在参数没有被传递进去时使用。

function A(a,b=1){   
 console.log(a+b);
}
A(1);    //2
A(2+3);  //5

4.箭头函数

在es6中,提供了一种简洁的函数写法,我们称作“箭头函数”。

写法:函数名=(形参)=>{……} 当函数体中只有一个表达式时,{}和return可以省略,当函数体中形参只有一个时,()可以省略。

特点:箭头函数中的this始终指向箭头函数定义时的离this最近的一个函数,如果没有最近的函数就指向window。

//省略写法

var people = name => 'hello' + name;
 
var getFullName = (firstName, lastName) => {
    var fullName = firstName + lastName;
    return fullName;
} 

5.属性简写

属性的简写。ES6 允许在对象之中,直接写变量。这时,属性名为变量名, 属性值为变量的值。

var foo = 'bar';var baz = {foo};  //等同于  var baz = {foo: foo}

6.方法简写

方法的简写。省略冒号与function关键字。

var o = {
  method() {
    return "Hello!";
  }
};
 
// 等同于
var o = {
  method: function() {
    return "Hello!";
  }
};

7.Object.keys()方法,获取对象的所有属性名或方法名

Object.keys()方法,获取对象的所有属性名或方法名(不包括原形的内容),返回一个数组。

 var obj={name: "john", age: "21", getName: function () { alert(this.name)}};
console.log(Object.keys(obj));    // ["name", "age", "getName"]
console.log(Object.keys(obj).length);    //3
 
console.log(Object.keys(["aa", "bb", "cc"]));    //["0", "1", "2"]
console.log(Object.keys("abcdef"));    //["0", "1", "2", "3", "4", "5"]

8.Object.assign ()原对象的属性和方法都合并到了目标对象

Object.assign (),assign方法将多个原对象的属性和方法都合并到了目标对象上面。可以接收多个参数,第一个参数是目标对象,后面的都是源对象。

var target  = {}; //目标对象
var source1 = {name : 'ming', age: '19'}; //源对象1
var source2 = {sex : '女'}; //源对象2
var source3 = {sex : '男'}; //源对象3,和source2中的对象有同名属性sex
Object.assign(target,source1,source2,source3);
 
console.log(target);    //{name : 'ming', age: '19', sex: '男'}

9.for…of 循环

是遍历所有数据结构的统一的方法。for…of循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如arguments对象、DOM NodeList 对象)、Generator 对象,以及字符串。

var arr=["小林","小吴","小佳"];
for(var v of arr){
    console.log(v);	
}
//小林 
//小吴 
//小佳

10.import和export

ES6标准中,JavaScript原生支持模块(module)了。这种将JS代码分割成不同功能的小块进行模块化,将不同功能的代码分别写在不同文件中,各模块只需导出公共接口部分,然后通过模块的导入的方式可以在其他地方使用。

export用于对外输出本模块(一个文件可以理解为一个模块)变量的接口。

import用于在一个模块中加载另一个含有export接口的模块。

import和export命令只能在模块的顶部,不能在代码块之中。

//导入部分
//全部导入
import Person from './example'
 
//将整个模块所有导出内容当做单一对象,用as起别名
import * as example from "./example.js"
console.log(example.name)
console.log(example.getName())
 
//导入部分
import { name } from './example'
 
//导出部分
// 导出默认
export default App
 
// 部分导出
export class User extend Component {};

11.Promise对象

Promise是异步编程的一种解决方案,将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。

它有三种状态,分别是pending-进行中、resolved-已完成、rejected-已失败。

Promise 构造函数包含一个参数和一个带有 resolve(解析)和 reject(拒绝)两个参数的回调。在回调中执行一些操作(例如异步),如果一切都正常,则调用 resolve,否则调用 reject。对于已经实例化过的 promise 对象可以调用 promise.then() 方法,传递 resolve 和 reject 方法作为回调。then()方法接收两个参数:onResolve和onReject,分别代表当前 promise 对象在成功或失败时。

var promise = new Promise((resolve, reject) => {
    var success = true;
    if (success) {
        resolve('成功');
    } else {
        reject('失败');
    }
}).then(
    (data) => { console.log(data)},
    (data) => { console.log(data)}
)

promise的执行过程

setTimeout(function() {
    console.log(0);
}, 0);
var promise = new Promise((resolve, reject) => {
    console.log(1);
    setTimeout(function () {
        var success = true;
        if (success) {
            resolve('成功');
        } else {
            reject('失败');
        }
    },2000);
}).then(
    (data) => { console.log(data)},
    (data) => { console.log(data)}
);
console.log(promise);	// 进行中
setTimeout(function () {
    console.log(promise);	// 已完成
},2500);
console.log(2);
 
//1
//Promise {}
//2
//0
//成功
//Promise {: undefined}

12.解构赋值

ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。

  • 数组的解构赋值
    数组中的值会自动被解析到对应接收该值的变量中,数组的解构赋值要一一对应 如果有对应不上的就是undefined
var [name, pwd, sex]=["小周", "123456", "男"];
console.log(name) //小周
console.log(pwd)//123456
console.log(sex)//男
  • 对象的解构赋值
    对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
var obj={name:"小周", pwd:"123456", sex:"男"}
var {name, pwd, sex}=obj;
console.log(name) //小周
console.log(pwd)//123456
console.log(sex)//男
 

你可能感兴趣的:(javascript,前端)