Fork me on GitHub

CORS

什么是CORS

跨域资源共享(CORS) 是一种机制,当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。出于安全原因,浏览器限制从脚本内发起的跨源HTTP请求。 例如,XMLHttpRequest和Fetch API遵循同源策略。 这意味着使用这些API的Web应用程序只能从加载应用程序的同一个域请求HTTP资源,除非响应报文包含了正确CORS响应头。

跨域资源共享( CORS )机制允许 Web 应用服务器进行跨域访问控制,从而使跨域数据传输得以安全进行。现代浏览器支持在 API 容器中(例如 XMLHttpRequest 或 Fetch )使用 CORS,以降低跨域 HTTP 请求所带来的风险。

什么情况下需要 CORS ?

跨域资源共享标准( cross-origin sharing standard )允许在下列场景中使用跨域 HTTP 请求:

  • XMLHttpRequestFetch 发起的跨域 HTTP 请求;
  • Web 字体 (CSS 中通过 @font-face 使用跨域字体资源);
  • WebGL;
  • canvas的 drawImage 将 Images/video 画面绘制到 canvas

CORS流程

跨域资源共享标准新增了一组 HTTP 首部字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。另外,规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP 认证相关数据)。

简单请求

某些请求不会触发 CORS 预检请求。本文称这样的请求为“简单请求”。若请求满足所有下述条件,则该请求可视为“简单请求”:

  • 使用下列方法之一:
    GET
    HEAD
    POST

  • Fetch 规范定义了对 CORS 安全的首部字段集合,不得人为设置该集合之外的其他首部字段。该集合为:
    Accept
    Accept-Language
    Content-Language
    Content-Type (需要注意额外的限制)
    DPR
    Downlink
    Save-Data
    Viewport-Width
    Width

  • Content-Type 的值仅限于下列三者之一:
    text/plain
    multipart/form-data
    application/x-www-form-urlencoded

  • 请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。

  • 请求中没有使用 ReadableStream 对象。

预检请求

“需预检的请求”(preflight)要求必须首先使用 OPTIONS 方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。”预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。

当请求满足下述任一条件时,即应首先发送预检请求:

  • 使用了下面任一 HTTP 方法:
    PUT
    DELETE
    CONNECT
    OPTIONS
    TRACE
    PATCH

  • 人为设置了对 CORS 安全的首部字段集合之外的其他首部字段。该集合为:
    Accept
    Accept-Language
    Content-Language
    Content-Type (需要注意额外的限制)
    DPR
    Downlink
    Save-Data
    Viewport-Width
    Width

  • Content-Type 的值不属于下列之一:
    application/x-www-form-urlencoded
    multipart/form-data
    text/plain

  • 请求中的XMLHttpRequestUpload 对象注册了任意多个事件监听器。

  • 请求中使用了ReadableStream对象。

附带身份凭证的请求

XMLHttpRequest 的 withCredentials 标志设置为 true,从而向服务器发送 Cookies

** 附带身份凭证的请求与通配符**
对于附带身份凭证的请求,服务器不得设置 Access-Control-Allow-Origin 的值为“*”。

这是因为请求的首部中携带了 Cookie 信息,如果 Access-Control-Allow-Origin 的值为“*”,请求将会失败。而将 Access-Control-Allow-Origin 的值设置为 http://foo.example,则请求将成功执行。

HTTP 响应首部字段

Access-Control-Allow-Origin: <origin> | *

其中,origin 参数的值指定了允许访问该资源的外域 URI。对于不需要携带身份凭证的请求,服务器可以指定该字段的值为通配符,表示允许来自所有域的请求。

Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header

在跨域访问时,XMLHttpRequest对象的getResponseHeader()方法只能拿到一些最基本的响应头,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,如果要访问其他头,则需要服务器设置本响应头。如上面例子,浏览器就能够通过getResponseHeader访问X-My-Custom-HeaderX-Another-Custom-Header 响应头了。

Access-Control-Max-Age: <delta-seconds>
该头指定了preflight请求的结果能够被缓存多久,delta-seconds 参数表示preflight请求的结果在多少秒内有效。

