ajax axios fetch 请求方法对比

axios:
axios.post(url[, data[, config]])
axios.get(url[, config])

axios({
method:’get’,
url:’http://bit.ly/2mTM3nY’,
responseType:’stream’
})
.then(function (response) {
response.data.pipe(fs.createWriteStream(‘ada_lovelace.jpg’))
});

fetch:
fetch(url, {config})
.then(
if (response.ok) {
return response.json()
} else {
// Find some way to get to execute .catch()
return Promise.reject(‘something went wrong!’)
}
)
.then(data => console.log(‘data is’, data))
.catch(error => ({ error }));

0. 断网 // 只会走catch 函数
以下都会走then
1. 可能尝试获取不存在的资源 404
2. 没有权限获取资源
3. 输入参数有误
4. 服务器抛出异常
5. 服务器超时
6. 服务器崩溃
7. API更改 …

[cc lang=”javascript”]
// 返回消息 response 对象
{
body: ReadableStream
bodyUsed: false
headers: Headers
ok : true
redirected : false
status : 200
statusText : “OK”
type : “cors”
url : “http://some-website.com/some-url”
__proto__ : Response
}
// response.json() | response.text() | response.blob() 等这些方法都返回promise
[/cc]

webpack 2 升级到 webpack 4

1 安装 webpck 4, 根据提示安装新的 版本,
涉及插件

[cc lang=”json”]”vue-loader”: “^15.0.10”,
“vue-style-loader”: “^4.1.0”,
“vue-template-compiler”: “^2.5.16”,
“copy-webpack-plugin”: “^4.0.1”,
“extract-text-webpack-plugin”: “^4.0.0-beta.0”,
“html-webpack-plugin”: “^3.1.0”,
“optimize-css-assets-webpack-plugin”: “^4.0.0”,
“webpack-bundle-analyzer”: “^2.11.1”,
“webpack-dev-middleware”: “^3.1.2”,
“webpack-dev-server”: “^3.1.3”,
“webpack-merge”: “^4.1.0”
[/cc]

2 更新各种loader
mini-css-extract-plugin
这个插件将css按模块化解压到单独的文件中,为每个包含css的js文件创键一个css文件。
style-loader 是将css文件以行内样式style的标签写入打包后的html页面,mini-css-extract-plugin是直接link方式引入进去
extract-text-webpack-plugin
根据创键实例、或者配置多个入口chunk来的,比如配置了一个入口文件,最终所有的css都会提取在一个样式文件中;如果n个入口(即创键了n个实例),则会生成多个css文件
3 webpack.optimize.CommonsChunkPlugin 提取公共代码 改为 optimization.splitChunks

4 vue-loader
*需要把 VueLoaderPlugin = require(‘vue-loader/lib/plugin’) 添加到plugin 中
*v15版本之后(使用了不同的策略 推导.vue文件中各个语言块使用的loader,将各个语言块视为独立的文件,使用webpack中配置了规则的loader处理,由此带来的配置变化就是针对样式处理,webpack.rules中必须显示提供对应loader处理的规则),所以原有vue-loader配置中内联传入的样式相关的loader可以去除

[cc lang=”js”]test: /\.vue$/,
loader: ‘vue-loader’,
options: {
// v15+版本不再需要内联的cssloader配置
// 如果不去掉会报错??(我的项目未去掉也没报错,emmm…?)
}
}[/cc]

*鉴于第2点的推导变化,<script><script/>标签内的js代码将被视为独立的js文件并根据webpack配置使用babel-loader转译;因此,如果项目配置babel-loader时使用exclude:/node_modules/排除了依赖包中代码的转译,并且导入了/node_modules/中的.vue文件,<script>部分将不会被转译,故需要将.vue文件加入排除名单中

[cc lang=”js”]{
test: /\.js$/,
exclude: file => (
/node_modules/.test(file) &&
!/\.vue\.js/.test(file)
),
loader:’babel-loader’
},[/cc]

5 package.json
// window下直接set会不支持,需要npm i cross-env
“dev”: “cross-env NODE_ENV=development …”
// webpack4
webpack运行时还会根据mode设置一个全局变量process.env.NODE_ENV,这里的process.env.NODE_ENV不是node中的环境变量,而是webpack.DefinePlugin中定义的全局变量,允许你根据不同的环境执行不同的代码.

