FLYSASA

Fair to be late, but never absent


  • 首页

  • 标签

  • 分类

  • 归档

12 同源策略及跨域

发表于 2017-12-23 | 分类于 JavaScript

题目1.什么是同源策略?

同源策略(Same origin Policy): 浏览器出于安全方面的考虑,只允许与本域下的接口交互。不同源的客户端脚本在没有明确授权的情况下,不能读写对方的资源。

本域指的是:

  • 同协议:如都是http或者https
  • 同域名:如都是http://haha.com/a 和http://haha.com/b
  • 同端口:如都是80端口(没有指定端口默认为80)

不同源案例:

  • http://zhihu.com 和 https://zhihu.com
    (协议不同)

  • http://zhihu.com和 http://zhihu.com.cn
    (域名不同,域名必须完全相同才可以)

  • http://zhihu.com 和 http://zhihu.com:8080
    (端口不同,第一个是80)

总结:如果想是同源,必须保证同协议同域名同端口,有一个不同,浏览器都认为是不同源

题目2.什么是跨域?跨域有几种实现形式?

  • 1.跨域

    跨域 : 指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对javascript施加的安全限制。

浏览器的同源策略会导致跨域,这里同源策略又分为以下两种:
1.DOM同源策略:禁止对不同源页面DOM进行操作。这里主要场景是iframe跨域的情况,不同域名的iframe是限制互相访问的。
2.XMLHttpRequest同源策略:禁止使用XHR对象向不同源的服务器地址发起HTTP请求。

跨域的严格一点的定义是:只要协议、域名、端口有任何一个的不同,就被当作是跨域。

  • 2.跨域实现形式:7种

    • 常用的七种跨域的方式,关于跨域大概可以分为 iframe 的跨域和纯粹的跨全域请求。其中:JSONP CORS Server Proxy (服务器代理)为跨全域方式,剩下4种是通过iframe跨域与其他页面通信的方式.
      1.JSONP
      2.CORS
      3.Server Proxy (服务器代理)
      4.document.domain(降域)
      5.postMessage
      6.location.hash
      7.window.name

题目3.: JSONP 的原理是什么?

JSONP(JSON with padding)
利用<script>标签没有跨域限制的“漏洞”来达到与第三方通讯的目的。
JSONP是通过 <script> 标签加载数据的方式去获取数据当做 JS 代码来执行 提前在页面上声明一个函数,函数名通过接口传参的方式传给后台,后台解析到函数名后在原始数据上「包裹」这个函数名,发送给前端。换句话说,JSONP 需要对应接口的后端的配合才能实现。

题目4: CORS是什么?

  • CORS 全称是跨域资源共享(Cross-Origin Resource Sharing),是一种 ajax 跨域请求资源的方式,支持现代浏览器,IE支持10以上。 实现方式很简单,当你使用 XMLHttpRequest 发送请求时,浏览器发现该请求不符合同源策略,会给该请求加一个请求头:Origin,后台进行一系列处理,如果确定接受请求则在返回结果中加入一个响应头:Access-Control-Allow-Origin; 浏览器判断该相应头中是否包含 Origin 的值,如果有则浏览器会处理响应,我们就可以拿到响应数据,如果不包含浏览器直接驳回,这时我们无法拿到响应数据。所以 CORS 的表象是让你觉得它与同源的 ajax 请求没啥区别,代码完全一样。
  • 整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

    实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

浏览器将CORS请求分为两类:简单请求和非简单请求

简单请求:

  • 请求方法是以下三种方法之一:
    • HEAD
    • GET
    • POST
  • HTTP的头信息不超出以下几种字段:
    • Accept
    • Accept-Language
    • Content-Language
    • Last-Event-ID
    • Content-Type:只限于三个值application/x-www-form-urlencoded、
      multipart/form-data、text/plain

对于简单请求,浏览器直接发出CORS请求,具体来说,就是在头信息之中,增加一个Origin字段。
示例:

1
2
3
4
5
6
GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

上面的头信息中,Origin字段用来说明本次请求来自哪个源(协议+域名+端口),服务器根据这个值决定是否同意这次请求。
如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段,就知道出错了,从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。
如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。

1
2
3
4
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8

上面的头信息之中,有三个与CORS请求相关的字段,都以Access-Control-开头
(1)Access-Control-Allow-Origin
该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。
(2)Access-Control-Allow-Credentials
该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。
(3)Access-Control-Expose-Headers
该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader(‘FooBar’)可以返回FooBar字段的值。

非简单请求

凡是不同时满足简单请求的两大条件的请求就属于非简单请求
非简单请求指对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为”预检”请求(preflight)。
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
下面是一段浏览器的JavaScript脚本

1
2
3
4
5
var url = 'http://api.alice.com/cors';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();

上面代码中,HTTP请求的方法是PUT,并且发送一个自定义头信息X-Custom-Header。
浏览器发现,这是一个非简单请求,就自动发出一个”预检”请求,要求服务器确认可以这样请求。下面是这个”预检”请求的HTTP头信息。

1
2
3
4
5
6
7
8
OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

“预检”请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。除了Origin字段,”预检”请求的头信息包括两个特殊字段。
(1)Access-Control-Request-Method
该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT。
(2)Access-Control-Request-Headers
该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header。

服务器收到”预检”请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。

1
2
3
4
5
6
7
8
9
10
11
12
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

上面的HTTP回应中,关键的是Access-Control-Allow-Origin字段,表示http://api.bob.com可以请求数据。(该字段也可以设为星号,表示同意任意跨源请求——Access-Control-Allow-Origin: *)

如果浏览器否定了”预检”请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest对象的onerror回调函数捕获。控制台会打印出如下的报错信息。

1
2
XMLHttpRequest cannot load http://api.alice.com.
Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.

服务器回应的其他CORS相关字段如下。

1
2
3
4
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000

(1)Access-Control-Allow-Methods
该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次”预检”请求。
(2)Access-Control-Allow-Headers
如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在”预检”中请求的字段。
(3)Access-Control-Allow-Credentials
该字段与简单请求时的含义相同。
(4)Access-Control-Max-Age
该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。

一旦服务器通过了”预检”请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。
下面是”预检”请求之后,浏览器的正常CORS请求。

1
2
3
4
5
6
7
PUT /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
X-Custom-Header: value
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

上面头信息的Origin字段是浏览器自动添加的。下面是服务器正常的回应。

1
2
Access-Control-Allow-Origin: http://api.bob.com
Content-Type: text/html; charset=utf-8

上面头信息中,Access-Control-Allow-Origin字段是每次回应都必定包含的。

参考阮一峰: 跨域资源共享CORS 详解

题目5:演示以上跨域的解决方式

1.JSONP实现跨域

Web 页面上调用 js 文件不受浏览器同源策略的影响,所以通过 Script 便签可以进行跨域的请求:
1.首先前端先设置好回调函数,并将其作为 url 的参数。
2.服务端接收到请求后,通过该参数获得回调函数名,并将数据放在参数中将其返回
3.收到结果后因为是 script 标签,所以浏览器会当做是脚本进行运行,从而达到跨域获取数据的目的。

实例演示:
后端逻辑:文件名server.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//后端逻辑
var url = require('url')
var http = require('http')
http.createServer(function (req, res) {
var data = {
name: "haha"
};
var callback = url.parse(req.url,true).query.callback;
//url.parse(req.url,true).query即{callback:jsonpCallback} ----------------获取函数名callback
res.writeHead(200)
res.end(`${callback}(${JSON.stringify(data)})`)

}).listen(3000, '127.0.0.1')
//服务端接收到请求后获取到回调函数名callback,将数据放在参数中将其返回,即返回callback({"name" : "haha"}) 函数调用
console.log('监听127.0.0.1:3000端口') //注意这里文字一定要引号包裹不然会报错
//注意终端提示:SyntaxError: Invalid or unexpected token 无效或意外的标记,出现这种情况一般是中文的标点符号或者漏写了符号导致的

前端页面:文件名index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--前端页面-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JSONP实现跨域</title>
</head>
<body>
<script>
function jsonpCallback(data) {
console.log(JSON.stringfy(data))
} //定义回调函数jsonpCallback,收到服务端返回结果callback({"name" : "haha"}),callback=jsonpCallback
</script>
<script src="http://127.0.0.1:3000?callback=jsonpCallback"></script> //web页面上调用js文件不收同源策略影响

</body>
</html>

验证过程:

  • 终端在后端文件server.js目录下,node server.js启动服务,监听端口3000
    启动3000端口服务器
  • 我们通过端口号的不同来模拟跨域的场景,通过127.0.0.1:8080端口来访问页面.
    这里我们打开另一个终端,在页面同目录下输入http-server启动端口
    启动8080端口服务器

这样就可以通过端口 8080 访问 index.html 刚才那个页面了,相当于是开启两个监听不同端口的 http 服务器,通过页面中的请求来模拟跨域的场景。打开浏览器,访问 http://127.0.0.1:8080就可以看到从http://127.0.0.1:3000获取到的数据了。
获取到数据
至此,通过 JSONP 跨域获取数据已经成功了,但是通过这种事方式也存在着一定的优缺点:

优点:
1.它不像XMLHttpRequest 对象实现 Ajax 请求那样受到同源策略的限制
2.兼容性很好,在古老的浏览器也能很好的运行
3.不需要 XMLHttpRequest 或 ActiveX 的支持;并且在请求完毕后可以通过调用 callback 的方式回传结果。
缺点:
1.它支持 GET 请求而不支持 POST 等其它类型的 HTTP 请求。
2.它只支持跨域 HTTP 请求这种情况,不能解决不同域的两个页面或 iframe 之间进行数据通信的问题
3.容易遭受XSS攻击,因为我们拿到的是对方接口的数据作为js执行,如果得到的是一个很危险js,获取了用户信息和cookies,这时执行了js就会出现安全问题。

2.CORS实现跨域

CORS 是一个 W3C 标准,全称是”跨域资源共享”(Cross-origin resource sharing)它允许浏览器向跨源服务器,发出 XMLHttpRequest 请求,从而克服了 ajax 只能同源使用的限制。

CORS 需要浏览器和服务器同时支持才可以生效,对于开发者来说,CORS 通信与同源的 ajax 通信没有差别,代码完全一样。浏览器一旦发现 ajax 请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。参考阮一峰http://www.ruanyifeng.com/blog/2016/04/cors.html

因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨源通信。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--前端页面-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
var xhr = new XMLHttpRequest()
xhr.open('GET', 'http://127.0.0.1:3000', true)
xhr.send()
xhr.onload = function () {
console.log(xhr.responseText)
}
</script>
</body>
</html>

这似乎跟一次正常的异步 ajax 请求没有什么区别,关键是在服务端收到请求后的处理:

1
2
3
4
5
6
7
8
var http = require('http')
http.createServer(function(req,res){
res.writeHead(200,{
'Acess-Control-Allow-Origin':'http://127.0.0.1:8080'
})
res.end('这是你要的数据: haha')
}).listen(3000,'127.0.0.1')
console.log('监听127.0.0.1:3000端口')