Access-Control-Allow-Credentials: true
该头指定了当浏览器的credentials设置为true时是否允许浏览器读取response的内容。当用在对preflight预检测请求的响应中时,它指定了实际的请求是否可以使用credentials。请注意:简单 GET 请求不会被预检。

Access-Control-Allow-Methods: <method>[, <method>]*

该字段用于预检请求的响应。其指明了实际请求所允许使用的 HTTP 方法。

Access-Control-Allow-Headers: <field-name>[, <field-name>]*

该首部字段用于预检请求的响应。其指明了实际请求中允许携带的首部字段。

HTTP 请求首部字段

以下是可用于发起跨域请求的首部字段。这些首部字段无须手动设置。

Origin: <origin>
此字段表明预检请求或实际请求的源站。值为源站 URI,不包含任何路径信息,只是服务器名称。在所有访问控制请求中,Origin 字段总是被发送。
有时候将该字段的值设置为空字符串是有用的,例如,当源站是一个 data URL 时。

Access-Control-Request-Method: <method>
此字段用于预检请求。其作用是,将实际请求所使用的 HTTP 方法告诉服务器。

Access-Control-Request-Headers: <field-name>[, <field-name>]*
此字段用于预检请求。其作用是,将实际请求所携带的首部字段告诉服务器。

常见服务器端nginx的cors配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
location / {
set $origin '';
if ($http_origin ~ "^https://www.test.com$") {
set $origin $http_origin;
}
if ($http_origin ~ "^https://test.com$") {
set $origin $http_origin;
}
if ($request_method = OPTIONS ) {
add_header 'Access-Control-Allow-Origin' $origin;
add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS';
add_header 'Access-Control-Max-Age' '1728000';
add_header 'Content-Length' '0';
add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
add_header 'Content-Type' 'text/plain';
return 204;
}
add_header 'Access-Control-Allow-Origin' $origin;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Max-Age' '1728000';
add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
add_header 'Access-Control-Expose-Headers' 'Connection,Content-Encoding';
}

Apache CORS

限制对某些URI的访问
最有效的方法之一,利用Apache rewrite, 环境变量,还有headers使Access-Control-Allow-* 对某些特定的URI生效,比如,以无认证信息形式利用GET跨域请求api(.*).json。

1
2
3
4
RewriteRule ^/api(.*)\.json$ /api$1.json [CORS=True]
Header set Access-Control-Allow-Origin "*" env=CORS
Header set Access-Control-Allow-Methods "GET" env=CORS
Header set Access-Control-Allow-Credentials "false" env=CORS

后端PHP的CORS snippets

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
<?php 
if($_SERVER['REQUEST_METHOD'] == "GET"){
header('Content-Type: text/plain');
echo "This HTTP resource is designed to handle POSTed XML input from arunranga.com and not be retrieved with GET";

}
elseif($_SERVER['REQUEST_METHOD'] == "OPTIONS"){
// 告诉客户端我们支持来自 test.com 的请求并且预请求有效期将仅有20天
if($_SERVER['HTTP_ORIGIN'] == "https://test.com"){
header('Access-Control-Allow-Origin: https://test.com');
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
header('Access-Control-Allow-Headers: X-TOKEN');
header('Access-Control-Max-Age: 1728000');
header("Content-Length: 0");
header("Content-Type: text/plain");
//exit(0);
}
else{
header("HTTP/1.1 403 Access Forbidden");
header("Content-Type: text/plain");
echo "You cannot repeat this request";
}
}
elseif($_SERVER['REQUEST_METHOD'] == "POST") {
/* 通过首先获得XML传送过来的blob来处理POST请求,然后做一些处理, 最后将结果返回客户端
*/
if($_SERVER['HTTP_ORIGIN'] == "https://test.com") {
$postData = file_get_contents('php://input');
$document = simplexml_load_string($postData);

// 对POST过来的数据进行一些处理

$ping = $_SERVER['HTTP_X_TOKEN'];


header('Access-Control-Allow-Origin: https://test.com');
header('Content-Type: text/plain');
echo // 处理之后的一些响应
}
else
die("POSTing Only Allowed from arunranga.com");
}
else
die("No Other Methods Allowed");