“dev”: “webpack –mode development”
“build”: “webpack –mode production”

6. minimize
默认为true,效果就是压缩js代码(代替以前的webpack.optimize.UglifyJsPlugin)

minimizer
可以自定义UglifyJsPlugin和一些配置,默认的压缩为uglifyjs-webpack-plugin 【当使用了OptimizeCSSAssetsPlugin插件,内置的uglifyjs-webpack-plugin插件会失效,这边需要重新配置下UglifyJsPlugin】

// 新增css的压缩
minimizer:
mode === “development”
? []
: [
new UglifyJsPlugin({
cache: true,
parallel: true,
sourceMap: mode === “development”
}),
new OptimizeCSSAssetsPlugin()
]

7 CSS文件提取插件
mini-css-extract-plugin 替代 extract-text-webpack-plugin

[cc lang=”js”]
//webpack 文件配置
const MiniCssExtractPlugin = require(‘mini-css-extract-plugin’);
const devMode = process.env.NODE_ENV !== ‘production’;

module.exports = {
plugins: [
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: devMode ? ‘[name].css’ : ‘[name].[hash].css’,
chunkFilename: devMode ? ‘[id].css’ : ‘[id].[hash].css’,
}),
],
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
hmr: process.env.NODE_ENV === ‘development’,
// if hmr does not work, this is a forceful method.
reloadAll: true,
},
},
‘css-loader’,
‘postcss-loader’,
‘sass-loader’,
],
},
],
},
};[/cc]

[cc lang=”js”]
//Extracting all CSS in a single file 生成css 到一个文件
optimization: {
splitChunks: {
cacheGroups: {
styles: {
name: ‘styles’,
test: /\.css$/,
chunks: ‘all’,
enforce: true,
},
},
},
},[/cc]

根据 entry point 或 chunk 打包不同css 文件 参考 https://www.npmjs.com/package/mini-css-extract-plugin

!!!优化css的处理器

[cc lang=”js”] new OptimizeCSSAssetsPlugin ({
// 默认是全部的CSS都压缩,该字段可以指定某些要处理的文件
assetNameRegExp: /\.(sa|sc|c)ss$/g,
// 指定一个优化css的处理器,默认cssnano
cssProcessor: require(‘cssnano’),

cssProcessorPluginOptions: {
preset: [ ‘default’, {
discardComments: { removeAll: true}, //对注释的处理
normalizeUnicode: false // 建议false,否则在使用unicode-range的时候会产生乱码
}]
},
canPrint: true // 是否打印编译过程中的日志
})[/cc]

!! 消除未使用的CSS
1、安装
npm i purify-webpack purify-css -D

2、引入及配置