关键是在于设置相应头中的 Access-Control-Allow-Origin,该值要与请求头中 Origin 一致才能生效,否则将跨域失败。
开启两个http服务器
开启服务器
打开浏览器访问localhost:8080,
跨域成功

成功的关键在于 Access-Control-Allow-Origin是否包含请求页面的域名,如果不包含的话,浏览器认为是跨域请求,会拦截返回的信息.
另外如果服务器设置:

1
2
3
res.writeHead(200,{
'Acess-Control-Allow-Origin': * //开放接口,任何人都能跨域调用该接口内的数据
})

CORS 的优缺点:

CORS 的优点:
1.使用简单方便,更为安全
2.支持 POST 请求方式
3.精确控制资源访问权限
4.客户端无需增加额外代码
缺点:

- CORS仅兼容 IE 10 以上
3.Server Proxy服务器代理

需要跨域的请求操作时发送请求给后端,让后端帮你代为请求,然后将获取的结果发送给你。
假设你的页面需要获取 CNode:Node.js专业中文社区 论坛上一些数据,如通过 https://cnodejs.org/api/v1/topics,因为不同域,所以你可以请求后端让其代为转发请求.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var url = require('url');
var http = require('http');
var https = require('https');

var server = http.createServer((req, res) => { // =>前面是参数,后面是函数体
var path = url.parse(req.url).path.slice(1);
if(path === 'topics'){
https.get('https://cnodejs.org/api/v1/topics',(resp) => {
var data = "";
resp.on('data',chunk => {
data += chunk;
})
resp.on('end', () => {
res.writeHead(200, {
'Content-Type': 'application/json; charset=utf-8'
});
res.end(data);
})
})
}
}).listen(3000,'127.0.0.1');
console.log('启动服务,监听 127.0.0.1:3000');

开启服务器:

端口3000
打开浏览器访问http://localhost:3000/topics就可以看到:
万能的服务器
获取到数据,跨域成功

四种iframe跨域

1.postMessage实现跨域

postMessage 是 HTML5 新增加的一项功能,跨文档消息传输(Cross Document Messaging),目前:Chrome 2.0+、Internet Explorer 8.0+, Firefox 3.0+, Opera 9.6+, 和 Safari 4.0+ 都支持这项功能,使用起来也特别简单。

首先创建 a.html 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>a.html</title>
</head>
<body>
<iframe src="http://localhost:8081/b.html" style='display: none;'></iframe>
<script>
window.onload = function(){
var targetOrigin = 'http://localhost:8081';
window.frames[0].postMessage('看到我发给你的信息没',targetOrigin) //window.frames[0]是内嵌的窗口
}
window.addEventListener('message',function(e){
console.log('a.html接收到信息:',e.data)
});
</script>
</body>
</html>

创建内嵌b.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>b.html</title>
</head>
<body>
<script>
window.addEventListener('message',function(e){
if(e.source != window.parent){
return;
}
var data = e.data;
console.log('b.html 接收到的消息:', data);
parent.postMessage('我看到你发的消息了!',e.origin)
})
</script>
</body>
</html>

然后同时开启服务器:注意b.html服务器开启更改接口命令:http-server -p 8081
开启服务器

在浏览器中打开http://127.0.0.1:8080/a.html
iframe跨域
跨域成功.
参考教程:Window.postMessage()

2.document.domain实现跨域:(降域)

对于主域相同而子域不同的情况下,可以通过设置 document.domain 的办法来解决,具体做法是可以在 http://www.example.com/a.html和http://sub.example.com/b.html两个文件分别加上 document.domain = “a.com”;然后通过 a.html 文件创建一个 iframe,去控制 iframe 的 window,从而进行交互,当然这种方法只能解决主域相同而二级域名不同的情况
测试的方式稍微复杂点,需要安装 nginx 做域名映射,如果你电脑没有安装 nginx,请先去安装一下: nginx news
安装教程:https://www.cnblogs.com/chuncn/archive/2011/10/14/2212291.html

先创建一个 a.html 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>a.html</title>
</head>
<body>
<script>
document.domain = 'example.com' //a,b分别加上 document.domain = "example.com"
var ifr = document.createElement('iframe'); //创建一个iframe 内嵌窗
ifr.src = 'http://sub.example.com/b.html';
ifr.style.display = 'none';
document.body.append(ifr)
ifr.onload = function(){
var win = ifr.contentWindow; //控制内嵌iframe的窗口信息
alert(win.data)
}
</script>
</body>
</html>

创建b.html文件

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>b.html</title>
</head>
<body>
<script>
document.domain = 'example.com';
window.data = '传送的数据:1111';
</script>
</body>
</html>

开启服务器
服务器开启

这时候只是开启了两个 http 服务器,还需要通过 nginx 做域名映射,将 Example Domain映射到 localhost:8080,sub.example.com 映射到localhost:8081上打开操作系统下的 hosts 文件:mac 是位于 /etc/hosts 文件,并添加:

1
2
127.0.0.1 www.example.com
127.0.0.1 sub.example.com

windows修改host参考:
http://chongzhuang.windowszj.com/faq/164.html

修改好后保存,这样在浏览器打开两个网址后就会访问本地的服务器.
之后打开 nginx 的配置文件:/usr/local/etc/nginx/nginx.conf,并在 http 模块里添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server {
listen 80;
server_name www.example.com;
location / {
proxy_pass http://127.0.0.1:8080/;
}
}
server {
listen 80;
server_name sub.example.com;
location / {
proxy_pass http://127.0.0.1:8081/;
}
}

如果访问本地的域名是www.example.com,就由 localhost:8080 代理该请求。所以我们这时候在打开浏览器访问www.example.com的时候其实访问的就是本地服务器 localhost:8080。

3.location.hash实现跨域

在 url 中,http://www.baidu.com#helloworld的#helloworld 就是 location.hash,改变 hash 值不会导致页面刷新,所以可以利用 hash 值来进行数据的传递,当然数据量是有限的。
假设localhost:8080下有文件 cs1.html要和 localhost:8081 下的 cs2.html传递消息,cs1.html首先创建一个隐藏的iframe,iframe 的 src指向 localhost:8081/cs2.html,这时的 hash 值就可以做参数传递。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CS1</title>
</head>
<body>
<script>
// http://localhost:8080/cs1.html
let ifr = document.createElement('iframe');
ifr.style.display = 'none';
ifr.src = "http://localhost:8081/cs2.html#data";
document.body.appendChild(ifr);

function checkHash() {
try {
let data = location.hash ? location.hash.substring(1) : '';
console.log('获得到的数据是:', data);
}catch(e) {

}
}
window.addEventListener('hashchange', function(e) {
console.log('获得的数据是:', location.hash.substring(1));
});
</script>
</body>
</html>

cs2.html 收到消息后通过 parent.location.hash 值来修改 cs1.html 的 hash 值,从而达到数据传递。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CS2</title>
</head>
<body>
<script>
// http://locahost:8081/cs2.html
switch(location.hash) {
case "#data":
callback();
break;
}
function callback() {
const data = "some number: 1111"
try {
parent.location.hash = data;
}catch(e) {
// ie, chrome 下的安全机制无法修改 parent.location.hash
// 所以要利用一个中间的代理 iframe
var ifrproxy = document.createElement('iframe');
ifrproxy.style.display = 'none';
ifrproxy.src = 'http://localhost:8080/cs3.html#' + data; // 该文件在请求域名的域下
document.body.appendChild(ifrproxy);
}
}
</script>
</body>
</html>

由于两个页面不在同一个域下IE、Chrome不允许修改parent.location.hash的值,所以要借助于 localhost:8080 域名下的一个代理 iframe 的 cs3.html 页面

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>cs3</title>
</head>
<body>
<script>
parent.parent.location.hash = self.location.hash.substring(1);
</script>
</body>
</html>

同样开启服务器
开启服务器

这里为了图方便,将 cs1,2,3 都放在同个文件夹下,实际情况的话 cs1.html 和 cs3.html 要与 cs2.html 分别放在不同的服务器才对。
之后打开浏览器访问 localhost:8080/cs1.html,注意不是 8081,就可以看到获取到的数据了,此时页面的 hash 值也已经改变。
跨域成功

缺点:

  • 数据直接暴露在了 url 中
  • 数据容量和类型都有限

4.window.name实现跨域

window.name(一般在 js 代码里出现)的值不是一个普通的全局变量,而是当前窗口的名字,这里要注意的是每个 iframe 都有包裹它的window,而这个 window 是top window 的子窗口,而它自然也有 window.name 的属性,window.name 属性的神奇之处在于 name 值在不同的页面(甚至不同域名)加载后依旧存在(如果没修改则值不会变化),并且可以支持非常长的 name 值(2MB)。
举个简单的例子:

你在某个页面的控制台输入:

1
2
window.name = "Hello World";
window.location = "http://www.baidu.com";

页面跳转到了百度首页,但是window.name却被保存了下来,还是 Hello World,跨域解决方案似乎可以呼之欲出了:

首先创建 a.html 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>a.html</title>
</head>
<body>
<script>
var data = '';
const ifr = document.createElement('iframe');
ifr.src = "http://localhost:8081/b.html";
ifr.style.display = 'none';
document.body.appendChild(ifr);
ifr.onload = function() {
ifr.onload = function() {
data = ifr.contentWindow.name;
console.log('收到数据:', data);
}
ifr.src = "about:blank";
}
</script>
</body>
</html>

再创建b文件:

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>b.html</title>
</head>
<body>
<script>
window.name = "你想要的数据!";
</script>
</body>
</html>

http://localhost:8080/a.html在请求远端服务器http://localhost:8081/b.html的数据,我们可以在该页面下新建一个 iframe,该iframe 的 src 属性指向服务器地址(利用 iframe 标签的跨域能力),服务器文件 b.html 设置好 window.name 的值。
但是由于 a.html页面和该页面iframe 的 src 如果不同源的话,则无法操作iframe里的任何东西,所以就取不到 iframe 的 name值,所以我们需要在b.html加载完后重新换个src设置成'about:blank;' ,如果不重新指向 src的话直接获取 window.name 的话会获取不到数据.

在a.html中重新设置ifr.src = "about:blank";后,打开两个http服务器,访问http://127.0.0.1:8080/a.html
跨域成功

11 ajax实践

发表于 2017-12-20 | 分类于 JavaScript