?>

Eslint入门介绍

Lint 的含义

  • 提供编码规范;
  • 提供自动检验代码的程序,并打印检验结果:告诉你哪一个文件哪一行代码不符合哪一条编码规范,方便你去修改代码

Eslint 的含义

Lint 是检验代码格式工具的一个统称,具体的工具有 Jslint 、 Eslint 等等 ………..

使用 Eslint

创建项目并安装依赖包
npm init 指令会在项目根目录下生成 package.json 文件。

1
2
3
4
mkdir lint_test
cd lint_test
npm install
npm install eslint --save-dev

–save-dev 会把 eslint 安装到 package.json 文件中的 devDependencies 属性中,意思是只是开发阶段用到这个包,上线时就不需要这个包了。

设置 package.json 文件

打开 package.json 文件,修改 script 属性如下:

1
2
3
4
5
"scripts": {
"test": "react-scripts test --env=jsdom",
"lint": "eslint src",
"lint:create": "eslint --init"
}
  • scripts 属性的意思是脚本,使用方法是在 cmd 窗口中输入 npm run 指令 的形式,如:npm run lint:create
  • “lint:create”: “eslint –init” 这个脚本是为了生成 .eslintrc.js 文件;
  • “lint”: “eslint src” Lint 自动检验 src 目录下所有的 .js 文件。

创建 .eslint.js 文件

1
npm run lint:create

创建完成后根目录下应该会出现 .eslintrc.js 文件

创建代码并校验代码

1
npm run lint

设置 –fix 参数

“lint”: “eslint src –fix”, 加上 –fix 参数,是 Eslint 提供的自动修复基础错误的功能。

配置文件讲解

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
// .eslintrc.js 
module.exports = {
"env": {
"browser": true,
"commonjs": true,
"es6": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaFeatures": {
"experimentalObjectRestSpread": true,
"jsx": true
},
"sourceType": "module"
},
"plugins": [
"react"
],
"rules": {
"indent": [
"error",
"tab"
],
"linebreak-style": [
"error",
"windows"
],
"quotes": [
"error",
"double"
],
"semi": [
"error",
"always"
]
}
}
environments - 指定脚本的运行环境。每种环境都有一组特定的预定义全局变量。
Globals - 脚本在执行期间访问的额外的全局变量。
rules

ESLint 附带有大量的规则,修改规则应遵循如下要求:

  • “off” 或 0 - 关闭规则
  • “warn” 或 1 - 开启规则,使用警告级别的错误:warn (不会导致程序退出)
  • “error” 或 2 - 开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出)

全局变量配置

如使用 window 对象,默认情况下会报 no-undef 的错误,需要在 .eslintrc 中进行相应配置。

1
2
3
4
5
6
7
8
{
"rules": {
// ...
},
"globals": {
"window": true
}
}

单行跳过 lint 校验

在实际编码时,可能会出现以下的代码。

1
2
3
4
5
6
const apple = "apple";
const balana = "balana";

module.exports = {
fruit: balana ;
}

在最上面定义了两个变量,在底部的配置文件中只可能用到其中一个变量,另一个用不到的在 eslint 校验时就会报错 no-unused-vars 的错误,意思是变量定义了但是没有被用到。

其中一种解决方案是在 .eslintrc 文件中配置 rules no-unused-vars: 0,意思是项目中不检验变量定义未使用这条规则。强烈不建议这样做,这个规则十分有用,可以规避编写代码时遗漏的变量。

另一种解决方案就是使用行内注释跳过对 apple 和 balana 两个变量跳过 eslint 校验,只影响这两个变量,不影响外部。

1
2
3
4
5
6
const apple = "apple";  // eslint-disable-line
const balana = "balana"; // eslint-disable-line

module.exports = {
fruit: balana ;
}

