1 纯JS获取scrolltop值
document.body 和 document.documentElement 区别:
- document.body 返回的是 body节点 即<body>
- document.documentElement 返回的是 html 节点 即<html>
在chrome中 获取 scrollTop 只能用 docume.body.scrollTop;
在IE中 没有doctype声明 使用 document.body.scrollTop;有doctype声明的使用document.documentElement.scrollTop;
SO... 兼容性写法 :
const scrollTop = document.documentElement.scrollTop || docume.body.scrollTop
2 详述JS闭包原理和意义
闭包是指有权访问另一个函数作用域中的变量和函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。
举个例子
1 var name = "visiliki" 2 function user() { 3 function getName () { 4 console.log(name) 5 } 6 getName() 7 } 8 user()
在 getName 函数中获取 name,首先在 getName 函数的作用域中查找 name,未找到,进而在 user 函数的作用域中查找,同样未找到,继续向上回溯,发现在全局作用域中存在 name,因此获取 name 值并打印。
这里很好理解,即变量都存在在指定的作用域中,如果在当前作用中找不到想要的变量,则通过作用域链向在父作用域中继续查找,直到找到第一个同名的变量为止(或找不到,抛出 ReferenceError 错误)。
这是 js 中作用域链的概念,即子作用域可以根据作用域链访问父作用域中的变量,那如果相反呢,在父作用域想访问子作用域中的变量呢?——这就需要通过闭包来实现。
闭包就是跨作用域访问变量 —— 内部作用域可以保持对外部作用域中变量的引用从而使得(更)外部作用域可以访问内部作用域中的变量。
为什么需要闭包?
局部变量无法共享和长久的保存,而全局变量可能造成变量污染,所以我们希望有一种机制既可以长久的保存变量又不会造成全局污染。
特点
占用更多内存
不容易被释放
何时使用?
变量既想反复使用,又想避免全局污染
如何使用?
定义外层函数,封装被保护的局部变量。
定义内层函数,执行对外部函数变量的操作。
外层函数返回内层函数的对象,并且外层函数被调用,结果保存在一个全局的变量中。
3. 深拷贝浅拷贝是什么
深浅拷贝的区别只适用于array【数组】与object【对象】
浅拷贝
相当于使两个数组指针指向相同的地址,任一个数组元素发生改变,影响另一个。
注意:当内存销毁的时候,只想对象的指针,必须重新定义,才能够使用
代码如下:
1 var a = {x:0} 2 var b = a 3 console.log(b) //{x:0} 4 b.x = 3 5 console.log(b) //{x:3} 6 console.log(a) //{x:3}
浅拷贝是一个传址,也就是把a的值赋给b的时候同时也把a的址赋给了b,当b(a)的值改变的时候,a(b)的值同时也会改变
深拷贝
两数组指针指向不同的地址,数组元素发生改变时不会相互影响
深拷贝的几种方法
JSON内置方法
1 var a = {x:0} 2 var b=JSON.parse(JSON.stringify(a)) 3 console.log(b) //{x:0} 4 b.x = 1 5 console.log(b) //{x:1} 6 console.log(a) //{x:0}
该方法是用json.parse()将对象转化为字符串,然后再用json.stringify()转回对象json字符串转换为对象的时候,会自己去构建新的内存地址存放数据
递归实现
1 function copy(obj) { 2 //首先确定递归的回调,最终达到对象或者数组的末端,达到深拷贝的要求 3 var newObj; 4 if (obj instanceof Array) { //确定类型 5 newObj = []; //创建一个空的数组 6 var i = obj.length; 7 while (i--) { 8 newObj[i] = copy(obj[i]); //递归回调 9 } 10 return newObj 11 } else if (obj instanceof Object) { //确定类型 12 newObj = {}; //创建一个空的对象 13 for (var k in obj) { //为这个对象添加一个新的属性 14 newObj[k] = copy(newObj[k]) //递归回调 15 } 16 return newObj //结束函数完成深拷贝 17 } else { 18 return obj //结束函数完成深拷贝 19 } 20 }
4. ES6的作用域
块级作用域let
作用域是一个变量的有效范围,也就是声明一个变量以后,这个变量在什么场合可以使用。以前的JS只有全局作用
域,还有一个函数作用域。
1 var name = 'visiliki'; 2 while (true) { 3 var name = 'violet'; 4 console.log(name) //violet 5 break 6 } 7 console.log(name) //violet 8 let name = 'soybean'; 9 while (true) { 10 let name = 'milk'; 11 console.log(name); //milk 12 break 13 } 14 console.log(name) //soybean
常量const
使用const可以声明一个常量,一旦声明,常量的值就不能改变
const PI = Math.PI PI = 23 //Identifier 'PI' has already been declared
注意,const限制的是常量分配值,而这个常量的值又是不可更改的。
5. 详述promise异步机制
promise是异步编程的一种解决方案,
promise是一个JS内置对象,这个对象接受一个函数作为参数,函数中又会传入两个函数,第一个是成功后调用的函数,第二个是失败后调用的函数。
1 function Promise(executor){ //executor执行器 2 let self = this; 3 self.status = 'pending'; //等待态 4 self.value = undefined; // 表示当前成功的值 5 self.reason = undefined; // 表示是失败的值 6 function resolve(value){ // 成功的方法 7 if(self.status === 'pending'){ 8 self.status = 'resolved'; 9 self.value = value; 10 } 11 } 12 function reject(reason){ //失败的方法 13 if(self.status === 'pending'){ 14 self.status = 'rejected'; 15 self.reason = reason; 16 } 17 } 18 executor(resolve,reject); 19 } 20 21 Promise.prototype.then = function(onFufiled,onRejected){ 22 let self = this; 23 if(self.status === 'resolved'){ 24 onFufiled(self.value); 25 } 26 if(self.status === 'rejected'){ 27 onRejected(self.reason); 28 } 29 } 30 module.exports = Promise;
cted 可以理解为失败的状态
- 构造一个promise实例需要给promise构造函数传入一个参数,传入的参数需要两个形参,两个形参都是function类型的函数,分别是resolve和reject
- promise上还有then方法,then方法就是用来指定promise对象的状态改变时确定执行的操作,resolve时执行第一个函数(onFulfilled),reject时执行第二个函数(onRejected)
- 当状态变为resolve时便不能再变为reject,反之同理
6.如何实现跨域访问
什么叫做跨域
默认情况下,XHR对象只能访问与包含它的页面位于同一个域中的资源,当协议,域名,端口有任何一个不同,都会被当作是不同的域。
对于端口和协议的不同,只能通过后台来解决,前端要解决的是域名不用的问题。
如何跨域
- Cors(跨源资源共享)
使用自定义的HTTP 头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。
实现此功能非常简单,只需由服务器发送一个响应标头即可。
CORS的兼容性写法:
function createCORSRequest(method, url){ var xhr = new XMLHttpRequest(); //非IE浏览器 if ("withCredentials" in xhr){ xhr.open(method, url, true); //IE浏览器 } else if (typeof XDomainRequest != "undefined"){ vxhr = new XDomainRequest(); xhr.open(method, url); } else { xhr = null; } return xhr; } var request = createCORSRequest("get", "http://www.somewhere-else.com/page/"); if (request){ request.onload = function(){ //对request.responseText 进行处理 }; request.send(); }
2 JSONP()
在js中,我们虽然不萌直接用XMLHttpRequest请求不同域上的数据时,但是在页面上引用不同域上的js脚本文件却是可以的,jsonp就是根据这个特性来实现的
jsonp由两个部分组成,回调函数和数据。回调函数时当响应来时应该在页面中调用的函数,而数据就是传入回调函数中的JSON数据。
1 <script type="text/javascript"> 2 function dosomething(jsondata){ 3 //处理获得的json数据 4 } 5 </script> 6 <script src="http://example.com/data.php?callback=dosomething"></script>
第一个script标签定义可一个处理数据的函数,第二个script载入了一个js文件,http://example.com/data.php是一个数据所在的地址,但是因为时当作js引入的,所以http://example.com/data.php返回的必须是一个能够执行的js文件,最后载入成功后会执行我们在url参数中指定的函数,并且会把我们需要的json数据作为参数引入。
JQuery支持jsonp的实现方式 (只能够实现get请求)
1 $.ajax({ 2 url:'http://www.baidu.com/login' , 3 type: 'Get' , 4 dataType:'jsonp', //请求方式为jsonp 5 jsonpCallback: 'callback', 6 data:{ 7 'username': 'visiliki' 8 } 9 })
3. window.name
window对象有个name
属性,该属性有个特征:
即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限。
window.name是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。
这里有三个页面:
a.com/app.html:应用页面。
a.com/proxy.html:代理文件,一般是一个没有任何内容的html文件,需要和应用页面在同一域下。
b.com/data.html:应用页面需要获取数据的页面,可称为数据页面。
app.html
1 <iframe src="b.com/data.html" id="iframe"></iframe> 2 <script> 3 var iframe = document.getElementById("iframe"); 4 iframe.src = "a.com/proxy.html";//这是一个与a.com/app.html同源的页面 5 iframe.onload = function(){ 6 var data = iframe.contentWindow.name; //取到数据 7 } 8 9 </script>
data.html
1 <script> 2 // 这里是要传输的数据,大小一般为2M,IE和firefox下可以大至32M左右 3 // 数据格式可以自定义,如json、字符串 4 window.name = "数据" 5 </script>
iframe首先的地址是b.com/data.html,所以能取到window.name
数据;
但是iframe现在跟app.html并不同源,app.html无法获取到数据,所以又将iframe的链接跳转至a.com/proxy.html这个代理页面,现在app.html跟iframe就同源了。
注意:iframe由b.com/data.html跳转到a.com/proxy.html页面,window.name
的value是不变的
获取数据以后销毁这个iframe,释放内存;这也保证了安全(不被其他域frame js访问)
1 <script type="text/javascript">
2 iframe.contentWindow.document.write('');
3 iframe.contentWindow.close();
4 document.body.removeChild(iframe);
5 </script>
4. document.domain + iframe
对于主域相同而子域不同的例子,可以通过设置document.domain的办法来解决。
具体的做法是可以在http://www.a.com/a.html 和http://script.a.com/b.html 两个文件中分别设置document.domain = 'a.com'
然后通过a.html文件中创建一个iframe,去控制iframe的contentDocument,这样两个js文件之间就可以“交互”了。
http://www.a.com/a.html页面
1 <iframe src="http://script.a.com/b.html" frameborder="0"></iframe>
2 <script>
3 document.domain = 'a.com';
4 </script>
http://script.a.com/b.html页面
1 <script>
2 document.domain = 'a.com';
3 </script>
这样俩个页面就可以通过js相互访问各种属性和对象了。
document.domain的设置是有限制的,我们只能把document.domain设置成自身或更高一级的父域,且主域必须相同。
例如:a.b.example.com 中某个文档的document.domain 可以设成a.b.example.com、b.example.com 、example.com中的任意一个
但是不可以设成 c.a.b.example.com,因为这是当前域的子域,也不可以设成baidu.com,因为主域已经不相同了。
5. HTML5的window.postMessage
window.postMessage(message,targetOrigin) 方法是html5新引进的特性,可以使用它来向其它的window对象发送消息,无论这个window对象是属于同源或不同源。
前IE8+、FireFox、Chrome、Opera等浏览器都已经支持window.postMessage方法。
window.postMessage允许两个窗口/帧之间跨域发送数据消息。从本质上讲,window.postMessage是一个跨域的无服务器垫片的Ajax。
用法:
otherWindow.postMessage(message, targetOrigin);
otherWindow: 对接收信息页面的window的引用。可以是页面中iframe的contentWindow属性;window.+open的返回值;通过name或下标从window.frames取到的值。
message: 所要发送的数据,string类型。
targetOrigin: 用于限制otherWindow,“*”表示不作限制
数据发送端
a.com/index.html中的代码:
1 <iframe id="ifr" src="b.com/index.html"></iframe> 2 <script type="text/javascript"> 3 window.onload = function() { 4 var ifr = document.getElementById('ifr'); 5 var targetOrigin = 'http://b.com'; // 设定接收端的域,*则为不限制 6 7 ifr.contentWindow.postMessage('I was there!', targetOrigin); 8 }; 9 </script>
数据接收端
b.com/index.html中的代码:
1 <script type="text/javascript"> 2 window.addEventListener('message', function(event){ 3 // 通过origin属性判断消息来源地址 4 if (event.origin == 'http://a.com') { 5 alert(event.data); // 弹出"I was there!" 6 alert(event.source); // 对a.com、index.html中window对象的引用 7 // 但由于同源策略,这里event.source不可以访问window对象 8 } 9 }, false); 10 </script>
6.代理与反向代理
代理,也称之为正向代理,是指一个位于客户端(A)和目标服务器(B)之间的服务器(C)。
(以下写简称)为了从B取得内容,A向代理发送一个请求并且指定目标(B),然后代理向B转交请求并将获得的内容返回给A。
数据流程
请求过程: 浏览器--代理服务器--目标服务器
返回过程: 目标服务器--代理服务器--浏览器
反向代理 是指以代理服务器来接受Internet上的连接请求,然后将请求转发给内部网络上的服务器。
并将从服务器上得到的结果返回给Internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器
数据流程
请求过程: 浏览器--反向代理服务器--处理数据的服务器
返回过程: 处理数据的服务器--反向代理服务器--浏览器
比较:
从用途上说
正向代理的典型用途是为在防火墙内的局域网客户端提供访问Internet的途径,正向代理还可以使用缓冲特性减少网络的使用率。
反向代理的典型用途是为后端的多台服务器提供负载平衡,或为后端较慢的服务器提供缓冲服务
从安全性来讲:
正向代理允许客户端通过它访问任意网站并且隐藏客户端自身,因此你必须采取安全措施以确保仅为经过授权的客户端提供服务。
反向代理对外都是透明的,访问者并不知道自己访问的是一个代理。
从使用方来看
正向代理是浏览器端进行配置的,与服务器端无关,甚至可以对服务端隐藏。
反向代理是服务器端配置的,对浏览器端是透明的
利用反向代理实现跨域
反向代理需要用到nginx,其详细介绍请看这里
实现原理
原理大体相同,但是处理的端不同,反向代理实在服务器端进行处理。首先修改hosts文件,将域名指向开发者的电脑本身,把自己伪装成服务端,再通过nginx对不同的请求进行转发,把静态资源指向开发者本地电脑的资源,将接口指向实际的服务器。
代理配置
设置hosts文件,将目标域名指向本机。
编辑nginx配置,对不同的资源请求,指向到对应地址。同样的,将静态资源指向本机服务,将接口指向真正的服务器。
程序运行过程
浏览器访问页面,假设访问淘宝页面:taobao.com/index.html
taobao.com域名解析先经过hosts文件配置,发现taobao.com域名指向127.0.0.1,则向本机发起请求。
nginx接收到taobao.com/index.html请求,根据nginx的配置,将把这个请求转发给127.0.0.1:3000。
浏览器运行index.html文件,发起taobao.com/api/getNew请求
nginx接收到taobao.com/api/getNew请求请求,根据nginx的配置,将把这个请求转发给真正的淘宝服务器中。
淘宝服务器将数据返回给nginx,再返回给浏览器执行。
简单的对比
使用charles等正向代理方式比较简单,需要掌握的知识点也比较少。但相应的其可配置性较弱,仅适合中小型项目使用。
使用nginx的反向代理则相对复杂一些,需要了解基本的nginx配置。但其可配置性较强,支持URL的正则匹配,设置优先级等,适合复杂的项目使用。
利用正向代理实现跨域
实现原理
对正向代理服务器进行配置,当获取非接口数据时,让代理服务器指向开发者本机的资源。当访问接口时,访问后端接口数据。
程序运行过程
浏览器访问页面,假设访问淘宝页面:taobao.com/index.html(假设这个页面中调用了taobao.com/api/getNew获取最新商品的接口)
taobao.com/index.html请求经过代理服务器,根据配置,index.html页面请求127.0.0.1:3000
127.0.0.1:3000返回index.html文件给浏览器。
浏览器运行index.html页面,发起taobao.com/api/getNew请求。
taobao.com/api/getNew请求经过代理服务器,但由于没有对这个接口进行特殊配置,这个接口会正常访问道淘宝服务器。
淘宝服务器接受到taobao.com/api/getNew请求,检查请求头的hosts字段,发现是taobao.com,没有跨域,将结果返回给代理服务器。
代理服务器拿到结果,返回给浏览器,浏览器进行解析显示。