多次bind绑定

let a = {}
let fn = function () { console.log(this) }
fn.bind().bind(a)
等同于
// fn.bind().bind(a) 等于
let fn2 = function fn1() {
  return function() {
    return fn.apply()
  }.apply(a)
}
fn2()
因此:不管我们给函数 bind 几次,fn 中的 this 永远由第一次 bind 决定,所以结果永远是 window

this的优先级


要注意:对于 new 的方式来说,this 被永远绑定在了 c 上面,不会被任何方式改变 this

==的类型转换问题


闭包问题

闭包存在的意义就是让我们可以间接访问函数内部的变量
在实际项目中 可以用来隐藏数据 只对外提供API
// 闭包隐藏数据,只提供API
function createCache() {
    const data = {} // 闭包中的数据,被隐藏,不被外界访问
    return {
        set: function (key, val) {
            data[key] = val
        },
        get: function (key) {
            return data[key]
        }
    }
}
 
const c = createCache()
c.set('a', 100)
console.log( c.get('a') )
//无法直接访问 data[a]

浅拷贝和深拷贝

浅拷贝:
Object.assign
如果value是对象的话 只能拷贝地址
let a = {
  age: 1
}
let b = Object.assign({}, a)
a.age = 2
console.log(b.age) // 1


运算符 ...
let a = {
  age: 1
}
let b = { ...a }
a.age = 2
console.log(b.age) // 1