此时使用 eslint 校验时就不会再报错了。

常见规则含义解释

  • object-shorthand 设置该规则,表示对象属性要简写。

    1
    2
    3
    4
    var foo = {x: x};    // 会报错
    var bar = {a: function () {}}; // 会报错
    var foo = {x}; // 不会报错
    var bar = {a () {}}; // 不会报错
  • prefer-arrow-callback 要求回调函数使用箭头函数

1
2
3
4
5
6
7
// 回调函数,函数的参数是个函数,这个参数函数就是回调函数
funciton bar () {}
// 不是回调函数,不会报错
// setTimeout 的第一个参数就是回调函数,不用箭头函数会报错
setTimeout(() => {
// .......
}, 1000)
  • no-param-reassign 禁止对函数的参数重新赋值
1
2
3
4
5
6
7
8
9
function bar ({ data = {} }) {
data.num = 12; // 会报错
}
/*
虽然报错,又不想要将该校验关闭,如果代码中只有 data 这个属性有这种情形,
可以在 .eslintrc.js 中的 rules 属性中添加配置(可忽略 data 属性做此校验):
'no-param-reassign': ['error', { 'props': true, 'ignorePropertyModificationsFor': ['data'] }],
'no-param-reassign': 0, // 关闭该属性校验
*/
  • no-trailing-spaces 禁止行尾空格

  • no-shadow 禁止变量声明与外层作用域的变量同名

1
2
3
function sum (num) {
let num = 2; // 报错,因为 num 变量作为参数已经申明过了
}

常用的几个规则

1
2
3
4
"quotes": [1, "single"],            # 单引号
"quote-props":[2, "as-needed"], # 双引号自动变单引号
"semi": [2, "never"], # 一行结尾不要写分号
"comma-dangle": [1,"always-multiline"] # 对象或数组多行写法时,最后一个值加逗号

二进制

前言:

bit(位):数据存储的最小单元。在计算机二进制系统中,位,简记为b,也称为比特(bit),每个二进制数字0或1就是一个位(bit),其中每 8bit = 1 byte(字节);

Java中的int数据类型占4个byte(字节),而1 byte(字节) = 8 bit(位);(说白了,在二进制系统中是以bit 作为数据存储单元的)

无符号数和有符号数

在计算器中参与运算的数有两大类:无符号数和有符号数

(1)有符号数:
 对于有符号数而言,符号的正、负机器是无法识别的,但由于“正、负”恰好是两种截然不同的状态,如果用“0”表示“正”,用“1”表示“符”,这样符号也被数字化了,
 并且规定将它放在有效数字的前面,即组成了有符号数。所以,在二进制中使用最高位(第一位)来表示符号,最高位是0,表示正数;最高位是1,表示负数。

(2)无符号数:
 无符号数是针对二进制来讲的,无符号数的表数范围是非负数。全部二进制均代表数值(所有位都用于表示数的大小),没有符号位。即第一个"0"或"1"不表示

例子:

(1)在Java中int数据类型是怎么在计算机中表示的呢?

 假设 int  number = 1 ,那么number在计算机系统中将表示如下(32bit):

 00000000  00000000  00000000  00000001

 同理可得,number=-1 时,在二进制中表示如下:

 10000000  00000000  00000000  00000001

 注意:最高位(第一位)是符号位,因为是number值为1是一个正数,所以最高位为0;



(2)二进制转十进制?

 要从右到左用二进制的每个数去乘以2的相应次方(次方要从0开始算起);

 假如:二进制数1101转化成十进制 ,那么 1101 = 1*2^0+0*2^1+1*2^2+1*2^3 = 1+0+4+8 = 13;

 注意:任何数的0次方都是1。
二进制中的原码、反码、补码
在计算机内,定点数有3种表示法:原码、反码和补码
原码:就是二进制定点表示法,即最高位为符号位,“0”表示正,“1”表示负,其余位表示数值的大小。
反码:表示法规定:正数的反码与其原码相同;负数的反码是对其原码逐位取反,但符号位除外。
补码:表示法规定:正数的补码与其原码相同;负数的补码是在其反码的末位加1。