题目1: ajax 是什么?有什么作用?

  • 1.ajax是什么?
    Ajax全称为”Asynchronous JavaScript and XML”(异步JavaScript和XML),是指一种创建交互式网页应用的网页开发技术,是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。
    2.Ajax = 异步 JavaScript 和 XML(标准通用标记语言的子集)
    3.Ajax是一种技术方案,但并不是一种新的编程语言新技术。它依赖的是现有的CSS/HTML/Javascript,而其中最核心的依赖是浏览器提供的XMLHttpRequest对象,是这个对象使得浏览器可以发出HTTP请求与接受HTTP响应。实现在页面不刷新的情况下和服务器进行数据交互。一句话实现两者的关系:我们使用XMLHttpRequest对象来发送一个Ajax请求。
  • 2.ajax作用:
    传统的WEB应用程序模型是这样工作的:用户的界面操作触发HTTP请求,服务器在接收到请求之后进行一些业务逻辑处理,如保存数据等,然后向客户端返回一个HTML页面。但这种方式并没有给予用户很好的应用体验,当服务器在处理数据的时候,用户则处于等待的状态,每一步操作都需要等待,太多的等待会使用户越来越没有耐心。而Ajax则大不相同,它通过Ajax引擎,使得应用过程很自然,操作很流畅,因为其只和服务器交换有用的数据,而页面显示等不必要的数据则不再重新加载。Ajax引擎其实就是JavaScript、XML、XMLHttpRequest等等各项技术的综合应用。
    通过 Ajax,我们可以使得客户端得到丰富的应用体验及交换操作,而用户不会感觉到有网页提交或刷新的过程,页面也不需要被重新加载,应用的数据交换都被隐藏。

  • 3.ajax优缺点:

    • 优点:
      • 更新数据页面无需刷新,用户体验更佳
      • 使用异步方式与服务器通信,响应速度更快
      • 可将服务器以前负担的一些工作转嫁到客户端,利用客户端的闲置能力来处理,减轻服务器和带宽的负担,节约空间和宽带租用成本。AJAX的原则是“按需取数据”,可最大程度减少冗余请求
      • 作为基于标准化的并被广泛支持的技术,无需下载插件或小程序
      • 使因特网应用程序更小、更快、更友好
    • 缺点:
      • 不支持浏览器back按钮
      • AJAX暴露了与服务器交互的细节带来安全问题
      • 对搜索引擎的支持较弱
      • 破坏了程序的异常机制
      • 不容易调试

题目2: 前后端开发联调需要注意哪些事情?后端接口完成前如何 mock 数据?

在开发之前,前后端需要协作商定数据和接口的各项细节,后端负责提供数据,前端负责展示数据(根据数据负责页面的开发)

  • 前后端开发联调注意事项:

    • URL:接口名称
    • 发送请求的参数和格式(get/post)
    • 数据响应的数据格式(数组/对象)
    • 根据前后端约定,整理接口文档

    • 如何mock数据

      • 搭建web服务器
      • 根据接口文档仿造假数据
      • 关联前后端文件,开启web服务器
      • 验证前端页面功能及显示是否正确

