近年来,从事web前端开发的程序员越来越多,都需要使用JavaScript,这篇文章主要整理一些最常见的javascript面试题以及答案。
值类型(基本类型):字符串(String)、数字(Number)、布尔(Boolean)、对空(Null)、未定义(Undefined)、Symbol(独一无二的值)。
引用数据类型:对象(Object)、数组(Array)、函数(Function)。
注:Symbol 是 ES6 引入了一种新的原始数据类型,表示独一无二的值。
1.存储位置不同:
基本数据类型:以栈的形式存储, 保存与赋值指向数据本身, 用typeof 来判断类型,存储空间固定。
引用类型:以堆的形式存储, 保存于赋值指向对象的一个指针, 用instanceof 来判断类型 , 存储空间不固定。
2.传值方式不同:
基本数据类型按值传递,无法改变一个基本数据类型的值
引用类型按引用传递,应用类型值可以改变
1. typeof
可以判断出'string','number','boolean','undefined','symbol'
但判断 typeof(null) 时值为 'object'; 判断数组和对象时值均为 'object'
2. instanceof
原理是构造函数的 prototype 属性是否出现在对象的原型链中的任何位置
functionA() {}
let a = new A();
a instanceof A //true,因为 Object.getPrototypeOf(a) === A.prototype;
3. Object.prototype.toString.call()
常用于判断浏览器内置对象,对于所有基本的数据类型都能进行判断,即使是 null 和 undefined
Object.prototype.toString.call(null)//"[object Null]"Object.prototype.toString.call(undefined)//"[object Undefined]"Object.prototype.toString.call(Object)//"[object Function]"
4. Array.isArray()
用于判断是否为数组。
typeof是一个运算符,用于检测数据的类型,比如基本数据类型null、undefined、string、number、boolean,以及引用数据类型object、function,但是对于正则表达式、日期、数组这些引用数据类型,它会全部识别为object
instanceof同样也是一个运算符,它就能很好识别数据具体是哪一种引用类型。它与isPrototypeOf的区别就是它是用来检测构造函数的原型是否存在于指定对象的原型链当中;
而isPrototypeOf是用来检测调用此方法的对象是否存在于指定对象的原型链中,所以本质上就是检测目标不同。
NaN 即非数值(Not a Number),NaN 属性用于引用特殊的非数字值,该属性指定的并不是不合法的数字。
NaN 属性 与 Number.Nan 属性相同。
提示: 请使用 isNaN() 来判断一个值是否是数字。原因是 NaN 与所有值都不相等,包括它自己。
本地对象:ECMA-262 把本地对象(native object)定义为“独立于宿主环境的 ECMAScript 实现提供的对象”。包含了:Object、Function、Array、String、Boolean、Number、Date、RegExp、Error、EvalError、RangeError、ReferenceError、SyntaxError、TypeError、URIError。
内置对象:ECMA-262 把内置对象(built-in object)定义为“由 ECMAScript 实现提供的、独立于宿主环境的所有对象,在 ECMAScript 程序开始执行时出现”。这意味着开发者不必明确实例化内置对象,它已被实例化了。包含了:Gobal 和 Math。
宿主对象:由ECMAScript实现的宿主环境提供的对象,可以理解为:浏览器提供的对象。所有的BOM和dom都是宿主对象。
栈(stack):由编译器自动分配释放,存放函数的参数值,局部变量等;
堆(heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由操作系统释放。
null 表示"没有对象",即该处不应该有值,转为数值时为0。典型用法是:
(1) 作为函数的参数,表示该函数的参数不是对象。
(2) 作为对象原型链的终点。
undefined 表示"缺少值",就是此处应该有一个值,但是还没有定义,转为数值时为NaN。典型用法是:
(1)变量被声明了,但没有赋值时,就等于undefined。
(2) 调用函数时,应该提供的参数没有提供,该参数等于undefined。
(3)对象没有赋值的属性,该属性的值为undefined。
(4)函数没有返回值时,默认返回undefined。
undeclared :js语法错误,没有申明直接使用,js无法找到对应的上下文。
Object.keys不会遍历继承的原型属性
for...in 会遍历继承的原型属性
匿名函数:就是没有函数名的函数,如:
(function(x, y){
alert(x + y);
})(2, 3);
这里创建了一个匿名函数(在第一个括号内),第二个括号用于调用该匿名函数,并传入参数。
JS允许将声明移动到顶部的默认行为称为提升。JS中创建函数的两种方法是函数声明和函数表达式。
函数声明
具有特定参数的函数称为函数声明,在JS中创建变量称为声明。如:
hoisted(); // logs "foo"functionhoisted() {
console.log('foo');
}
函数表达式
当使用表达式创建函数时,称为函数表达式。如:
notHoisted(); // TypeError: notHoisted is not a functionvar notHoisted = function() {
console.log('bar');
};
在js中,当运算符在运算时,如果两边数据不统一,CPU就无法计算,这时我们编译器会自动将运算符两边的数据做一个数据类型转换,转成一样的数据类型再计算
这种无需程序员手动转换,而由编译器自动转换的方式就称为隐式转换
例如1 > "0"这行代码在js中并不会报错,编译器在运算符时会先把右边的"0"转成数字0`然后在比较大小
隐式转换规则:
1. 转成string类型: +(字符串连接符) 2..转成number类型:++/--(自增自减运算符) + - * / %(算术运算符) > < >= <= == != === !=== (关系运算符)
2. 转成boolean类型:!(逻辑非运算符)
例子:
console.log([] == []) // falseconsole.log([] == ![]) // trueconsole.log([] !== []) // trueconsole.log(NaN != NaN) // trueconsole.log(null == undefined) // trueconsole.log(null === undefined) // falseconsole.log(1 == true) // trueconsole.log(null > 0) // falseconsole.log(true + 1) //2console.log(undefined + 1) // NaNconsole.log({} + 1) // [object Object]1console.log([] + {}) // [object Object]console.log([2,3] + [1,2]) //2,31,2
(1)两等号判等,会在比较时进行类型转换;
(2)三等号判等(判断严格),比较时不进行隐式类型转换,(类 型不同则会返回false);
(3)Object.is 在三等号判等的基础上特别处理了NaN、-0和+0,保证-0和+0不再相同,但Object.is(NaN, NaN)会返回true。Object.is应被认为有其特殊的用途,而不能用它认为它比其它的相等对比更宽松或严格。
1、对于string,number等基础类型,==和===有区别
1)不同类型间比较,==之比较“转化成同一类型后的值”看“值”是否相等,===如果类型不同,其结果就是不等。
2)同类型比较,直接进行“值”比较,两者结果一样。
2、对于Array,Object等高级类型,==和===没有区别
进行“指针地址”比较。
3、基础类型与高级类型,==和===有区别
1)对于==,将高级转化为基础类型,进行“值”比较。
2)因为类型不同,===结果为false。
ES5 有俩种:var 和 function
ES6 有六种:增加四种,let、const、class 和 import
注意:let、const、class声明的全局变量再也不会和全局对象的属性挂钩。
事件代理/事件委托是利用事件冒泡的特性,将本应该绑定在多个元素上的事件绑定在他们的祖先元素上,尤其在动态添加子元素的时候,可以非常方便的提高程序性能,减小内存空间。
冒泡型事件:事件按照从最特定的事件目标到最不特定的事件目标(document对象)的顺序触发。
捕获型事件:事件从最不精确的对象(document 对象)开始触发,然后到最精确(也可以在窗口级别捕获事件,不过必须由开发人员特别指定)。
在添加事件时用addEventListener(event,fn,useCapture)方法,基中第3个参数useCapture是一个Boolean值,用来设置事件是在事件捕获时执行,还是事件冒泡时执行。
注意:IE浏览器用attachEvent()方法,此方法没有相关设置,不过IE的事件模型默认是在事件冒泡时执行的,也就是在useCapture等于false的时候执行,所以把在处理事件时把useCapture设置为false是比较安全,也实现兼容浏览器的效果。
w3c的方法是e.stopPropagation(),IE则是使用e.cancelBubble = true。例如:
window.event.cancelBubble = true;e.stopPropagation();
return false也可以阻止冒泡。
w3c的方法是e.preventDefault(),IE则是使用e.returnValue = false,比如:
functionstopDefault( e ) { if ( e && e.preventDefault ) e.preventDefault(); //IE中阻止函数器默认动作的方式 else window.event.returnValue = false; }
return false也能阻止默认行为。
分为三大阶段:捕获阶段--目标阶段--冒泡阶段
事件代理简单说就是:事件不直接绑定到某元素上,而是绑定到该元素的父元素上,进行触发事件操作时(例如'click'),再通过条件判断,执行事件触发后的语句(例如'alert(e.target.innerhtml)')
好处:(1)使代码更简洁;(2)节省内存开销
使用addEventListener的方式来绑定多个事件。例如
var btn = document.getElementById('btn')btn.addEventListener('click', fn1)btn.addEventListener('click', fn2)functionfn1 () {console.log('我是方法1') }functionfn2 () {console.log('我是方法2') }
onclick鼠标点击某个对象;onfocus获取焦点;onblur失去焦点;onmousedown鼠标被按下等,常用的如下:
鼠标单击事件( onclick )
鼠标经过事件( onmouseover )
鼠标移开事件( onmouseout )
光标聚焦事件( onfocus )
失焦事件( onblur )
内容选中事件( onselect )
文本框内容改变事件( onchange )
加载事件( onload )
卸载事件( onunload )
闭包的概念:闭包就是能读取其他函数内部变量的函数。
避免全局变量的污染
希望一个变量长期存储在内存中(缓存变量)
缺点:
内存泄露(消耗)
常驻内存,增加内存使用量
使用场景:封装功能时(需要使用私有的属性和方法),函数防抖、函数节流、函数柯里化、给元素伪数组添加事件需要使用元素的索引值。
意外的全局变量(在函数内部没有使用var进行声明的变量)
console.log
闭包
对象的循环引用
未清除的计时器
DOM泄露(获取到DOM节点之后,将DOM节点删除,但是没有手动释放变量,拿对应的DOM节点在变量中还可以访问到,就会造成泄露)
1) 创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型。
2) 属性和方法被加入到 this 引用的对象中。
3) 新创建的对象由 this 所引用,并且最后隐式的返回 this 。
this永远指向函数运行时所在的对象,而不是函数被创建时所在的对象。
普通的函数调用,函数被谁调用,this就是谁。
构造函数的话,如果不用new操作符而直接调用,那即this指向window。用new操作符生成对象实例后,this就指向了新生成的对象。
匿名函数或不处于任何对象中的函数指向window 。
如果是call,apply等,指定的this是谁,就是谁。
1) this总是指向函数的直接调用者(而非间接调用者)
2) 如果有new关键字,this指向new出来的那个对象
3) 在事件中,this指向目标元素,特殊的是IE的attachEvent中的this总是指向全局对象window。
它的功能是把对应的字符串解析成JS代码并运行;应该避免使用eval,不安全,非常耗性能(2次,一次解析成js语句,一次执行)。
call apply bind都可以改变函数调用的this指向。
函数.call(对象,arg1,arg2....)函数.apply(对象,[arg1,arg2,...])var ss=函数.bind(对象,arg1,arg2,....)
1.第一个参数都是指定函数内部中this的指向(函数执行时所在的作用域),然后根据指定的作用域,调用该函数。
2.都可以在函数调用时传递参数。call,bind方法需要直接传入,而apply方法需要以数组的形式传入。
3.call,apply方法是在调用之后立即执行函数,而bind方法没有立即执行,需要将函数再执行一遍。
4.改变this对象的指向问题不仅有call,apply,bind方法,也可以使用that变量来固定this的指向。
作用域链的原理和原型链很类似,如果这个变量在自己的作用域中没有,那么它会寻找父级的,直到最顶层。
注意:JS没有块级作用域,若要形成块级作用域,可通过(function(){})();立即执行的形式实现。
首先理解三个概念:
prototype:原型对象,每个函数都有一个 prototype 属性,再通过 new 命令实例对象时,该属性会成为该实例的原型对象。
constructor:构造函数。指向原型对象的 constructor
__proto__:实例对象的原型
在javascript中,实例对象与原型之间的链接,叫做原型链。Javascript解析引擎在读取一个Object的属性的值时,会沿着原型链向上寻找,如果最终没有找到,则该属性值为undefined;如果最终找到该属性的值,则返回结果。
1.原型链继承
2.构造函数继承
3.组合继承(原型链继承+构造函数继承)
4.原型式继承
5.寄生继承
6.组合寄生继承
代码示例:
//借助构造函数实现继承:缺点是父构造函数的原型链继承不了,若要全部继承除非将所有属性和方法定义在构造函数中functionParent1() { this.name = 'parent1';}functionChild1() { //这么做的好处是定义在父构造函数中的引用类型的属性,对于子构造函数的每个实例来说是独立的 //并且在子构造函数实例化时,可以给父构造函数传参 Parent.call(this); this.type = 'child1';}//借助原型链实现继承:缺点是继承的引用类型属性是共享的,子构造函数的实例更改会影响其他实例上的这个属性,比如 play 属性functionParent2() { this.name = 'parent2'; this.play = [1, 2, 3];}functionChild2() { this.type = 'Child2';}Child2.prototype = new Parent2();//组合方式:缺点是会执行两次父构造函数functionChild3() { //执行第一次 Parent2.call(this); this.type = 'Child3';}Child3.prototype = new Parent2(); //执行第二次//组合优化1,不需要再将定义在父构造函数中的属性和方法再继承一次,只需要继承原型链上的Child3.prototype = Parent2.prototype;//缺点是无法区分一个实例是子函构造函数实例化的还是父构造函数实例化的let s1 = new Child3();//s1.constructor 指向了 Parent2,而不是 Child3,因为 Child3 原型对象的属性 constructor 继承了 Parent2 原型对象上的//如果你强行执行 Child3.prototype.constructor = Child3 的话,也会将 Parent2.prototype.constructor 改成 Child3//组合优化2,通过 Object.create() 创建一个中间对象,将两个原型对象区别开来,并且继承父构造函数的原型链Child3.prototype = Object.create(Parent2.prototype);Child3.prototype.constructor = Child3;//即 Child3.prototype.__proto__ === Parent2.prototype 为 true//如此 Child3.prototype 和 Parent2.prototype 被隔离开,是两个对象,不会相互影响//这种方式为理想继承方式
通过var声明的变量会被提升至作用域的顶端。不仅仅是变量,函数声明也一样会被提升。
当同一作用域内同时出现变量和函数声明提升时,变量仍然在函数前面。
在Javscript中,解析器在向执行环境中加载数据时,对函数声明和函数表达式并非是一视同仁的,解析器会率先读取函数声明,并使其在执行任何代码之前可用(可以访问),至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解析执行。
window对象代表浏览器中打开的一个窗口。document对象代表整个html文档。实际上,document对象是window对象的一个属性。
页面加载完成有两种事件,一是ready,表示文档结构已经加载完成(不包含图片等非文字媒体文件),二是onload,指示页面包含图片等文件在内的所有元素都加载完成。
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
浅拷贝
// 第一层为深拷贝Object.assign()
Array.prototype.slice()
扩展运算符 ...
深拷贝
JSON.parse(JSON.stringify())
递归函数
functioncloneObject(obj) {
var newObj = {} //如果不是引用类型,直接返回if (typeof obj !== 'object') {
return obj
}
//如果是引用类型,遍历属性else {
for (var attr in obj) {
//如果某个属性还是引用类型,递归调用
newObj[attr] = cloneObject(obj[attr])
}
}
return newObj
}
Javascrip中国每个函数都会有一个Arguments对象实例arguments,它引用着函数的实参,可以用数组下标的方式"[]"引用arguments的元素。arguments.length为函数实参个数,arguments.callee引用函数自身。
在函数代码中,使用特殊对象arguments,开发者无需明确指出参数名,通过使用下标就可以访问相应的参数。
functiontest() {
var s = "";
for (var i = 0; i < arguments.length; i++) {
alert(arguments[i]);
s += arguments[i] + ",";
}
return s;
}
test("name", "age");//name,age
arguments虽然有一些数组的性质,但其并非真正的数组,只是一个类数组对象。其并没有数组的很多方法,不能像真正的数组那样调用.jion(),.concat(),.pop()等方法。
在代码中出现表达式-"use strict"; 意味着代码按照严格模式解析,这种模式使得Javascript在更严格的条件下运行。
好处:
消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;
消除代码运行的一些不安全之处,保证代码运行的安全;
提高编译器效率,增加运行速度;
为未来新版本的Javascript做好铺垫。
坏处:
同样的代码,在"严格模式"中,可能会有不一样的运行结果;
一些在"正常模式"下可以运行的语句,在"严格模式"下将不能运行。
跨域需要针对浏览器的同源策略来理解,同源策略指的是请求必须是同一个端口,同一个协议,同一个域名,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。
受浏览器同源策略的影响,不是同源的脚本不能操作其他源下面的对象。想要操作另一个源下的对象是就需要跨域。
常用解决方案:
跨域资源共享(CORS)
nginx代理跨域
nodejs中间件代理跨域
jsonp跨域
(1)JSON 是一种轻量级的数据交换格式。
(2)JSON 独立于语言和平台,JSON 解析器和 JSON 库支持许多不同的编程语言。
(3)JSON的语法表示三种类型值,简单值(字符串,数值,布尔值,null),数组,对象
Json是通过动态创建script标签,回调函数的方式实现的,而Ajax是页面无刷新请求数据操作。
原生Js的实现:
(function (window,document) {"use strict"; var jsonp = function (url,data,callback) {//1.将传入的data数据转化为url字符串形式// {id:1,name:'fly63'} => id=1&name=fly63 var dataString = url.indexof('?') == -1? '?': '&';for(var key in data){ dataString += key + '=' + data[key] + '&'; };//2 处理url中的回调函数// cbFuncName回调函数的名字 :my_json_cb_名字的前缀 + 随机数(把小数点去掉) var cbFuncName = 'my_json_cb_' + Math.random().toString().replace('.',''); dataString += 'callback=' + cbFuncName;//3.创建一个script标签并插入到页面中 var scriptEle = document.createElement('script'); scriptEle.src = url + dataString;//4.挂载回调函数window[cbFuncName] = function (data) { callback(data);// 处理完回调函数的数据之后,删除jsonp的script标签document.body.removeChild(scriptEle); }//5.append到页面中document.body.appendChild(scriptEle); }window.$jsonp = jsonp;// 因为jsonp是一个私有函数外部不能调用,所有jsonp函数作文window对象的一个方法,供外部调用})(window,document)
(1)Cookie数量和长度的限制。每个domain最多只能有20条cookie,每个cookie长度不能超过4KB,否则会被截掉。
(2)安全性问题。如果cookie被 人拦截了,那人就可以取得所有的session信息。即使加密也与事无补,因为拦截者并不需要知道cookie的意义,他只要原样转发cookie就可以达到目的了。
(3)有些状态不可能保存在客户端。例如,为了防止重复提交表单,我们需要在服务器端保存一个计数器。如果我们把这个计数器保存在客户端,那么它起不到任何作用。
通过使用多个非主要域名来请求静态文件,如果静态文件都放在主域名下,那静态文件请求的时候带有的cookie的数据提交给server是非常浪费的,还不如隔离开。因为cookie有域的限制,因此不能跨域提交请求,故使用非主要域名的时候,请求头中就不会带有cookie数据,这样可以降低请求头的大小,降低请求时间,从而达到降低整体请求延时的目的。同时这种方式不会将cookie传入server,也减少了server对cookie的处理分析环节,提高了server的http请求的解析速度。
sessionStorage和localStorage是HTML5 Web Storage api提供的,可以方便的在web请求之间保存数据。有了本地数据,就可以避免数据在浏览器和服务器间不必要地来回传递。sessionStorage、localStorage、cookie都是在浏览器端存储的数据,其中sessionStorage的概念很特别,引入了一个“浏览器窗口”的概念。sessionStorage是在同源的同窗口(或tab)中,始终存在的数据。也就是说只要这个浏览器窗口没有关闭,即使刷新页面或进入同源另一页面,数据仍然存在。关闭窗口后,sessionStorage即被销毁。同时“独立”打开的不同窗口,即使是同一页面,sessionStorage对象也是不同的cookies会发送到服务器端。其余两个不会。Microsoft指出InternetExplorer8增加cookie限制为每个域名50个,但IE7似乎也允许每个域名50个cookie。
Firefox每个域名cookie限制为50个。
Opera每个域名cookie限制为30个。
Firefox和Safari允许cookie多达4097个字节,包括名(name)、值(value)和等号。
Opera允许cookie多达4096个字节,包括:名(name)、值(value)和等号。
InternetExplorer允许cookie多达4095个字节,包括:名(name)、值(value)和等号。
除非被清除,否则永久保存 clear()可清楚
sessionStorage 仅在当前会话下有效,关闭页面或浏览器后被清除
函数柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
柯里化的目的是,减少代码冗余,以及增加代码的可读性。下面看一个与柯里化有关的经典例子:
// 实现一个add方法,使计算结果能够满足类似如下的预期:// add(1)(2)(3) = 6;// add(1, 2, 3)(4) = 10;// add(1)(2)(3)(4)(5) = 15;var add_currying=function(...rest){ var sum=0;for(let item of rest){ sum+=item; } var add_back = (...rest) => {for(let item of rest){ sum+=item; }return add_back; }; add_back.toString = () => sum;return add_back;}console.log(add_currying(1,2,3)); //6console.log(add_currying(1,2)(3,4)); //10console.log(add_currying(1,2)(3)(4,5)); //15console.log(add_currying(1)(2)(3)(4)(5)(6)); //21//打印出来会自动使用toString,即使是写var a=add_currying(1,2,3)也会自动调用此方法(默认将函数语句以字符串打出)//而为了打印出我们想要的结果我们就需要自己重写toString方法//如果不用es6的三点运算符就只能使用以前的Array.prototype.slice.call(arguments)方法
在进行窗口的resize、scroll,输入框内容校验等操作时,如果事件处理函数调用的频率无限制,会加重浏览器的负担,导致用户体验非常糟糕。
此时我们可以采用debounce(防抖)和throttle(节流)的方式来减少调用频率,同时又不影响实际效果。
函数防抖(debounce):
当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。
如下,持续触发scroll事件时,并不执行handle函数,当1000毫秒内没有触发scroll事件时,才会延时触发scroll事件。
functiondebounce(fn, wait) {
var timeout = null;
returnfunction() {
if(timeout !== null) clearTimeout(timeout);
timeout = setTimeout(fn, wait);
}
}
// 处理函数 functionhandle() {
console.log(Math.random());
}
// 滚动事件window.addEventListener('scroll', debounce(handle, 1000)); 函数节流
函数节流(throttle):
当持续触发事件时,保证一定时间段内只调用一次事件处理函数。
节流通俗解释就比如我们水龙头放水,阀门一打开,水哗哗的往下流,秉着勤俭节约的优良传统美德,我们要把水龙头关小点,最好是如我们心意按照一定规律在某个时间间隔内一滴一滴的往下滴。
如下,持续触发scroll事件时,并不立即执行handle函数,每隔1000毫秒才会执行一次handle函数。
var throttle =function(func, delay) {
var prev = Date.now();
returnfunction() {
var context = this;
var args = arguments;
var now = Date.now();
if (now - prev >= delay) {
func.apply(context, args);
prev = Date.now();
}
}
}
functionhandle() {
console.log(Math.random());
}
window.addEventListener('scroll', throttle(handle, 1000));
JS的垃圾回收机制是为了以防内存泄漏,内存泄漏的含义就是当已经不需要某块内存时这块内存还存在着,垃圾回收机制就是间歇的不定期的寻找到不再使用的变量,并释放掉它们所指向的内存。
JS中最常见的垃圾回收方式是标记清除。
工作原理:是当变量进入环境时,将这个变量标记为“进入环境”。当变量离开环境时,则将其标记为“离开环境”。标记“离开环境”的就回收内存。
工作流程:
垃圾回收器,在运行的时候会给存储在内存中的所有变量都加上标记。
去掉环境中的变量以及被环境中的变量引用的变量的标记。
再被加上标记的会被视为准备删除的变量。
垃圾回收器完成内存清除工作,销毁那些带标记的值并回收他们所占用的内存空间。
全局变量、闭包、DOM清空或删除时,事件未清除、子元素存在引用
文档对象模型或 DOM 定义了一个接口,该接口允许 JavaScript 之类的语言访问和操作 HTML 文档。元素由树中的节点表示,并且接口允许我们操纵它们。但是此接口需要付出代价,大量非常频繁的 DOM 操作会使页面速度变慢。
vue 通过在内存中实现文档结构的虚拟表示来解决此问题,其中虚拟节点(VNode)表示 DOM 树中的节点。当需要操纵时,可以在虚拟 DOM的 内存中执行计算和操作,而不是在真实 DOM 上进行操纵。这自然会更快,并且允许虚拟 DOM 算法计算出最优化的方式来更新实际 DOM 结构。
一旦计算出,就将其应用于实际的 DOM 树,这就提高了性能,这就是为什么基于虚拟 DOM 的框架(例如 Vue 和 react)如此突出的原因。
所谓异步,就是向服务器发送请求的时候,我们不必等待结果,而是可以同时做其他的事情,等到有了结果它自己会根据设定进行后续操作,与此同时,页面是不会发生整页刷新的,提高了用户体验。
创建Ajax的过程:
1) 创建XMLHttpRequest对象(异步调用对象)
var xhr = new XMLHttpRequest();
2) 创建新的Http请求(方法、URL、是否异步)
xhr.open(‘get’,’example.php’,false);
3) 设置响应HTTP请求状态变化的函数。
onreadystatechange事件中readyState属性等于4。响应的HTTP状态为200(OK)或者304(Not Modified)。
4) 发送http请求
xhr.send(data);
5) 获取异步调用返回的数据
注意:
1) 页面初次加载时,尽量在web服务器一次性输出所有相关的数据,只在页面加载完成之后,用户进行操作时采用ajax进行交互。
2) 同步ajax在IE上会产生页面假死的问题。所以建议采用异步ajax。
3) 尽量减少ajax请求次数
4) ajax安全问题,对于敏感数据在服务器端处理,避免在客户端处理过滤。对于关键业务逻辑代码也必须放在服务器端处理。
functionajax() { //创建一个 XHR 对象 let oAjax = window.XMLHttpRequest ? (new XMLHttpRequest()) : (new window.ActiveXobject('Microsoft.XMLHTTP')); //返回一个函数,这是函数柯里化操作,不用每次调用 ajax 都判断浏览器环境 //但是会占用更多的内存,因为总是会保存外部函数的作用域 returnfunction(url, fnSucc, fnFaild) { //只要 XHR 对象的 readyState 属性的值发生改变,就触发这个事件 oAjax.onreadystatechange = function() { // readyState 属性是 0-4 的值,当为 4 时,表示已经接收到全部响应数据,并可以在客户端使用 if (oAjax.readyState === 4) { //响应的 HTTP 状态 let s = oAjax.status; if (s === 200 || s === 206 || s === 304) { //将响应主体被返回的文本作为参数传给这个函数,并执行这个函数 if (fnSucc) fnSucc(oAjax.responseText); } else { if (fnFaild) fnFaild(oAjax.status); } } }; //启动一个请求,准备发送 oAjax.open('GET', url, true); //发送请求 oAjax.send(null); }}
最直观的区别就是GET把参数包含在URL中,POST通过request body传递参数。
GET在浏览器回退时是无害的,而POST会再次提交请求。
GET产生的URL地址可以被Bookmark,而POST不可以。
GET请求会被浏览器主动cache,而POST不会,除非手动设置。
GET请求只能进行url编码,而POST支持多种编码方式。
GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
GET请求在URL中传送的参数是有长度限制的,而POST么有。
对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
GET参数通过URL传递,POST放在Request body中。
Object.hasOwnProperty(proName):是用来判断一个对象是否有你给出名称的属性。不过需要注意的是,此方法无法检查该对象的原型链中是否具有该属性,该属性必须是对象本身的一个成员。
JS的延迟加载有助与提高页面的加载速度。
defer和async、动态创建DOM方式(用得最多)、按需异步载入JS
defer:延迟脚本。立即下载,但延迟执行(延迟到整个页面都解析完毕后再运行),按照脚本出现的先后顺序执行。
async:异步脚本。下载完立即执行,但不保证按照脚本出现的先后顺序执行。
同步的概念在操作系统中:不同进程协同完成某项工作而先后次序调整(通过阻塞、唤醒等方式),同步强调的是顺序性,谁先谁后。异步不存在顺序性。
同步:浏览器访问服务器,用户看到页面刷新,重新发请求,等请求完,页面刷新,新内容出现,用户看到新内容之后进行下一步操作。
异步:浏览器访问服务器请求,用户正常操作,浏览器在后端进行请求。等请求完,页面不刷新,新内容也会出现,用户看到新内容。
若请求的资源编码,如外引js文件编码与页面编码不同。可根据外引资源编码方式定义为 charset="utf-8"或"gbk"。
比如:http://www.fly63.com/a.html 中嵌入了一个http://www.fly63.com/test.js
a.html 的编码是gbk或gb2312的。 而引入的js编码为utf-8的 ,那就需要在引入的时候
模块化开发指的是在解决某一个复杂问题或者一系列问题时,依照一种分类的思维把问题进行系统性的分解。模块化是一种将复杂系统分解为代码结构更合理,可维护性更高的可管理的模块方式。对于软件行业:系统被分解为一组高内聚,低耦合的模块。
(1)定义封装的模块
(2)定义新模块对其他模块的依赖
(3)可对其他模块的引入支持。在JavaScript中出现了一些非传统模块开发方式的规范。 CommonJS的模块规范,AMD(Asynchronous Module Definition),CMD(Common Module Definition)等。AMD是异步模块定义,所有的模块将被异步加载,模块加载不影响后边语句运行。
AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。区别:
1) 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。
2) CMD 推崇依赖就近,AMD 推崇依赖前置。
3) AMD 的 API 默认是一个当多个用,CMD 的 API 严格区分,推崇职责单一。
当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(reflow)。每个页面至少需要一次回流,就是在页面第一次加载的时候,这时候是一定会发生回流的,因为要构建render tree。在回流的时候,浏览器会使渲染树中受到影响的部分失效,并重新构造这部分渲染树,完成回流后,浏览器会重新绘制受影响的部分到屏幕中,该过程成为重绘。
当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。则就叫称为重绘。
区别:
回流必将引起重绘,而重绘不一定会引起回流。比如:只有颜色改变的时候就只会发生重绘而不会引起回流
当页面布局和几何属性改变时就需要回流,比如:添加或者删除可见的DOM元素,元素位置改变,元素尺寸改变——边距、填充、边框、宽度和高度,内容改变
可视区域距离页面顶部的距离
var scrollTop=document.documentElement.scrollTop||document.body.scrollTop
(1)innerXXX(不兼容ie)
window.innerHeight 可视区高度,包含滚动条宽度
window.innerWidth 可视区宽度,包含滚动条宽度
(2)document.documentElement.clientXXX(兼容ie)
document.documentElement.clientWidth可视区宽度,不包含滚动条宽度
document.documentElement.clientHeight可视区高度,不包含滚动条宽度
(1)元素节点:nodeType ===1;
(2)文本节点:nodeType ===3;
(3)属性节点:nodeType ===2;
innerHTML(元素内包含的内容)
outerHTML(自己以及元素内的内容)
document.write 将内容写入页面,清空替换掉原来的内容,会导致重绘
document.innerHTML 将内容写入某个Dom节点,不会重绘
(1)offsetWidth (content宽度+padding宽度+border宽度)
(2)offsetHeight(content高度+padding高度+border高度)
(3)clientWidth(content宽度+padding宽度)
(4)clientHeight(content高度+padding高度)
(1)创建新节点
createDocumentFragment() //创建一个DOM片段
createElement() //创建一个具体的元素
createTextNode() //创建一个文本节点
(2)添加、移除、替换、插入
appendChild()
removeChild()
replaceChild()
insertBefore() //在已有的子节点前插入一个新的子节点
(3)查找
getElementsByTagName() //通过标签名称
getElementsByName() //通过元素的Name属性的值(IE容错能力较强,会得到一个数组,其中包括id等于name值的)
getElementById() //通过元素Id,唯一性
BOM全称Browser Object Model,即浏览器对象模型,主要处理浏览器窗口和框架。
DOM全称Document Object Model,即文档对象模型,是 HTML 和XML 的应用程序接口(API),遵循W3C 的标准,所有浏览器公共遵守的标准。
JS是通过访问BOM(Browser Object Model)对象来访问、控制、修改客户端(浏览器),由于BOM的window包含了document,window对象的属性和方法是直接可以使用而且被感知的,因此可以直接使用window对象的document属性,通过document属性就可以访问、检索、修改XHTML文档内容与结构。因为document对象又是DOM的根节点。
可以说,BOM包含了DOM(对象),浏览器提供出来给予访问的是BOM对象,从BOM对象再访问到DOM对象,从而js可以操作浏览器以及浏览器读取到的文档。
pop、push、shift、unshift、splice、reverse、sort、concat、join、slice、toString、indexOf、lastIndexOf、reduce、reduceRight、forEach、map、filter、every、some
pop:删除并返回数组最后一个元素(改变原数组);
push:返回添加完成后的数组的长度(改变原数组);
shift:移除并返回数组的第一个元素(改变原数组);
unshift:在数组头部插入一个元素
slice:slice(下标,个数)返回裁剪后的数组(不改变原数组);
splice:插入,删除或替换数组的元素
concat:合并数组返回组合数组(不改变原数组);
join:将数组用标识符链接成字符串返回拼接好的字符串(不改变原数组);
reverse:翻转数组(改变原数组);
toString:将数组转换成一个字符串;
split:把字符串分割开,以数组方式储存;
forEach:主要用于遍历数组;
every:主要用于检查数组中每个元素是否符合函数的条件,如果其中有一个不符合,则返回false;
indexOf:主要用于在数组中查找元素,并把元素的位置返回来。
charAt():根据下标找到对应值
charCodeAt():通过下标值找到对应字符Unicode编码
indexOf():通过字符查找对应下标(首次出现)
lastIndexOf():通过字符找最后一次出现的下标值
slice():截取字符串,2个参数,(起始位置,结束位置)
split():把字符串按分隔符分割成数组
substring():截取字符串,(起始位置,结束位置)
substr():截取指定位置和长度的字符串,(起始位置,长度)
toLowerCase():字符串转为小写
toUpperCase():字符串转成大写
trim():去掉字符串前后所有空格
话不多说,来看第一个例子:
var arr=[0,1,2,3,4,5,6,7,8,9];//设置一个数组console.log(arr.slice(2,7));//2,3,4,5,6console.log(arr.splice(2,7));//2,3,4,5,6,7,8//由此我们简单推测数量两个函数参数的意义,
slice(start,end)第一个参数表示开始位置,第二个表示截取到的位置(不包含该位置)
splice(start,length)第一个参数开始位置,第二个参数截取长度
接着看第二个:
var x=y=[0,1,2,3,4,5,6,7,8,9]
console.log(x.slice(2,5));//2,3,4console.log(x);[0,1,2,3,4,5,6,7,8,9]原数组并未改变
//接下来用同样方式测试spliceconsole.log(y.splice(2,5));//2,3,4,5,6console.log(y);//[0,1,7,8,9]显示原数组中的数值被剔除掉了
slice和splice虽然都是对于数组对象进行截取,但是二者还是存在明显区别,函数参数上slice和splice第一个参数都是截取开始位置,slice第二个参数是截取的结束位置(不包含),而splice第二个参数(表示这个从开始位置截取的长度),slice不会对原数组产生变化,而splice会直接剔除原数组中的截取数据!
咱们可以使用object.property_name = value向对象添加属性,delete object.property_name 用于删除属性。
例如:
let user = newObject();
// adding a property
user.name='Anil';
user.age =25;
console.log(user);
delete user.age;
console.log(user);
预加载:常用的是new Image();,设置其src来实现预载,再使用onload方法回调预载完成事件。
functionloadImage(url, callback){ var img = new Image(); //创建一个Image对象,实现图片预下载 img.src = url;if (img.complete){ // 如果图片已经存在于浏览器缓存,直接调用回调函数 callback.call(img);return; // 直接返回,不用再处理onload事件 } img.onload = function(){ //图片下载完毕时异步调用callback函数。 callback.call(img);//将回调函数的this替换为Image对象 ,如果你直接用img.width的时候,图片还没有完全下载下来 };
}
懒加载:主要目的是作为服务器前端的优化,缓解服务器前端压力,一次性请求次数减少或延迟请求。实现方式:
1.第一种是纯粹的延迟加载,使用setTimeOut、setInterval进行加载延迟.
2.第二种是条件加载,符合某些条件,或触发了某些事件才开始异步下载。
3.第三种是可视区加载,即仅加载用户可以看到的区域,这个主要由监控滚动条来实现,一般会在距用户看到某图片前一定距离遍开始加载,这样能保证用户拉下时正好能看到图片。
回调函数是作为参数或选项传递给某个方法的普通JS函数。它是一个函数,在另一个函数完成执行后执行,因此称为回调。在JS中,函数是对象,因此,函数可以接受函数作为参数,并且可以由其他函数返回。
宿主对象:这些是运行环境提供的对象。这意味着它们在不同的环境下是不同的。例如,浏览器包含像windows这样的对象,但是Node.js环境提供像Node List这样的对象。
原生对象:这些是JS中的内置对象。它们也被称为全局对象,因为如果使用JS,内置对象不受是运行环境影响。
渐进增强:针对低版本浏览器进行构建页面,保证最基本的功能,然后再针对高级浏览器进行效果、交互等改进,达到更好的用户体验。
优雅降级:一开始就构建完整的功能,然后再针对低版本浏览器进行兼容。
web socket:在一个单独的持久连接上提供全双工、双向的通信。使用自定义的协议(ws://、wss://),同源策略对web socket不适用。
web worker:运行在后台的JavaScript,不影响页面的性能。
创建worker:var worker = new Worker(url);
向worker发送数据:worker.postMessage(data);
接收worker返回的数据:worker.onmessage
终止一个worker的执行:worker.terminate();
JS引擎线程:解释执行JS代码、用户输入、网络请求等
GUI线程(渲染线程):绘制用户界面、与JS主线程互斥
HTTP网络请求线程:处理用户的GET、POST等请求,等拿到返回结果后,将回调函数推入事件队列
定时器触发线程:setTimeout、setInterval等待时间结束后,将执行函数推入事件队列中
事件处理线程:将click、mouse、input等交互事件发生后,将事件处理函数推入事件队列中
JavaScript 语言的一大特点就是单线程,同一个时间只能做一件事。单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。JavaScript 语言的设计者意识到这个问题,将所有任务分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous),在所有同步任务执行完之前,任何的异步任务是不会执行的。
当我们打开网站时,网页的渲染过程就是一大堆同步任务,比如页面骨架和页面元素的渲染。而像加载图片音乐之类占用资源大耗时久的任务,就是异步任务。
JS 异步有一个机制,就是遇到宏任务,先执行宏任务,将宏任务放入 Event Queue,然后再执行微任务,将微任务放入 Event Queue,但是,这两个 Queue 不是一个 Queue。当你往外拿的时候先从微任务里拿这个回调函数,然后再从宏任务的 Queue 拿宏任务的回调函数。
宏任务:整体代码 script,setTimeout,setInterval
微任务:Promise,process.nextTick
(1)js是单线程的,但是分同步异步
(2)微任务和宏任务皆为异步任务,它们都属于一个队列
(3)宏任务一般是:script,setTimeout,setInterval、setImmediate
(4)微任务:原生Promise
(5)遇到微任务,先执行微任务,执行完后如果没有微任务,就执行下一个宏任务,如果有微任务,就按顺序一个一个执行微任务
优化图片
图像格式的选择(GIF:提供的颜色较少,可用在一些对颜色要求不高的地方)
优化css(压缩合并css,如margin-top,margin-left...)
网址后加斜杠(如www.campr.com/目录,会判断这个“目录是什么文件类型,或者是目录。)
标明高度和宽度(如果浏览器没有找到这两个参数,它需要一边下载图片一边计算大小,如果图片很多,浏览器需要不断地调整页面。这不但影响速度,也影响浏览体验。当浏览器知道了高度和宽度参数后,即使图片暂时无法显示,页面上也会腾出图片的空位,然后继续加载后面的内容。从而加载时间快了,浏览体验也更好了。)
减少http请求(合并文件,合并图片)。
一个程序至少有一个进程,一个进程至少有一个线程。线程的划分尺度小于进程,使得多线程程序的并发性高。
另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。
1.去掉或样式丢失的时候能让页面呈现清晰的结构:html本身是没有表现的,我们看到例如
2.屏幕阅读器(如果访客有视障)会完全根据你的标记来“读”你的网页。
3.PDA、手机等设备可能无法像普通电脑的浏览器一样来渲染网页(通常是因为这些设备对CSS的支持较弱)。
4.有利于SEO:和搜索引擎建立良好沟通,有助于爬虫抓取更多的有效信息:爬虫依赖于标签来确定上下文和各个关键字的权重。
5.便于团队开发和维护,语义化更具可读性,是下一步吧网页的重要动向,遵循W3C标准的团队都遵循这个标准,可以减少差异化。
1.CDN缓存更方便
2.突破浏览器并发限制(一般每个域名建立的链接不超过6个)
3.Cookieless,节省带宽,尤其是上行带宽一般比下行要慢
4.对于UGC的内容和主站隔离,防止不必要的安全问题(上传js窃取主站cookie之类的)。正是这个原因要求用户内容的域名必须不是自己主站的子域名,而是一个完全独立的第三方域名。
5.数据做了划分,甚至切到了不同的物理集群,通过子域名来分流比较省事。这个可能被用的不多。
PS:关于Cookie的问题,带宽是次要的,安全隔离才是主要的。关于多域名,也不是越多越好,虽然服务器端可以做泛解释,浏览器做dns解释也是耗时间的,而且太多域名,如果要走https的话,还有要多买证书和部署的问题。
对内:模块模式;对外:继承
代码重用
避免全局变量(命名空间,封闭空间,模块化mvc…)
拆分函数避免函数过于臃肿
注释
模块化:可复用,侧重的功能的封装,主要是针对Javascript代码,隔离、组织复制的javascript代码,将它封装成一个个具有特定功能的的模块。
组件化:可复用,更多关注的UI部分,页面的每个部件,比如头部,弹出框甚至确认按钮都可以成为一个组件,每个组件有独立的HTML、css、js代码。
定义:浏览器缓存(Browser Caching)是为了加速浏览,浏览器在用户磁盘上对最近请求过的文档进行存储,当访问者再次请求这个页面时,浏览器就可以从本地磁盘显示文档,这样就可以加速页面的阅览。
cache的作用:
1、减少延迟,让你的网站更快,提高用户体验。
2、避免网络拥塞,减少请求量,减少输出带宽。
实现手段:
Cache-Control中的max-age是实现内容cache的主要手段,共有3种常用策略:max-age和Last-Modified(If-Modified-Since)的组合、仅max-age、max-age和ETag的组合。
对于强制缓存,服务器通知浏览器一个缓存时间,在缓存时间内,下次请求,直接用缓存,不在时间内,执行比较缓存策略。
对于比较缓存,将缓存信息中的Etag和Last-Modified通过请求发送给服务器,由服务器校验,返回304状态码时,浏览器直接使用缓存。
400 客户端请求有语法错误,不能被服务器所理解
403 服务器收到请求但是拒绝提供服务|
200 客户端请求成功|
404 请求资源不存在 eg:输入错误的URL |
500 服务器发生不可预期的错误 |
503 服务器当前不能处理客户端请求,一段时间后可能恢复正常
JS有三类的错误:
加载时错误:加载web页面时出现的错误(如语法错误)称为加载时错误,它会动态生成错误。
运行时错误:由于滥用html语言中的命令而导致的错误。
逻辑错误:这些错误是由于对具有不同操作的函数执行了错误的逻辑而导致的
设计模式是软件设计中常见问题的通用可重用解决方案,以下是一些设计模式是:
创建模式:该模式抽象了对象实例化过程。
结构型模式:这些模式处理不同的类和对象以提供新功能。
行为模式:也称发布-订阅模式,定义了一个被观察者和多个观察者的、一对多的对象关系。
并行设计模式:这些模式处理多线程编程范例。
架构设计模式:这些模式用于处理架构设计。
const let
模板字符串
箭头函数
函数的参数默认值
对象和数组解构
for...of 和 for...in
箭头函数是在es6或更高版本中编写函数表达式的简明方法。箭头函数不能用作构造函数,也不支持this,arguments,super或new.target关键字,它最适合非方法函数。 通常,箭头函数看起来像 const function_name =()=> {}。
const greet=()=>{console.log('hello');}
greet();
1、普通函数
可以通过bind、call、apply改变this指向
可以使用new
2、箭头函数
本身没有this指向,
它的this在定义的时候继承自外层第一个普通函数的this
被继承的普通函数的this指向改变,箭头函数的this指向会跟着改变
箭头函数外层没有普通函数时,this指向window
不能通过bind、call、apply改变this指向
使用new调用箭头函数会报错,因为箭头函数没有constructor
promise是js中的一个对象,用于生成可能在将来产生结果的值。 值可以是已解析的值,也可以是说明为什么未解析该值的原因。
promise 可以有三种状态:
pending:初始状态,既不是成功也不是失败
fulfilled:意味着操作完全成功
rejected:意味着操作失败
一个等待状态的promise对象能够成功后返回一个值,也能失败后带回一个错误,当这两种情况发生的时候,处理函数会排队执行通过then方法会被调用。
resolve和reject分别是两个函数,当在回调中调用时,会改变promise实例的状态,resolve改变状态为成功,reject为失败。
functionPromise(exector) { let _this = this; //status表示一种状态 let status = “pending“; let value = undefined; let reason = undefined; //成功 functionresolve(value) { if (status == “pending“) { _this.value = value; _this.status = “resolve“; } } //执行失败 functionreject(value) { if (status == “pending“) { _this.value = value; _this.status = “reject“ } } //异步操作 try { exector(resolve, reject) } catch (e) { reject(e) } //测试then Promise.prototype.then = function(reject, resolve) { let _this = this; if (this.status == “resolve“) { reject(_this.value) } if (this.status == “reject“) { resolve(_this.reason) } }} //new Promise测试let promise = new Promise((reject,resolve)=>{ resolve(“return resolve“);});promise.then(data => { console.log(`success${data}`);}, err => { console.log(`err${data}`);})
1、什么是Async/Await
async/await是写异步代码的新方式,使用的方式看起来像同步
async/await是基于Promise实现的,它不能用于普通的回调函数。
2、什么是promise
为了解决异步嵌套而产生,让代码更易于理解
区别:async/await让代码更像同步,进一步优化了代码
(1)回调函数(异步回调)
回调是一个函数被作为一个参数传递到另一个函数里,在那个函数执行完后再执行。
(2)promise
promise对象是commonJS工作组提出的一种规范,一种模式,目的是为了异步编程提供统一接口。
(3)async/await
(4)事件监听
采用事件驱动模式。任务的执行不取决于代码的顺序,而取决于某个事件是否发生。
(5)发布/订阅
module和exports是Node.js给每个js文件内置的两个对象。可以通过console.log(module)和console.log(exports)打印出来。如果你在main.js中写入下面两行,然后运行$ node main.js:
console.log(exports);//输出:{}console.log(module);//输出:Module {..., exports: {}, ...} (注:...代表省略了其他一些属性)
从打印咱们可以看出,module.exports和exports一开始都是一个空对象{},实际上,这两个对象指向同一块内存。这也就是说module.exports和exports是等价的(有个前提:不去改变它们指向的内存地址)。
例如:exports.age = 18和module.export.age = 18,这两种写法是一致的(都相当于给最初的空对象{}添加了一个属性,通过require得到的就是{age: 18})。
import和exports 帮助咱们编写模块化的JS代码。使用import和exports,咱们可以将代码分割成多个文件。import只允许获取文件的某些特定变量或方法。可以导入模块导出的方法或变量。
//index.jsimport name,age from'./person';
console.log(name);
console.log(age);
//person.jslet name ='Sharad', occupation='developer', age =26;
export { name, age};
1、for...in 循环:只能获得对象的键名,不能获得键值;for...of 循环:允许遍历获得键值
2、对于普通对象,没有部署原生的 iterator 接口,直接使用 for...of 会报错,列如:
var obj = {'name':'Jim Green','age': 12 }for(let key of obj) {console.log('for of obj', key) } // Uncaught TypeError: obj is not iterable
3、for...in 循环不仅遍历数字键名,还会遍历手动添加的其它键,甚至包括原型链上的键。for...of 则不会这样,列如:
letarr= [1, 2, 3]arr.set='world'//手动添加的键Array.prototype.name='hello'//原型链上的键for(letiteminarr) {console.log('item', item)}/*item0item1item2itemsetitemname*/for(letvalueofarr) {console.log('value', value)}/*value1value2value3*/
4、forEach 循环无法中途跳出,break 命令或 return 命令都不能奏效;for...of 循环可以与break、continue 和 return 配合使用,跳出循环
5、无论是 for...in 还是 for...of 都不能遍历出 Symbol 类型的值,遍历 Symbol 类型的值需要用 Object.getOwnPropertySymbols() 方法
总之,for...in 循环主要是为了遍历对象而生,不适用于遍历数组;for...of 循环可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象
Proxy 也就是代理,可以帮助我们完成很多事情,例如对数据的处理,对构造函数的处理,对数据的验证,说白了,就是在我们访问对象前添加了一层拦截,可以过滤很多操作,而这些过滤,由你来定义。语法如下
let p = newProxy(target, handler);
参数
target :需要使用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
handler: 一个对象,其属性是当执行一个操作时定义代理的行为的函数(可以理解为某种触发器)。具体的handler相关函数
vue3.0使用了Proxy替换了原先遍历对象使用Object.defineProperty方法给属性添加set,get访问器的笨拙做法。
handler解析
关于handler拦截属性,有如下:
get(target,propKey,receiver):拦截对象属性的读取, 依次为目标对象、属性名和 proxy 实例本身,最后一个参数可选
set(target,propKey,value,receiver):拦截对象属性的设置
has(target,propKey):拦截propKey in proxy的操作,返回一个布尔值
deleteProperty(target,propKey):拦截delete proxy[propKey]的操作,返回一个布尔值
ownKeys(target):拦截Object.keys(proxy)、for...in等循环,返回一个数组
getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象
defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc),返回一个布尔值
preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值
getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象
isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值
setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值
apply(target, object, args):拦截 Proxy 实例作为函数调用的操作
construct(target, args):拦截 Proxy 实例作为构造函数调用的操作
Reflect
若需要在Proxy内部调用对象的默认行为,建议使用Reflect,其是ES6中操作对象而提供的新 API
基本特点:只要Proxy对象具有的代理方法,Reflect对象全部具有,以静态方法的形式存在,修改某些Object方法的返回结果,让其变得更合理(定义不存在属性行为的时候不报错而是返回false)
Generator函数可以说是Iterator接口的具体实现方式。Generator 最大的特点就是可以控制函数的执行。
语法上,Generator 函数是一个状态机,封装了多个内部状态。
形式上,Generator是一个函数。不同于普通函数,是可以暂停执行的,所以函数名之前要加星号,以示区别。
整个Generator函数就是一个封装的异步任务,或者说是异步任务的容器,异步操作需要暂停的地方,都用yield语句。
function*foo(x) {lety=2*(yield(x+1))letz=yield(y/3)return(x+y+z)}letit=foo(5)console.log(it.next())//=> {value:6, done:false}console.log(it.next(12))//=> {value:8, done:false}console.log(it.next(13))//=> {value:42, done:true}