对于有符号数而言:

(1)二进制的最高位是符号位:0表示正数,1表示负数

(2)正数的原码、反码、补码都一样;

(3)负数的反码 =  它的原码符号位不变,其他位取反(0 ->1 ; 1->0 );

(4)负数的补码 = 它的反码 +1;

(5)0的反码、补码都是0;

(6)在计算机运算的时候,都是以补码的方式来运算的;
负数的二进制

负数的原码 = (十进制的相反数),按位取反, 然后 + 1;
负数的反码 = 它的原码符号位不变,其他位取反(0 ->1 ; 1->0 )
负数的补码 = 它的反码 +1;

例如:-7的二进制等于7的二进制按位取反,然后+1

下面以8位为例:

第一步:

  7的二进制:0000 0111

第二步:

  7的二进制(取反):1111 1000

第三步:

  取反后加1:11111001

所以-7的二进制是:1111 1001

那么怎么求一个二进制负数的十进制数?把上面的步骤反过来就是了

一个负数:11111001

第一步:

  减一:11111000

第二步:

  按位取反:00000111

第三步:

  计算然后取相反数:-(12^2 + 12^1 + 1*2^0) = -7

计算结果是-7,

那么这个二进制数的十进制就是:-7

八进制、十六进制、十进制、二进制

十进制负数转八进制、十六进制

负数转换成八进制、十六进制,只需在补码(二进制)的基础上,3位合成一位计算,或者4位合成一位计算

-3的转换成二进制为:

1111 1111 1111 1111 1111 1111 1111 1101

八进制则将-3的二进制从右至左每3位为一个单元,不够三位用0补 即:

011 111 111 111 111 111 111 111 111 111 101
计算每一个单元,结果为:37777777775

十六进制则将-3的二进制从右至左每4位合并为一个单元,即:

1111 1111 1111 1111 1111 1111 1111 1101
计算后为: FFFFFFFD

转换十进制-3为八进制和十六进制

十六进制(0,1,2,3,4,5,6,7,8,9,A,B,C,D,F)

>>,<<,>>>移位运算
1、<<

有符号左移位,将运算数的二进制整体左移指定位数,低位用0补齐。

正数:5<<2

1、转化为二进制:

0000 0000 0000 0000 0000 0000 0000 0101

2、整体左移指2位数

0000 0000 0000 0000 0000 0000 0001 0100

3、转化为十进制

12^4+12^2=16+4=20

M<<N相当于M*2^n.

负数:-5<<2

1、转化为二进制:

1111 1111 1111 1111 1111 1111 1111 1011

2、整体左移指2位数

1111 1111 1111 1111 1111 1111 1110 1100

3、转化为十进制

-20

M<<N相当于M*2^n.

2、>>

>>有符号右移位,将运算数的二进制整体右移指定位数,正数高位用0补齐,负数高位用1补齐(保持负数符号不变)

正数:5>>2

1、转化为二进制:

0000 0000 0000 0000 0000 0000 0000 0101

2、整体右移指2位数

0000 0000 0000 0000 0000 0000 0000 0001

3、转化为十进制

1*2^0=1

M>>N相当于M/2^n 取商

负数:-5>>2

1、转化为二进制:

1111 1111 1111 1111 1111 1111 1111 1011

2、整体右移指2位数

1111 1111 1111 1111 1111 1111 1111 1110

3、转化为十进制

-2

M<<N相当于如果运算数是偶数,那么那么它的运算结果就是 x = -(|x| / 2),如果运算数是奇数,那么它的运算结果就是 x = -(|x| / 2) - 1

3、>>>

>>>无符号右移位,不管正数还是负数,高位都用0补齐(忽略符号位)

1、正数的>>>无符号右移位和>>有符号右移位计算结果相同

2、负数

-5>>>2

1、转化为二进制:

1111 1111 1111 1111 1111 1111 1111 1011

2、整体右移指2位数

0011 1111 1111 1111 1111 1111 1111 1110