[cc lang=”js”]const glob = require(‘glob’)
const PurifyCssPlugin = require(‘purifycss-webpack’)
plugins: [
new PurifyCssPlugin ({
paths: glob.sync(path.join(__dirname, ‘/*.html’))
})
][/cc]

!! !1. 开发模式要用到 new webpack.HotModuleReplacementPlugin()
2. 开发模式 配置里不能用 contentHash, chunkHash, 直接用hash 或者不用
3. 大量输出日志处理

[cc lang=”js”]stats: {
children: false
},[/cc]

4

debugger Android ios webview (调试 app webview)

webpack 开启本地服务,host 设置 0.0.0.0 端口最好是80(mac 开启本地服务需要加sudo使用80端口)
Charles 设置 proxy 端口(默认8888) 设置DNS Spoofing 把需要的域名如: m.baidu.com指向0.0.0.0
手机 wifi 设置代理 电脑ip:8888

完成, app H5 页面的m.baidu.com的服务就是电脑webpack 的服务

调试工具:
android 可以通过 chrome devtool 调试,usb 连接手机, devtool 找到 remote devices
ios 电脑safari devtool, 手机safari 开启 web inspector, mac 打开 safari 调试
ios safari 通过 chrome 调试的方法 https://github.com/RemoteDebug/remotedebug-ios-webkit-adapter

2 charles 设置代理
ios safari 输入localhost 不能被转发 可以输入 localhost. 或 http://localhost.charlesproxy.com/ 代替localhost

3 通过 手机 浏览器 访问本地服务器
1 在您的开发计算机与 Android 设备之间设置远程调试。 完成后,您应在 Inspect Devices 对话框的左侧菜单中看到 Android 设备,还应看到 Connected 状态指示器。

2 在 DevTools 的 Inspect Devices 对话框中,启用 Port forwarding。
3 在左侧的 Device port 文本字段中,输入 Android 设备上您想要从其访问网站的 localhost 端口号。例如,如果您想要从 localhost:5000 访问网站,则应输入 5000。
4 在右侧的 Local address 文本字段中,输入开发计算机网络服务器上运行的您的网站的 IP 地址或主机名,后面紧跟端口号。例如,如果您的网站在 localhost:7331 上运行,则应输入 localhost:7331。
5 可以device port 可以和服务器端口一样,这样可以解决一些问题端口不同问题
https://developers.google.com/web/tools/chrome-devtools/remote-debugging/local-server#port-forwarding

Base64

[cc lang=”javascript”]

var base64Encode = function(e) {
return btoa(encodeURIComponent(e).replace(/%([0-9A-F]{2})/g, function(e, t) {
return String.fromCharCode(“0x” + t)
}))
};
var base64Decode= function(e) {
return decodeURIComponent(atob(e).split(“”).map(function(e) {
return “%” + (“00” + e.charCodeAt(0).toString(16)).slice(-2)
}).join(“”))
};
var urlBase64ToUint8Array = function(e) {
for (var t = “=”.repeat((4 – e.length % 4) % 4), n = (e + t).replace(/\-/g, “+”).replace(/_/g, “/”), r = atob(n), i = new Uint8Array(r.length), o = 0; o < r.length; ++o) i[o] = r.charCodeAt(o); return i; } [/cc]

JSON Web Token (JWT)

前后端分离通过Restful API进行数据交互时,如何验证用户的登录信息及权限。在原来的项目中,使用的是最传统也是最简单的方式,前端登录,后端根据用户信息生成一个token,并保存这个 token 和对应的用户id到数据库或Session中,接着把 token 传给用户,存入浏览器 cookie,之后浏览器请求带上这个cookie,后端根据这个cookie值来查询用户,验证是否过期。

但这样做问题就很多,如果我们的页面出现了 XSS 漏洞,由于 cookie 可以被 JavaScript 读取,XSS 漏洞会导致用户 token 泄露,而作为后端识别用户的标识,cookie 的泄露意味着用户信息不再安全。尽管我们通过转义输出内容,使用 CDN 等可以尽量避免 XSS 注入,但谁也不能保证在大型的项目中不会出现这个问题。

在设置 cookie 的时候,其实你还可以设置 httpOnly 以及 secure 项。设置 httpOnly 后 cookie 将不能被 JS 读取,浏览器会自动的把它加在请求的 header 当中,设置 secure 的话,cookie 就只允许通过 HTTPS 传输。secure 选项可以过滤掉一些使用 HTTP 协议的 XSS 注入,但并不能完全阻止。

httpOnly 选项使得 JS 不能读取到 cookie,那么 XSS 注入的问题也基本不用担心了。但设置 httpOnly 就带来了另一个问题,就是很容易的被 XSRF,即跨站请求伪造。当你浏览器开着这个页面的时候,另一个页面可以很容易的跨站请求这个页面的内容。因为 cookie 默认被发了出去。

另外,如果将验证信息保存在数据库中,后端每次都需要根据token查出用户id,这就增加了数据库的查询和存储开销。若把验证信息保存在session中,有加大了服务器端的存储压力。那我们可不可以不要服务器去查询呢?如果我们生成token遵循一定的规律,比如我们使用对称加密算法来加密用户id形成token,那么服务端以后其实只要解密该token就可以知道用户的id是什么了。不过呢,我只是举个例子而已,要是真这么做,只要你的对称加密算法泄露了,其他人可以通过这种加密方式进行伪造token,那么所有用户信息都不再安全了。恩,那用非对称加密算法来做呢,其实现在有个规范就是这样做的,就是我们接下来要介绍的 JWT。

Json Web Token(JWT)
JWT 是一个开放标准(RFC 7519),它定义了一种用于简洁,自包含的用于通信双方之间以 JSON 对象的形式安全传递信息的方法。JWT 可以使用 HMAC 算法或者是 RSA 的公钥密钥对进行签名。它具备两个特点:

简洁(Compact)

可以通过URL, POST 参数或者在 HTTP header 发送,因为数据量小,传输速度快

自包含(Self-contained)

负载中包含了所有用户所需要的信息,避免了多次查询数据库
Header 头部
头部包含了两部分,token 类型和采用的加密算法
Payload 负载
这部分就是我们存放信息的地方了,你可以把用户 ID 等信息放在这里,JWT 规范里面对这部分有进行了比较详细的介绍,常用的由 iss(签发者),exp(过期时间),sub(面向的用户),aud(接收方),iat(签发时间)。
Signature 签名
前面两部分都是使用 Base64 进行编码的,即前端可以解开知道里面的信息。Signature 需要使用编码后的 header 和 payload 以及我们提供的一个密钥,然后使用 header 中指定的签名算法(HS256)进行签名。签名的作用是保证 JWT 没有被篡改过。

三个部分通过.连接在一起就是我们的 JWT 了,它可能长这个样子,长度貌似和你的加密算法和私钥有关系。

首先,前端通过Web表单将自己的用户名和密码发送到后端的接口。这一过程一般是一个HTTP POST请求。建议的方式是通过SSL加密的传输(https协议),从而避免敏感信息被嗅探。后端核对用户名和密码成功后,将用户的id等其他信息作为JWT Payload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT。形成的JWT就是一个形同lll.zzz.xxx的字符串。后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localStorage或sessionStorage上,退出登录时前端删除保存的JWT即可。前端在每次请求时将JWT放入HTTP Header中的Authorization位。(解决XSS和XSRF问题)后端检查是否存在,如存在验证JWT的有效性。例如,检查签名是否正确;检查Token是否过期;检查Token的接收方是否是自己(可选)。验证通过后后端使用JWT中包含的用户信息进行其他逻辑操作,返回相应结果。

参考 https://docs.aws.amazon.com/zh_cn/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html

async/await/promise/ setTimeout/ event loop

promise:
[cc lang=”javascript”]
const p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error(‘fail’)), 3000)
})

const p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(p1), 1000)
})

p2
.then(result => console.log(result))
.catch(error => console.log(error))
// Error: fail
[/cc]
上面代码中,p1是一个 Promise,3 秒之后变为rejected。p2的状态在 1 秒之后改变,resolve方法返回的是p1。由于p2返回的是另一个 Promise,导致p2自己的状态无效了,由p1的状态决定p2的状态。所以,后面的then语句都变成针对后者(p1)。又过了 2 秒,p1变为rejected,导致触发catch方法指定的回调函数。

!!! 注意,调用resolve或reject并不会终结 Promise 的参数函数的执行。如下
[cc lang=”js”]
new Promise((resolve, reject) => {
resolve(1); //这是因为立即 resolved 的 Promise 是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。
如果加上return语句就不会打印2
console.log(2);
}).then(r => {
console.log(r);
});
// 2
// 1
[/cc]
promise then 方法
then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
then里的 回调函数完成以后,会将返回结果作为参数,传入下一次回调函数。如果回调函数返回promise,这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用
promise catch 方法
Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数.
建议用catch 不要用then 第二个方法
!!! catch方法返回的还是一个 Promise 对象,因此后面还可以接着调用then方法

promise.all([ p1, p2, p3 ]).then().catch

promise.race([ p1, p2, p3]).then().catch
那个率先改变的 Promise 实例的返回值,就传递给then或catch的回调函数
[cc lang=”js”]
const p = Promise.race([
fetch(‘/resource-that-may-take-a-while’),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error(‘request timeout’)), 5000)
})
]);

p
.then(console.log)
.catch(console.error);
//上面代码中,如果 5 秒之内fetch方法无法返回结果,变量p的状态就会变为rejected,从而触发catch方法指定的回调函数

[/cc]

1.async 函数返回的是一个 Promise 对象,async 函数没有返回值,它会返回 Promise.resolve(undefined)

2 await 如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。
如果它等到的是一个 Promise 对象,await就忙起来了,它会阻塞asycn函数内后面的代码,等着Promise对象resolve,然后得到 resolve 的值,作为 await 表达式的运算结果
3。1 js 执行机制 event loop
首先判断JS是同步还是异步,同步就进入主进程,异步就进入event table
异步任务在event table中注册函数,当满足触发条件后,被推入event queue
同步任务进入主线程后一直执行,直到主线程空闲时,才会去event queue中查看是否有可执行的异步任务,如果有就推入主进程中
3.2event loop (2)
macro-task(宏任务):包括整体代码script,setTimeout,setInterval
micro-task(微任务):Promise,process.nextTick

!! 执行一个宏任务,过程中如果遇到微任务,就将其放到微任务的【事件队列】里
当前宏任务执行完成后,会查看微任务的【事件队列】,并将里面全部的微任务依次执行完
[cc lang=”javascript”]
async function async1() {
console.log( ‘async1 start’ )
await async2() // return promise 此时返回的Promise会被放入到回调队列中等待,await会让出线程
//等外面执行完成后再回到这生成promise.resolve(undefined) 又被放到micro-task, 再次让出线程跳出async
console.log( ‘async1 end’ )
}

async function async2() {
console.log( ‘async2’)
}

console.log( ‘script start’ )

setTimeout( function () {
console.log( ‘setTimeout’ )
}, 0 )

async1();

new Promise( function ( resolve ) {
console.log( ‘promise1’ )
resolve(); // 回调then 会放入micro-task 让出线程
console.log(34)
} ).then( function () {
console.log( ‘promise2’ )
return Promise.resolve();
} ).then(function(){console.log(11)
return Promise.resolve();
}).then(function(){console.log(22)})

console.log( ‘script end’ )

//script start
//async1 start
//async2
//promise1
// 34
//script end
//promise2
//async1 end
//11
//22
// setTimeout
[/cc]

javascript 事件流

DOM标准规定事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段

1).尽管“DOM2级事件”标准规范明确规定事件捕获阶段不会涉及事件目标,但是在IE9、Safari、Chrome、Firefox和Opera9.5及更高版本都会在捕获阶段触发事件对象上的事件。结果,就是有两次机会在目标对象上面操作事件。

2).并非所有的事件都会经过冒泡阶段 。所有的事件都要经过捕获阶段和处于目标阶段,但是有些事件会跳过冒泡阶段:如,获得输入焦点的focus事件和失去输入焦点的blur事件。

//事件代理同时绑定了li和span,当点击span的时候,li和span都会冒泡

//解决方法一、span的事件处理程序中阻止冒泡
$(document).on('click', 'span', function(e){
alert('li span');
e.stopPropagation();
})
//解决方法二 li的事件处理程序中检测target元素
$(document).on('click', 'li', function (e) {
if (e.target.nodeName == 'SPAN') {
e.stopPropagation();
return;
}
alert('li li');
});

注意: 1. dom.addEventListener中e.currentTarget 是指dom而 jQuery on 方法 e.currentTarget与该方法接收的第二个参数有关
对象this始终等于e.currentTarget的值,而e.target则只是包含事件的实际目标,
2. addEventListener(‘click’, handle, false), 第三个参数 是否在捕获阶段调用事件处理程序,
第三个参数 或者是 options 一个指定有关 listener 属性的可选参数对象。可用的选项如下:
capture: Boolean,表示 listener 会在该类型的事件捕获阶段传播到该 EventTarget 时触发。
once: Boolean,表示 listener 在添加之后最多只调用一次。如果是 true, listener 会在其被调用之后自动移除。
passive: Boolean,设置为true时,表示 listener 永远不会调用 preventDefault()。如果 listener 仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告。
mozSystemGroup: 只能在 XBL 或者是 Firefox’ chrome 使用,这是个 Boolean,表示 listener 被添加到 system group。

渲染流程

Reference Link

DOM Tree:浏览器将HTML解析成树形的数据结构。

  • domLoading:这是整个过程的起始时间戳,浏览器即将开始解析第一批收到的 HTML 文档字节。
  • domInteractive:表示浏览器完成对所有 HTML 的解析并且 DOM 构建完成的时间点。
  • domContentLoaded: 当初始的 HTML 文档被完全加载和解析完成之后,一般表示 DOM 和 CSSOM 均准备就绪的时间点,意味着现在我们可以构建渲染树了Render Tree 。如果没有阻塞解析器的 JavaScript,则 DOMContentLoaded 将在 domInteractive 后立即触发。domContentLoaded 事件触发得越早,其他应用逻辑开始执行的时间就越早
  • domComplete:顾名思义,所有处理完成,并且网页上的所有资源(图像等)都已下载完毕, 加载转环已停止旋转
  • loadEvent:作为每个网页加载的最后一步,浏览器会触发 onload 事件,以便触发额外的应用逻辑。

CSS Rule Tree:浏览器将CSS解析成树形的数据结构 cssom。

  • JavaScript 可能会查询 CSSOM, 所以在加载 link stylesheet 解析css 时会阻止后面的js 执行

Render Tree: DOM和CSSOM合并后生成Render Tree。

layout: 有了Render Tree,浏览器已经能知道网页中有哪些节点、各个节点的CSS定义以及他们的从属关系,从而去计算出每个节点在屏幕中的位置。

painting: 按照算出来的规则,通过显卡,把内容画到屏幕上。

reflow(回流):当浏览器发现某个部分发生了点变化影响了布局,需要倒回去重新渲染,内行称这个回退的过程叫 reflow。reflow 会从 <html> 这个 root frame 开始递归往下,依次计算所有的结点几何尺寸和位置。reflow 几乎是无法避免的。现在界面上流行的一些效果,比如树状目录的折叠、展开(实质上是元素的显 示与隐藏)等,都将引起浏览器的 reflow。鼠标滑过、点击……只要这些行为引起了页面上某些元素的占位面积、定位方式、边距等属性的变化,都会引起它内部、周围甚至整个页面的重新渲 染。通常我们都无法预估浏览器到底会 reflow 哪一部分的代码,它们都彼此相互影响着。

repaint(重绘):改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性时,屏幕的一部分要重画,但是元素的几何尺寸没有变。

注意:(1)display:none 的节点不会被加入Render Tree,而visibility: hidden 则会,所以,如果某个节点最开始是不显示的,设为display:none是更优的。

     (2)display:none 会触发 reflow,而 visibility:hidden 只会触发 repaint,因为没有发现位置变化。

   (3)有些情况下,比如修改了元素的样式,浏览器并不会立刻reflow 或 repaint 一次,而是会把这样的操作积攒一批,然后做一次 reflow,这又叫异步 reflow 或增量异步 reflow。但是在有些情况下,比如resize 窗口,改变了页面默认的字体等。对于这些操作,浏览器会马上进行 reflow。

javascript new 关键字

new 实现了一下
1. 创建了一个全新的对象。
2. 这个对象会被执行[[Prototype]](也就是__proto__)链接。
3. 生成的新对象会绑定到函数调用的this。
4. 通过new创建的每个对象将最终被[[Prototype]]链接到这个函数的prototype对象上。
5. 如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用会自动返回这个新的对象。
Number(数字)
String(字符串)
Boolean(布尔)
Symbol(符号)(第六版新增)
Object(对象)
Function(函数)
Array(数组)
Date(日期)
RegExp(正则表达式)
Null(空)typeof Null 也等object
Undefined(未定义)

/**
* 模拟实现 new 操作符
* @param {Function} ctor [构造函数]
* @return {Object|Function|Regex|Date|Error} [返回结果]
*/
function newOperator(ctor){
  if(typeof ctor !== 'function'){
    throw 'newOperator function the first param must be a function';
 }
  // ES6 new.target 是指向构造函数
  newOperator.target = ctor;
  // 1.创建一个全新的对象,
  // 2.并且执行[[Prototype]]链接
  // 4.通过`new`创建的每个对象将最终被`[[Prototype]]`链接到这个函数的`prototype`对象上。
  var newObj = Object.create(ctor.prototype);

  // ES5 arguments转成数组 当然也可以用ES6 [...arguments], Aarry.from(arguments);
  // 除去ctor构造函数的其余参数
  var argsArr = [].slice.call(arguments, 1);

  // 3.生成的新对象会绑定到函数调用的`this`,也就是newObj 添加加实例属性
  // 获取到ctor函数返回结果
  var ctorReturnResult = ctor.apply(newObj, argsArr);

  // 小结4 中这些类型中合并起来只有Object和Function两种类型 typeof null 也是'object'所以要不等于null,排除null
  var isObject = typeof ctorReturnResult === 'object' && ctorReturnResult !== null;
  var isFunction = typeof ctorReturnResult === 'function';
  if(isObject || isFunction){
    return ctorReturnResult; //如果有自己的return,那实例只是返回的东西
  }
  // 5.如果函数没有返回对象类型`Object`(包含`Functoin`, `Array`, `Date`, `RegExg`, `Error`),那么`new`表达式中的函数调用会自动返回这个新的对象。
  return newObj;
}