题目3:点击按钮,使用 ajax 获取数据,如何在数据到来之前防止重复点击?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var isLoading = false //添加状态锁,初始为false,用于判断是否在加载数据
btn.addEventListener('click',function(){
if(!isLoading){
return; //如果正在请求数据,这次点击什么都不做
}
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function(){
if(xhr.readystate === 4){
if(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304){
console.log(xhr.responseText)
}else{
console.log("error")
}
isLoading = false; //readystate = 4数据到来,状态锁变为false,可以再次点击
}
xhr.open('get',url,true);
xhr.send()
isloading = true //数据发送,进入等待数据状态,状态锁变为true
})

题目4:实现加载更多的功能,效果范例 。

实现代码:
Github
本地mock成功:
本地mock成功.png

10 闭包_定时器_BOM

发表于 2017-12-14 | 分类于 JavaScript

题目1: 下面的代码输出多少?修改代码让 fnArr[i]()输出i。使用 两种以上的方法

1
2
3
4
5
6
7
8
9
10
11
var fnArr = [];
for (var i = 0; i < 10; i ++) {
fnArr[i] = function(){
return i;
};
}
console.log( fnArr[3]() ); //打印出10
/*
因为当i自增到10,才跳出for循环,然后开始执行fnArr[3](),匿名函数function(){return i;} 此时才被调用,
调用的时候i=10,所以返回10.
*/

解决办法:

1
2
3
4
5
6
7
8
9
10
//方法1:
for (var i = 0; i < 10; i++) {
fnArr[i] = (function(i){ //这个括号里的i是自有变量,与外部for里的i毫无关联,只受传入的参数影响
return function(){ //返回一个函数function(){}
return i;
};
})(i); //这个i为外部传入的参数
}
console.log(fnArr[3]()) //打印结果为3.
//注意这里fnArr[3]()能调用,说明fnArr[i]是个函数,所以上面需要需要返回一个函数function(){}
1
2
3
4
5
6
7
8
9
//方法2:
for (var i = 0; i < 10; i++) {
(function(i){
fnArr[i] = function(){
return i;
}
})(i)
}
console.log(fnArr[3]()) //打印结果为3.
1
2
3
4
5
6
7
//方法3:
for (let i = 0; i < 10; i++) { //let 语句声明一个块级作用域的本地变量
fnArr[i] = function(){
return i;
};
}
console.log(fnArr[3]()); //打印结果为3

总结:

  • 可以用立即表达式的方式在内部再声明一个内部自有变量,形成新的作用域阻隔变量提升,来解决因为变量提升所产生的问题

题目2: 封装一个汽车对象,可以通过如下方式获取汽车状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
var Car = (function(){
var speed = 0;
function setSpeed(s){
speed = s
}
/* 补充*/
function getSpeed(){
return speed;
}
function accelerate(){
speed += 10;
}
function decelerate(){
if(speed >= 10){
speed -= 10;
}else{
console.log('减速幅度大于当前最低速度,无法减速')
}
}
function getStatus(){
if(speed > 0){
console.log('running');
}else{
console.log('stop');
}
}
return {
setSpeed: setSpeed,
/*补充*/
getSpeed: getSpeed,
accelerate: accelerate,
decelerate: decelerate,
getStatus: getStatus
}
})()
Car.setSpeed(30);
Car.getSpeed(); //30
Car.accelerate();
Car.getSpeed(); //40;
Car.decelerate();
Car.decelerate();
Car.getSpeed(); //20
Car.getStatus(); // 'running';
Car.decelerate();
Car.decelerate();
Car.getStatus(); //'stop';
//Car.speed; //error

测试

题目3:下面这段代码输出结果是? 为什么?

1
2
3
4
5
6
7
8
9
var a = 1;
setTimeout(function(){
a = 2;
console.log(a);
}, 0);
var a ;
console.log(a);
a = 3;
console.log(a);

解题思路:

1
2
3
4
5
6
7
8
9
10
11
//声明前置
var a;
var a ;
a = 1;
setTimeout(function(){
a = 2;
console.log(a); //打印出2,但是由于setTimeout运行机制,需等到所有代码执行完,才会执行
}, 0);
console.log(a); //打印出1,因为上面a被赋值为1
a = 3;
console.log(a); //打印出3,因为上面a被赋值为3

因为定时器的运行机制:将指定的代码移出本次执行,等到下一轮 Event Loop 时,再检查是否到了指定时间。如果到了,就执行对应的代码;如果不到,就等到再下一轮 Event Loop 时重新判断时间

所以最终打印顺序及结果是

1
2
3
1
3
2


题目4:下面这段代码输出结果是? 为什么?

1
2
3
4
5
6
var flag = true;
setTimeout(function(){ //由于setTimeout运行机制,要等到所有代码执行完,才会被执行,所以这里也永远不被执行
flag = false;
},0)
while(flag){} /*while循环的特性,只要指定条件是true,循环就可以一直执行代码.因为flag为true,while无限循环,不会执行之后的代码*/
console.log(flag); //不被执行

代码没有任何输出

题目5: 下面这段代码输出?如何输出delayer: 0, delayer:1…(使用闭包来实现)

1
2
3
4
5
6
for(var i=0;i<5;i++){
setTimeout(function(){
console.log('delayer:' + i );
}, 0);
console.log(i);
}

整理以上代码:声明提前,setTimeout代码执行置后

1
2
3
4
5
6
7
var i
for(i=0;i<5;i++){
console.log(i); //因为定时器执行机制, 所以此行优先执行,先输出0 1 2 3 4
setTimeout(function(){
console.log('delayer:' + i ); //执行完所有代码后开始执行,i已经自增到5,因为只有一个全局变量i,此时i的值为5,所以输出5次 delayer:5
}, 0);
}

解题思路:可以在函数内部再声明一个内部变量

1
2
3
4
5
6
7
8
for(var i=0;i<5;i++){
(function(i){ //这个i是函数自有变量 ,与外部变量i不是同一个变量,不受for循环里的i值影响,只受传入的参数i影响
setTimeout(function(){
console.log('delayer:' + i ); // 这里的i也是自有内部变量,当所有代码执行完后,开始执行,传入参数依次是0 1 2 3 4
}, 0);
console.log(i);
})(i); //i为传入参数 传入参数依次是 0 1 2 3 4
}

所以最终输出结果是:

1
2
3
4
5
6
7
8
9
10
0
1
2
3
4
delayer:0
delayer:1
delayer:2
delayer:3
delayer:4


题目6: 如何获取元素的真实宽高

主流浏览器通过window.getComputedStyle来获取真实style,低版本IE通过element.currentStyle来获取真实style

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<style>
.box{
border: 3px solid red;
height: 300px;
width: 500px;
}
</style>

<div class="box"></div>

<script>
var box = document.querySelector('.box') //获取元素节点

//获取元素的真实style
function trueStyle(element,pseudoElt){
return element.currentStyle ? element.currentStyle : window.getComputedStyle(element,pseudoElt);
}
var tureWidth = trueStyle(box).width;
var trueHeight = trueStyle(box).height;

题目7: URL 如何编码解码?为什么要编码?

  • 编码方式:
    encodeURI()
    encodeURIComponent()
    区别:
    encodeURI方法不会对下列字符编码 ASCII字母、数字、~!@#$&*()=:/,;?+'
    encodeURIComponent方法不会对下列字符编码 ASCII字母、数字、~!*()'
    所以encodeURIComponent比encodeURI编码的范围更大。

  • 解码方式:
    decodeURI()
    decodeURIComponent()

  • 编码原因:

    对于Url来说,之所以要进行编码,是因为Url中有些字符会引起歧义。
    比如说“name1=value1”,其中value1的值是“va&lu=e1”字符串,那么实际在传输过程中就会变成这样“name1=va&lu=e1”。我们的本意是就只有一个键值对,但是服务端会解析成两个键值对,这样就产生了歧义。
    又如,Url的编码格式采用的是ASCII码,而不是Unicode,这也就是说你不能在Url中包含任何非ASCII字符,例如中文。否则如果客户端浏览器和服务端浏览器支持的字符集不同的情况下,中文可能会造成问题。
    URL编码的原则就是使用安全的字符(没有特殊用途或者特殊意义的可打印字符)去表示那些不安全的字符.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    [参考]https://www.cnblogs.com/jerrysion/p/5522673.html
    -------------------

    ### 题目8补全如下函数,判断用户的浏览器类型
    ```javascript
    function isAndroid() {
    return /android/i.test(navigator.userAgent)
    }
    function isIphone(){
    return /iphone/i.test(navigator.userAgent)
    }
    function isIpad() {
    return /ipad/i.test(navigator.userAgent)
    }
    function isIOS() {
    return /iphone|ipad/i.test(navigator.userAgent)
    }

9 事件

发表于 2017-12-10 | 分类于 JavaScript

1: DOM0 事件和DOM2级在事件监听使用方式上有什么区别?

  • DOM0级事件处理程序:
    通过JavaScript指定事件处理程序,将一个函数赋值给一个元素的事件处理程序属性。

每个元素都有自己的事件处理程序属性,这些属性名称通常为小写,如onclick等,将这些属性的值设置为一个函数,就可以指定事件处理程序,如下:

1
2
3
4
5
6
7
8
<input id="btnClick" type="button" value="Click Here" />

<script type="text/javascript">
var btnClick = document.getElementById('btnClick');
btnClick.onclick = function showMessage() {
alert(this.id);
};
</script>

DOM0级方法指定的事件处理程序被认为是元素的方法,事件处理程序在元素的作用域下运行,this指向当前元素,在事件处理程序中可通过this访问元素的任何属性和方法,以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。
可将事件处理程序属性赋值为null来删除通过DOM0级方法指定的事件处理程序。

1
btn.onclick = null; //删除事件处理程序

  • DOM2级事件处理程序:
    DOM2级事件定义了两个方法用于处理指定和删除事件处理程序的操作:
    1
    2
    addEventListener()
    removeEventListener()

所有的DOM节点都包含这两个方法,并且它们都接受3个参数:事件名
、事件处理函数、布尔值,布尔值如果是true表示在捕获阶段调用事件处理程序,false则是在事件冒泡阶段处理。
上面的例子可以这样写:

1
2
3
4
5
6
7
8
<input id="btnClick" type="button" value="Click Here" />

<script type="text/javascript">
var btnClick = document.getElementById('btnClick');
btnClick.addEventListener('click', function() {
alert(this.id);
}, false);
</script>

上面的事件会在冒泡阶段被触发,这里添加的事件处理程序也是在其依附的元素的作用域中运行,使用DOM2级方法添加事件处理程序的好处是可以添加多个事件处理程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
<input id="btnClick" type="button" value="Click Here" />

<script type="text/javascript">
var btnClick = document.getElementById('btnClick');

btnClick.addEventListener('click', function() {
alert(this.id);
}, false);

btnClick.addEventListener('click', function() {
alert('Hello!');
}, false);
</script>

这两个事件处理程序会按照添加它们的顺序触发。

通过addEventListener()添加的事件处理程序只能通过removeEventListener()移除,移除时参数与添加的时候相同,这就意味着刚才我们添加的匿名函数无法移除,因为匿名函数虽然方法体一样,但是句柄却不相同,所以正确的做法是将作为参数的的函数赋值给一个变量,这样添加和删除事件处理程序时大家引用的就是同一个函数了。

1
2
3
4
5
6
7
8
9
10
11
12
<input id="btnClick" type="button" value="Click Here" />

<script type="text/javascript">
var btnClick = document.getElementById('btnClick');

var handler=function() {
alert(this.id);
}

btnClick.addEventListener('click', handler, false);
btnClick.removeEventListener('click', handler, false);
</script>

将事件处理程序添加到事件流的冒泡阶段可以最大限度地兼容各种浏览器。IE9、Firefox、Chrome、Safari、Opera都支持DOM2级事件处理程序。

  • 区别:
    • DOM0级同一个事件处理程序只能对应一个处理函数,定义多次则新方法会覆盖老方法。DOM2级可同时添加多个事件处理程序且会按添加他们的顺序先后执行。
    • DOM0级简单且具有跨浏览器优势,DOM2级不具备跨浏览器优势。

2: attachEvent与addEventListener的区别?

  1. 参数个数不相同,这个最直观,addEventListener有三个参数,attachEvent只有两个,attachEvent添加的事件处理程序只能发生在冒泡阶段,addEventListener第三个参数可以决定添加的事件处理程序是在捕获阶段还是冒泡阶段处理(我们一般为了浏览器兼容性都设置为冒泡阶段)
  2. 第一个参数意义不同,addEventListener第一个参数是事件类型(比如click,load),而attachEvent第一个参数指明的是事件处理函数名称(onclick,onload)
  3. 事件处理程序的作用域不相同,addEventListener的作用域是元素本身,this是指的触发元素,attachEvent事件处理程序会在全局变量内运行,this是window,所以刚才例子才会返回undefined,而不是元素id
  4. 为一个事件添加多个事件处理程序时,执行顺序不同,addEventListener添加会按照添加顺序执行,而attachEvent添加多个事件处理程序时顺序无规律(添加的方法少的时候大多是按添加顺序的反顺序执行的,但是添加的多了就无规律了),所以添加多个的时候,不依赖执行顺序的还好,若是依赖于函数执行顺序,最好自己处理,不要指望浏览器

    3: 解释IE事件冒泡和DOM2事件传播机制?

  • IE的事件流叫做事件冒泡(event bubbling),即事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。
    示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <!DOCTYPE html>
    <html>
    <head>
    <title>Event Bubbling Example</title>
    </head>
    <body>
    <div id="myDiv">Click Me</div>
    </body>
    </html>

当点击div区域,click事件首先在元素上发生,而这个元素就是我们单击的元素。然后click事件沿DOM树向上传播,在每一级节点上都会发生直至传播到document对象。
事件冒泡模型

所有现代浏览器都支持事件冒泡,但IE5.5及更早版本中的事件冒泡会跳过<html>元素,从<body>直接到document,IE9、Firefox、Chrome、Safari则将事件一直冒泡到window对象。

  • DOM2级事件传播机制规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。首先发生的是事件捕获,为截获事件提供了机会。然后是实际的目标接收到事件。最后一个阶段是冒泡阶段,可以在这个阶段对事件做出响应。以前面简单的HTML页面为例,单击
    元素会按照下图所示顺序触发事件。

DOM事件流

以上面代码为例,捕获阶段事件从ducument到再到后就停止了,下一阶段是“处于目标”阶段,事件在

上发生并在事件处理中被视为冒泡阶段的一部分,接着冒泡阶段发生,事件又传播回文档。

IE9、Opera、Firefox、Chrome、Safari都支持DOM事件流,IE8及更早版本不支持事件流,只支持事件冒泡。
即使DOM2级事件规范明确要求捕获阶段不会涉及事件目标,但IE9,Firefox,Chrome,Safari和Opera9.5及更高版本都会在事件捕获阶段触发事件对象上的事件,结果就是有两个机会在目标对象上操作事件。

4:如何阻止事件冒泡? 如何阻止默认事件?

  • 阻止事件冒泡使用event.stopPropagation()
    方法阻止事件在DOM中继续传播,防止再触发定义在别的节点上的监听函数,但是不包括在当前节点上新定义的事件监听函数
  • 阻止默认事件使用event.preventDefault()
    方法不会阻止事件的进一步传播,只要在事件的传播过程中(捕获阶段、目标阶段、冒泡阶段皆可),使用了preventDefault方法,该事件的默认方法就不会执行

5:有如下代码,要求当点击每一个元素li时控制台展示该元素的文本内容。不考虑兼容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<ul class="ct">
<li>这里是</li>
<li>饥人谷</li>
<li>前端6班</li>
</ul>
<script>
var ct = document.querySelector(".ct")
ct.addEventListener('click',function(e){
var target = e.target
if(target.tagName.toLowerCase()==='li'){
console.log(target.innerText)
}
})
</script>

6: 补全代码,要求:

当点击按钮开头添加时在

  • 这里是
  • 元素前添加一个新元素,内容为用户输入的非空字符串;当点击结尾添加时在最后一个 li 元素后添加用户输入的非空字符串.
    当点击每一个元素li时控制台展示该元素的文本内容。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    <ul class="ct">
    <li>这里是</li>
    <li>饥人谷</li>
    <li>前端6班</li>
    </ul>
    <input id="ipt-add-content" placeholder="添加内容"/>
    <button id="btn-add-start">开头添加</button>
    <button id="btn-add-end">结尾添加</button>

    <script>
    var ct = document.querySelector('.ct')
    var ipt = document.querySelector('#ipt-add-content')
    var startBtn = document.querySelector('#btn-add-start')
    var endBtn = document.querySelector('#btn-add-end')

    ct.addEventListener('click',function(e){
    if(e.target.tagName.toLowerCase() === 'li')
    console.log(e.target.innerText);
    })

    startBtn.addEventListener('click',function(){
    var newLi = document.createElement('li')
    newLi.innerText = ipt.value;
    if(newLi.innerText.match(/\S/))
    ct.insertBefore(newLi,ct.firstChild);
    })

    endBtn.addEventListener('click',function(){
    var newLi = document.createElement('li')
    newLi.innerText = ipt.value;
    if(newLi.innerText.match(/\S/))
    ct.appendChild(newLi);
    })
    </script>

    7: 补全代码,要求:当鼠标放置在li元素上,会在img-preview里展示当前li元素的data-img对应的图片。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <body>
    <ul class="ct">
    <li data-img="https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2060237827,3671274443&fm=11&gp=0.jpg">鼠标放置查看图片1</li>
    <li data-img="https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3046951468,909997987&fm=27&gp=0.jpg">鼠标放置查看图片2</li>
    <li data-img="https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2672761046,1103793992&fm=27&gp=0.jpg">鼠标放置查看图片3</li>
    </ul>
    <div class="img-preview"></div>

    <script>
    var ct = document.querySelector('.ct')
    var imgPre = document.querySelector('.img-preview')
    ct.addEventListener('mouseover',function(e){
    if(e.target.tagName.toLowerCase() === 'li')
    var imgSrc = e.target.getAttribute('data-img')
    var img = document.createElement('img')
    img.setAttribute('src',imgSrc)
    imgPre.appendChild(img)
    })
    ct.addEventListener('mouseout',function(e){
    imgPre.innerHTML = ''
    })
    </script>
    </body>

    DEMO演示

    8 DOM操作

    发表于 2017-12-08 | 分类于 JavaScript

    1.dom对象的innerText和innerHTML有什么区别?

    • innerText是一个可写属性,返回元素内包含的文本内容,在多层次的时候会按照元素由浅到深的顺序拼接其内容
    • innerHTML属性作用和innerText类似,但是不是返回元素的文本内容,而是返回元素的HTML结构,在写入的时候也会自动构建DOM
      示例:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      <!DOCTYPE html>
      <html lang="en">
      <head>
      <meta charset="UTF-8">
      <title>DOM</title>
      </head>
      <body>
      <div id="ct">
      <p>
      123
      <span>456</span>
      </p>
      </div>

      <script>
      var elem = document.getElementById('ct');
      console.log(elem.innerText) //123 456
      console.log(elem.innerHTML) //<p>123<span>456</span></p>
      </script>
      </body>
      </html>

    2.elem.children和elem.childNodes的区别?

    • elem.children返回元素中的元素子节点;
    • elem.childNodes返回元素中的所有子节点(包括空白文本节点),childNodes中保存着一个NodeList类数组对象(有length属性但并不是Array的实例),可通过方括号语法来访问NodeList的值。

    3.查询元素有几种常见的方法?ES5的元素选择方法是什么?

    • getElementById()
      getElementById方法返回匹配指定ID属性的元素节点。如果没有发现匹配的节点,则返回null。这也是获取一个元素最快的方法.
      var elem = document.getElementById("ct");

    • getElementsByClassName()
      etElementsByClassName方法返回一个类似数组的对象(HTMLCollection类型的对象),包括了所有class名字符合指定条件的元素(搜索范围包括本身),元素的变化实时反映在返回结果中。这个方法不仅可以在document对象上调用,也可以在任何元素节点上调用。
      var elements = document.getElementsByClassName("num");

    • getElementsByTagName()
      getElementsByTagName方法返回所有指定标签的元素(搜索范围包括本身)。返回值是一个HTMLCollection对象,也就是说,搜索结果是一个动态集合,任何元素的变化都会实时反映在返回的集合中。这个方法不仅可以在document对象上调用,也可以在任何元素节点上调用。
      var paras = document.getElementsByTagName("p");

    • getElementsByName()
      getElementsByName方法用于选择拥有name属性的HTML元素,比如form、img、frame、embed和object,返回一个NodeList格式的对象,不会实时反映元素的变化。
      var imgs = document.getElementsByName("image");

    ES5的元素选择方法:

    • querySelector()
      querySelector方法返回匹配指定的CSS选择器的元素节点。如果有多个节点满足匹配条件,则返回第一个匹配的节点。如果没有发现匹配的节点,则返回null。querySelector方法无法选中CSS伪元素。

    • querySelectorAll()
      querySelectorAll方法返回匹配指定的CSS选择器的所有节点,返回的是NodeList类型的对象。NodeList对象不是动态集合,所以元素节点的变化无法实时反映在返回结果中。

      querySelectorAll方法的参数,可以是逗号分隔的多个CSS选择器,返回所有匹配其中一个选择器的元素。

    4.如何创建一个元素?如何给元素设置属性?如何删除属性?

    创建:

    • 创建元素:creatElement(tagName):用来生成HTML元素节点,参数为引号包裹的元素的标签名。

      1
      var newDiv = document.createElement("div");
    • 创建文本节点:creatTextNode(content):用来生成文本节点,参数为所要生成的文本节点的内容。

      1
      2
      var newDiv = document.createElement("div");
      var newContent = document.createTextNode("Hello");
    • 创建DOM片段:creatDocumentFragment():生成一个DocumentFragment对象。该对象是一个存在于内存的DOM片段,不属于当前文档,常用来生成较复杂的DOM结构,然后插入当前文档。这样做的好处在于,因为DocumentFragment不属于当前文档,对它的任何改动,都不会引发网页的重新渲染,比直接修改当前文档的DOM有更好的性能表现。

      1
      var docFragment = document.createDocumentFragment();

    设置属性:

    • setAttribute(attr,value):接收两个参数,引号包裹的属性名与引号包裹的属性值
      1
      2
      var node = document.getElementById("div1");
      node.setAttribute("my_attrib", "newVal");

    等同于

    1
    2
    3
    4
    5
    var node = document.getElementById("div1");
    var a = document.createAttribute("my_attrib");
    a.value = "newVal";
    node.setAttributeNode(a);
    `

    ###删除属性:

    1
    removeAttribute(attr):接收一个参数,即要删除的属性名

    node.removeAttribute(‘id’);

    1
    2
    3
    4
    5
    6
    7
    8
    示例:
    ![](http://upload-images.jianshu.io/upload_images/7280908-5e4a98c077798892.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)



    # 5.如何给页面元素添加子元素?如何删除页面元素下的子元素?
    - 添加元素:
    appendChild(): 在元素末尾添加元素

    var newDiv = document.createElement(“div”);
    var newContent = document.createTextNode(“Hello”);
    newDiv.appendChild(newContent);

    1
    2
    3

    - 插入元素:
    insertBefore(newchild,refchild):在某个元素之前插入元素

    var newDiv = document.createElement(“div”);
    var newContent = document.createTextNode(“Hello”);
    newDiv.insertBefore(newContent, newDiv.firstChild);

    1
    2
    - 替换元素
    replaceChild(newEle,oldEle)接受两个参数:要插入的元素和要替换的元素

    newDiv.replaceChild(newElement, oldElement);

    1
    2
    - 删除元素:
    removeChild()

    parentNode.removeChild(childNode);

    1
    2
    3
    4
    5
    6
    7
    8
    9

    # 6.element.classList有哪些方法?如何判断一个元素的 class 列表中是包含某个 class?如何添加一个class?如何删除一个class?
    - add(class1,class2,…):在元素中添加一个或多个类名。如果指定的类名已存在,则不会添加。存在返回true,否则返回false。
    - contains(class):返回布尔值,判断指定的类名是否存在。
    - item(index):返回类名在元素中的索引值。索引值从 0 开始,在区间范围外则返回null。
    - remove(class1,class2,…):移除元素中一个或多个类名。移除不存在的类名,不会报错。
    - toggle(class,true|false):在元素中切换类名。第一个参数为要在元素中移除的类名,并返回 false。 如果该类名不存在则会在元素中添加类名,并返回 true。 第二个是可选参数,是个布尔值用于设置元素是否强制添加或移除类,不管该类名是否存在。

    # 7.如何选中如下代码所有的li元素? 如何选中btn元素?



    • list1

    • list2

    • list3




    `

    7 正则表达式

    发表于 2017-12-08 | 分类于 JavaScript

    1.\d,\w,\s,[a-zA-Z0-9],\b,.,*,+,?,x{3},^,$分别是什么?

    字符 等价类 含义
    \d [0-9] 数字字符
    \w [a-zA-Z_0-9] 单词字符,字母、数字下划线
    \s [\t\n\x0B\f\r] 空白符(空格或Tab)
    [a-zA-Z0-9] – a-z,A-Z,0-9的字符
    \b – 单词边界
    . [^\r\n] 除了回车符和换行符之外的所有字符
    * {0,} 出现零次或多次(任意次)
    + {1,} 出现一次或多次(至少出现一次)
    ? {0,1} 出现零次或一次(最多出现一次)
    x{3} x出现3次,比如’xxx’
    ^ 在中括号内代表取反义符号/在整体正则中表示以xxx开头
    $ 以xxx结尾

    2. 写一个函数trim(str),去除字符串两边的空白字符

    1
    2
    3
    4
    function trim(str){
    return str.replace(/^\s+|\s+$/g,'')
    }
    console.log(trim(' he he ')) // 输出he he

    3. 写一个函数isEmail(str),判断用户输入的是不是邮箱

    1
    2
    3
    4
    5
    6
    function isEmail(str){
    var reg = /^\w+@\w+\.\w+$/
    return reg.test(str)
    }
    console.log(isEmail('haha@email.com')) //true
    console.log(isEmail('hah')) //false

    4. 写一个函数isPhoneNum(str),判断用户输入的是不是手机号

    1
    2
    3
    4
    5
    function isPhoneNumber(str){
    return /^1\d{10}$/.test(str)
    }
    console.log(isPhoneNumber('18012349856')) //true
    console.log(isPhoneNumber('28012349632')) //false

    5. 写一个函数isValidUsername(str),判断用户输入的是不是合法的用户名(长度6-20个字符,只能包括字母、数字、下划线)

    1
    2
    3
    4
    5
    6
    function isValidUsername(str){
    var reg = /^\w{6,20}$/
    return reg.test(str)
    }
    console.log(isValidUsername('haha180')) //true
    console.log(isValidUsername('--haha180')) //false

    6. 写一个函数isValidPassword(str), 判断用户输入的是不是合法密码(长度6-20个字符,只包括大写字母、小写字母、数字、下划线,且至少至少包括两种)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function isValidPassword(str){
    if(!/^\w{6,20}$/.test(str)) return false
    if(/^[A-Z]{6,20}$/.test(str)) return false
    if(/^[a-z]{6,20}$/.test(str)) return false
    if(/^\d{6,20}$/.test(str)) return false
    return true
    }
    console.log(isValidUsername('haha')) //false
    console.log(isValidUsername('haha123')) //true

    7. 写一个正则表达式,得到如下字符串里所有的颜色

    1
    2
    3
    var re = /#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})(?=;)/g
    var subj = "color: #121212; background-color: #AA00ef; width: 12px; bad-colors: f#fddee "
    console.log( subj.match(re) ) // ['#121212', '#AA00ef']

    8. 下面代码输出什么? 为什么? 改写代码,让其输出[“”hunger””, “”world””].

    1
    2
    3
    4
    5
    6
    var str = 'hello  "hunger" , hello "world"';
    var pat = /".*"/g;
    str.match(pat);
    //输出[""hunger" , hello "world""],因为默认贪婪模式下会尽可能多的匹配""中的内容,再量词后面加上?即可改为非贪婪模式
    改写:
    var pat = /".*?"/g; // [""hunger"", ""world""]

    6 Math数组Date

    发表于 2017-12-07 | 分类于 JavaScript

    Math

    ##1.写一个函数,返回从min到max之间的 随机整数,包括min不包括max

    1
    2
    3
    function getrandom(min,max){
    return Math.floor(Math.random()*(max-min)+min)
    }

    2.写一个函数,返回从min都max之间的 随机整数,包括min包括max

    1
    2
    3
    function getrandom(min,max){
    return Math.floor(Math.random()*(max-min+1)+min)
    }

    3.写一个函数,生成一个长度为 n 的随机字符串,字符串字符的取值范围包括0到9,a到 z,A到Z。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function getRandStr(len){
    //补全函数
    var dict='0123456789abcdefghigklmnopqstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ';
    var randomstr = '';
    for(var i=0;i<len;i++){
    randomstr += dict[Math.floor(Math.random()*dict.length) ];
    }
    return randomstr;
    }
    var str = getRandStr(10);
    console.log(str);

    4.写一个函数,生成一个随机 IP 地址,一个合法的 IP 地址为 0.0.0.0~255.255.255.255

    1
    2
    3
    4
    5
    6
    7
    function randomIP(len){
    var arr=[];
    for(var i=0;i<4;i++){
    arr.push(Math.floor(Math.random()*256) )
    }
    return arr.join('.')
    }

    5.写一个函数,生成一个随机颜色字符串,合法的颜色为#000000~ #ffffff

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function getRandColor(){
    var dict='0123456789abcdef'
    var str=''
    for(var i=0;i<6;i++){
    str += dict[Math.floor(Math.random()*16)]
    }
    return str = "#"+str;
    }
    var color = getRandColor()
    console.log(color)

    数组

    1.数组方法里push、pop、shift、unshift、join、splice分别是什么作用?用 splice函数分别实现push、pop、shift、unshift方法

    栈方法

    push和pop都为栈方法,index和length会自动改变,它能够让我们像堆栈那样先入后出使用数组.该方法会改变原来的数组。

    • push:
      使用push可以在数组最后添加一个或多个元素,并且返回数组的长度
      如:

      1
      2
      var arr=[2,3,4,5];
      arr.push(10);// [2,3,4,5,10]


      push可以用来给一个数组添加另一个数组的所有元素.
      如:

      1
      2
      3
      var str1= ['hello','my']
      var str2=['lovely','world']
      Array.prototype.push.apply(str1,str2);//返回长度4, 把str2中元素加给str1

    • pop:
      pop方法用来删除数组的最后一个元素,并且返回这个被删除的元素,该方法会改变原来的数组。
      1
      2
      var arr= ['bye','world'];
      arr.pop();//删除world

    队列方法

    shift和unshift是先入先出的模拟队列方法,index和length会自动改变。

    • shift
      shift方法相当于把数组整体往左移一位,这样就挤掉了第一个数,所以该方法可以删除数组中的第一个元素,也就是index最小的元素,并且后面元素的index和数组的length会自动减一。该方法会返回被删除的元素。
      1
      2
      var arr=[2,3,4,5];
      arr.shift();

    • unshift
      与shift对应的就是unshift,unshift方法相当于把数组往右移位(这样移动的前几位就空了),并且把括号里的数给空出来的那几位。所以unshift()
      方法可以在数组的开头添加一个或者多个元素,并返回数组新的 length 值。
      1
      2
      var arr=['you','are','so']
      arr.unshift('cool');

    join

    .join()方法是把数组元素(对象调用其toString()方法)使用参数作为连接符连接成一字符串,该方法不会修改原数组内容。其中作为连接符的参数可以指定,如果不写,默认就是逗号。

    1
    2
    3
    4
    var arr =['l','o','v','e','l','y','g','i','r','l'];
    arr.join('');
    arr.join();
    arr.join('-')

    split

    split后面括号里的参数会从字符串中被移除,返回存进一个数组当中的子字符串。如果忽略参数,则返回原字符串。如果参数是一个空字符串,则原字符串将被转换为由字符串中字符组成的一个数组。
    split()方法通过把字符串分割成子字符串来把一个 String对象分割成一个字符串数组。返回一个数组,这样分割之后就可以使用一些数组的方法。

    1
    2
    3
    4
    var str = 'hello my lovely girl';
    str.split();
    str.split('');
    str.split('l');

    用 splice 函数分别实现push、pop、shift、unshift方法

    • splice:splice()方法用于一次性解决数组添加、删除,会自动调整索引和length,该方法有三个参数,依次为:
      • 开始索引位置
        从数组的哪一位开始修改内容。如果超出了数组的最大索引值,则从数组末尾开始添加内容;如果是负值,则表示从数组末位开始的第几位。
        • 删除元素的位移
          表示要移除位数的个数。如果为0,则可以达到插入的效果,而第三个参数就是要插入的数。如果第二个参数大于从索引位置(第一个参数)开始之后的元素总数,那么从索引位置开始之后的元素都会删除(包含索引位置的元素)。
        • 插入的新元素,当然也可以写多个。若不写,就是删除元素。
          如例:
          1
          2
          3
          4
          5
          6
          var arr = [2,4,6,8]
          arr.splice(1,1)
          arr.splice(3,1,10)
          arr.splice(-1,1)
          arr.splice(1,0,7,9)
          arr.splice(2,5)

    1.png

    用splice实现push:
    1
    2
    3
    4
    function push(arr,val) {
    arr.splice(arr.length,0,val);
    return arr.length;
    }

    用splice实现pop:
    1
    2
    3
    4
    5
    function pop(arr){
    return arr.splice(arr.length-1,1[0])
    } //因为splice以数组的形式返回被删除的元素,而pop
    返回的是最后一个被删除的元素,为了保持返回类型一致,这里加入索引值.
    由于splice返回的数组中只有一个元素,所以[0]即是被删除的元素。

    用splice实现shift:
    1
    2
    3
    function shift(arr){
    return arr.splice(0,1)[0]
    }

    用splice实现unshift:
    1
    2
    3
    4
    function unshift(arr,val){
    arr.splice(0,0,val);
    return arr.length;
    }

    总结:

    1.push和unshift返回值是新数组的长度值,pop和shift返回的是被删除的元素(非数组形式).push和pop都是在数组元素末位加减,unshift和shift都是在数组元素首位加减.
    2.splice删除元素时,返回的是第一个参数索引值位置上的元素(数组形式),当第一个参数大于最大索引值返回空数组. splice插入元素时,在第一个参数索引值位置前面插入元素,返回的是空数组[].

    2、写一个函数,操作数组,数组中的每一项变为原来的平方,在原数组上操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function squareArr(arr){
    for(var i=0; i<arr.length;i++){ //遍历数组
    arr[i]= Math.pow(arr[i],2) //将每个位置上的值变为原来的平方
    }
    return arr;
    };
    var arr = [2, 4, 6]
    squareArr(arr)
    console.log(arr) // [4, 16, 36]

    3.写一个函数,操作数组,返回一个新数组,新数组中只包含正数,原数组不变

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function filterPositive(arr){
    var a = [];
    for(var i=0;i< arr.length;i++){
    if(typeof arr[i]==='number' && arr[i] > 0){
    a.push(arr[i]);
    }
    }
    return a;
    }
    var arr = [3, -1, 2, '饥人谷', true]
    var newArr = filterPositive(arr)
    console.log(newArr) //[3, 2]
    console.log(arr) //[3, -1, 2, '饥人谷', true]

    Date

    1、 写一个函数getChIntv,获取从当前时间到指定日期的间隔时间

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function getChIntv(dateStr){
    var targetDate = new Date(dateStr)
    var curDate = new Date()
    var offset = Math.abs(targetDate-curDate)
    var totalSecons = Math.floor(offset/1000)
    var second = totalSecons % 60
    var totalMinutes = Math.floor( totalSecons/60)
    var minutes = totalMinutes % 60
    var totalHours = Math.floor(totalMinutes/60)
    var hours = totalHours % 24
    var totalDays = Math.floor(totalHours/24)
    return totalDays + '天' + hours + '小时' + minutes + '分钟' + second + '秒'
    }
    var str = getChIntv("2017-02-08");
    console.log(str);

    2.把hh-mm-dd格式数字日期改成中文日期

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function getChsDate(timeStr){
    var dict = ['零','一','二','三','四','五','六','七','八','九','十','十一','十二','十三','十四','十五','十六','十七','十八','十九','二十','二十一','二十二','二十三','二十四','二十五','二十六','二十七','二十八','二十九','三十','三十一']

    var arr = timeStr.split('-') //["2015", "01", "08"]
    var year = arr[0] // "2015"
    var month = arr[1] // "01"
    var day = arr[2] // "08"

    var chYear = dict[parseInt(year[0])] + dict[parseInt(year[1])] + dict[parseInt(year[2])] + dict[parseInt(year[3])]
    var chMonth = dict[parseInt(month)]
    var chDay = dict[parseInt(day)]

    return chYear + '年' + chMonth + '月' + chDay + '日'
    }
    var str = getChsDate('2015-01-08');
    console.log(str); // 二零一五年一月八日

    3.写一个函数,参数为时间对象毫秒数的字符串格式,返回值为字符串。假设参数为时间对象毫秒数t,根据t的时间分别返回如下字符串:

    • 刚刚( t 距当前时间不到1分钟时间间隔)
    • 3分钟前 (t距当前时间大于等于1分钟,小于1小时)
    • 8小时前 (t 距离当前时间大于等于1小时,小于24小时)
    • 3天前 (t 距离当前时间大于等于24小时,小于30天)
    • 2个月前 (t 距离当前时间大于等于30天小于12个月)
    • 8年前 (t 距离当前时间大于等于12个月)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
        function friendlyDate(timeStr) {
      var nowTime = Date.now()//现在时间
      offset = (nowTime - timeStr)/1000/60 //相差分钟时间
      if (offset < 1) {
      return "刚刚"
      }else if(offset >= 1 && offset < 60) {
      return parseInt(offset) + "分钟前"
      }else if(offset >= 60 & offset < 24*60) {
      return parseInt(offset/60) + "小时前"
      }else if(offset >= 24*60 && offset < 60*24*30) {
      return parseInt(offset/60/24) + "天前"
      }else if(offset >= 60*24*30 && offset < 60*24*30*12) {
      return parseInt(offset/60/24/30) + "个月前"
      }else {
      return parseInt(offset/60/24/30/12) + "年前"
      }
      }
      var str = friendlyDate( '1484286699422' ) // 10个月前
      var str2 = friendlyDate('1483941245793') // 11个月前

    5 字符串与相关概念

    发表于 2017-12-06 | 分类于 JavaScript

    1、使用数组拼接出如下字符串 ,其中styles数组里的个数不定

    1
    <dl class="product"><dt>女装</dt><dd>短款</dd><dd>冬季</dd><dd>春装</dd></dl>

    方法1:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    var prod = {
    name: '女装',
    styles: ['短款', '冬季', '春装']
    };
    function getTpl(data){
    var dress = [];
    dress.push("<dl class=\"product\">");// .push给数组后面添加元素
    dress.push("<dt>"+data.name+"</dt>");
    for(var key in data.styles){
    dress.push("<dd>"+data.styles[key]+"</dd>")
    }
    dress.push("</dl>");
    var a = dress.join("");
    return a;
    };
    var result = getTpl(prod); //result为下面的字符串

    <dl class="product"><dt>女装</dt><dd>短款</dd><dd>冬季</dd><dd>春装</dd></dl>

    方法2:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function getTpl(data){
    var dress = "<dt class=\"product\">";
    for(var key in data){
    if(typeof data[key]==="string"){
    dress += ("<dt>"+data[key]+"</dt>")
    }else{
    for(var i=0, i<data[key].length; i++){
    dress += ("<dd>" + data[key][i] + "</dd>")
    }
    }
    }
    dress += "</dt>";
    return dress;
    console.log(dress)
    };

    2.写出两种以上声明多行字符串的方法

    字符串默认只能写在一行内,分成多行将会报错,如果需分成多行可以使用如下方法:

    • 方法一:在每一行的尾部使用反斜杠\,注意:反斜杠的后面必须是换行符,而不能有其他字符(比如空格),否则会报错。

      1
      2
      3
      4
      5
      var str = 'abcdeabcdeabcdean\
      cdeabcdeabcdeabcd\
      eancdeabcdeabcd\
      eabcdeancdeab\
      cdeabcdeabcdeancde'
    • 方法2:连接运算符(+)可以连接多个单行字符串,用来模拟多行字符串。

      1
      2
      3
      4
      5
      var str = 'abcdeabcdeabcdean'
      +'cdeabcdeabcdeabcd'
      +'eancdeabcdeabcd'
      +'eabcdeancdeab'
      +'cdeabcdeabcdeancde'
    • 方法3:利用多行注释,生成多行字符串的变通方法。

      1
      2
      3
      4
      5
      6
      var longString = (function() {/*
      abcdeabcdeabcdeancde
      abcdeabcdeabcdeancde
      abcdeabcdeabcdeancde
      abcdeabcdeabcdeancde
      */}).toString().split('\n').slice(1,-1).join('');

    3.补全如下代码,让输出结果为字符串:hello\\饥人谷

    1
    2
    var str = 'hello\\\\饥人谷';
    console.log(str);

    4.以下代码输出什么?为什么

    1
    2
    var str = 'jirengu\nruoyu';
    console.log(str.length); //13,因为字面量(转义序列)\n作为一个字符来解析。

    5.写一个函数,判断一个字符串是回文字符串,如 abcdcba是回文字符串, abcdcbb不是

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function plalindrome(str){
    var newStr = str.split('').reverse().join('')// 切割成数组/数组内元素反列/数组重新组合成字符串
    if(newStr === str){
    console.log('yes')
    }else{
    console.log('no')
    }
    }
    plalindrome('abcdcba');//yes
    plalindrome('abcdcbb');//no

    6.写一个函数,统计字符串里出现出现频率最多的字符

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    var dostring = "hello my world hohohoho."
    function maxChar(str) {
    var i, list={};
    for(i = 0; i < str.length; i++) {
    if(list[str[i]]) {
    ++list[str[i]];//list中有这个字符,出现次数加1
    }else{
    list[str[i]] = 1//没有这个字符,赋值为1
    }
    }
    var counts; //声明初始counts
    var maxValue = 0; //声明最大值
    for(var key in list){ //遍历
    if(list[key] > maxValue) { //遍历到的属性的值大于maxValue
    counts = key; //把该属性赋给counts
    maxValue = list[key]; //再把值赋给他
    }
    }
    console.log(counts,maxValue);
    }
    maxChar(dostring);

    7.写一个camelize函数,把my-short-string形式的字符串转化成myShortString形式的字符串,如

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    camelize("background-color") == 'backgroundColor'
    camelize("list-style-image") == 'listStyleImage'

    function camelize(str){
    var arr = str.split('-');
    for( var i=1;i<arr.length;i++){
    arr[i] = arr[i].replace(arr[i][0],arr[i][0].toUpperCase());//从第二个元素开始,将其首字母用大写代替,注意这里从i=1即第二个开始
    }
    return arr.join('');
    }
    var str = 'my-short-string'
    console.log(camelize(str))//myShortString

    8.写一个 ucFirst函数,返回第一个字母为大写的字符 (***)

    1
    ucFirst("hunger") == "Hunger"
    1
    2
    3
    4
    function ucFirst(str){
    return str = str.replace(str[0],str[0].toUpperCase());//用逗号后面的str[0].toUpperCase()代替前面的str[0]
    }
    ucFirst("hunger")

    9、写一个函数truncate(str, maxlength), 如果str的长度大于maxlength,会把str截断到maxlength长,并加上…,如

    1
    2
    truncate("hello, this is hunger valley,", 10) == "hello, thi...";
    truncate("hello world", 20) == "hello world"
    1
    2
    3
    4
    5
    6
    7
    8
    方法1:
    function truncate(str,maxlength){
    if(str.length > maxlength){
    return str.substr(0,maxlength)+'...';
    }
    return str;
    }
    truncate("hello, this is hunger valley,", 10)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    方法2:
    function truncate(str, maxlength){
    if(str.length > maxlength){
    var arr=str.split('').slice(0,maxlength);
    arr[maxlength]='...';
    console.log(arr.join(''));
    }else{
    console.log(str);
    }
    }

    10.什么是 JSON格式数据?JSON格式数据如何表示对象?window.JSON 是什么?

    • JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。是JavaScript的一个严格的子集但不从属于javascript,JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C, C++, C#, Java, JavaScript, Perl, Python等)。这些特性使JSON成为理想的数据交换语言。优点:易于人阅读和编写,同时也易于机器解析和生成(网络传输速度)。

    • JSON对象,表示一组无序的键值对,而每个键值对中的值可以是简单值也可以是复杂数据类型的值,基本要符合以下规则.

      • 复合类型的值只能是数组或对象,不能是函数、正则表达式对象、日期对象。
      • 简单类型的值只有四种:字符串、数值(必须以十进制表示)、布尔值和null(不能使用NaN, Infinity, -Infinity和undefined)。
      • 字符串必须使用双引号表示,不能使用单引号。
      • 对象的键名必须放在双引号里面。
      • 数组或对象最后一个成员的后面,不能加逗号

      • JSON 语法是 JavaScript 对象表示语法的子集。

        • 数据在名称/值对中:名称/值对组合中的名称写在前面(在双引号中),值对写在后面(同样在双引号中),中间用冒号:隔开
        • 数据由逗号分隔
        • 花括号保存对象
        • 方括号保存数组名称/值对
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          //js中的对象
          var person = {
          name : "jirengu" ,
          age : 28
          };
          //JSON中
          var person = { //花括号保存对象
          "name" : "jirengu", //字符串和键名要在双引号里
          "age" : 28 //最后一行不能加逗号
          }
          var person2 = [ //方括号保存数组
          {"name" : "jirengu", "age" : 28 }
          {"name" : "jirengu2", "age" : 25 }
          ]
    • window.JSON:

      • window.JSON是ECMAScript 5定义的一个原生的浏览器内置对象,用来检测对JSON的支持情况;
      • JSON对象内置了JSON.parse()和JSON.stringify()方法;
      • 当HTML页面指定了DOCTYPE且浏览器模式为IE8时,才支持内置的window.JSON对象,IE8版本以上才内置支持JSON.parse()函数方法。
        1
        2
        3
        4
        if(window.JOSN){
        jsonObj2 = JSON.parse(json);
        }else{
        }

    11、如何把JSON 格式的字符串转换为 JS 对象?如何把 JS对象转换为 JSON 格式的字符串?

    • parse()用于从一个JSON 格式字符串中解析出js对象,如

      1
      2
      3
      4
      5
      var str = '{"name":"huangxiaojian","age":"23"}'
      JSON.parse(str)
      //
      {name: "haha",age: "23"}
      注意:单引号写在{}外,每个属性名都必须用双引号,否则会抛出异常。
    • stringify()用于从一个JS对象解析出JSON 格式字符串,如

      1
      2
      3
      4
      5
      var
      a = {a:1,b:2}
      JSON.stringify(a)
      //
      "{"a":1,"b":2}"

    4 引用类型对象拷贝

    发表于 2017-12-04 | 分类于 JavaScript

    1.javascript引用类型有哪些?非引用类型有哪些?

    引用类型:对象、数组、函数、正则表达式,指保存在堆内存中的对象,变量中保存的实际上是一个指针,这个指针指向内存中的另一个位置,由该位置保存对象。
    非引用类型: 数值、布尔、字符串、null、undefined,这些是基本类型,指的事保存在栈内存中的简单数据段。

    2.如下代码输出什么?为什么?

    1
    2
    3
    4
    5
    6
    var obj1 = {a:1, b:2};
    var obj2 = {a:1, b:2};
    console.log(obj1 == obj2);//输出false 因为obj1和obj2指向不同的地址
    console.log(obj1 = obj2);//输出{a:1,b:2} obj1 = obj2赋值操作,
    也就是obj2将储存在变量对象中的值(即指向对象位置的指针)复制了一份放到obj1的内存空间中,这时obj1和obj2都指向堆内存里的同一个对象
    console.log(obj1 == obj2);//输出true 因为上一步的操作obj1和obj2指向了同一个地址

    3.如下代码输出什么?为什么?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    var a = 1
    var b = 2
    var c = { name: '饥人谷', age: 2 }
    var d = [a, b, c]

    var aa = a
    var bb = b
    var cc = c//复制的是指针
    var dd = d//复制的是指针

    a = 11
    b = 22
    c.name = 'hello'
    d[2]['age'] = 3

    console.log(aa) //输出1, a为基本类型值,var aa=a只是将a的值赋给1,a和aa指向不同的栈内存位置,互不影响
    console.log(bb) //输出2, 同上
    console.log(cc) //输出{name:'hello', age: 3},c为引用类型值,var cc = c是将c指向堆内存的指针复制给cc,所以cc和c引用的是同一个对象,
    对象c.name='hello',name改变,所以cc同样改变, d[2]['age'] = 3同样age也变为3
    console.log(dd) //输出[1,2,{name: 'hello', age: 3}], d为引用类型值,var dd=d是指d指向堆内存的指针复制给dd,a和b是基本类型值按值传递,d引用类型的值改变,dd也相应改变.

    4.如下代码输出什么? 为什么?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    var a = 1
    var c = { name: 'jirengu', age: 2 }

    function f1(n){
    ++n
    }
    function f2(obj){
    ++obj.age
    }

    f1(a)
    f2(c)
    f1(c.age)
    console.log(a) // 输出1, 因为f1(a)想当于声明了一个var n=a,把a的值1赋给了n,++n将n值加1,
    它们在不同的栈内存位置中互不影响,所以a值不改变,最后输出1
    console.log(c)//输出{ name: 'jirengu', age:3 } f2(c)相当于在f2(obj)中声明一个 var obj=c,将c的指向堆内存的指针复制给obj,
    ++obj.age,age加1,同样改变了c.age为3

    5.过滤如下数组,只保留正数,直接在原数组上操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    var arr = [3,1,0,-1,-3,2,-5]
    function filter(arr){
    }
    filter(arr)
    console.log(arr) // [3,1,2]

    如下:
    var arr = [3,1,0,-1,-3,2,-5]
    function filter(arr) {
    for (var i = 0; i < arr.length; i++) {
    if (arr[i] <= 0) {
    arr.splice(i, 1);//从第i个开始,删除个数为1,即删除数组第i个元素
    i--//删除之后数组长度缩减 ,原位置的元素被新元素代替所以需减1
    }
    }
    return arr
    }
    console.log(arr) // [3,1,2]

    6.过滤如下数组,只保留正数,原数组不变,生成新数组

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    var arr = [3,1,0,-1,-3,2,-5]
    function filter(arr){
    }
    var arr2 = filter(arr)
    console.log(arr2) // [3,1,2]
    console.log(arr) // [3,1,0,-1,-3,2,-5]

    如下:
    var arr = [3,1,0,-1,-3,2,-5]
    function filter(arr){
    var arr2=[]// 将[]的指向堆内存的指针复制给arr2,这个堆内存的位置与arr指向的堆内存不一样
    for(var i=0;i<arr.length;i++){
    arr2[i] =arr[i]
    }
    for(var i =0; i<arr.length;i++){
    if(arr2[i]<=0){
    arr2.splice(i,1)
    i--
    }
    }
    return arr2
    }
    var arr2 = filter(arr)
    console.log(arr2) // [3,1,2]
    console.log(arr) // [3,1,0,-1,-3,2,-5]

    7.写一个深拷贝函数,用两种方式实现

    深拷贝:在对含有指针成员的对象进行拷贝时,必须要自己定义拷贝构造函数,使拷贝后的对象指针成员有自己的内存空间,即进行深拷贝

    • 方法1:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      var obj={
      name:'fang',
      friend:{
      name:'haha',
      age:18,
      sex:'female'
      }
      }

      function copy(obj){
      var newObj={}
      for(var key in obj){
      if(obj.hasOwnProperty(key)){
      if(typeof obj[key]==='number'||typeof obj[key]==='boolean'||typeof obj[key]==='string'||obj[key]===undefined
      ||obj[key]===null){
      newObj[key]=obj[key]
      }else{
      newObj[key]=copy(obj[key])
      }
      }
      }
      return newObj
      }
      var obj2= copy(obj)
      console.log(obj2)
    • 方法2:

      1
      2
      3
      4
      5
      6
      7
      function copy(obj){
      var newObj=JSON.parse(JSON.stringify(obj))// JSON.stringify(obj)把对象转化为字符串,
      JSON.parse(JSON.stringify(obj))把字符串重新生成一个对象
      return newObj
      }
      var obj2= copy(obj)
      console.log(obj2)

    3 函数与作用域

    发表于 2017-12-04 | 分类于 JavaScript

    1.函数声明和函数表达式有什么区别?

    1.函数声明:function functionName (){}

    函数声明(Function Declaration) 可以定义命名的函数变量,而无需给变量赋值。Function Declaration 是一种独立的结构,不能嵌套在非功能模块中。可以将它类比为 Variable Declaration(变量声明)。就像 Variable Declaration 必须以“var”开头一样,Function Declaration 必须以“function”开头。

    2.函数表达式: var fn = function (){}

    函数表达式(Function Expression)将函数定义为表达式语句(通常是变量赋值)的一部分。通过 函数表达式定义的函数可以是命名的,也可以是匿名的。函数表达式不能以“function”开头

    区别:用函数声明定义的函数,函数可以在函数声明之前调用,声明不必放在调用的前面。而用函数表达式定义的函数只能在声明之后调用。【根本原因在于解析器对这两种定义方式读取的顺序不同:解析器会事先读取函数声明,即函数声明放在任意位置都可以被调用;对于函数表达式,解析器只有在读到函数表达式所在那行的时候才解析声明并执行】

    2.什么是变量的声明前置?什么是函数的声明前置?

    • 变量声明前置:只是提升声明部分(未赋值状态),赋值部分保持原位置不变。
      实例:
      1
      2
      3
      4
      5
      6
      function say(){
      console.log(name);
      var name='tom';
      console.log(name);
      }
      say();

    上面式子可视为

    1
    2
    3
    4
    5
    6
    7
    function say(){
    var name;//变量name声明前置,由于name未赋值,故为undefined
    console.log(name);//存在局部name,则无视全局name
    name='tom';//变量赋值保持原位,变量name被赋值为'tom'
    console.log(name);//输出'tom'
    }
    say();//输出结果为 undefined tom

    • 函数声明前置:会将函数的声明和定义全都提升至作用域顶部。
      实例:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      var say = function(){
      console.log('1');
      };

      function say(){
      console.log('2');
      };

      say();

    上面式子可视为

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var say; //变量声明前置

    function say(){
    console.log('2');
    } //函数声明前置

    say = function(){ //变量赋值保持原位执行,say函数被覆盖
    console.log('1');
    };

    say(); //输出'1'

    注意:函数的声明优先级高于变量的声明。

    3.arguments是什么?

    arguments 是一个类数组对象。代表传给一个function的参数列表。

    • arguments length属性
      arguments 是个类数组对象,其包含一个 length 属性,可以用 arguments.length 来获得传入函数的参数个数。

    示例:

    1
    2
    3
    4
    5
    6
    function func() {
    console.log( arguments.length);
    }
    func();// 0
    func(1, 2);// 2
    func(1, 2, 3);// 3

    • arguments 转数组

    通常使用下面的方法来将 arguments 转换成数组:
    Array.prototype.slice.call(arguments);
    还有一个更简短的写法:
    [].slice.call(arguments);

    参考文章

    4.函数的”重载”怎样实现?

    首先申明,在 JS 中没有重载! 同名函数会覆盖。 但可以在函数体针对不同的参数调用执行相应的逻辑。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function printPeopleInfo(name, age, sex){
    if(name){
    console.log(name);
    }

    if(age){
    console.log(age);
    }

    if(sex){
    console.log(sex);
    }
    }
    printPeopleInfo('Byron', 26);//输出结果byron 26
    printPeopleInfo('Byron', 26, 'male');//输出结果byron 26 male

    5.立即执行函数表达式是什么?有什么作用?

    • 立即执行函数表达式

      1
      2
      3
      4
      var i = function(){ return 10; }();
      true && function(){ /* code */ }();
      0, function(){ /* code */ }();
      (function(){ /* code */ }());
    • 立即执行函数表达式的作用:
      1.不必为函数命名,避免了污染全局变量;
      2.立即执行函数表达式内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量,隔离作用域。

    6.求n!,用递归来实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function factor(n){
    if (n < 0 ) {
    return "wrong";
    }
    else if (n === 1 || n === 0) {
    return 1;
    }
    else {
    return n * factor(n-1);
    };

    factor(5) //120

    7.以下代码输出什么?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function getInfo(name, age, sex){
    console.log('name:',name);
    console.log('age:', age);
    console.log('sex:', sex);
    console.log(arguments);
    arguments[0] = 'valley';//将vally赋给第一个参数name
    console.log('name', name);
    }

    getInfo('饥人谷', 2, '男');
    getInfo('小谷', 3);
    getInfo('男');

    输出结果

    1
    2
    3
    4
    5
    name: 饥人谷
    age: 2
    sex: 男
    ["饥人谷", 2, "男"]
    name valley

    1
    2
    3
    4
    5
    name: 小谷
    age: 3
    sex: undefined
    ["小谷",3]
    name valley
    1
    2
    3
    4
    5
    name: 男
    age: undefined
    sex: undefined
    ["男"]
    name valley

    8.写一个函数,返回参数的平方和

    1
    2
    3
    4
    5
    6
    function sumOfSquares(){
    }
    var result = sumOfSquares(2,3,4)
    var result2 = sumOfSquares(1,3)
    console.log(result) //29
    console.log(result2) //10

    如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function sumOfSquares(){
    var i =arguments.length
    var sum=0
    for(var n=0;n<i;n++){
    sum=sum+arguments[n]*arguments[n]
    }
    return sum
    }
    var result = sumOfSquares(2,3,4)
    var result2 = sumOfSquares(1,3)
    console.log(result)
    console.log(result2)

    9.如下代码的输出?为什么

    1
    2
    3
       console.log(a);
    var a = 1;
    console.log(b);

    声明前置:

    1
    2
    3
    4
    var a ;//变量声明前置
    console.log(a);//输出undefined
    a=1;
    console.log(b);//b没有声明,报错 Uncaught ReferenceError: b is not defined

    10.如下代码的输出?为什么

    1
    2
    3
    4
    5
    6
    7
    8
       sayName('world');
    sayAge(10);
    function sayName(name){
    console.log('hello ', name);
    }
    var sayAge = function(age){
    console.log(age);
    };
    1
    2
    3
    4
    5
    6
    7
    8
       function sayName(name){
    console.log('hello ', name);
    }//函数声明前置
    sayName('world');//输出结果为hello world
    sayAge(10);//报错 sayAge is not a function
    var sayAge = function(age){
    console.log(age);
    };//函数表达式须放在调用前面,因为函数表达式只有执行到这一行才会去声明

    11.如下代码输出什么? 写出作用域链查找过程伪代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    var x = 10
    bar()
    function foo() {
    console.log(x)
    }
    function bar(){
    var x = 30
    foo()
    }

    /* 1.
    globalContext = {
    AO:{
    x:10
    foo:function
    bar:function
    },
    Scope:null
    }

    foo.[[scope]] = globalContext.AO
    bar.[[scope]] = globalContext.AO

    2.调用bar()
    barContext = {
    AO:{
    x:30
    },
    Scope:bar.[[scope]] = globalContext.AO
    }
    x变成30

    3.调用foo()
    fooContext = {
    AO:{},
    Scope:foo.[[scope]] = globalContext.AO
    }
    输出10
    */

    12.如下代码输出什么? 写出作用域链查找过程伪代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    var x = 10;
    bar()
    function bar(){
    var x = 30;
    function foo(){
    console.log(x)
    }
    foo();
    }


    /*
    1.
    globalContext={
    AO:{
    x:10
    bar:function
    }
    scope: null
    }

    bar.[[scope]]=globalContext.AO

    2.调用函数bar()
    barContext={
    AO:{
    x:30
    foo:function
    }
    scope: bar.[[scope]]=globalContext.AO
    }

    foo.[[scope]]=barContext.AO

    3.调用函数foo()
    fooContext={
    AO:{}
    }
    scope: foo.[[scope]]=barContext.AO

    输出30
    */

    13.如下代码输出什么? 写出作用域链查找过程伪代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    var x = 10;
    bar()
    function bar(){
    var x = 30;
    (function (){
    console.log(x)
    })()
    }

    /*
    1.
    globalContext={
    AO:{
    X:10
    bar:function
    }
    scope: null
    }

    bar.[[scope]]=globalContext.AO

    2.调用bar()
    barContext={
    AO:{
    X:30
    function
    }
    scope: bar.[[scope]]=globalContext.AO
    }

    function.[[scope]]=barContext.AO

    3.调用立即执行函数
    fonctionContext={
    AO:{}
    scope:function.[[scope]]=barContext.AO
    }

    输出30
    */

    14.1以下代码输出什么? 写出作用域链查找过程伪代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    var a = 1;

    function fn(){
    console.log(a)
    var a = 5
    console.log(a)
    a++
    var a
    fn3()
    fn2()
    console.log(a)

    function fn2(){
    console.log(a)
    a = 20
    }
    }

    function fn3(){
    console.log(a)
    a = 200
    }

    fn()
    console.log(a)

    /*
    1.
    globalContext={
    AO:{
    a:1/200
    fn:function
    fn3:function
    }
    scope:none

    fn.[[scope]]=globalContext.AO
    fn3.[[scope]]=globalContext.AO
    }
    2.调用函数fn()
    fnContext={
    AO:{
    a:undefined/5/6/20
    fn2:function
    }
    scope: fn.[[scope]]=globalContext.AO
    }

    fn2.[[scope]]=fnContext.AO

    3.调用函数fn3()
    fn3Context={
    AO:{}
    scope:fn3.[[scope]]=globalContext.AO
    }

    4.调用函数fn2()
    fn2Context={
    AO:{}
    scope:fn2.[[scope]]=fnContext.AO
    }


    开始执行:
    console.log(a)//fn()变量初始值为undefined,所以输出undefined
    var a=5//fncontext.AO中的a赋值为5
    console.log(a)//输出5
    a++//fnContext.AO中的a赋值为加1为6
    var a//声明a赋值不变
    fn3()//调用fn3()
    执行fn3():
    console.log(a)//fn3.[[scope]]=globalContext.AO,因为fn3Context.AO中没有a值,所以
    去globalContext.AO找,a值为1,所以输出1
    a = 200// globalContext.AO中a赋值为200
    执行完fn3,跳出fn3,继续执行fn中的fn2()
    fn2()//调用执行fn2
    console.log(a)//此时fnContext.AO中的a赋值为6,所以输出6
    a=20// fnContext.AO中的a赋值为20
    跳出fn2,继续执行fn
    console.log(a)//此时fnContext.AO中的a赋值为20,输出20
    此时fn执行结束,继续执行
    console.log(a)//此时globalContext.AO中a赋值为200

    综上输出结果:undefined 5 1 6 20 200
    */
    1234
    Nie

    Nie

    Fair to be late, but never absent

    32 日志
    4 分类
    GitHub
    © 2017 — 2018 Nie
    博客
    |
    主题 — v5.1.4