3、转化为十进制

1073741822

闭包经典题目

记录一道关于闭包的经典题目。

1
2
3
4
5
6
7
8
9
10
11
12
function fun(n,o) {
console.log(o)
return {
fun:function(m){
return fun(m,n);
}
};
}
var a = fun(0); a.fun(1); a.fun(2); a.fun(3); //undefined,?,?,?
var b = fun(0).fun(1).fun(2).fun(3); //undefined,?,?,?
var c = fun(0).fun(1); c.fun(2); c.fun(3); //undefined,?,?,?
//问:三行a,b,c的输出分别是什么?

一道常被人轻视的前端JS面试题

最近在网上看到一道javascript的面试题,很有意思:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Foo() {
getName = function () { alert (1); };
return this;
}
Foo.getName = function () { alert (2);};
Foo.prototype.getName = function () { alert (3);};
var getName = function () { alert (4);};
function getName() { alert (5);}

//请写出以下输出结果:
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();

这里是分割线


此题涉及的知识点众多,包括变量定义提升、this指针指向、运算符优先级、原型、继承、全局变量污染、对象属性及原型属性优先级等等。
此题包含7小问,分别说下。

第一问

先看此题的上半部分做了什么,首先定义了一个叫Foo的函数,之后为Foo创建了一个叫getName的静态属性存储了一个匿名函数,之后为Foo的原型对象新创建了一个叫getName的匿名函数。之后又通过函数变量表达式创建了一个getName的函数,最后再声明一个叫getName函数。

第一问的 Foo.getName 自然是访问Foo函数上存储的静态属性,结果自然是2

第二问

第二问直接调用 getName 函数。既然是直接调用那么就是访问当前上文作用域内的叫getName的函数,所以跟1 2 3都没什么关系。此处有两个坑,一是变量声明提升,二是函数表达式

变量声明提升

即所有声明变量或声明函数都会被提升到当前函数的顶部。
例如下代码:

1
2
console.log(x);
var x = 3;

代码执行阶段,js引擎会将声明语句提升至当前执行块的最前方,变为:

1
2
3
var x;
console.log(x);
x = 3;

函数表达式

var getNamefunction getName 都是声明语句,区别在于 var getName 是函数表达式,而function getName 是函数声明。

函数表达式最大的问题,在于js会将此代码拆分为两行代码分别执行。

例如下代码:

1
2
3
console.log(x);//输出:function x(){}
var x = 1;
function x(){}

实际上执行代码的过程:先将 var x=1 拆分为 var x;x = 1; 两行,再将 var x;function x(){} 两行提升至最上方变成:

1
2
3
4
var x;
function x(){}
console.log(x);
x = 1;

所以最终函数声明的x覆盖了变量声明的x,console.log输出为x函数。
同理,原题中代码最终执行时的是:

1
2
3
4
5
6
7
8
9
10
var getName; //getName变量提升
function Foo() {
getName = function () { alert (1); };
return this;
}
function getName() { alert (5);} //提升函数声明,覆盖var的声明
Foo.getName = function () { alert (2);};
Foo.prototype.getName = function () { alert (3);};
getName = function () { alert (4);}; //最终的赋值再次覆盖function getName声明
getName();//最终输出4

第三问

第三问的 Foo().getName(); 先执行了Foo函数,然后调用Foo函数的返回值对象的getName属性函数。

Foo函数的第一句 getName = function () { alert (1); }; 是一句函数赋值语句,注意它没有var声明,所以先向当前Foo函数作用域内寻找getName变量,没有。再向当前函数作用域上层,即外层作用域内寻找是否含有getName变量,找到了,也就是第二问中的alert(4)函数,将此变量的值赋值为 function(){alert(1)}
此处实际上是将外层作用域内的getName函数修改了。

注意:此处若依然没有找到会一直向上查找到window对象,若window对象中也没有getName属性,就在window对象中创建一个getName变量。

之后Foo函数的返回值是this, 此时this的指向是由所在函数的调用方式决定的。而此处的直接调用方式,this指向window对象。