深拷贝:
JSON.parse(JSON.stringify(object))
let a = {
  age: 1,
  jobs: {
    first: 'FE'
  }
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE
  • 会忽略 undefined
  • 会忽略 symbol
  • 不能序列化函数
  • 不能解决循环引用的对象
手写深拷贝
function deepClone(obj){
    if(typeof obj !=='object'||obj == null){
        return obj
    }
    let res;
    if(Array.isArray(obj)){
        res = [];
    }else{
        res = {};
    }
    for (let key in obj){
        if (obj.hasOwnProperty(key)){
            res[key] = deepClone(obj[key])
        }
    }
    return res;
}


函数提升和变量提升

要注意 函数提升优先于变量提升,函数提升会把整个函数挪到作用域顶部,变量提升只会把声明挪到作用域顶部

ES6模块化和Nodejs模块化
模块化的好处:
  • 解决命名冲突
  • 提供复用性
  • 提高代码可维护性
Nodejs
(commonjs)
demo:
// a.js
module.exports = {
    a: 1
}
// or
exports.a = 1

// b.js
var module = require('./a.js')
module.a // -> log 1
在浏览器中会出现堵塞情况



ES module
结合了commonjs和AMD的优点
也就是一直在用的import/export
export function test (args) {
  // body...
  console.log(args); 
}
   
// 默认导出模块,一个文件中只能定义一个
export default function() {...};
export const name = "lyn";
 
// _代表引入的export default的内容
import { test, name } from './a.js';  
test(`my name is ${name}`);
es6输出的是值的引用
并且是静态引入 编译时就引入了
两者区别:
  1. CommonJS 支持动态导入,也就是 require(${path}/xx.js),后者目前不支持,但是已有提案
  2. CommonJS 是同步导入,因为用于服务端,文件都在本地,同步导入即使卡住主线程影响也不大。而后者是异步导入,因为用于浏览器,需要下载文件,如果也采用同步导入会对渲染有很大影响
  3. CommonJS 在导出时都是值拷贝,就算导出的值变了,导入的值也不会改变,所以如果想更新值,必须重新导入一次。但是 ES Module 采用实时绑定的方式,导入导出的值都指向同一个内存地址,所以导入值会跟随导出值变化
  4. ES Module 会编译成 require/exports来执行的

手写一个promise 重点

// 三个常量用于表示状态
const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'

function MyPromise(fn) {
    const that = this
    this.state = PENDING

    // value 变量用于保存 resolve 或者 reject 中传入的值
    this.value = null

    // 用于保存 then 中的回调
//因为当执行完 Promise 时状态可能还是等待中
//这时候应该把 then 中的回调保存起来用于状态改变时使用
    that.resolvedCallbacks = []
    that.rejectedCallbacks = []


    function resolve(value) {
         // 首先两个函数都得判断当前状态是否为等待中
        if(that.state === PENDING) {
            that.state = RESOLVED
            that.value = value

            // 遍历回调数组并执行
            that.resolvedCallbacks.map(cb=>cb(that.value))
        }
    }
    function reject(value) {
        if(that.state === PENDING) {
            that.state = REJECTED
            that.value = value
            that.rejectedCallbacks.map(cb=>cb(that.value))
        }
    }

    // 完成以上两个函数以后,我们就该实现如何执行 Promise 中传入的函数了
    try {
        fn(resolve,reject)
    }cach(e){
        reject(e)
    }
}

// 最后我们来实现较为复杂的 then 函数
MyPromise.prototype.then = function(onFulfilled,onRejected){
  const that = this

  // 判断两个参数是否为函数类型,因为这两个参数是可选参数
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v=>v
  onRejected = typeof onRejected === 'function' ? onRejected : e=>throw e

  // 当状态不是等待态时,就去执行相对应的函数。
//如果状态是等待态的话,就往回调函数中 push 函数
  if(this.state === PENDING) {
      this.resolvedCallbacks.push(onFulfilled)
      this.rejectedCallbacks.push(onRejected)
  }
  if(this.state === RESOLVED) {
      onFulfilled(that.value)
  }
  if(this.state === REJECTED) {
      onRejected(that.value)
  }
}

进程和线程

放在应用上来说,进程就代表了一个程序。线程是进程中的更小单位,描述了执行一段指令所需的时间
如果以浏览器为例:
当你打开一个 Tab 页时,其实就是创建了一个进程
一个进程中可以有多个线程,比如渲染线程、JS 引擎线程、HTTP 请求线程等等。
当你发起一个请求时,其实就是创建了一个线程,当请求结束后,该线程可能就会被销毁

注意:
如果宏任务中的异步代码有大量的计算并且需要操作 DOM 的话,为了更快的响应界面响应,我们可以把操作 DOM 放入微任务中



Node中的event loop

Node 中的 Event loop 和浏览器中的不相同。
Node 的 Event loop 分为6个阶段,它们会按照顺序反复运行
   ┌───────────────────────┐
┌─>│        timers         │<————— 执行 setTimeout()、setInterval() 的回调
│  └──────────┬────────────┘
|             |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
│  ┌──────────┴────────────┐
│  │     pending callbacks │<————— 执行由上一个 Tick 延迟下来的 I/O 回调(待完善,可忽略)
│  └──────────┬────────────┘
|             |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
│  ┌──────────┴────────────┐
│  │     idle, prepare     │<————— 内部调用(可忽略)
│  └──────────┬────────────┘     
|             |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
|             |                   ┌───────────────┐
│  ┌──────────┴────────────┐      │   incoming:   │ - (执行几乎所有的回调,除了 close callbacks 以及 timers 调度的回调和 setImmediate() 调度的回调,在恰当的时机将会阻塞在此阶段)
│  │         poll          │<─────┤  connections, │ 
│  └──────────┬────────────┘      │   data, etc.  │ 
│             |                   |               | 
|             |                   └───────────────┘
|             |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
|  ┌──────────┴────────────┐      
│  │        check          │<————— setImmediate() 的回调将会在这个阶段执行
│  └──────────┬────────────┘
|             |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
│  ┌──────────┴────────────┐
└──┤    close callbacks    │<————— socket.on('close', ...)
   └───────────────────────┘
nodejs与浏览器相比,最大的区别就是
nodejs的 MacroTask 分好几种,而这好几种又有不同的 task queue,而不同的 task queue 又有顺序区别,而 MicroTask 是穿插在每一种【注意不是每一个!】MacroTask 之间的。

如图中所示:
setTimeout/setInterval 属于 timers 类型;
setImmediate 属于 check 类型;
socket 的 close 事件属于 close callbacks 类型;
其他 MacroTask 都属于 poll 类型。
process.nextTick 本质上属于 MicroTask,但是它先于所有其他 MicroTask 执行;
所有 MicroTask 的执行时机,是不同类型 MacroTask 切换的时候。
idle/prepare 仅供内部调用,我们可以忽略。
pending callbacks 不太常见,我们也可以忽略。

总的来说 nodejs的event loop可以总结为:
  • 先执行所有类型为 timers 的 MacroTask,然后执行所有的 MicroTask(注意 NextTick 要优先哦);
  • 进入 poll 阶段,执行几乎所有 MacroTask,然后执行所有的 MicroTask;
  • 再执行所有类型为 check 的 MacroTask,然后执行所有的 MicroTask;
  • 再执行所有类型为 close callbacks 的 MacroTask,然后执行所有的 MicroTask;
  • 至此,完成一个 Tick,回到 timers 阶段;
  • ……
  • 如此反复,无穷无尽……
还有一些细节问题:
setTimeout 与 setImmediate 的顺序
Node 对 timers 的过期检查不一定靠谱 因此不能在预定时间立刻执行
这导致setTimeout 与 setImmediate 的顺序不确定
如:
setTimeout(() => {
  console.log('timeout')
}, 0)

setImmediate(() => {
  console.log('immediate')
})
此时,虽然 setTimeout 延时为 0
但是一般情况 Node 把 0 会设置为 1ms
所以,当 Node 准备 event loop 的时间大于 1ms 时:
进入 timers 阶段时,setTimeout 已经到期,则会先执行 setTimeout;
反之,若进入 timers 阶段用时小于 1ms:
setTimeout 尚未到期,则会错过 timers 阶段,
先进入 check 阶段,执行 setImmediate
poll阶段
poll有两个功能:
  • 获取新的 I/O 事件,并执行这些 I/O 的回调,之后适当的条件下 node 将阻塞在这里
  • 当有 immediate 或已超时的 timers,执行它们的回调
具体来说,可以分为几下几种情况:
  • setImmediate 的 queue 不为空,则进入 check 阶段,然后是 close callbacks 阶段……
  • setImmediate 的 queue 为空,但是 timers 的 queue 不为空,则直接进入 timers 阶段,然后又来到 poll 阶段……
  • setImmediate 的 queue 为空,timers 的 queue 也为空,此时会阻塞在这里,因为无事可做,也确实没有循环下去的必要
  • pending callbacks阶段
    有时候也被叫做 I/O callbacks 
    它所执行的回调是比较特殊的、且不需要关心的
    严格来说:
    i/o callbacks并不是处理文件i/o的callback 而是处理一些系统调用错误,
    比如网络 stream, pipe, tcp, udp通信的错误callback


    手写call bind apply

    call
    Function.prototype.myCall(context,...args){
        if(this === Function.prototype ){
            return undefined;
        }
        context = context ||window;
        const fn = Symbol();
        context[fn]=this;
        const res = context[fn](...args);
        delete context[fn];
        return res;
    }
    apply
    Function.prototype.myCall(context,args){
        if(this === Function.prototype ){
            return undefined;
        }
        context = context ||window;
        const fn = Symbol();
        context[fn]=this;
        let res;
        if(Array.isArray(args)){
            res = context[fn](args);
        }else{
            res = context[fn](...args);
        }
        delete context[fn];
        return res;
    }
    bind
    Function.prototype.myBind = function (context,...args){
        if(this === Function.prototype){
            throw new TypeError('Error');
        }
       context = context ||window;
        const self = this;
        return function () {
            return self.bind(context, ...args)
        }
    }

    手写instanceof

    function myInstanceof(target,origin){
        const proto = target.__proto__;
        if(proto){
            if(proto === origin.prototype){
                return true;
            }else{
                return myInstanceof(proto,origin)
            }
        }else{
            return false;
        }
    }

    0.1+0.2=0.3

    计算机是通过二进制来存储东西的,所以 0.1 在二进制中会表示为0.1 = 2^-4 * 1.10011(0011)
    也就是说,十进制小数的二进制表示是无限循环的
    但JS采用的浮点数标准是IEEE 754双精度版本(64位) 会裁剪数字 导致了精度丢失
    跨域
    JSONP
    可能有多个JSONP使用同一个回调函数 这时候可以做一下封装
    function jsonp(url, jsonpCallback, success) {
      let script = document.createElement('script')
      script.src = url
      script.async = true
      script.type = 'text/javascript'
      window[jsonpCallback] = function(data) {
        success && success(data)
      }
      document.head.appendChild(script);
    }
    jsonp('http://xxx', 'callback', function(value) {
      console.log(value)
    })
    JSONP 使用简单且兼容性不错,但是只限于 get 请求

    CORS
    后端

    document.domain

    • 该方式只能用于主域名相同的情况下,比如 a.test.com 和 b.test.com 适用于该方式。
    • 只需要给页面添加 document.domain = 'test.com' 表示主域名都相同就可以实现跨域

    postMessage
    这种方式通常用于获取嵌入页面中的第三方页面数据。一个页面发送消息,另一个页面判断来源并接收消息


    cookie问题

    属性 作用
    value 如果用于保存用户登录态,应该将该值加密,不能使用明文的用户标识
    http-only 不能通过 JS 访问 Cookie,减少 XSS 攻击
    secure 只能在协议为 HTTPS 的请求中携带
    same-site 规定浏览器不能在跨域请求中携带 Cookie,减少 CSRF 攻击


    浏览器缓存机制

    从三个角度来说,分别是:缓存位置  缓存策略   实际场景应用缓存策略
    1 缓存位置
    从缓存位置上来说分为四种,并且各自有优先级,当依次查找缓存且都没有命中的时候,才会去请求网络
    Service Worker
    是运行在浏览器背后的独立线程
    使用 Service Worker的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全
    可以自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。

    如果Service Worker没有命中的话,会根据优先级去查找数据
    但是:不管我们是从 Memory Cache 中还是从网络请求中获取的数据,浏览器都会显示我们是从 Service Worker 中获取的内容。
    Memory Cache
    就是内存中的缓存 读取比磁盘快 但是时效性短 一旦关闭 Tab 页面,内存中的缓存也就被释放了
    Disk Cache
    是存储在硬盘中的缓存,读取速度慢点  但是容量大 时效性长
    在所有浏览器缓存中,Disk Cache 覆盖面基本是最大的。
    它会根据 ·HTTP Herder· 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,
    哪些资源已经过期需要重新请求。
    并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据

    Push Cache

    在以上三种缓存都没有命中时,它才会被使用。并且缓存时间也很短暂,只在会话(Session)中存在,一旦会话结束就被释放。
    在其中的缓存只能被使用一次
    2.缓存策略
    常浏览器缓存策略分为两种:强缓存和协商缓存,并且缓存策略都是通过设置 HTTP Header 来实现的
    强制缓存
    强制缓存可以通过设置两种HTTP HEADER来实现:
    Expires 和 Cache-Control
    其中 Expiries 表示资源会在 (某一具体时间) 后过期 Expiries受限于本地时间,如果修改了本地时间,可能会造成缓存失效。
    而Cache-Control优先级更高 表示资源会在某一时间段后过期 

    Cache-Control的值有:
    • max-age* 设置缓存存储的最大周期,超过这个时间缓存被认为过期(单位秒)
    • no-cache* 不用强制缓存 交给服务端处理 (可以使用服务端缓存策略—协商缓存)
    • no-store 不要强制缓存 也不要服务端缓存
    • private 表明响应只能被单个用户缓存,不能作为共享缓存(即代理服务器不能缓存它)
    • public 表明响应可以被任何对象(包括:发送请求的客户端,代理服务器,等等)缓存
    协商缓存
    是一个服务端缓存策略:由服务端判断可不可以使用缓存资源,但不是在服务端做缓存
    说白了,就是用服务器判断客户端资源是否和服务端一样
    如果一样,则返回304 如果不一样 就返回200
    (根据资源标识,来判断两边的资源是否一样)
    资源标识也在resoponse headers中
    • Last-Modified:资源最后修改时间

    • Etag:资源的唯一标识 类似于人的指纹

    两者中优先使用Etag
    ETag是根据资源内容生成的 也就是一旦内容发生改变 ETag就会改变
    而Last-Modified则是最后修改时间,以秒计时,如果在不可感知的时间内修改了文件,则服务端仍不会改变Last-Modified
    3.实际场景应用缓存策略
    频繁变动的资源
    对于频繁变动的资源
    首先 或者 Last-Modified 来验证资源是否有效。
    这样的做法虽然不能节省请求数量,但是能显著减少响应数据大小。
    代码文件
    一般来说,现在都会使用工具来打包代码,那么我们就可以对文件名进行哈希处理,
    只有当代码修改后才会生成新的文件名。
    基于此,我们就可以给代码文件设置缓存有效期一年 
    Cache-Control: max-age=31536000
    这样只有当 HTML 文件中引入的文件名发生了改变才会去下载最新的代码文件
    否则就一直使用缓存

    浏览器渲染过程

    生成DOM树

    网络中传输的内容都是 0 1这些字节数据 所以浏览器必须这些字节数据转换为字符串
    也就是我们写的代码
    其中Token:标记 还是字符串,是构成代码的最小单位

    生成CSSOM树
    其实转换 CSS 到 CSSOM 树的过程和上一小节的过程是极其类似

    生成Render Tree
    将以上两者组合为渲染树
    但在这一过程中 不是简单的将两者合并就行了
    Render Tree只包括需要显示的节点和这些节点的样式信息如果某个节点是 display: none 
    那么就不会在渲染树中显示。

    生成Render Tree以后,就会进行布局(回流——然后调用GPU绘制,合成图层,显示在屏幕上 进行一些底层的硬件操作

    为什么DOM性能差

    因为 DOM是属于渲染引擎中的东西,而 JS 又是 JS 引擎中的东西。
    当我们通过 JS 操作 DOM 的时候,其实这个操作涉及到了两个线程之间的通信,那么势必会带来一些性能上的损耗。操作 DOM 次数一多,也就等同于一直在进行线程之间的通信,并且操作 DOM 可能还会带来重绘回流的情况,所以也就导致了性能上的问题。

    什么情况会阻塞渲染

    渲染的前提是生成渲染树,所以 HTML 和 CSS 肯定会阻塞渲染。
    如果你想渲染的越快,你越应该降低一开始需要渲染的文件大小,并且扁平层级,优化选择器。
    当浏览器在解析到 script 标签时,会暂停构建 DOM,完成后才会从暂停的地方重新开始。
    也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载 JS文件,这也是都建议将 script 标签放在 body 标签底部的原因。
    也可以通过给script标签加上async和defer属性
    • 加上 defer 属性以后,表示该 JS 文件会并行下载,但是会放到渲染顺序执行
    • 加上 async 属性,表示 JS 文件下载和解析不会阻塞渲染

    回流和重绘

    • 重绘是当节点需要更改外观而不会影响布局的,比如改变 color 就叫称为重绘
    • 回流是布局或者几何属性需要改变就称为回流。
    • 回流必定会发生重绘,重绘不一定会引发回流。回流所需的成本比重绘高的多,改变父节点里的子节点很可能会导致父节点的一系列回流
    eventloop也与回流和重绘有关:
    • 当 Eventloop 执行完 Microtasks 后,会判断 document 是否需要更新,因为浏览器是 60Hz 的刷新率,每 16.6ms 才会更新一次。
    • 然后判断是否有 resize 或者 scroll 事件,有的话会去触发事件,所以 resize 和 scroll 事件也是至少 16ms 才会触发一次,并且自带节流功能。
    • 判断是否触发了 media query
    • 更新动画并且发送事件
    • 判断是否有全屏操作事件
    • 执行 requestAnimationFrame回调
    • 执行 IntersectionObserver 回调,该方法用于判断元素是否可见,可以用于懒加载上,但是兼容性不好 更新界面
    • 以上就是一帧中可能会做的事情。如果在一帧中有空闲时间,就会去执行 requestIdleCallback回调
    减少回流和重绘
    使用 transform 替代 top
    使用 visibility 替换display: none 
    因为前者只会引起重绘,后者会引发回流(改变了布局)
    不要把节点的属性值放在一个循环里当成循环里的变量
    for(let i = 0; i < 1000; i++) {
        // 获取 offsetTop 会导致回流
        console.log(document.querySelector('.test').style.offsetTop)
    }
    不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局
    动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用 requestAnimationFrame
    CSS 选择符从右往左匹配查找,避免节点层级过多
    将频繁重绘或者回流的节点设置为图层,图层能够阻止该节点的渲染行为影响别的节点。比如对于 video 标签来说,浏览器会自动将该节点变为图层

    XSS

    跨站脚本攻击
    说白了,XSS就是攻击者想尽一切办法将可以执行的代码注入到网页中。
    可以使用两种预防方法:
    转义字符
    于用户的输入应该是永远不信任的。
    最普遍的做法就是转义输入输出的内容,对于引号、尖括号、斜杠进行转义
    CSP
    CSP 本质上就是建立白名单
    开发者明确告诉浏览器哪些外部资源可以加载和执行。
    我们只需要配置规则,如何拦截是由浏览器自己实现的。
    我们可以通过这种方式来尽量减少 XSS 攻击。
    可以通过两种方式来开启CSP:
    • 设置 HTTP Header 中的 Content-Security-Policy
    • 设置 meta 标签的方式 <meta http-equiv="Content-Security-Policy">



    CSRF

    跨站请求伪造
    原理就是攻击者构造出一个后端请求地址,诱导用户点击或者通过某些途径自动发起请求。
    如果用户是在登录状态下的话,后端就以为是用户在操作,从而进行相应的逻辑。
    预防方法
    • Get 请求不对数据进行修改
    • 不让第三方网站访问到用户 Cookie
    • 阻止第三方网站请求接口
    • 请求时附带验证信息,比如验证码或者 Token
    设置SameSite:
    可以对 Cookie 设置 SameSite 属性。
    该属性表示 Cookie 不随着跨域请求发送,可以很大程度减少 CSRF 的攻击,
    但是该属性目前并不是所有浏览器都兼容。
    验证 Referer:
    对于需要防范 CSRF 的请求,我们可以通过验证 Referer 来判断该请求是否为第三方网站发起的。
    Token:
    服务器下发一个随机 Token,每次发起请求时将 Token 携带上,服务器验证 Token 是否有效




    击劫持
    点击劫持是一种视觉欺骗的攻击手段。攻击者将需要攻击的网站通过 iframe 嵌套的方式嵌入自己的网页中,并将 iframe 设置为透明,在页面中透出一个按钮诱导用户点击
    预防方法:
    X-FRAME-OPTIONS
    X-FRAME-OPTIONS 是一个 HTTP 响应头 用来防御iframe 嵌套的点击劫持攻击
    该响应头有三个值可选:
    • DENY,表示页面不允许通过 iframe 的方式展示
    • SAMEORIGIN,表示页面可以在相同域名下通过 iframe 的方式展示
    • ALLOW-FROM,表示页面可以在指定来源的 iframe 中展示
    JS防御
    对于某些远古浏览器来说,只有通过 JS 的方式来防御点击劫持了。
    <head>
      <style id="click-jack">
        html {
          display: none !important;
        }
      </style>
    </head>
    <body>
      <script>
        if (self == top) {
          var style = document.getElementById('click-jack')
          document.body.removeChild(style)
        } else {
          top.location = self.location
        }
      </script>
    </body>

    性能优化

    性能优化的内容比较碎片 但总体思路是从两个方面下手:让加载更快 和 让渲染更快

    加载更快:
    • 减少资源体积:压缩代码(可使用webpack打包压缩)
    • 减少访问次数:合并代码  SSR服务器端渲染(服务端把渲染好的东西直接给浏览器) 缓存
    • 使用更快的网络:CDN











    单例模式

    说白了,单例模式就是要求一个类有且只有一个实例
    实现方法:
    class Singleton {
        constructor(name) {
            this.name = name;
            this.instance = null;
        }
        // 构造一个广为人知的接口,供用户对该类进行实例化
        static getInstance(name) {
            if(!this.instance) {
                this.instance = new Singleton(name);
            }
            return this.instance;
        }
    }

    函数柯里化

    柯里化说白了就是高阶函数的一个特殊用法而已
    科学定义:柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
    说白了:就是用闭包把参数保存起来,当参数的数量足够执行函数了,就开始执行函数
    它的应用场景是参数复用

    柯里化的实现:
    • 判断当前函数传入的参数是否大于或等于fn需要参数的数量,如果是,直接执行fn
    • 如果传入参数数量不够,返回一个闭包,暂存传入的参数,并重新返回currying函数
    //柯里化
    function currying (fn,...args){
        if(args.length >= fn.length){
            return fn(...args);
        }else{
            return (...args2) => currying(fn ,...args,...args2)
        }
    }

    手动实现JSONP

    一共分为四步:
    • 1.将传入的data数据转化为url字符串形式
    • 2.处理url中的回调函数
    • 3.创建一个script标签并插入到页面中
    • 4.挂载回调函数
    (function (window,document) {
        "use strict";
        var jsonp = function (url,data,callback) {
    
            // 1.将传入的data数据转化为url字符串形式
            // {id:1,name:'jack'} => id=1&name=jack
            var dataString = url.indexof('?') == -1? '?': '&';
            for(var key in data){
                dataString += key + '=' + data[key] + '&';
            };
    
            // 2 处理url中的回调函数
            // cbFuncName回调函数的名字 :my_json_cb_名字的前缀 + 随机数(把小数点去掉)
            var cbFuncName = 'my_json_cb_' + Math.random().toString().replace('.','');
            dataString += 'callback=' + cbFuncName;
    
            // 3.创建一个script标签并插入到页面中
            var scriptEle = document.createElement('script');
            scriptEle.src = url + dataString;
    
            // 4.挂载回调函数
            window[cbFuncName] = function (data) {
                callback(data);
                // 处理完回调函数的数据之后,删除jsonp的script标签
                document.body.removeChild(scriptEle);
            }
    
            document.body.appendChild(scriptEle);
        }
    
        window.$jsonp = jsonp;
    
    })(window,document)