于是Foo函数返回的是window对象,相当于执行 window.getName() ,而window中的getName已经被修改为alert(1),所以最终会输出1

此处考察了两个知识点,一个是变量作用域问题,一个是this指向问题。

第四问

此处直接调用getName函数,相当于 window.getName() ,因为这个变量已经被Foo函数执行时修改了,遂结果与第三问相同,为1

第五问

第五问 new Foo.getName(); ,考察的是js的运算符优先级问题。
js运算符优先级:

参考链接:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Operator_Precedence

通过查上表可以得知点(.)的优先级19高于new操作,遂相当于是:

1
new (Foo.getName)();

所以实际上将getName函数作为了构造函数来执行,弹出2。

第六问

第六问 new Foo().getName() ,首先看运算符优先级括号高于new,实际执行为
(new Foo()).getName()

遂先执行Foo函数,而Foo此时作为构造函数却有返回值,所以这里需要说明下js中的构造函数返回值问题。

构造函数的返回值

在传统语言中,构造函数不应该有返回值,实际执行的返回值就是此构造函数的实例化对象。

而在js中构造函数可以有返回值也可以没有。

  1. 没有返回值则按照其他语言一样返回实例化对象。

  2. 若有返回值则检查其返回值是否为引用类型。如果是非引用类型,如基本类型(string,number,boolean,null,undefined, sybmol)则与无返回值相同,实际返回其实例化对象。

  3. 若返回值是引用类型,则实际返回值为这个引用类型。

原题中,返回的是this,而this在构造函数中本来就代表当前实例化对象,最终Foo函数返回实例化对象。

之后调用实例化对象的getName函数,因为在Foo构造函数中没有为实例化对象添加任何属性,遂到当前对象的原型对象([[prototype]])中寻找getName,找到了, 最终输出3。

第七问

第七问, new new Foo().getName();同样是运算符优先级问题。
最终实际执行为:
new (new Foo().getName)()

先初始化Foo的实例化对象,然后将其原型上的getName函数作为构造函数再次new。
遂最终结果为3。

–End–

此文转载于:http://www.cnblogs.com/xxcanghai/

Hexo安装过程

1、GitHub创建个人仓库

注册github账号,登录后,Create a new repository,输入repository name, 完整的仓库名格式:[user-name]/[repository-name].github.io 格式,user-name为github的账号名,repository-name为仓库名。

2、全局安装hexo

打开cmd,输入如下命令:

1
npm install -g hexo

3、创建项目名myblog,并初始化项目

1
2
3
4
5
// 本地运行
cd myblog
hexo init
npm install
hexo server

4、部署到github

修改根目录下面的_config.yml文件,找到deploy字段,并填写完整,

1
2
3
4
5
6
# Deployment
## Docs: https://hexo.io/docs/deployment.html
deploy:
type: git
repo: https://github.com/howarchou/howarchou.github.io.git
branch: master

尤其注意字段后面的空格。然后执行如下命令,即可发布到github。

1
2
3
npm install hexo-deployer-git --save
hexo clean
hexo deploy

注意 deploy时可能要你输入github的username和password。

5、查看效果

浏览器访问:https://howarchou.github.io 即可看到效果。

6、个性化域名

1、在项目文件夹下的source目录,新建一个CNAME文件,没有后缀,里面仅一行代码,写上域名, 然后执行:

1
2
3
hexo clean
hexo g
hexo d

2、去万网购买一个域名并认证,进入dns解析控制台,我用的dnspod,新建两条A记录,分别指向185.199.108.153和185.199.109.153;再增加一条CNAME记录,主机记录名为blog,指向刚才浏览器访问的网址:howarchou.github.io.
注意后面有个点,过几分钟dns解析生效后就可以看效果了。比如我的域名是zhougg.com, 访问地址是:blog.zhougg.com 。
至此,搭建的个性博客就完成了。

  • © 2020 Howar Chou
  • Powered by Hexo Theme Ayer
  • PV: UV:

请我喝杯咖啡吧~

支付宝
微信