Code Monkey home page Code Monkey logo

webvueblog / leetcode Goto Github PK

View Code? Open in Web Editor NEW
385.0 12.0 67.0 5.29 MB

🎲【每日更新 question & answers】一个 ☝️ 正经的前端学习,每天进步一点点!手写源码,api,算法;包含JavaScript / Vue / React / TypeScript /HTML / CSS / Nodejs / Leetcode……Suggest 👍

Home Page: https://webvueblog.github.io/Leetcode/#/Blog/BlogList

JavaScript 96.90% HTML 2.96% CSS 0.14%
javascript html css vue nodejs typescript ecmascritpt webpack react front-end node

leetcode's Introduction

正经的前端学习

新增issues博客

28529a0f8f19ed720fa16ec5c005a7f

Starred

Stargazers over time

中文 | English

一个 ☝️ 正经的前端学习 开源 仓库,每天进步一点!欢迎大家前来讨论,如果觉得对你的学习有一定的帮助,欢迎点个Star (此仓库每天都会手动更新)

日志

  • 正经的前端学习(在更)
  • 深入理解JS核心技术(在更)
  • 深入手写JS原生API(在更)

😃 对自己的寄语

  • 吾日三省吾身
  • 不愤不启,不悱不发。举一隅不以三隅反,则不复也
  • 死磕自己,遇见更好的自己,365天无节假日
  • 不跟别人比,做好自己;不诋毁他人,向优秀者学习
  • 每一个阶段的话或事,可能是你下一阶段认为的蠢事
  • 希望大家在这浮夸的前端圈里,保持冷静
  • 重在坚持
  • 没有人是一座孤岛

福利 🧧,👏 模拟面试,限时30分钟

首先 感谢 🙏 您的 Star, 模拟面试可以 😌 (我会针对您的个人 👤 年限出题时间为30分钟内,所以请你 把握👌好机会)。最后,一个人可以走的更快,但一群人才能走的更远,和大家一起共勉,多折腾折腾,做一个低调务实优秀的**好青年,加我也要注意 ⚠️ 请求同意备注!!!

☕️ 赞助

地址 ⬇️

https://github.com/webVueBlog/Leetcode

🐤 交流讨论 && 如何学习 && 转载声明 && 帮忙修正以及补充

第一:你可以直接在本仓库阅读即可,阶段性学习。 (可以转载里面的所有知识点用到任何地方,但请添加仓库的地址)有问题欢迎提交issues

Leetcode题解

展开查看

👩🏻‍💻:webVueBlog的leetcode刷题📒

深入理解JS核心技术

展开查看
Number Title
1 在 JavaScript 中创建对象的可能方式有哪些
2 什么是原型链
3 调用、应用和绑定有什么区别
4 什么是 JSON 及其常用操作
5 数组切片方法的目的是什么
6 数组拼接方法的目的是什么
7 切片和拼接有什么区别
8 你如何比较Object和Map
9 == 和 === 运算符有什么区别
10 什么是 lambda 或箭头函数
11 什么是一级函数
12 什么是一阶函数
13 什么是高阶函数
14 什么是一元函数
15 什么是柯里化函数
16 什么是纯函数
17 let 关键字的作用是什么
18 let 和 var 有什么区别
19 选择名称let作为关键字的原因是什么
20 如何在 switch 块中重新声明变量而不会出错
21 什么是暂时性死区
22 什么是 IIFE(立即调用函数表达式)
23 您如何在 JavaScript 中解码或编码 URL
24 什么是memoization
25 什么是Hoisting
26 ES6 中的类是什么
27 什么是闭包
28 什么是模块
29 为什么需要模块
30 javascript中的作用域是什么
31 什么是service worker?
32 如何使用 service worker 操作 DOM
33 如何在 service worker 重启时重用信息
34 什么是索引数据库
35 什么是网络存储
36 什么是发布消息
37 什么是 Cookie
38 为什么需要 Cookie
39 cookie 中有哪些选项
40 如何删除 cookie
41 cookie、本地存储和会话存储有什么区别
42 localStorage 和 sessionStorage 的主要区别是什么
43 您如何访问网络存储
44 会话存储上可用的方法有哪些
45 什么是存储事件及其事件处理程序
46 为什么需要网络存储
47 你如何检查网络存储浏览器支持
48 你如何检查网络工作者浏览器支持
49 举个 web worker 的例子
50 webworkers对DOM有什么限制
51 什么是承诺
52 为什么需要承诺
53 承诺的三种状态是什么
54 什么是回调函数
55 为什么我们需要回调
56 什么是回调地狱
57 什么是服务器发送事件
58 您如何接收服务器发送的事件通知
59 如何检查浏览器对服务器发送事件的支持
60 服务器发送的事件有哪些可用的事件
61 承诺的主要规则是什么
62 什么是回调中的回调
63 什么是承诺链
64 什么是promise.all
65 承诺中比赛方法的目的是什么
66 什么是javascript中的严格模式
67 为什么需要严格模式
68 你如何声明严格模式
69 双感叹号的目的是什么
70 删除运算符的目的是什么
71 什么是 typeof 运算符
72 什么是未定义属性
73 什么是空值
74 null 和 undefined 有什么区别
75 什么是评估
76 窗口和文档有什么区别
77 你如何在javascript中访问历史记录
78 你如何检测大写锁定键是否打开
79 什么是 NaN
80 未声明变量和未定义变量有什么区别
81 什么是全局变量
82 全局变量有什么问题
83 什么是 NaN 属性
84 isFinite 函数的目的是什么
85 什么是事件流
86 什么是事件冒泡
87 什么是事件捕获
88 如何使用 JavaScript 提交表单
89 您如何找到操作系统详细信息
90 文档加载和 DOMContentLoaded 事件有什么区别
91 本机,主机和用户对象之间有什么区别
92 用于调试 JavaScript 代码的工具或技术有哪些
93 与回调相比,promise 的优缺点是什么
94 属性和属性有什么区别
95 什么是同源策略
96 void 0的目的是什么
97 JavaScript 是编译型语言还是解释型语言
98 JavaScript 是区分大小写的语言吗
99 Java和JavaScript之间有什么关系吗
100 什么是事件
101 谁创建了 JavaScript
102 preventDefault 方法有什么用
103 stopPropagation 方法有什么用
104 return false 涉及哪些步骤
105 什么是物料清单
106 setTimeout 有什么用
107 setInterval 有什么用
108 为什么 JavaScript 被视为单线程
109 什么是事件委托
110 什么是 ECMAScript

☀ 阶段三十五(715)

展开查看

⭐ 阶段三十四(715)

展开查看

🌙 阶段三十三(690)

展开查看

👇 阶段三十二(668)

展开查看

🐂 阶段三十一(648)

展开查看

🐂 阶段三十(623)

展开查看

🤨 阶段二十九(598)

展开查看

😛 阶段二十八(565)

展开查看

😛 阶段二十七(555)

展开查看

😛 阶段二十六(544)

展开查看

😛 阶段二十五(532)

展开查看

😛 阶段二十四(525)

展开查看

😛 阶段二十三(514)

展开查看

📕 阶段二十二(500)

展开查看

😋 阶段二十一(480)

展开查看

✔ 阶段二十(470)

展开查看

😗 阶段十九(460)

展开查看

🐉 阶段十八(450)

展开查看

🐔 阶段十七(440)

展开查看

🐟 阶段十六(425)

展开查看

🦐 阶段十五(401)

展开查看

🦂 阶段十四(384)

展开查看

😘 阶段十三(370)

展开查看

🥰 阶段十二(340)

展开查看

😉 阶段十一(324)

展开查看

🙃 阶段十(306)

展开查看

😍 阶段九(285)

展开查看

🧑🏻 阶段八(250)

展开查看

🧑🏻‍💻 阶段七(215)

展开查看

😇 阶段六(190)

展开查看

🧑🏻‍💻 阶段五(175)

展开查看

🥲 阶段四(150)

展开查看

🧑🏻‍💻 阶段三(145)

展开查看

🤣 阶段二(100)

展开查看

🧑🏻‍💻 阶段一(50)

展开查看

全栈架构师

展开查看

webVueBlog Stars

展开查看

JavaScript

  • postcss-pxtorem Convert pixel units to rem (root em) units using PostCSS

  • eslint Find and fix problems in your JavaScript code.

  • dva 🌱 React and redux based, lightweight and elm-style framework. (Inspired by elm and choo)

  • nice-js-leetcode 好青年 | leetcode 今日事今日毕(✅ Solutions to LeetCode by JavaScript, 100% test coverage, runtime beats 100% / LeetCode 题解 / GitHub Actions集成LeetCode每日一题至issues)

  • postcss-plugin-px2rem postcss plugin px2rem

  • LeetHub Automatically sync your leetcode solutions to your github account - top 5 trending GitHub repository

  • pinyin 🇨🇳 汉字拼音 ➜ hàn zì pīn yīn

  • electron-quick-start Clone to try a simple Electron app

  • awesome-css 国内css平台从业者交流,Github Actions自动化部署

  • nice-my-friend 😳轻松查看和过滤所有关注和关注。通过 GitHub Action 自动更新。

  • cloud-email 微信小程序实现邮件发送,借助小程序云开发进行邮件验证码发送

  • cloud-pay 10行代码实现微信小程序支付,借助小程序云开发云函数实现微信支付

  • tcb-subscribe-demo 小程序·云开发快速接入小程序订阅消息,开发开课提醒小程序

  • Fruit-store-mp 🍊微信小程序-水果商城-云开发

  • tcb-demo-basic 小程序·云开发系列教程——基础能力DEMO

  • mp-book 小程序·云开发系列教程

  • react-native A framework for building native applications using React

  • react A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • basket.js A script and resource loader for caching & loading files with localStorage

  • puppeteer-webperf Automating Web Performance testing with Puppeteer 🎪

  • file-breakpoint-continue 🐬 Node+Vue 实现大文件 📁 上传,断点续传等 https://webvueblog.github.io/file-breakpoint-continue/ 💎

  • todolist-vue 用 Todoist 组织一切,管理工作和生活的To Do List,使用 Vite、Vue 和 Vuex 构建的 TodoMVC

  • express-node ⚡ express-node-mysql-react全家桶

  • Leetcode 【每日更新 question & answers】一个 ☝️ 正经的前端学习,每天进步一点点!手写源码,api,算法;包含JavaScript / Vue / React / TypeScript /HTML / CSS / Nodejs / Leetcode……Suggest 👍

  • eslint-plugin-prettier ESLint plugin for Prettier formatting

  • sinon Test spies, stubs and mocks for JavaScript.

  • uuid Generate RFC-compliant UUIDs in JavaScript

  • core-js Standard Library

  • JavaScript Algorithms and Data Structures implemented in JavaScript for beginners, following best practices.

  • egg 🥚 Born to build better enterprise frameworks and apps with Node.js & Koa

  • koa-router Router middleware for koa.

  • plupload Plupload is JavaScript API for building file uploaders. It supports multiple file selection, file filtering, chunked upload, client side image downsizing and when necessary can fallback to alternative runtimes, like Flash and Silverlight.

  • ejs Embedded JavaScript templates -- http://ejs.co

  • cookies Signed and unsigned cookies based on Keygrip

  • express-resource Resourceful routing for Express

  • examples Example Koa apps

  • koa Expressive middleware for node.js using ES2017 async functions

  • kityminder 百度脑图

  • webuploader It's a new file uploader solution!

  • digital_video_concepts 数字视频相关技术和概念

  • customize-cra Override webpack configurations for create-react-app 2.0

  • eslint-plugin-vue Official ESLint plugin for Vue.js

  • sass-resources-loader SASS resources (e.g. variables, mixins etc.) loader for Webpack. Also works with less, post-css, etc.

  • standard-version 🏆 Automate versioning and CHANGELOG generation, with semver.org and conventionalcommits.org

  • xgplayer A HTML5 video player with a parser that saves traffic

  • vue-meta Manage HTML metadata in Vue.js components with SSR support

  • standard 🌟 JavaScript Style Guide, with linter & automatic code fixer

  • roughViz Reusable JavaScript library for creating sketchy/hand-drawn styled charts in the browser.

  • Chart.js Simple HTML5 Charts using the tag

  • oss-browser OSS Browser 提供类似windows资源管理器功能。用户可以很方便的浏览文件,上传下载文件,支持断点续传等。

  • the-super-tiny-compiler ⛄ Possibly the smallest compiler ever

  • webpack-cli Webpack's Command Line Interface

  • emotion 👩‍🎤 CSS-in-JS library designed for high performance style composition

  • preact ⚛️ Fast 3kB React alternative with the same modern API. Components & Virtual DOM.

  • material-ui MUI Core (formerly Material-UI) is the React UI library you always wanted. Follow your own design system, or start with Material Design.

  • create-react-app Set up a modern web app by running one command.

  • jsmpeg MPEG1 Video Decoder in JavaScript

  • webxr Repository for the WebXR Device API Specification.

  • three.js JavaScript 3D Library.

  • aframe 🅰️ web framework for building virtual reality experiences.

  • tailwindcss A utility-first CSS framework for rapid UI development.

  • module-federation-examples Implementation examples of module federation , by the creators of module federation

  • webpack A bundler for javascript and friends. Packs many modules into a few bundled assets. Code Splitting allows for loading parts of the application on demand. Through "loaders", modules can be CommonJs, AMD, ES6 modules, CSS, Images, JSON, Coffeescript, LESS, ... and your custom stuff.

  • jsmpeg-player MPEG1 Video Player Based On JSMpeg.

  • JavaScript30 有关 @wesbos 的课程 JavaScript-30 的中文练习指南

  • h265player 一套完整的Web版H.265播放器解决方案,非常适合学习交流和实际应用。基于JS码流解封装、WebAssembly(FFmpeg)视频解码,利用Canvas画布投影、AudioContext播放音频。

  • FE-Interview 🔥🔥🔥 前端面试,独有前端面试题详解,前端面试刷题必备,1000+前端面试真题,Html、Css、JavaScript、Vue、React、Node、TypeScript、Webpack、算法、网络与安全、浏览器

  • floating-ui JavaScript positioning library for tooltips, popovers, dropdowns, and more

  • markmap Visualize markdown documents as mindmaps

  • ripple A tiny foundation for building reactive views

  • audio-sculptor you can edit audio(such as clip, splice and replace) in browsers😊

  • axios Promise based HTTP client for the browser and node.js

  • codemirror5 In-browser code editor (version 5, legacy)

  • compression-webpack-plugin Prepare compressed versions of assets to serve them with Content-Encoding

  • cors-anywhere CORS Anywhere is a NodeJS reverse proxy which adds CORS headers to the proxied request.

  • driver.js A light-weight, no-dependency, vanilla JavaScript engine to drive the user's focus across the page

  • fuse4js FUSE bindings for Javascript and node.js

  • imagemin [Unmaintained] Minify images seamlessly

  • imagemin-mozjpeg Imagemin plugin for mozjpeg

  • js-pinyin js汉字转拼音

  • pinyin4js A opensource javascript library for converting chinese to pinyin。welcome Star : P

  • jsencrypt A Javascript library to perform OpenSSL RSA Encryption, Decryption, and Key Generation.

  • jsonlint A JSON parser and validator with a CLI.

  • jszip Create, read and edit .zip files with Javascript

  • lib-flexible 可伸缩布局方案

  • simple-statistics simple statistics for node & browser javascript

  • moment Parse, validate, manipulate, and display dates in javascript.

  • morgan HTTP request logger middleware for node.js

  • nprogress For slim progress bars like on YouTube, Medium, etc

  • prerender-spa-plugin Prerenders static HTML in a single-page application.

  • randexp.js Create random strings that match a given regular expression.

  • register-service-worker A script to simplify service worker registration with hooks for common events.

  • resize-observer-polyfill A polyfill for the Resize Observer API

  • vuex 🗃️ Centralized State Management for Vue.js.

  • webpack-bundle-analyzer Webpack plugin and CLI utility that represents bundle content as convenient interactive zoomable treemap

  • es6-promise A polyfill for ES6-style Promises

  • element-react Element UI

  • chalk 🖍 Terminal string styling done right

  • ora Elegant terminal spinner

  • Inquirer.js A collection of common interactive command line user interfaces.

  • commander.js node.js command-line interfaces made easy

  • markdown-it-toc-done-right A table of contents (TOC) plugin for Markdown-it with focus on semantic and security. Made to work gracefully with markdown-it-anchor.

  • markdown-it-anchor Header anchors for markdown-it.

  • ThreeExample.js 《Three.js 入门指南》书例代码

  • right-menu 📜 @right-menu 是一个使用 TypeScript 开发的右键菜单插件, 🏆 可以在 JS / TS / Vue / React 等多端框架使用, 🦄 支持多级菜单 / 异步渲染 / 骨架Loading / 自适应主题 / mac黑夜模式

  • vue-i18n 🌐 Internationalization plugin for Vue.js

  • vuelidate Simple, lightweight model-based validation for Vue.js

  • react Cheatsheets for experienced React developers getting started with TypeScript

  • Awesome-qr.js An awesome QR code generator written in JavaScript.

  • chimee a video player framework aims to bring wonderful experience on browser

  • vue-video-player @videojs component for @vuejs

  • flv.js HTML5 FLV Player

  • ECMAScript2016-Design-Patterns Design Patterns for ES6 (使用es6实现的设计模式)

  • webfunny_monitor webfunny是一款轻量级的前端监控系统,webfunny也是一款前端性能监控系统,无埋点监控前端日志,实时分析前端健康状态。webfunny is a lightweight front-end monitoring system and webfunny is also a front-end performance monitoring system. It monitors front-end logs and analyzes front-end health status in real time.

  • Daily-Interview-Question 我是依扬(木易杨),公众号「高级前端进阶」作者,每天搞定一道前端大厂面试题,祝大家天天进步,一年后会看到不一样的自己。

  • 33-js-concepts 📜 每个 JavaScript 工程师都应懂的33个概念 @leonardomso

  • front-end-interview-handbook ⚡️ Front End interview preparation materials for busy engineers

  • 30-seconds-of-interviews A curated collection of common interview questions to help you prepare for your next interview.

  • vue-ssr-boilerplate Vue.js Server Side Rendering Boilerplate without Polluting Vuex

  • eslint-config-vue

  • clipboard.js ✂️ Modern copy to clipboard. No Flash. Just 3kb gzipped 📋

  • vue-countTo It's a vue component that will count to a target number at a specified duration https://panjiachen.github.io/countTo/demo/

  • Mock A simulation data generator

  • anime JavaScript animation engine

  • medium-to-own-blog Switch from Medium to your own blog in a few minutes

  • draft-js A React framework for building text editors.

  • node-tenpay 微信支付 for nodejs

  • wechat 微信公共平台消息接口服务中间件

  • electron-vue An Electron & Vue.js quick start boilerplate with vue-cli scaffolding, common Vue plugins, electron-packager/electron-builder, unit/e2e testing, vue-devtools, and webpack.

  • bowser a browser detector

  • compressorjs JavaScript image compressor.

  • hotkeys ➷ A robust Javascript library for capturing keyboard input. It has no dependencies.

  • tech-interview-handbook 💯 Curated interview preparation materials for busy engineers

  • globby User-friendly glob matching

  • dayjs ⏰ Day.js 2kB immutable date-time library alternative to Moment.js with the same modern API

  • history.js History.js gracefully supports the HTML5 History/State APIs (pushState, replaceState, onPopState) in all browsers. Including continued support for data, titles, replaceState. Supports jQuery, MooTools and Prototype. For HTML5 browsers this means that you can modify the URL directly, without needing to use hashes anymore. For HTML4 browsers it will revert back to using the old onhashchange functionality.

  • javascript-algorithms 📝 Algorithms and data structures implemented in JavaScript with explanations and links to further readings

  • vue-analysis 👍 Vue.js 源码分析

  • react-developer-roadmap Roadmap to becoming a React developer

  • regexr RegExr is a HTML/JS based tool for creating, testing, and learning about Regular Expressions.

  • stats.js JavaScript Performance Monitor

  • dom-examples Code examples that accompany various MDN DOM and Web API documentation pages

  • leon 🧠 Leon is your open-source personal assistant.

  • js-cloudimage-360-view Engage your customers with a stunning 360 view of your products. Any questions or issues, please report to https://github.com/scaleflex/js-cloudimage-360-view/issues

  • localForage 💾 Offline storage, improved. Wraps IndexedDB, WebSQL, or localStorage using a simple but powerful API.

  • leetcode LeetCode Solutions: A Record of My Problem Solving Journey.( leetcode题解,记录自己的leetcode解题之路。)

  • WasmVideoPlayer Play file/stream with wasm & webgl & web audio api, using ffmpeg for multi codec support, especially for h265,support http, websocket, http-flv stream.

  • codecbox.js video and audio codecs for javascript based on ffmpeg and asm.js

  • algorithm-visualizer 🎆Interactive Online Platform that Visualizes Algorithms from Code

  • fullPage.js fullPage plugin by Alvaro Trigo. Create full screen pages fast and simple

  • crypto-js JavaScript library of crypto standards.

  • wtfjs 🤪 A list of funny and tricky JavaScript examples

  • weekly 前端精读周刊。帮你理解最前沿、实用的技术。

  • WebRTC-Experiment WebRTC, WebRTC and WebRTC. Everything here is all about WebRTC!!

  • samples WebRTC Web demos and samples

  • HQChart HQChart - H5, 微信小程序 沪深/港股/数字货币/期货/美股 K线图(kline),走势图,缩放,拖拽,十字光标,画图工具,截图,筹码图. 分析家语法,通达信语法,(麦语法),第3方数据替换接口

  • videojs-contrib-dash Video.js plugin for supporting the MPEG-DASH playback through a video.js player

  • screenity The most powerful screen recorder & annotation tool for Chrome 🎥

  • 30-Days-Of-React 30 Days of React challenge is a step by step guide to learn React in 30 days. It requires HTML, CSS, and JavaScript knowledge. You should be comfortable with JavaScript before you start to React. If you are not comfortable with JavaScript check out 30DaysOfJavaScript. This is a continuation of 30 Days Of JS. This challenge may take more than 100 days, follow your own pace.

  • spy-debugger 微信调试,各种WebView样式调试、手机浏览器的页面真机调试。便捷的远程调试手机页面、抓包工具,支持:HTTP/HTTPS,无需USB连接设备。

  • 30-seconds-of-code Short JavaScript code snippets for all your development needs

  • flowchart.js Draws simple SVG flow chart diagrams from textual representation of the diagram

  • ueditor rich text 富文本编辑器

  • StreamSaver.js StreamSaver writes stream to the filesystem directly asynchronous

  • FileSaver.js An HTML5 saveAs() FileSaver implementation

  • vue-scrollto Adds a directive that listens for click events and scrolls to elements.

  • faker.js generate massive amounts of realistic fake data in Node.js and the browser

  • js-cookie A simple, lightweight JavaScript API for handling browser cookies

  • lodash A modern JavaScript utility library delivering modularity, performance, & extras.

  • vue-awesome-swiper 🏆 Swiper component for @vuejs

  • Buttons A CSS button library built using Sass and Compass

  • neditor 基于 ueditor的更现代化的富文本编辑器,支持HTTPS

  • vue-lazyload A Vue.js plugin for lazyload your Image or Component in your application.

  • swiper Most modern mobile touch slider with hardware accelerated transitions

  • Charts 轻量级图表,提供常用图表如折线图、柱状图、饼状图等,支持动画效果

  • 30-Days-Of-JavaScript 30 days of JavaScript programming challenge is a step-by-step guide to learn JavaScript programming language in 30 days. This challenge may take more than 100 days, please just follow your own pace.

  • renren-fast-vue renren-fast-vue基于vue、element-ui构建开发,实现renren-fast后台管理前端功能,提供一套更优的前端解决方案。

  • syntaxhighlighter SyntaxHighlighter is a fully functional self-contained code syntax highlighter developed in JavaScript.

  • howler.js Javascript audio library for the modern web.

  • autoprefixer Parse CSS and add vendor prefixes to rules by Can I Use

  • svg-sprite-loader Webpack loader for creating SVG sprites.

  • fe-interview 宇宙最强的前端面试指南 (https://lucifer.ren/fe-interview)

  • vue-image-crop-upload A beautiful vue component for image cropping and uploading. (vue图片剪裁上传组件)

  • vue-baberrage A simple Barrage plugin Base on Vue.js. | 基于Vue.js弹幕插件.

  • Darkmode.js 🌓 Add a dark-mode / night-mode to your website in a few seconds

  • wavesurfer.js Navigable waveform built on Web Audio and Canvas

  • pdf.js PDF Reader in JavaScript

  • html-webpack-plugin Simplifies creation of HTML files to serve your webpack bundles

  • Vue.Draggable Vue drag-and-drop component based on Sortable.js

  • ffmpeg.js Port of FFmpeg with Emscripten

  • node Node.js JavaScript runtime ✨🐢🚀✨

  • vue-cli 🛠️ webpack-based tooling for Vue.js Development

  • rollup Next-generation ES module bundler

  • NeteaseCloudMusicApi 网易云音乐 Node.js API service

  • react-music 基于React的在线音乐播放器(移动端高仿安卓网易云音乐)(重构是不可能的,这辈子都不会用 hooks 重构)

  • curvejs Made curve a dancer in HTML5 canvas - 魔幻线条

  • javascript JavaScript Style Guide

  • nodebestpractices ✅ The Node.js best practices list (June 2022)

  • weapp-plugin-demo 有赞微商城所有小程序插件的演示demo

  • vue-admin-template a vue2.0 minimal admin template

  • electron-vue-admin vue electron admin template web: http://panjiachen.github.io/vue-admin-template

  • webpack A full-featured Webpack + vue-loader setup with hot reload, linting, testing & css extraction.

  • form-create 🔥🔥🔥 强大的动态表单生成器|form-create is a form generation component that can generate dynamic rendering, data collection, verification and submission functions through JSON.

  • canvas

  • special-vue-series-code-analyzing 「Vue生态库源码系列」,Vue、Vue-router、Vuex、Vue-cli、Vue-loader、Vue-devtools等

  • weui weui+是在weui和zepto基础上开发的增强UI组件,目前分为表单,基础,组件,js插件四大类,共计百余项功能,是最全的weui样式

  • radarCanvas 微信小程序 雷达图组件 component

  • vue-canvas-poster vue生成海报图,一个通过 css 属性画 canvas 图片的轻量级的 vue 组件 (Vue poster,a lightweight vue component that draws canvas images via css properties.)

  • blur-admin AngularJS Bootstrap Admin Panel Framework

  • AdminLTE AdminLTE - Free admin dashboard template based on Bootstrap 4

  • es6tutorial 《ECMAScript 6入门》是一本开源的 JavaScript 语言教程,全面介绍 ECMAScript 6 新增的语法特性。

  • v2.vuejs.org 📄 Documentation for Vue 2

  • sol-weapp 👏红包雨,大转盘,小程序营销组件,小程序商城常用组件 https://sunniejs.github.io/sol-weapp/

  • Blog 记录成长的过程

  • nuxt.js The Intuitive Vue(2) Framework

  • echarts-for-weixin Apache ECharts 的微信小程序版本

  • weixin 微信小游戏辅助合集(加减大师、包你懂我、大家来找茬腾讯版、头脑王者、好友画我、悦动音符、我最在行、星途WeGoing、猜画小歌、知乎答题王、腾讯**象棋、跳一跳、题多多黄金版)

  • vant-weapp 轻量、可靠的小程序 UI 组件库

  • vue-cli-plugin-vue-next A Vue CLI plugin for trying out vue-next (experimental)

  • chinese-independent-blogs 中文独立博客列表

  • vue-router 🚦 The official router for Vue 2

  • odometer-for-wechatapp 微信小程序odometer数字滚动动画组件

  • html2wxml 用于微信小程序的HTML和Markdown格式的富文本渲染组件,支持代码高亮

  • Painter 小程序生成图片库,轻松通过 json 方式绘制一张可以发到朋友圈的图片

  • inmap 大数据地理可视化

  • fe-interview 前端面试每日 3+1,以面试题来驱动学习,提倡每日学习与思考,每天进步一点!每天早上5点纯手工发布面试题(死磕自己,愉悦大家),5000+道前端面试题全面覆盖,HTML/CSS/JavaScript/Vue/React/Nodejs/TypeScript/ECMAScritpt/Webpack/Jquery/小程序/软技能……

  • express Fast, unopinionated, minimalist web framework for node.

  • vuep 🎡 A component for rendering Vue components with live editor and preview.

Java

  • JS-Sorting-Algorithm 一本关于排序算法的 GitBook 在线书籍 《十大经典排序算法》,多语言实现。

  • mall mall项目是一套电商系统,包括前台商城系统及后台管理系统,基于SpringBoot+MyBatis实现,采用Docker容器化部署。 前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程、会员中心、客户服务、帮助中心等模块。 后台管理系统包含商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等模块。

  • JustAuth 🏆Gitee 最有价值开源项目 🚀:100: 小而全而美的第三方登录开源组件。目前已支持Github、Gitee、微博、钉钉、百度、Coding、腾讯云开发者平台、OSChina、支付宝、QQ、微信、淘宝、Google、Facebook、抖音、领英、小米、微软、今日头条、Teambition、StackOverflow、Pinterest、人人、华为、企业微信、酷家乐、Gitlab、美团、饿了么、推特、飞书、京东、阿里云、喜马拉雅、Amazon、Slack和 Line 等第三方平台的授权登录。 Login, so easy!

  • Sa-Token 这可能是史上功能最全的Java权限认证框架!目前已集成——登录认证、权限认证、分布式Session会话、微服务网关鉴权、单点登录、OAuth2.0、踢人下线、Redis集成、前后台分离、记住我模式、模拟他人账号、临时身份切换、账号封禁、多账号认证体系、注解式鉴权、路由拦截式鉴权、花式token生成、自动续签、同端互斥登录、会话治理、密码加密、jwt集成、Spring集成、WebFlux集成...

  • vhr 微人事是一个前后端分离的人力资源管理系统,项目采用SpringBoot+Vue开发。

  • pdf-bookmark pdf bookmark generator 目录 书签 大纲

  • JimuReport 「低代码可视化报表」类似excel操作风格,在线拖拽完成设计!功能涵盖: 报表设计、图形报表、打印设计、大屏设计等,完全免费!秉承“简单、易用、专业”的产品理念,极大的降低报表开发难度、缩短开发周期、解决各类报表难题。

  • GitHub-Chinese-Top-Charts 🇨🇳 GitHub中文排行榜,各语言分设「软件 | 资料」榜单,精准定位中文好项目。各取所需,高效学习。

  • LeetCodeAnimation Demonstrate all the questions on LeetCode in the form of animation.(用动画的形式呈现解LeetCode题目的思路)

  • NYCSDE 公众号【码农田小齐】的分类合集

  • hello-algorithm 🌍 针对小白的算法训练 | 包括四部分:①.算法基础 ②.力扣图解 ③.大厂面经 ④.CS_汇总 | 附:1、千本开源电子书 2、百张技术思维导图(项目花了上百小时,希望可以点 star 支持,🌹感谢~)

  • gushici 一言·古诗词 API (Hitokoto API),随机返回一条古诗词名句。采用 Vert.x + Redis 全异步开发,毫秒级稳定响应。

TypeScript

  • vuese 🤗 One-stop solution for vue component documentation. Original org: https://github.com/vuese

  • react-docgen-typescript A simple parser for react properties defined in typescript instead of propTypes.

  • pro-components 🏆 Use Ant Design like a Pro!

  • GGEditor A visual graph editor based on G6 and React

  • L7 🌎 Large-scale WebGL-powered Geospatial Data Visualization analysis framework which relies on Mapbox GL or AMap to render basemaps.

  • G2 📊 A highly interactive data-driven visualization grammar for statistical charts.

  • G6 ♾ A Graph Visualization Framework in JavaScript

  • umi 🌋 Pluggable enterprise-level react application framework.

  • jest Delightful JavaScript Testing.

  • todolist-react TypeScript版本-使用 React 和 Redux 构建的 TodoMVC (props&Event&Context&Mobx&Redux)

  • redux Predictable state container for JavaScript apps

  • svg-path-editor Online editor to create and manipulate SVG paths

  • regulex 🚧 Regular Expression Excited!

  • core 🚀 The Node.js Framework highly focused on developer ergonomics, stability and confidence

  • koa-swagger-decorator using decorator to automatically generate swagger doc for koa-router

  • naive-ui A Vue 3 Component Library. Fairly Complete. Theme Customizable. Uses TypeScript. Fast.

  • formily Alibaba Group Unified Form Solution -- Support React/ReactNative/Vue2/Vue3

  • tdesign-vue A Vue.js UI components lib for TDesign.

  • typescript-eslint ✨ Monorepo for all the tooling which enables ESLint to support TypeScript

  • better-scroll 📜 inspired by iscroll, and it supports more features and has a better scroll perfermance

  • dumi 📖 Documentation Generator of React Component

  • date-fns ⏳ Modern JavaScript date utility library ⌛️

  • react-dnd Drag and Drop for React

  • formatjs The monorepo home to all of the FormatJS related libraries, most notably react-intl.

  • ant-design An enterprise-class UI design language and React UI library

  • react-router Declarative routing for React

  • qiankun 📦 🚀 Blazing fast, simple and complete solution for micro frontends.

  • svelte Cybernetically enhanced web apps

  • typescript-tutorial TypeScript 入门教程

  • mini-vue 实现最简 vue3 模型( Help you learn more efficiently vue3 source code )

  • vitest A Vite-native test framework. It's fast!

  • vue-codemirror @codemirror code editor component for @vuejs

  • countUp.js Animates a numerical value by counting to it

  • docx Easily generate .docx files with JS/TS with a nice declarative API. Works for Node and on the Browser.

  • path-to-regexp Turn a path string such as /user/:name into a regular expression

  • react-sortablejs React bindings for SortableJS

  • tui.editor 🍞📝 Markdown WYSIWYG Editor. GFM Standard + Chart & UML Extensible.

  • svgicon SVG icon components and tool set

  • TypeScript TypeScript 使用手册(中文版)翻译。http://www.typescriptlang.org

  • vee-validate ✅ Form Validation for Vue.js

  • any-rule 🦕 常用正则大全, 支持web / vscode / idea / Alfred Workflow多平台

  • ant-design-icons ⭐ Ant Design SVG Icons

  • notable The Markdown-based note-taking app that doesn't suck.

  • npkill List any node_modules directories in your system, as well as the space they take up. You can then select which ones you want to erase to free up space.

  • snabbdom A virtual DOM library with focus on simplicity, modularity, powerful features and performance.

  • developer-roadmap Roadmap to becoming a developer in 2022

  • docsearch 📘 The easiest way to add search to your documentation.

  • TypeScriptSamples Community Driven Samples for TypeScript

  • github-pages-deploy-action Automatically deploy your project to GitHub Pages using GitHub Actions. This action can be configured to push your production-ready code into any branch you'd like.

  • excalidraw Virtual whiteboard for sketching hand-drawn like diagrams

  • 3d-book-image-css-generator Generate a 3D image from a book cover and export to HTML/CSS to embed on your website.

  • immutable-js Immutable persistent data collections for Javascript which increase efficiency and simplicity.

  • html2canvas Screenshots with JavaScript

  • vuex-module-decorators TypeScript/ES7 Decorators to create Vuex modules declaratively

  • react-qq-music 🎵 基于 React 的QQ音乐 mac 客户端播放器(PC) Online Music Player(qqmusic)

  • csslayout A collection of popular layouts and patterns made with CSS. Now it has 100+ patterns and continues growing!

  • ssr A most advanced ssr framework support React/Vue2/Vue3 on Earth that implemented serverless-side render specification.

  • typescript-library-starter Starter kit with zero-config for building a library in TypeScript, featuring RollupJS, Jest, Prettier, TSLint, Semantic Release, and more!

  • vitepress Vite & Vue powered static site generator.

  • vue-class-component ES / TypeScript decorator for class-style Vue components.

  • vuex-class-component A Type Safe Vuex Module or Store Using ES6 Classes and ES7 Decorators written in TypeScript.

  • vuex-class Binding helpers for Vuex and vue-class-component

  • router 🚦 The official router for Vue.js

  • vue-property-decorator Vue.js and Property Decorator

  • vscode-yaml YAML support for VS Code with built-in kubernetes syntax support

  • vuetify 🐉 Material Component Framework for Vue

  • devtools ⚙️ Browser devtools extension for debugging Vue.js applications.

  • wangEditor wangEditor —— 开源 Web 富文本编辑器

  • TypeScript TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • javascript-datastructures-algorithms 📚 collection of JavaScript and TypeScript data structures and algorithms for education purposes. Source code bundle of JavaScript algorithms and data structures book

  • bytemd Hackable Markdown Editor and Viewer

  • rollup-plugin-vue Roll .vue files

  • vditor ♏ 一款浏览器端的 Markdown 编辑器,支持所见即所得(富文本)、即时渲染(类似 Typora)和分屏预览模式。An In-browser Markdown editor, support WYSIWYG (Rich Text), Instant Rendering (Typora-like) and Split View modes.

  • rap2-delos 阿里妈妈前端团队出品的开源接口管理工具RAP第二代

  • echarts Apache ECharts is a powerful, interactive charting and data visualization library for browser

  • vant Lightweight Mobile UI Components built on Vue

  • ant-design-pro 👨🏻‍💻👩🏻‍💻 Use Ant Design like a Pro!

  • core 🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • vite Next generation frontend tooling. It's fast!

  • composition-api Composition API plugin for Vue 2

  • vue 🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • artipub Article publishing platform that automatically distributes your articles to various media channels

Vue

  • vuese-explorer 🏄An online experience playground for vuese

  • varlet-vue2 基于 Vue2 的 Material design 风格移动端组件库 Material design mobile component library for Vue2

  • varlet 基于 Vue3 的 Material design 风格移动端组件库 Material design mobile component library for Vue3

  • cssgridgenerator 🧮 Generate basic CSS Grid code to make dynamic layouts!

  • vue-docs 🔥 vue-docs https://learn-docs.github.io/vue-docs/

  • vue-vben-admin A modern vue admin. It is based on Vue3, vite and TypeScript. It's fast!

  • vue-pure-admin 🔥 ✨✨ ✨ Vue3.0+TypeScript+Vite2.0+Element-Plus编写的一套后台管理系统(兼容移动端)

  • mavonEditor mavonEditor - A markdown editor based on Vue that supports a variety of personalized features

  • vue-color 🎨 Vue Color Pickers for Sketch, Photoshop, Chrome & more http://vue-color.surge.sh

  • vue-countup-v2 Vue.js component wrap for countUp.js

  • vue-splitpanes

  • vue-typescript-admin-template 🖖 A vue-cli 3.0 + typescript minimal admin template

  • vue-virtual-scroller ⚡️ Blazing fast scrolling for any amount of data

  • vue-screen-capture 基于 html2canvas 的vue截图组件

  • vue-sample-svg-icons An opinionated example of how to use SVG icons in a Vue.js application

  • shopro-uniapp Shopro分销商城 uniapp前端开源代码,一款落地生产的 基于uni-app的多端商城。使用文档:https://gitee.com/itmonkey-cn/shopro.git

  • ViewUI A high quality UI Toolkit built on Vue.js 2.0

  • ant-design-vue 🌈 An enterprise-class UI components based on Ant Design and Vue. 🐜

  • vue-admin-better 🚀🚀🚀vue admin,vue3 admin,vue3.0 admin,vue后台管理,vue-admin,vue3.0-admin,admin,vue-admin,vue-element-admin,ant-design,vue-admin-beautiful-pro,vab admin pro,vab admin plus,vue admin plus,vue admin pro

  • Vue-mmPlayer 🎵 基于 Vue 的在线音乐播放器(PC) Online music player

  • form-design 动态表单页面设计--自动生成页面

  • vue-manage-system 基于Vue3 + Element Plus 的后台管理系统解决方案

  • vue-super-flow Flow chart component based on Vue。vue flowchart

  • vue2-elm 基于 vue2 + vuex 构建一个具有 45 个页面的大型单页面应用

  • form-generator ✨Element UI表单设计及代码生成器

  • element A Vue.js 2.0 UI Toolkit for Web

  • vant-shop-demo 商城常用的组件开发基于 vant ui 开发,让商城开发变得更简单

  • mint-ui Mobile UI elements for Vue.js

  • iview-admin Vue 2.0 admin management system template based on iView

  • vue-weixin Vue2 全家桶仿 微信App 项目,支持多人在线聊天和机器人聊天

  • vue-3d-model 📷 vue.js 3D model viewer component

  • DataV Vue数据可视化组件库(类似阿里DataV,大屏数据展示),提供SVG的边框及装饰、图表、水位图、飞线图等组件,简单易用,长期更新(React版已发布)

  • ColorUI 鲜亮的高饱和色彩,专注视觉的小程序组件库

Other

  • awesome-wechat-weapp 微信小程序开发资源汇总 💯

  • LogicStack-LeetCode 公众号「宫水三叶的刷题日记」刷穿 LeetCode 系列文章源码

  • audio_video_streaming 音视频流媒体权威资料整理,500+份文章,论文,视频,实践项目,协议,业界大神名单。

  • new-pac 翻墙-科学上网、免费翻墙、免费科学上网、VPN、一键翻墙浏览器,vps一键搭建翻墙服务器脚本/教程,免费shadowsocks/ss/ssr/v2ray/goflyway账号/节点,免费自由上网、fanqiang、翻墙梯子,电脑、手机、iOS、安卓、windows、Mac、Linux、路由器翻墙

  • es6-equivalents-in-es5 WIP - ES6 Equivalents In ES5

  • awesome-nestjs A curated list of awesome things related to NestJS 😎

  • ppchart http://ppchart.com

  • awesome-java A curated list of awesome frameworks, libraries and software for the Java programming language.

  • awesome-javascript 🐢 A collection of awesome browser-side JavaScript libraries, resources and shiny things.

  • amdjs-api Houses the Asynchronous Module Definition API

  • tdesign Enterprise Design System

  • CS-Notes 📚 技术面试必备基础知识、Leetcode、计算机操作系统、计算机网络、系统设计

  • Best-App 收集&推荐优秀的 Apps/硬件/技巧/周边等

  • FrontEndGitHub :octocat:GitHub最全的前端资源汇总仓库(包括前端学习、开发资源、数据结构与算法、开发工具、求职面试等)

  • rust-fe Rust是未来前端基础设施

  • Front-End-Interview-Notebook 🐜前端面试复习笔记

  • Blog 冴羽写博客的地方,预计写四个系列:JavaScript深入系列、JavaScript专题系列、ES6系列、React系列。

  • reverse-interview-zh 技术面试最后反问面试官的话

  • Node.js-Troubleshooting-Guide Node.js 应用线上/线下故障、压测问题和性能调优指南手册(一期更新结束)

  • git-flight-rules Flight rules for git

  • webpack-and-spa-guide Webpack 4 和单页应用入门

  • how-web-works What happens behind the scenes when we type www.google.com in a browser?

  • Become-A-Full-Stack-Web-Developer Free resources for learning Full Stack Web Development

  • modern-js-cheatsheet Cheatsheet for the JavaScript knowledge you will frequently encounter in modern projects.

  • Front-End-Checklist 🗂 The perfect Front-End Checklist for modern websites and meticulous developers

  • promise-fun Promise packages, patterns, chat, and tutorials

  • mysql-tutorial MySQL入门教程(MySQL tutorial book)

  • English-level-up-tips An advanced guide to learn English which might benefit you a lot 🎉 . 可能是让你受益匪浅的英语进阶指南。

  • system-design-algorithms Advanced data structure and algorithm for system design,系统设计需要了解的算法

  • awesome-resume Resume,Resume Templates,程序员简历例句,简历模版,

  • css-modules Documentation about css-modules

  • awesome-nodejs ⚡ Delightful Node.js packages and resources

  • awesome-vue 🎉 A curated list of awesome things related to Vue.js

  • functional-programming-jargon Jargon from the functional programming world in simple terms!

  • coding-interview-university A complete computer science study plan to become a software engineer.

  • awesome-webpack-cn 印记中文 - webpack 优秀中文文章

  • free-programming-books 📚 Freely available programming books

  • LeetcodeTop 汇总各大互联网公司容易考察的高频leetcode题🔥

  • vue3-News 🔥 Find the latest breaking Vue3、Vue CLI 3+ & Vite News. (2021/2022)

  • javascript_concurrency_translation 《JavaScript Concurrency》英文版全书翻译 ->《JavaScript并发编程》,主要内容是Promises, Generators, Web Workers实现JavaScript并发编程相关

  • wx-h5-mall 微信商城

  • wxappUnpacker 小程序反编译(支持分包)

  • vue-admin We are refactoring it, using the latest Vue and Bulma. WIP

  • imgcook Generate pages from any sketch or images.

  • awesome-nuxt A curated list of awesome things related to Nuxt.js

  • document-style-guide 中文技术文档的写作规范

CSS

  • todomvc-app-css CSS for TodoMVC apps

  • animate.css 🍿 A cross-browser library of CSS animations. As easy to use as an easy thing.

  • css-ripple-effect Pure CSS (no JavaScript) implementation of Android Material design "ripple" animation

  • uni-app-tools this is some uni-app toolset, more routing extensions

  • normalize.css A modern alternative to CSS resets

Markdown

  • fucking-algorithm 刷算法全靠套路,认准 labuladong 就够了!English version supported! Crack LeetCode, not only how, but also why.

Shell

HTML

  • 1024bibi 🔥 1024bibi.com bolg https://1024bibi.com/

  • js-xss Sanitize untrusted HTML (to prevent XSS) with a configuration specified by a Whitelist

  • ueditor_rails [Abandoned] UEditor integration with Rails

  • alpine A rugged, minimal framework for composing JavaScript behavior in your markup.

  • pagedjs Display paginated content in the browser and generate print books using web technology

  • pinyin-engine JavaScript 拼音匹配引擎

  • screenfull Simple wrapper for cross-browser usage of the JavaScript Fullscreen API

  • node-interview How to pass the Node.js interview of ElemeFE.

  • html HTML Standard

  • gentelella Free Bootstrap 4 Admin Dashboard Template

  • tabler Tabler is free and open-source HTML Dashboard UI Kit built on Bootstrap

  • WeixinResource 微信开发资源汇总 | WeChat Development Resources Summary

Python

  • v8-internals 面向编译器开发人员的V8内部实现文档

  • MockingBird 🚀AI拟声: 5秒内克隆您的声音并生成任意语音内容 Clone a voice in 5 seconds to generate arbitrary speech in real-time

  • system-design-primer Learn how to design large-scale systems. Prep for the system design interview. Includes Anki flashcards.

C

  • ffmpeg-libav-tutorial FFmpeg libav tutorial - learn how media works from basic to transmuxing, transcoding and more

CoffeeScript

  • pinyin-converter A simple Javascript plugin to convert pinyin with numbers to tone marks

PHP

  • dootask DooTask是一款轻量级的开源在线项目任务管理工具,提供各类文档协作工具、在线思维导图、在线流程图、项目管理、任务分发、即时IM,文件管理等工具。

  • catch-admin CatchAdmin是一款基于thinkphp6 和 element admin 开发的后台管理系统,基于 ServiceProvider,系统模块完全接耦。随时卸载安装模块。提供了完整的权限和数据权限等功能,大量内置的开发工具提升你的开发体验。官网地址:

  • alipay 一个PHP文件搞定支付宝支付系列,包括电脑网站支付,手机网站支付,现金红包、消费红包、扫码支付,JSAPI支付、单笔转账到支付宝账户、交易结算(分账、分润)、网页授权获取用户信息等

  • weixinPay 微信支付单文件版。一个PHP文件搞定微信支付系列。包括原生支付(扫码支付),H5支付,公众号支付,现金红包、企业付款到零钱等。新增V3版。

  • woocommerce-to-wechatapp-mini WooCommerce微信小程序迷你版

SCSS

  • magic CSS3 Animations with special effects

  • material-dashboard Material Dashboard - Open Source Bootstrap 5 Material Design Admin

Stylus

Dart

  • flutter Flutter makes it easy and fast to build beautiful apps for mobile and beyond

Less

  • iview-weapp 一套高质量的微信小程序 UI 组件库

Rust

  • deno A modern runtime for JavaScript and TypeScript.

列表2

展开查看

Rust

  • exa A modern replacement for ‘ls’.

  • swc Rust-based platform for the Web

  • tools The Rome Toolchain. A linter, compiler, bundler, and more for JavaScript, TypeScript, HTML, Markdown, and CSS.

  • sonic 🦔 Fast, lightweight & schema-less search backend. An alternative to Elasticsearch that runs on a few MBs of RAM.

  • bat A cat(1) clone with wings.

  • deno A modern runtime for JavaScript and TypeScript.

  • relay Relay is a JavaScript framework for building data-driven React applications.

Go

  • dns.toys A DNS server that offers useful utilities and services over the DNS protocol. Weather, world time, unit conversion etc.

  • flagr Flagr is a feature flagging, A/B testing and dynamic configuration microservice

  • fasthttp Fast HTTP package for Go. Tuned for high performance. Zero memory allocations in hot paths. Up to 10x faster than net/http

  • goproxy A global proxy for Go modules.

  • delve Delve is a debugger for the Go programming language.

  • fiber ⚡️ Express inspired web framework written in Go

  • xbar Put the output from any script or program into your macOS Menu Bar (the BitBar reboot)

  • k8s-deployment-strategies Kubernetes deployment strategies explained

  • 7days-golang 7 days golang programs from scratch (web framework Gee, distributed cache GeeCache, object relational mapping ORM framework GeeORM, rpc framework GeeRPC etc) 7天用Go动手写/从零实现系列

  • mahjong-helper 日本麻将助手:牌效+防守+记牌(支持雀魂、天凤)

  • picsum-photos Lorem Ipsum... but for photos.

  • http2 old repo for HTTP/2 support for Go (see README for new home)

  • cloud-on-k8s Elastic Cloud on Kubernetes

  • lazydocker The lazier way to manage everything docker

  • kruise Automate application management on Kubernetes (project under CNCF)

  • mkcert A simple zero-config tool to make locally trusted development certificates with any names you'd like.

  • esbuild An extremely fast JavaScript and CSS bundler and minifier

  • go-web-framework-benchmark ⚡ Go web framework benchmark

  • rclone "rsync for cloud storage" - Google Drive, S3, Dropbox, Backblaze B2, One Drive, Swift, Hubic, Wasabi, Google Cloud Storage, Yandex Files

  • fx Terminal JSON viewer

  • gnet 🚀 gnet is a high-performance, lightweight, non-blocking, event-driven networking framework written in pure Go./ gnet 是一个高性能、轻量级、非阻塞的事件驱动 Go 网络框架。

  • portainer Making Docker and Kubernetes management easy.

  • ctop Top-like interface for container metrics

  • whoami Tiny Go webserver that prints os information and HTTP request to output

  • vault A tool for secrets management, encryption as a service, and privileged access management

  • rook Storage Orchestration for Kubernetes

  • external-storage [EOL] External storage plugins, provisioners, and helper libraries

  • cert-manager Automatically provision and manage TLS certificates in Kubernetes

  • TopList 今日热榜,一个获取各大热门网站热门头条的聚合网站,使用Go语言编写,多协程异步快速抓取信息,预览:https://mo.fish

  • krew 📦 Find and install kubectl plugins

  • ingress-nginx NGINX Ingress Controller for Kubernetes

  • charts ⚠️(OBSOLETE) Curated applications for Kubernetes

  • metrics-server Scalable and efficient source of container resource metrics for Kubernetes built-in autoscaling pipelines.

  • autoscaler Autoscaling components for Kubernetes

  • istio Connect, secure, control, and observe services.

  • coredns CoreDNS is a DNS server that chains plugins

  • nps 一款轻量级、高性能、功能强大的内网穿透代理服务器。支持tcp、udp、socks5、http等几乎所有流量转发,可用来访问内网网站、本地支付接口调试、ssh访问、远程桌面,内网dns解析、内网socks5代理等等……,并带有功能强大的web管理端。a lightweight, high-performance, powerful intranet penetration proxy server, with a powerful web management terminal.

  • nginx-prometheus-exporter NGINX Prometheus Exporter for NGINX and NGINX Plus

  • seaweedfs SeaweedFS is a fast distributed storage system for blobs, objects, files, and data lake, for billions of files! Blob store has O(1) disk seek, cloud tiering. Filer supports Cloud Drive, cross-DC active-active replication, Kubernetes, POSIX FUSE mount, S3 API, S3 Gateway, Hadoop, WebDAV, encryption, Erasure Coding.

  • k9s 🐶 Kubernetes CLI To Manage Your Clusters In Style!

  • k3s Lightweight Kubernetes

  • night Weekly Go Online Meetup via Bilibili|Go 夜读|通过 bilibili 在线直播的方式分享 Go 相关的技术话题,每天大家在微信/telegram/Slack 上及时沟通交流编程技术话题。

  • build-web-application-with-golang A golang ebook intro how to build a web with golang

  • openbilibili-go-common 🙈!🙉!🙊!我不清楚这些是啥… 道德心泛滥的麻烦出门右转关注996.icu!

  • cadvisor Analyzes resource usage and performance characteristics of running containers.

  • rancher Complete container management platform

  • drone Drone is a Container-Native, Continuous Delivery Platform

  • consul-template Template rendering, notifier, and supervisor for @HashiCorp Consul and Vault data.

  • gosu Simple Go-based setuid+setgid+setgroups+exec

  • faas OpenFaaS - Serverless Functions Made Simple

  • fn The container native, cloud agnostic serverless platform.

  • minio Multi-Cloud ☁️ Object Storage

  • helm The Kubernetes Package Manager

  • kubeless Kubernetes Native Serverless Framework

  • traefik The Cloud Native Application Proxy

  • hugo The world’s fastest framework for building websites.

  • kcptun A Stable & Secure Tunnel based on KCP with N:M multiplexing and FEC. Available for ARM, MIPS, 386 and AMD64。KCPプロトコルに基づく安全なトンネル。KCP 프로토콜을 기반으로 하는 보안 터널입니다。

  • harbor An open source trusted cloud native registry project that stores, signs, and scans content.

  • flannel flannel is a network fabric for containers, designed for Kubernetes

  • etcd Distributed reliable key-value store for the most critical data of a distributed system

  • kubeadm Aggregator for issues filed against kubeadm

  • consul Consul is a distributed, highly available, and data center aware solution to connect and configure applications across dynamic, distributed infrastructure.

  • minikube Run Kubernetes locally

  • dashboard General-purpose web UI for Kubernetes clusters

  • cri-o Open Container Initiative-based implementation of Kubernetes Container Runtime Interface

  • beats 🐠 Beats - Lightweight shippers for Elasticsearch & Logstash

  • iris The fastest HTTP/2 Go Web Framework. Minimum resources, better performance. Iris is environmentally friendly 🍃 | 谢谢 |

  • raft Golang implementation of the Raft consensus protocol

  • envconsul Launch a subprocess with environment variables using data from @HashiCorp Consul and Vault.

  • gin Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin.

  • api2go JSONAPI.org Implementation for Go

  • gocode An autocompletion daemon for the Go programming language

  • casbin An authorization library that supports access control models like ACL, RBAC, ABAC in Golang

  • gofpdf A PDF document generator with high level support for text, drawing and images

  • tour [mirror] A Tour of Go

  • httprouter A high performance HTTP request router that scales well

  • mux A powerful HTTP router and URL matcher for building Go web servers with 🦍

  • gorm The fantastic ORM library for Golang, aims to be developer friendly

  • vegeta HTTP load testing tool and library. It's over 9000!

  • redis Type-safe Redis client for Golang

  • graphql An implementation of GraphQL for Go / Golang

  • awesome-go A curated list of awesome Go frameworks, libraries and software

  • nsq A realtime distributed messaging platform

  • ngrok Introspected tunnels to localhost

  • origin Conformance test suite for OpenShift

  • go-advice List of advice and tricks for Go ʕ◔ϖ◔ʔ

  • colly Elegant Scraper and Crawler Framework for Golang

  • gitlab-ci-multi-runner This repository is a mirror for automated DockerHub builds. Please go to:

  • tidb TiDB is an open-source, cloud-native, distributed, MySQL-Compatible database for elastic scale and real-time analytics. Try free: https://tidbcloud.com/free-trial

  • kubernetes Production-Grade Container Scheduling and Management

  • prometheus The Prometheus monitoring system and time series database.

  • go-is-not-good A curated list of articles complaining that go (golang) isn't good enough

  • docker_practice Learn and understand Docker&Container technologies, with real DevOps practice!

  • moby Moby Project - a collaborative project for the container ecosystem to assemble container-based systems

C

  • ngx_brotli NGINX module for Brotli compression

  • ImageMagick 🧙‍♂️ ImageMagick 7

  • openssl TLS/SSL and crypto library

  • pngquant Lossy PNG compressor — pngquant command based on libimagequant library

  • musl Unofficial mirror of etalabs musl repository. Updated daily.

  • sysstat Performance monitoring tools for Linux

  • siege Siege is an http load tester and benchmarking utility

  • apue 《UNIX环境高级编程》随书代码与课后习题

  • C Collection of various algorithms in mathematics, machine learning, computer science, physics, etc implemented in C for educational purposes.

  • libfaketime libfaketime modifies the system time for a single application

  • nginx An official read-only mirror of http://hg.nginx.org/nginx/ which is updated hourly. Pull requests on GitHub cannot be accepted and will be automatically closed. The proper way to submit changes to nginx is via the nginx development mailing list, see http://nginx.org/en/docs/contributing_changes.html

  • wrk Modern HTTP benchmarking tool

  • libuv Cross-platform asynchronous I/O

  • linux Linux kernel source tree

  • disque Disque is a distributed message broker

  • jq Command-line JSON processor

  • ccap node.js generate captcha using c++ library CImg without install any other lib or software

  • openvpn OpenVPN is an open source VPN daemon

  • shadowsocks-libev Bug-fix-only libev port of shadowsocks. Future development moved to shadowsocks-rust

  • polipo The Polipo caching HTTP proxy

  • redis Redis is an in-memory database that persists on disk. The data model is key-value, but many different kind of values are supported: Strings, Lists, Sets, Sorted Sets, Hashes, Streams, HyperLogLogs, Bitmaps.

  • scrcpy Display and control your Android device

  • wcdb WCDB is a cross-platform database framework developed by WeChat.

  • tmux tmux source code

  • SQLAdvisor 输入SQL,输出索引优化建议

  • postgres Mirror of the official PostgreSQL GIT repository. Note that this is just a mirror - we don't work with pull requests on github. To contribute, please see https://wiki.postgresql.org/wiki/Submitting_a_Patch

  • FFmpeg Mirror of https://git.ffmpeg.org/ffmpeg.git

  • Chinese-uvbook 翻译的libuv的中文教程

  • the_silver_searcher A code-searching tool similar to ack, but faster.

Python

  • chinese-programmer-wrong-pronunciation **程序员容易发音错误的单词

  • SMSBoom 短信轰炸/短信测压/ | 一个健壮免费的python短信轰炸程序,专门炸坏蛋蛋,百万接口,多线程全自动添加有效接口,支持异步协程百万并发,全免费的短信轰炸工具!!高一美术生开发全网首发!!

  • MockingBird 🚀AI拟声: 5秒内克隆您的声音并生成任意语音内容 Clone a voice in 5 seconds to generate arbitrary speech in real-time

  • asciinema Terminal session recorder 📹

  • easytrader 提供同花顺客户端/国金/华泰客户端/雪球的基金、股票自动程序化交易以及自动打新,支持跟踪 joinquant /ricequant 模拟交易 和 实盘雪球组合, 量化交易组件

  • ECDICT Free English to Chinese Dictionary Database

  • CheatSheetSeries The OWASP Cheat Sheet Series was created to provide a concise collection of high value information on specific application security topics.

  • helm-charts You know, for Kubernetes

  • photo2cartoon 人像卡通化探索项目 (photo-to-cartoon translation project)

  • cpython The Python programming language

  • dd-agent Datadog Agent Version 5

  • geektime_dl 把极客时间装进 Kindle

  • serverless-function-compute-examples A collection of boilerplates and examples of serverless architectures built with the Serverless framework and Alibaba Cloud Function Compute.

  • DXY-COVID-19-Crawler 2019新型冠状病毒疫情实时爬虫及API | COVID-19/2019-nCoV Realtime Infection Crawler and API

  • WeRoBot WeRoBot 是一个微信公众号开发框架

  • ansible-for-devops Ansible for DevOps examples.

  • ceph-ansible Ansible playbooks to deploy Ceph, the distributed filesystem.

  • devops-exercises Linux, Jenkins, AWS, SRE, Prometheus, Docker, Python, Ansible, Git, Kubernetes, Terraform, OpenStack, SQL, NoSQL, Azure, GCP, DNS, Elastic, Network, Virtualization. DevOps Interview Questions

  • public-speaking-with-meaning 《我也有话要说》—— 普通人的当众讲话技能

  • wxpy 微信机器人 / 可能是最优雅的微信个人号 API ✨✨

  • dnsmasq-china-list Chinese-specific configuration to improve your favorite DNS server. Best partner for chnroutes.

  • interview_internal_reference 2021年最新总结,阿里,腾讯,百度,美团,头条等技术面试题目,以及答案,专家出题人分析汇总。

  • explainshell match command-line arguments to their help text

  • binlog2sql Parse MySQL binlog to SQL you want

  • redash Make Your Company Data Driven. Connect to any data source, easily visualize, dashboard and share your data.

  • nginx-proxy Automated nginx proxy for Docker containers using docker-gen

  • node-gyp Node.js native addon build tool

  • couplet-dataset Dataset for couplets. 70万条对联数据库。

  • chinese-xinhua 📙 中华新华字典数据库。包括歇后语,成语,词语,汉字。

  • visdom A flexible tool for creating, organizing, and sharing visualizations of live, rich data. Supports Torch and Numpy.

  • pytorch-examples Simple examples to introduce PyTorch

  • examples A set of examples around pytorch in Vision, Text, Reinforcement Learning, etc.

  • scikit-learn scikit-learn: machine learning in Python

  • sklearn-pandas Pandas integration with sklearn

  • numpy The fundamental package for scientific computing with Python.

  • wait-for-it Pure bash script to test and wait on the availability of a TCP host and port

  • glances Glances an Eye on your system. A top/htop alternative for GNU/Linux, BSD, Mac OS and Windows operating systems.

  • seq2seq-couplet Play couplet with seq2seq model. 用深度学习对对联。

  • py12306 🚂 12306 购票助手,支持集群,多账号,多任务购票以及 Web 页面管理

  • dumb-init A minimal init system for Linux containers

  • supervisor Supervisor process control system for UNIX

  • word-statistics-cet4 12年36套四级真题试卷的单词频率统计程序

  • w3-goto-world 🍅 Git/AWS/Google 镜像 ,SS/SSR/VMESS节点,WireGuard,IPFS, DeepWeb,Capitalism 、行业研究报告的知识储备库

  • shadowsocks

  • openshift-ansible Install and config an OpenShift 3.x cluster

  • kubeapp Some commonly used kubernetes app 🎉🎉🎉~~~

  • house-renting Possibly the best practice of Scrapy 🕷 and renting a house 🏡

  • elasticsearch-docker Official Elasticsearch Docker image

  • wechat_jump_game 微信《跳一跳》Python 辅助

  • sentry Sentry is cross-platform application monitoring, with a focus on error reporting.

  • awesome-python A curated list of awesome Python frameworks, libraries, software and resources

  • pgcli Postgres CLI with autocompletion and syntax highlighting

  • system-design-primer Learn how to design large-scale systems. Prep for the system design interview. Includes Anki flashcards.

  • robotframework Generic automation framework for acceptance testing and RPA

  • tieba_post python模拟百度贴吧登陆,发帖

  • you-get ⏬ Dumb downloader that scrapes the web

  • httplib2 Small, fast HTTP client library for Python. Features persistent connections, cache, and Google App Engine support. Originally written by Joe Gregorio, now supported by community.

  • autojump A cd command that learns - easily navigate directories from the command line

  • public-apis A collective list of free APIs

  • YouCompleteMe A code-completion engine for Vim

  • douban.fm 📻 douban.fm based on Python

  • FreeWifi How to get free wifi.

  • fuck-login 模拟登录一些知名的网站,为了方便爬取需要登录的网站

  • ansible Ansible is a radically simple IT automation platform that makes your applications and systems easier to deploy and maintain. Automate everything from code deployment to network configuration to cloud management, in a language that approaches plain English, using SSH, with no agents to install on remote systems. https://docs.ansible.com.

  • git-webhook :octocat: 使用 Python Flask + SQLAchemy + Celery + Redis + React 开发的用于迅速搭建并使用 WebHook 进行自动化部署和运维,支持 Github / GitLab / Gogs / GitOsc。

  • certbot Certbot is EFF's tool to obtain certs from Let's Encrypt and (optionally) auto-enable HTTPS on your server. It can also act as a client for any other CA that uses the ACME protocol.

  • packaging.python.org Python Packaging User Guide

  • legit Git for Humans, Inspired by GitHub for Mac™.

  • scrapy-redis Redis-based components for Scrapy.

  • rq Simple job queues for Python

  • scrapy-proxies Random proxy middleware for Scrapy

  • core-scrapy python-scrapy demo

  • scrapybook Scrapy Book Code

  • scrapy-examples Multifarious Scrapy examples. Spiders for alexa / amazon / douban / douyu / github / linkedin etc.

  • scrapy Scrapy, a fast high-level web crawling & scraping framework for Python.

  • flask-admin Simple and extensible administrative interface framework for Flask

  • flasky Companion code to my O'Reilly book "Flask Web Development", second edition.

  • jieba 结巴中文分词

  • gevent Coroutine-based concurrency library for Python

  • v2ex-gae Legacy code of V2EX running on Google App Engine

  • PyTricks Collection of less popular features and tricks for the Python programming language

  • requests A simple, yet elegant, HTTP library.

  • flask The Python micro framework for building web applications.

  • zhihu-py3 [不再维护] 后继者 zhihu-oauth https://github.com/7sDream/zhihu-oauth 已被 DMCA,亦不再开发,仅提供代码存档:

  • flask-sockets [DEPRECATED] Alternative: https://github.com/miguelgrinberg/flask-sock

JavaScript

  • ethereumbook Mastering Ethereum, by Andreas M. Antonopoulos, Gavin Wood

  • table-of-general-standard-chinese-characters Table of General Standard Chinese Characters(通用规范汉字表)

  • web3.js Ethereum JavaScript API

  • dt-sql-parser SQL Parsers for BigData, built with antlr4.

  • beautify-qrcode

  • cos-js-sdk-v5 腾讯云 COS JS SDK(XML API)

  • async-pool Run multiple promise-returning & async functions with limited concurrency using native ES6/ES7

  • heroicons A set of free MIT-licensed high-quality SVG icons for UI development.

  • javascript-algorithms 📝 Algorithms and data structures implemented in JavaScript with explanations and links to further readings

  • algorithm-visualizer 🎆Interactive Online Platform that Visualizes Algorithms from Code

  • microbundle 📦 Zero-configuration bundler for tiny modules.

  • gm GraphicsMagick for node

  • iro.js 🎨 Modular color picker widget for JavaScript, with support for a bunch of color formats

  • color-names Large list of handpicked color names 🌈

  • buffer The buffer module from node.js, for the browser.

  • wechat-app-2048 2048小游戏 微信小程序开发 wechat weapp

  • phaser Phaser is a fun, free and fast 2D game framework for making HTML5 games for desktop and mobile web browsers, supporting Canvas and WebGL rendering.

  • Wechatsync 一键同步文章到多个内容平台,支持今日头条、WordPress、知乎、简书、掘金、CSDN、typecho各大平台,一次发布,多平台同步发布。解放个人生产力

  • text-encoding Polyfill for the Encoding Living Standard's API

  • base64-js Base64 encoding/decoding in pure JS

  • size-limit Calculate the real cost to run your JS app or lib to keep good performance. Show error in pull request if the cost exceeds the limit.

  • dns-packet An abstract-encoding compliant module for encoding / decoding DNS packets

  • json-schema-editor-visual A json-schema editor of high efficient and easy-to-use, base on React.

  • react-jsonschema-form A React component for building Web forms from JSON Schema.

  • npm-install GitHub Action for install npm dependencies with caching without any configuration

  • tailwind-nextjs-starter-blog This is a Next.js, Tailwind CSS blogging starter template. Comes out of the box configured with the latest technologies to make technical writing a breeze. Easily configurable and customizable. Perfect as a replacement to existing Jekyll and Hugo individual blogs.

  • webextension-polyfill A lightweight polyfill library for Promise-based WebExtension APIs in Chrome

  • markdownload A Firefox and Google Chrome extension to clip websites and download them into a readable markdown file.

  • ffmpeg.wasm FFmpeg for browser and node, powered by WebAssembly

  • color 🌈 Javascript color conversion and manipulation library

  • marktext 📝A simple and elegant markdown editor, available for Linux, macOS and Windows.

  • vizzu-lib Library for animated data visualizations and data stories.

  • tabulator Interactive Tables and Data Grids for JavaScript

  • kutty Kutty is a tailwind plugin for building web applications. It has a set of accessible and reusable components that are commonly used in web applications.

  • headless-gl 🎃 Windowless WebGL for node.js

  • gpu.js GPU Accelerated JavaScript

  • web 8th Wall Web projects and resources.

  • react-joyride Create guided tours in your apps

  • jison Bison in JavaScript.

  • webp2jpg-online Use the browser's online image format converter, no need to upload files, you can convert jpeg, jpg, png, gif, webp, svg, ico, bmp files to jpeg, png, webp animation, gif, base64,avif,mozjpeg. 使用浏览器的在线图片格式转化器,无需上传文件,可将jpeg、jpg、png、gif、webp、svg、ico、bmp文件转换为jpeg、png、webp、webp动画、gif、base64、avif、mozjpeg,提供了多个可自定义选项来满足常见需求。

  • greenlet 🦎 Move an async function into its own thread.

  • useWorker ⚛️ useWorker() - A React Hook for Blocking-Free Background Tasks

  • thread-loader Runs the following loaders in a worker pool

  • core-js Standard Library

  • pako high speed zlib port to javascript, works in browser & node.js

  • plugins 🍣 The one-stop shop for official Rollup plugins

  • html-webpack-plugin Simplifies creation of HTML files to serve your webpack bundles

  • read-package-json The thing npm uses to read package.json files with semantics and defaults and validation and stuff

  • normalize-package-data normalizes package metadata, typically found in package.json file.

  • v8-compile-cache Require hook for automatic V8 compile cache persistence

  • prism Lightweight, robust, elegant syntax highlighting.

  • ws Simple to use, blazing fast and thoroughly tested WebSocket client and server for Node.js

  • just a very small v8 javascript runtime for linux only

  • inferno 🔥 An extremely fast, React-like JavaScript library for building modern user interfaces

  • bullshit-everyday 每天生成一篇废话

  • karma Spectacular Test Runner for JavaScript

  • mini-code 流行框架与库的源码分析与最小实现

  • koan KOAN (Koa, Angular, Node, Mongo) starter kit for full-stack JavaScript web development.

  • enquirer Stylish, intuitive and user-friendly prompts, for Node.js. Used by eslint, webpack, yarn, pm2, pnpm, RedwoodJS, FactorJS, salesforce, Cypress, Google Lighthouse, Generate, tencent cloudbase, lint-staged, gluegun, hygen, hardhat, AWS Amplify, GitHub Actions Toolkit, @airbnb/nimbus, and many others! Please follow Enquirer's author: https://github.com/jonschlinkert

  • agenda Lightweight job scheduling for Node.js

  • globalize A JavaScript library for internationalization and localization that leverages the official Unicode CLDR JSON data

  • nodejs-integration-tests-best-practices ✅ Beyond the basics of Node.js testing. Including a super-comprehensive best practices list and an example app (April 2022)

  • react-tracking 🎯 Declarative tracking for React apps.

  • handlebars.js Minimal templating on steroids.

  • mustache.js Minimal templating with {{mustaches}} in JavaScript

  • You-Dont-Need-Momentjs List of functions which you can use to replace moment.js + ESLint Plugin

  • bignumber.js A JavaScript library for arbitrary-precision decimal and non-decimal arithmetic

  • jsonparse A streaming JSON parser written in pure JavaScript for node.js

  • clean-code-javascript 🛁 Clean Code concepts adapted for JavaScript

  • the-super-tiny-compiler ⛄ Possibly the smallest compiler ever

  • query-string Parse and stringify URL query strings

  • methods HTTP verbs that node supports

  • intro.js Lightweight, user-friendly onboarding tour library

  • iconv-lite Convert character encodings in pure javascript.

  • maptalks.js A light and plugable JavaScript library for integrated 2D/3D maps.

  • react-beautiful-dnd Beautiful and accessible drag and drop for lists with React

  • nanoid A tiny (130 bytes), secure, URL-friendly, unique string ID generator for JavaScript

  • rax 🐰 Rax is a progressive framework for building universal application. https://rax.js.org

  • clean-css Fast and efficient CSS optimizer for node.js and the Web

  • gulp-clean-css Minify css with clean-css.

  • fabric.js Javascript Canvas Library, SVG-to-Canvas (& canvas-to-SVG) Parser

  • dom-examples Code examples that accompany various MDN DOM and Web API documentation pages

  • didact A DIY guide to build your own React

  • simple-keyboard Javascript Virtual Keyboard - Customizable, responsive and lightweight

  • braces Faster brace expansion for node.js. Besides being faster, braces is not subject to DoS attacks like minimatch, is more accurate, and has more complete support for Bash 4.3.

  • Web-Dev-For-Beginners 24 Lessons, 12 Weeks, Get Started as a Web Developer

  • file-type Detect the file type of a Buffer/Uint8Array/ArrayBuffer

  • clipboard-copy Lightweight copy to clipboard for the web

  • d3-graphviz Graphviz DOT rendering and animated transitions using D3

  • worker-loader A webpack loader that registers a script as a Web Worker

  • pdf.js PDF Reader in JavaScript

  • pdfkit A JavaScript PDF generation library for Node and the browser

  • react-runkit RunKit Embed Component.

  • react-live A flexible playground for live editing React components

  • loupe Visualizing the javascript runtime at runtime

  • wmr 👩‍🚀 The tiny all-in-one development tool for modern web apps.

  • react-pdf 📄 Create PDF files using React

  • pdf-to-markdown A PDF to Markdown converter

  • You-Dont-Need-Lodash-Underscore List of JavaScript methods which you can use natively + ESLint Plugin

  • qrbtf An art QR code (qrcode) beautifier. 艺术二维码生成器。https://qrbtf.com

  • react-pdf Display PDFs in your React app as easily as if they were images.

  • faker.js generate massive amounts of realistic fake data in Node.js and the browser

  • gatsby-remark-markmap Visualize code blocks in Markdown files using markmap.

  • postcss-custom-properties Use Custom Properties in CSS

  • download Download and extract files

  • node-qrcode qr code generator

  • svg-term-cli Share terminal sessions via SVG and CSS

  • sharp High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, AVIF and TIFF images. Uses the libvips library.

  • html-to-react A lightweight library that converts raw HTML to a React DOM structure.

  • jsonld.js A JSON-LD Processor and API implementation in JavaScript

  • apexcharts.js 📊 Interactive JavaScript Charts built on SVG

  • weekly

  • wappalyzer Identify technology on websites.

  • prepack A JavaScript bundle optimizer.

  • mathjs An extensive math library for JavaScript and Node.js

  • unified ☔️ interface for parsing, inspecting, transforming, and serializing content through syntax trees

  • ua-parser-js UAParser.js - Detect Browser, Engine, OS, CPU, and Device type/model from User-Agent data. Supports browser & node.js environment.

  • shepherd Guide your users through a tour of your app

  • react-loading css实现loading样式组件

  • GSAP GreenSock's GSAP JavaScript animation library (including Draggable).

  • undici An HTTP/1.1 client, written from scratch for Node.js

  • nprogress For slim progress bars like on YouTube, Medium, etc

  • jsdelivr A free, fast, and reliable Open Source CDN for npm, GitHub, Javascript, and ESM

  • This-Repo-Has-1712-Stars Yes, it's true 💜

  • canvas-confetti 🎉 on-demand confetti gun

  • npmrank npm dependencies graph metrics

  • remark remark is a popular tool that transforms markdown with plugins. These plugins can inspect and change your markup. You can use remark on the server, the client, CLIs, deno, etc.

  • react-toast-notifications 🍞 A toast notification system for react

  • chrome_extensions_and_apps_programming Chrome扩展及应用开发

  • react-autosuggest WAI-ARIA compliant React autosuggest component

  • http-decision-diagram An activity diagram to describe the resolution of HTTP response status codes, given various headers.

  • monaco-editor A browser based code editor

  • cropperjs JavaScript image cropper.

  • marked A markdown parser and compiler. Built for speed.

  • react-markdown Markdown component for React

  • chrome-extensions-samples Chrome Extensions Samples

  • minimist parse argument options

  • chrome-extensions-examples All Chrome Extension examples collected into one repository

  • WebFundamentals Best practices for modern web development

  • nginxconfig.io ⚙️ NGINX config generator on steroids 💉

  • he A robust HTML entity encoder/decoder written in JavaScript.

  • flowy The minimal javascript library to create flowcharts ✨

  • resume.github.com Resumes generated using the GitHub informations

  • How-To-Ask-Questions-The-Smart-Way 本文原文由知名 Hacker Eric S. Raymond 所撰寫,教你如何正確的提出技術問題並獲得你滿意的答案。

  • package-build-stats This is the cloud function that powers the core of building, minifying and gzipping of packages in bundlephobia

  • react-dropzone Simple HTML5 drag-drop zone with React.js.

  • toastr Simple javascript toast notifications

  • bundlephobia 🏋️ Find out the cost of adding a new frontend dependency to your project

  • highlight.js JavaScript syntax highlighter with language auto-detection and zero dependencies.

  • codelf A search tool helps dev to solve the naming things problem.

  • json-editor JSON Schema Based Editor

  • jsoneditor A web-based tool to view, edit, format, and validate JSON

  • react-json-view JSON viewer for react

  • showthedocs

  • mermaid Generation of diagram and flowchart from text in a similar manner as markdown

  • dropzone Dropzone is an easy to use drag'n'drop library. It supports image previews and shows nice progress bars.

  • cypress Fast, easy and reliable testing for anything that runs in a browser.

  • react-color 🎨 Color Pickers from Sketch, Photoshop, Chrome, Github, Twitter & more

  • carbon 🖤 Create and share beautiful images of your source code

  • smooth-doc Ready to use documentation theme for Gatsby.

  • autoprefixer Parse CSS and add vendor prefixes to rules by Can I Use

  • emotion 👩‍🎤 CSS-in-JS library designed for high performance style composition

  • unfetch 🐕 Bare minimum 500b fetch polyfill.

  • domino Server-side DOM implementation based on Mozilla's dom.js

  • react-ace React Ace Component

  • ace-builds Packaged version of Ace code editor

  • geojson.io IMPORTANT: development of this project has been paused, see the README (fast, simple map creation)

  • commander.js node.js command-line interfaces made easy

  • codesandbox-client An online IDE for rapid web development

  • rollup Next-generation ES module bundler

  • drawio Source to app.diagrams.net

  • regexr RegExr is a HTML/JS based tool for creating, testing, and learning about Regular Expressions.

  • sharingbuttons.io Quickly generate social sharing buttons with a tiny performance footprint

  • react-fontawesome Font Awesome React component

  • JavaScript-Load-Image Load images provided as File or Blob objects or via URL. Retrieve an optionally scaled, cropped or rotated HTML img or canvas element. Use methods to parse image metadata to extract IPTC and Exif tags as well as embedded thumbnail images, to overwrite the Exif Orientation value and to restore the complete image header after resizing.

  • random-useragent Get a random user agent (with an optional filter to select from a specific set of user agents).

  • qs A querystring parser with nesting support

  • github-readme-stats ⚡ Dynamically generated stats for your github readmes

  • page.js Micro client-side router inspired by the Express router

  • editly Slick, declarative command line video editing & API

  • zh-address-parse 全网识别准确度最高的**大陆收货地址智能解析

  • china-area-data **省市区数据

  • jaeger-client-node 🛑 This library is DEPRECATED!

  • bundlesize Keep your bundle size in check

  • safe-regex detect possibly catastrophic, exponential-time regular expressions

  • hackathon-starter A boilerplate for Node.js web applications

  • redux-orm A small, simple and immutable ORM to manage relational data in your Redux store.

  • react-virtualized React components for efficiently rendering large lists and tabular data

  • whistle HTTP, HTTP2, HTTPS, Websocket debugging proxy

  • immer Create the next immutable state by mutating the current one

  • mdx Markdown for the component era

  • tailwindcss A utility-first CSS framework for rapid UI development.

  • jsbin Collaborative JavaScript Debugging App

  • redux-logger Logger for Redux

  • Recoil Recoil is an experimental state management library for React apps. It provides several capabilities that are difficult to achieve with React alone, while being compatible with the newest features of React.

  • redux-actions Flux Standard Action utilities for Redux.

  • redux-saga An alternative side effect model for Redux apps

  • sequelize-pool Resource pool implementation. It can be used to throttle expensive resources.

  • just-react 「React技术揭秘」 一本自顶向下的React源码分析书

  • easy-monitor 企业级 Node.js 应用性能监控与线上故障定位解决方案

  • gatsby-starter-prismic Get started with your Photography portfolio with Prismic.io & Gatsby

  • xgplayer A HTML5 video player with a parser that saves traffic

  • bing Bing 壁纸 API

  • mime Mime types for JavaScript

  • form-data A module to create readable "multipart/form-data" streams. Can be used to submit forms and file uploads to other web applications.

  • formstream multipart/form-data encoded stream, helper for file upload.

  • chinese-colors 🇨🇳🎨Chinese traditional color cheatsheet online

  • prettier Prettier is an opinionated code formatter.

  • dairyApp 一款史上颜值最高日记app,忆念做设计的日子😄

  • execa Process execution for humans

  • depcheck Check your npm module for unused dependencies

  • snowpack ESM-powered frontend build tool. Instant, lightweight, unbundled development. ✌️

  • readability A standalone version of the readability lib

  • release-it 🚀 Automate versioning and package publishing

  • node-graceful-fs fs with incremental backoff on EMFILE

  • lottie-web Render After Effects animations natively on Web, Android and iOS, and React Native. http://airbnb.io/lottie/

  • node-pool Generic resource pooling for node.js

  • RecordRTC RecordRTC is WebRTC JavaScript library for audio/video as well as screen activity recording. It supports Chrome, Firefox, Opera, Android, and Microsoft Edge. Platforms: Linux, Mac and Windows.

  • BlogHelper 帮助国内用户写作的托盘助手,一键发布本地文章到主流博客平台(知乎、简书、博客园、CSDN、SegmentFault、掘金、开源**),剪贴板图片一键上传至图床(新浪、Github、图壳、腾讯云、阿里云、又拍云、七牛云)(欢迎Star,🚫禁止Fork)

  • minipack 📦 A simplified example of a modern module bundler written in JavaScript

  • raw-body Get and validate the raw body of a readable stream

  • Chart.js Simple HTML5 Charts using the tag

  • why-did-you-render why-did-you-render by Welldone Software monkey patches React to notify you about potentially avoidable re-renders. (Works with React Native as well.)

  • ncc Compile a Node.js project into a single file. Supports TypeScript, binary addons, dynamic requires.

  • micro Asynchronous HTTP microservices

  • node-fs-extra Node.js: extra methods for the fs object like copy(), remove(), mkdirs()

  • modules-webmake Bundle CommonJS/Node.js modules for web browser

  • madge Create graphs from your CommonJS, AMD or ES6 module dependencies

  • ccxt A JavaScript / Python / PHP cryptocurrency trading API with support for more than 100 bitcoin/altcoin exchanges

  • react-ga React Google Analytics Module

  • bowser a browser detector

  • tencent-serverless-http This library enables you to utilize Tencent Cloud API Gateway to respond to web and API requests using your existing Node.js application framework.

  • fc-express-nodejs8 express 在函数计算中无缝运行

  • funcraft (have) Fun with Serverless(API Gateway & Function Compute)

  • appmetrics Node Application Metrics provides a foundational infrastructure for collecting resource and performance monitoring data for Node.js-based applications.

  • p-retry Retry a promise-returning or async function

  • matcha A caffeine driven, simplistic approach to benchmarking.

  • detect-browser Unpack a browser type and version from the useragent string

  • mini-css-extract-plugin Lightweight CSS extraction plugin

  • node-schedule A cron-like and not-cron-like job scheduler for Node.

  • node-cron Cron for NodeJS.

  • csv-parser Streaming csv parser inspired by binary-csv that aims to be faster than everyone else

  • eslint-plugin-react React-specific linting rules for ESLint

  • chinese-colors China colors and Japan traditional trandtional colors

  • ChineseBQB 🇨🇳 Chinese sticker pack,More joy / 表情包的博物馆, Github最有毒的仓库, **表情包大集合, 聚欢乐~

  • odoo Odoo. Open Source Apps To Grow Your Business.

  • terser 🗜 JavaScript parser, mangler and compressor toolkit for ES6+

  • react-app-rewired Override create-react-app webpack configs without ejecting

  • Administrative-divisions-of-China 中华人民共和国行政区划:省级(省份)、 地级(城市)、 县级(区县)、 乡级(乡镇街道)、 村级(村委会居委会) ,**省市区镇村二级三级四级五级联动地址数据。

  • 2019-ncov 全国新型冠状病毒,肺炎疫情实时省市地图

  • loadable-components The recommended Code Splitting library for React ✂️✨

  • react-chrome-extension-boilerplate Boilerplate for Chrome Extension React.js project

  • nodemailer ✉️ Send e-mails with Node.JS – easy as cake!

  • strml.net STRML: Projects & Work

  • animate_resume 动态简历(animate_resume)

  • RSSHub 🍰 Everything is RSSible

  • mercurius Implement GraphQL servers and gateways with Fastify

  • serverless-offline Emulate AWS λ and API Gateway locally when developing your Serverless project

  • ml5-library Friendly machine learning for the web! 🤖

  • cors Cross-Origin Resource Sharing(CORS) for koa

  • co-wechat Wechat for Koa

  • serverless-http Use your existing middleware framework (e.g. Express, Koa) in AWS Lambda 🎉

  • serverless-express Run your unmodified express app on AWS Lambda via the Serverless framework.

  • serverless-aliyun-function-compute Serverless Alibaba Cloud Function Compute Plugin – Add Alibaba Cloud Function Compute support to the Serverless Framework

  • serverless-graphql Serverless GraphQL Examples for AWS AppSync and Apollo

  • components The Serverless Framework's new infrastructure provisioning technology — Build, compose, & deploy serverless apps in seconds...

  • examples Serverless Examples – A collection of boilerplates and examples of serverless architectures built with the Serverless Framework on AWS Lambda, Microsoft Azure, Google Cloud Functions, and more.

  • UglifyJS JavaScript parser / mangler / compressor / beautifier toolkit

  • workflow 一个工作流平台

  • zignis-plugin-read A command line tool to help getting main part of a page

  • impress.js It's a presentation framework based on the power of CSS3 transforms and transitions in modern browsers and inspired by the idea behind prezi.com.

  • evil-huawei Evil Huawei - 华为作过的恶

  • markdown-it Markdown parser, done right. 100% CommonMark support, extensions, syntax plugins & high speed

  • graphql.js A Simple and Isomorphic GraphQL Client for JavaScript

  • gatsby-starter-netlify-cms Example gatsby + netlify cms project

  • reactjs-interview-questions List of top 500 ReactJS Interview Questions & Answers....Coding exercise questions are coming soon!!

  • nvs Node Version Switcher - A cross-platform tool for switching between versions and forks of Node.js

  • node-readability Server side readability with node.js

  • blog This is the project which is used as my blog.

  • simple-icons SVG icons for popular brands

  • WebSocket-Node A WebSocket Implementation for Node.JS (Draft -08 through the final RFC 6455)

  • cli The Sequelize CLI

  • select Programmatically select the text of a HTML element

  • clipboard.js ✂️ Modern copy to clipboard. No Flash. Just 3kb gzipped 📋

  • node-config Node.js Application Configuration

  • shields Concise, consistent, and legible badges in SVG and raster format

  • webpack-chain A chaining API to generate and simplify the modification of Webpack configurations.

  • fe-interview 前端面试每日 3+1,以面试题来驱动学习,提倡每日学习与思考,每天进步一点!每天早上5点纯手工发布面试题(死磕自己,愉悦大家),5000+道前端面试题全面覆盖,HTML/CSS/JavaScript/Vue/React/Nodejs/TypeScript/ECMAScritpt/Webpack/Jquery/小程序/软技能……

  • anime JavaScript animation engine

  • react Cheatsheets for experienced React developers getting started with TypeScript

  • markdown-resume 👔支持 Markdown 和富文本的在线简历排版工具

  • NodeBB Node.js based forum software built for the modern web

  • markdown-nice 支持主题设计的 Markdown 编辑器,让排版变 Nice

  • awesome-mac  Now we have become very big, Different from the original idea. Collect premium software in various categories.

  • universal-resume Minimal and formal résumé (CV) website template for print, mobile, and desktop. https://bit.ly/ur_demo

  • co-wechat-api Wechat API. Support Async Functions

  • wechat 微信公共平台消息接口服务中间件

  • awesome-npx 🌟 packages and resources that work really well with https://github.com/zkat/npx 🕶

  • react-lazyload Lazy load your component, image or anything matters the performance.

  • react-motion A spring that solves your animation problems.

  • nconf Hierarchical node.js configuration with files, environment variables, command-line arguments, and atomic object merging.

  • oss-browser OSS Browser 提供类似windows资源管理器功能。用户可以很方便的浏览文件,上传下载文件,支持断点续传等。

  • react-developer-roadmap Roadmap to becoming a React developer

  • babel-usage Examples about babel(babel 的用例)

  • strapi 🚀 Open source Node.js Headless CMS to easily build customisable APIs

  • color-thief Grab the color palette from an image using just Javascript. Works in the browser and in Node.

  • d3-cloud Create word clouds in JavaScript.

  • prerender-node Express middleware for prerendering javascript-rendered pages on the fly for SEO

  • nodejieba "结巴"中文分词的Node.js版本

  • bloomfilter.js JavaScript bloom filter using FNV for fast hashing

  • astexplorer A web tool to explore the ASTs generated by various parsers.

  • babel-plugin-import-graphql Enables import syntax for .graphql and .gql files

  • babel-plugin-graphql-tag Compiles GraphQL tagged template strings using graphql-tag.

  • kue Kue is a priority job queue backed by redis, built for node.js.

  • promise-limit limits calls to functions that return promises

  • graphql-markdown The easiest way to document your GraphQL schema.

  • remote-jobs A list of semi to fully remote-friendly companies (jobs) in tech.

  • federation-demo Demo of Apollo Federation

  • react-highlight-words React component to highlight words within a larger body of text

  • npm-check Check for outdated, incorrect, and unused dependencies.

  • full-icu-npm convenience loader for 'small-icu' node builds

  • prerender-spa-plugin Prerenders static HTML in a single-page application.

  • fontmin Minify font seamlessly

  • Free-Chinese-Fonts 免费中文字体

  • font-spider Smart webfont compression and format conversion tool

  • tina 💃 一款轻巧的渐进式微信小程序框架

  • wechat-format 微信公众号排版编辑器,转换 Markdown 到微信特制的 HTML

  • node Node.js JavaScript runtime ✨🐢🚀✨

  • cls-bluebird

  • async-listener polyfill version of the 0.11 version of the asyncListener API

  • node-continuation-local-storage implementation of nodejs/node-v0.x-archive#5243

  • express-http-context Get and set request-scoped context anywhere

  • bodyparser a body parser for koa

  • shimmer

  • gatsby Build blazing fast, modern apps and websites with React

  • graphql-schema-linter Validate GraphQL schema definitions against a set of rules

  • uuid Generate RFC-compliant UUIDs in JavaScript

  • pinyin 🇨🇳 汉字拼音 ➜ hàn zì pīn yīn

  • avatar 💎 Beautiful avatars as a microservice

  • casual Fake data generator for javascript

  • express-winston express.js middleware for winstonjs

  • page-skeleton-webpack-plugin Webpack plugin to generate the skeleton page automatically

  • node-rate-limiter-flexible Node.js rate limit requests by key with atomic increments in single process or distributed environment.

  • tool 开发效率提升:Mac生产力工具链推荐

  • graphql-type-json JSON scalar type for GraphQL.js

  • node-dev Zero-conf Node.js reloading

  • markdownlint A Node.js style checker and lint tool for Markdown/CommonMark files.

  • node-rdkafka Node.js bindings for librdkafka

  • DeepLearning-500-questions 深度学习500问,以问答形式对常用的概率知识、线性代数、机器学习、深度学习、计算机视觉等热点问题进行阐述,以帮助自己及有需要的读者。 全书分为18个章节,50余万字。由于水平有限,书中不妥之处恳请广大读者批评指正。 未完待续............ 如有意合作,联系[email protected] 版权所有,违权必究 Tan 2018.06

  • bull Premium Queue package for handling distributed jobs and messages in NodeJS.

  • ChromeAppHeroes 🌈谷粒-Chrome插件英雄榜, 为优秀的Chrome插件写一本中文说明书, 让Chrome插件英雄们造福人类~ ChromePluginHeroes, Write a Chinese manual for the excellent Chrome plugin, let the Chrome plugin heroes benefit the human~ 公众号「0加1」同步更新

  • hiper 🚀 A statistical analysis tool for performance testing

  • hexo-theme-yilia 一个简洁优雅的hexo主题 A simple and elegant theme for hexo.

  • reactjs-interview-questions List of top 304 ReactJS Interview Questions & Answers

  • gh-oauth-server

  • vuepress 📝 Minimalistic Vue-powered static site generator

  • gitment A comment system based on GitHub Issues.

  • json2csv Convert json to csv with column titles

  • http-server a simple zero-configuration command-line http server

  • stream-handbook how to write node programs with streams

  • prerender Node server that uses Headless Chrome to render a javascript-rendered page as HTML. To be used in conjunction with prerender middleware.

  • wordcloud2.js Tag cloud/Wordle presentation on 2D canvas or HTML

  • styled-jsx Full CSS support for JSX without compromises

  • react-helmet A document head manager for React

  • node-heapdump Make a dump of the V8 heap for later inspection.

  • next-routes Universal dynamic routes for Next.js

  • vue-touch Hammer.js wrapper for Vue.js

  • browserslist 🦔 Share target browsers between different front-end tools, like Autoprefixer, Stylelint and babel-preset-env

  • dayjs ⏰ Day.js 2kB immutable date-time library alternative to Moment.js with the same modern API

  • babel-plugin-lodash Modular Lodash builds without the hassle.

  • image-webpack-loader Image loader module for webpack

  • statsd Daemon for easy but powerful stats aggregation

  • openfass-node-restful-api Simple example of an OpenFaaS RESTful API

  • ace Ace (Ajax.org Cloud9 Editor)

  • graphql-constraint-directive Validate GraphQL fields

  • single-spa The router for easy microfrontends

  • markdown-here Google Chrome, Firefox, and Thunderbird extension that lets you write email in Markdown and render it before sending.

  • stackedit In-browser Markdown editor

  • passport Simple, unobtrusive authentication for Node.js.

  • validator.js String validation

  • serverless ⚡ Serverless Framework – Build web, mobile and IoT applications with serverless architectures using AWS Lambda, Azure Functions, Google CloudFunctions & more! –

  • css-doodle 🎨 A web component for drawing patterns with CSS.

  • vue-study

  • wechat-weapp-movie 🎬电影推荐 - 微信小程序

  • react-tutorial 基于 umi 的 Ant Design 实战教程配套代码 https://www.yuque.com/ant-design/course

  • autocannon fast HTTP/1.1 benchmarking tool written in Node.js

  • mpvue 基于 Vue.js 的小程序开发框架,从底层支持 Vue.js 语法和构建工具体系。

  • taskbook Tasks, boards & notes for the command-line habitat

  • lowdb Simple to use local JSON database. Powered by plain JavaScript (supports Node, Electron and the browser)

  • parse-server API server module for Node/Express

  • quill Quill is a modern WYSIWYG editor built for compatibility and extensibility.

  • showdown A bidirectional Markdown to HTML to Markdown converter written in Javascript

  • winston A logger for just about everything.

  • fluent-logger-node A structured logger for Fluentd (Node.js)

  • js-yaml JavaScript YAML parser and dumper. Very fast.

  • lavas 基于 Vue 的 PWA 解决方案,帮助开发者快速搭建 PWA 应用,解决接入 PWA 的各种问题

  • sw-precache-webpack-plugin Webpack plugin that generates a service worker using sw-precache that will cache webpack's bundles' emitted assets. You can optionally pass sw-precache configuration options to webpack through this plugin.

  • trained-to-thrill Trains! Yey!

  • localForage 💾 Offline storage, improved. Wraps IndexedDB, WebSQL, or localStorage using a simple but powerful API.

  • workbox 📦 Workbox: JavaScript libraries for Progressive Web Apps

  • sequelize-transparent-cache Simple to use and universal cache layer for Sequelize

  • sketch-to-html 从 sketch 转换成 html,已无更新。。

  • consolidate.js Template engine consolidation library for node.js

  • hashi-ui A modern user interface for @hashicorp Consul & Nomad

  • node-consul Consul client

  • long-timeout

  • ccapture.js A library to capture canvas-based animations at a fixed framerate

  • node-jws JSON Web Signatures

  • codemirror5 In-browser code editor (version 5, legacy)

  • awesome-blockchain-cn 收集所有区块链(BlockChain)技术开发相关资料,包括Fabric和Ethereum开发资料

  • chokidar Minimal and efficient cross-platform file watching library

  • email-templates 📫 Create, preview, and send custom email templates for Node.js. Highly configurable and supports automatic inline CSS, stylesheets, embedded images and fonts, and much more!

  • node-cron A simple cron-like job scheduler for Node.js

  • juice Juice inlines CSS stylesheets into your HTML source.

  • graphql-network Chrome Devtool that provides a "network"-style tab for GraphQL requests to allow developers to debug more easily.

  • pegasus Load JSON while still loading other scripts

  • chinese-poetry The most comprehensive database of Chinese poetry 🧶最全中华古诗词数据库, 唐宋两朝近一万四千古诗人, 接近5.5万首唐诗加26万宋诗. 两宋时期1564位词人,21050首词。

  • bluebird 🐦 ⚡ Bluebird is a full featured promise library with unmatched performance.

  • offline-plugin Offline plugin (ServiceWorker, AppCache) for webpack (https://webpack.js.org/)

  • react-boilerplate 🔥 A highly scalable, offline-first foundation with the best developer experience and a focus on performance and best practices.

  • knox S3 Lib

  • recompose A React utility belt for function components and higher-order components.

  • react-apollo-graphql-github-example Apollo React example for Github GraphQL API

  • lokka Simple JavaScript Client for GraphQL

  • react-loadable ⏳ A higher order component for loading components with promises.

  • sw-toolbox [Deprecated] A collection of service worker tools for offlining runtime requests

  • complexity-report [UNMAINTAINED] Software complexity analysis for JavaScript projects

  • graphql-tools-sequelize Integration of GraphQL-Tools and Sequelize ORM

  • graphql-with-sequelize GraphQL working with Sequelize.js

  • graphql-bookshelf Some help defining GraphQL schema around BookshelfJS models

  • join-monster-graphql-tools-adapter Use Join Monster to fetch your data with Apollo Server.

  • dataloader-sequelize Batching and simplification of Sequelize with facebook/dataloader

  • apollo-resolvers Expressive and composable resolvers for Apollostack's GraphQL server

  • react-apollo ♻️ React integration for Apollo Client

  • join-monster A GraphQL to SQL query execution layer for query planning and batch data fetching.

  • node-jsonwebtoken JsonWebToken implementation for node.js http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html

  • knex A query builder for PostgreSQL, MySQL, CockroachDB, SQL Server, SQLite3 and Oracle, designed to be flexible, portable, and fun to use.

  • dataloader DataLoader is a generic utility to be used as part of your application's data fetching layer to provide a consistent API over various backends and reduce requests to those backends via batching and caching.

  • log4js-node A port of log4js to node.js

  • graphql-sequelize GraphQL & Relay for MySQL & Postgres via Sequelize

  • ali-oss Aliyun OSS(Object Storage Service) JavaScript SDK for the Browser and Node.js

  • next.js The React Framework

  • FileSaver.js An HTML5 saveAs() FileSaver implementation

  • jwt Koa middleware for validating JSON Web Tokens

  • request 🏊🏾 Simplified HTTP request client.

  • superagent Ajax for Node.js and browsers (JS HTTP client)

  • webpack-sentry-plugin Webpack plugin to upload source maps to Sentry

  • aliyun-sdk-js 阿里云 SDK for Javascript,支持在浏览器和 Nodejs 环境使用,支持大部分阿里云服务。

  • thunkify-wrap a more powerful thunkify

  • electron-pdf 📄 A command line tool to generate PDF from URL, HTML or Markdown files.

  • joi-router Configurable, input and output validated routing for koa

  • koa-body koa body parser middleware

  • jszip Create, read and edit .zip files with Javascript

  • 30-seconds-of-code Short JavaScript code snippets for all your development needs

  • jsPDF Client-side JavaScript PDF generation for everyone.

  • parcel The zero configuration build tool for the web. 📦🚀

  • api-easy Fluent (i.e. chainable) syntax for generating vows tests against RESTful APIs.

  • AndroidInterview-Q-A The top Internet companies android interview questions and answers

  • android-training-course-in-chinese Android官方培训课程中文版

  • three.js JavaScript 3D Library.

  • nodebestpractices ✅ The Node.js best practices list (June 2022)

  • chalk 🖍 Terminal string styling done right

  • react-static ⚛️ 🚀 A progressive static site generator for React.

  • jsdoc An API documentation generator for JavaScript.

  • web-audio-recorder-js .wav/.ogg/.mp3 recorder with Web Audio API

  • tv4 Tiny Validator for JSON Schema v4

  • egg 🥚 Born to build better enterprise frameworks and apps with Node.js & Koa

  • lerna 🐉 Lerna is a fast, modern build system for managing and publishing multiple JavaScript/TypeScript packages from the same repository.

  • json-server Get a full fake REST API with zero coding in less than 30 seconds (seriously)

  • newman Newman is a command-line collection runner for Postman

  • js-code-to-svg-flowchart js2flowchart - a visualization library to convert any JavaScript code into beautiful SVG flowchart. Learn other’s code. Design your code. Refactor code. Document code. Explain code.

  • node-mongodb-native The Official MongoDB Node.js Driver

  • node-glob glob functionality for node.js

  • nock HTTP server mocking and expectations library for Node.js

  • jscodeshift A JavaScript codemod toolkit.

  • zipkin-js Zipkin instrumentation for Node.js and browsers

  • engineer-manager A list of engineering manager resource links.

  • connect Connect is a middleware layer for Node.js

  • node-delegates Nodejs method and accessor delegation utility

  • fastify Fast and low overhead web framework, for Node.js

  • webpackmonitor A tool for monitoring webpack optimization metrics through the development process

  • enzyme JavaScript Testing utilities for React

  • dotenv Loads environment variables from .env for nodejs projects.

  • svg-sprite-loader Webpack loader for creating SVG sprites.

  • svgo ⚙️ Node.js tool for optimizing SVG files

  • tech-interview-handbook 💯 Curated interview preparation materials for busy engineers

  • axios Promise based HTTP client for the browser and node.js

  • joi The most powerful data validation library for JS

  • sequelize An easy-to-use and promise-based multi SQL dialects ORM tool for Node.js | Postgres, MySQL, MariaDB, SQLite, MSSQL, Snowflake & DB2

  • koa-router Router middleware for koa.

  • ava Node.js test runner that lets you develop with confidence 🚀

  • supertest 🕷 Super-agent driven library for testing node.js HTTP servers using a fluent API.

  • examples Example Koa apps

  • react-hot-boilerplate Minimal live-editing example for React

  • loader-utils utils for webpack loaders

  • web-animations-js JavaScript implementation of the Web Animations API

  • js-cookie A simple, lightweight JavaScript API for handling browser cookies

  • koa Expressive middleware for node.js using ES2017 async functions

  • express Fast, unopinionated, minimalist web framework for node.

  • autotrack Automatic and enhanced Google Analytics tracking for common user interactions on the web.

  • lighthouse Automated auditing, performance metrics, and best practices for the web.

  • project-guidelines A set of best practices for JavaScript projects

  • choo 🚂🚋 - sturdy 4kb frontend framework

  • nanomorph 🚅 - Hyper fast diffing algorithm for real DOM nodes

  • node-practice Node.js 实践教程

  • css-in-js React: CSS in JS techniques comparison

  • flux-comparison 📝 Practical comparison of different Flux solutions

  • react-primitives Primitive React Interfaces Across Targets

  • quasar Quasar Framework - Build high-performance VueJS user interfaces in record time

  • d3 Bring data to life with SVG, Canvas and HTML. 📊📈🎉

  • superfine Absolutely minimal view layer for building web interfaces.

  • kevins-wheels-virtual-dom 自己造轮子,实现virtual dom及dom diff

  • react-lego 一种特别注重扩展和复用的 React 组件编写规则。

  • roof-bus cool event bus

  • RxJS The Reactive Extensions for JavaScript

  • q A promise library for JavaScript

  • node-http2 An HTTP/2 client and server implementation for node.js

  • ractive Next-generation DOM manipulation

  • github-blogs-collector Collector for blogs in github

  • react-ssr-boilerplate React SSR

  • chai BDD / TDD assertion framework for node.js and the browser that can be paired with any testing framework.

  • casperjs CasperJS is no longer actively maintained. Navigation scripting and testing utility for PhantomJS and SlimerJS

  • video.js Video.js - open source HTML5 video player

  • lie A basic but performant promise implementation.

  • browser-sync Keep multiple browsers & devices in sync when building websites. http://browsersync.io

  • weekly 前端精读周刊。帮你理解最前沿、实用的技术。

  • why-did-you-update 💥 Puts your console on blast when React is making unnecessary updates.

  • blendid A delicious blend of gulp tasks combined into a configurable asset pipeline and static site builder

  • gulp-plumber Fixing Node pipes

  • javascript-algorithms 💻 JavaScript implementations of computer science algorithms

  • devtool [OBSOLETE] runs Node.js programs through Chromium DevTools

  • acorn A small, fast, JavaScript-based JavaScript parser

  • tapable Just a little module for plugins.

  • should.js BDD style assertions for node.js -- test framework agnostic

  • updtr Update outdated npm modules with zero pain™

  • compression-webpack-plugin Prepare compressed versions of assets to serve them with Content-Encoding

  • serve Static file serving and directory listing

  • sockjs-client WebSocket emulation - Javascript client

  • react-hot-loader Tweak React components in real time. (Deprecated: use Fast Refresh instead.)

  • node-inspector Node.js debugger based on Blink Developer Tools

  • istanbul Yet another JS code coverage tool that computes statement, line, function and branch coverage with module loader hooks to transparently add coverage when running tests. Supports all JS coverage use cases including unit tests, server side functional tests and browser tests. Built for scale.

  • nodemon Monitor for any changes in your node.js application and automatically restart the server - perfect for development

  • nodeunit Easy unit testing in node.js and the browser, based on the assert module.

  • sw-precache [Deprecated] A node module to generate service worker code that will precache specific resources so they work offline.

  • virtual-dom A Virtual DOM and diffing algorithm

  • 1-liners Functional tools that couldn’t be simpler.

  • fantasy-land Specification for interoperability of common algebraic structures in JavaScript

  • mostly-adequate-guide Mostly adequate guide to FP (in javascript)

  • mostly-adequate-guide-chinese 函数式编程指北中文版

  • ramda 🐏 Practical functional Javascript

  • gulp A toolkit to automate & enhance your workflow

  • echarts-plus A visualization grammar extension for Echarts to make it more powerful. Help you build echarts option and series more effective!

  • gulp-rev-replace Rewrite occurences of filenames which have been renamed by gulp-rev

  • fetch A window.fetch JavaScript polyfill.

  • preact ⚛️ Fast 3kB React alternative with the same modern API. Components & Virtual DOM.

  • gh-pages General purpose task for publishing files to a gh-pages branch on GitHub

  • country-data Country related data such as ISO codes, currencies etc

  • gulp-rev Static asset revisioning by appending content hash to filenames: unicorn.cssunicorn-d41d8cd98f.css

  • pug Pug – robust, elegant, feature rich template engine for Node.js

  • react A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • husky Git hooks made easy 🐶 woof!

  • fuse-box A blazing fast js bundler/loader with a comprehensive API 🔥

  • classnames A simple javascript utility for conditionally joining classNames together

  • webpack-bundle-analyzer Webpack plugin and CLI utility that represents bundle content as convenient interactive zoomable treemap

  • history Manage session history with JavaScript

  • webpack-cli Webpack's Command Line Interface

  • webpack-dev-server Serves a webpack app. Updates the browser on changes. Documentation https://webpack.js.org/configuration/dev-server/.

  • tips Most commonly used git tips and tricks.

  • react-a11y Identifies accessibility issues in your React.js elements

  • webpack.js.org 印记中文 - webpack 中文文档

  • css-grid-polyfill A working implementation of css grids for current browsers.

  • blog 个人博客😝😋😄

  • proposal-observable Observables for ECMAScript

  • symbol-observable Symbol.observable ponyfill

  • react-native-drawer React Native Drawer

  • react-native-router-flux The first declarative React Native router

  • react-native-douban 基于 React-Native & 豆瓣Open API

  • docker-ssh SSH Server for Docker containers ~ Because every container should be accessible

  • jackblog-react Jackblog react 版, 个人博客系统, 使用服务端渲染(Universal / Isomorphic), react, redux, react-router, react-bootstrap, immutablejs, redux-form等

  • remote-redux-devtools Redux DevTools remotely.

  • redux-devtools-extension Redux DevTools extension.

  • create-react-app Set up a modern web app by running one command.

  • exceljs Excel Workbook Manager

  • sheetjs 📗 SheetJS Community Edition -- Spreadsheet Data Toolkit

  • vimium The hacker's browser.

  • eme Elegant Markdown Editor.

  • react-mobx-demo React Mobx Demo

  • weapp-gold 微信小程序的掘金信息流

  • yargs yargs the modern, pirate-themed successor to optimist.

  • bundle-loader Bundle Loader

  • webpack.js.org Repository for webpack documentation and more!

  • webpack-howto

  • url-loader A loader for webpack which transforms files into base64 URIs

  • vue-lazyload A Vue.js plugin for lazyload your Image or Component in your application.

  • medium-editor Medium.com WYSIWYG editor clone. Uses contenteditable API to implement a rich text solution.

  • extract-text-webpack-plugin [DEPRECATED] Please use https://github.com/webpack-contrib/mini-css-extract-plugin Extracts text from a bundle into a separate file

  • bootstrap The most popular HTML, CSS, and JavaScript framework for developing responsive, mobile first projects on the web.

  • learnGitBranching An interactive git visualization and tutorial. Aspiring students of git can use this app to educate and challenge themselves towards mastery of git!

  • dadash some common functions written by own, btw, it does't finished and it's ugly...

  • reactjs-todos

  • vue-hackernews-2.0 HackerNews clone built with Vue 2.0, vue-router & vuex, with server-side rendering

  • redux-tutorial-cn Redux Tutorial 中文翻译

  • mocha ☕️ simple, flexible, fun javascript test framework for node.js & the browser

  • jsdom A JavaScript implementation of various web standards, for use with Node.js

  • javascript JavaScript Style Guide

  • vuex 🗃️ Centralized State Management for Vue.js.

  • node-http-proxy A full-featured http proxy for node.js

  • material-ui MUI Core (formerly Material-UI) is the React UI library you always wanted. Follow your own design system, or start with Material Design.

  • react-native A framework for building native applications using React

  • react-router-tutorial

  • webpack A bundler for javascript and friends. Packs many modules into a few bundled assets. Code Splitting allows for loading parts of the application on demand. Through "loaders", modules can be CommonJs, AMD, ES6 modules, CSS, Images, JSON, Coffeescript, LESS, ... and your custom stuff.

  • hammer.js A javascript library for multi-touch gestures :// You can touch this

  • todomvc Helping you select an MV* framework - Todo apps for React.js, Ember.js, Angular, and many more

  • react-tutorial Code from the React tutorial.

  • 500lines 500 Lines or Less

  • youmightnotneedjquery

  • jquery jQuery JavaScript Library

  • hacker-scripts Based on a true story

  • vue-strap Bootstrap components built with Vue.js

  • vue-slide A lightweight slide component for vue

  • WebFrontEndStack web front end stack: browsers, platforms, libraries, frameworks, tools etc.

  • vue-cli 🛠️ webpack-based tooling for Vue.js Development

  • reveal.js The HTML Presentation Framework

  • Vue-cnodejs 基于vue.js重写Cnodejs.org社区的webapp

  • promiseA A simple Promises/A+ implementation.

AsciiDoc

  • bitcoinbook Mastering Bitcoin 2nd Edition - Programming the Open Blockchain

Lua

C++

  • ModSecurity ModSecurity is an open source, cross platform web application firewall (WAF) engine for Apache, IIS and Nginx that is developed by Trustwave's SpiderLabs. It has a robust event-based programming language which provides protection from a range of attacks against web applications and allows for HTTP traffic monitoring, logging and real-time analysis. With over 10,000 deployments world-wide, ModSecurity is the most widely deployed WAF in existence.

  • node-canvas Node canvas is a Cairo backed Canvas implementation for NodeJS.

  • neutralinojs Portable and lightweight cross-platform desktop application development framework

  • obs-multi-rtmp OBS ≧ 26.1用複数サイト同時配信プラグイン

  • CPlusPlusThings C++那些事

  • nghttp2 nghttp2 - HTTP/2 C Library and tools

  • rocksdb A library that provides an embeddable, persistent key-value store for fast storage.

  • xprofiler 🌀An addon for node.js, which supporting output performance log and real-time profiling through sampling.

  • v8-profiler-next node bindings for the v8 profiler

  • v8-profiler node bindings for the v8 profiler

  • grpc The C based gRPC (C++, Python, Ruby, Objective-C, PHP, C#)

  • envoy Cloud-native high-performance edge/middle/service proxy

  • handy 🔥简洁易用的C++11网络库 / 支持单机千万并发连接 / a simple C++11 network server framework

  • images Source code of images.weserv.nl, to be used on your own server(s).

  • ceph Ceph is a distributed object, block, and file storage platform

  • cppjieba "结巴"中文分词的C++版本

  • llnode An lldb plugin for Node.js and V8, which enables inspection of JavaScript states for insights into Node.js processes and their core dumps.

  • terminal The new Windows Terminal and the original Windows console host, all in the same place!

  • pytorch Tensors and Dynamic neural networks in Python with strong GPU acceleration

  • my-notes 工程师的自我修养

  • electron :electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS

  • incubator-weex Apache Weex (Incubating)

  • mosh Mobile Shell

  • tensorflow An Open Source Machine Learning Framework for Everyone

  • trafficserver Apache Traffic Server™ is a fast, scalable and extensible HTTP/1.1 and HTTP/2 compliant caching proxy server.

  • protobuf Protocol Buffers - Google's data interchange format

Vue

  • vue-vben-admin A modern vue admin. It is based on Vue3, vite and TypeScript. It's fast!

  • md ✍ WeChat Markdown Editor | 一款高度简洁的微信 Markdown 编辑器:支持 Markdown 语法、色盘取色、多图上传、一键下载文档、自定义 CSS 样式、一键重置等特性

  • picx 基于 GitHub API & jsDelivr 开发的具有 CDN 加速功能的图床管理工具。无需下载与安装,网页端在线使用!免费!稳定!便捷!极速!

  • best-resume-ever 👔 💼 Build fast 🚀 and easy multiple beautiful resumes and create your best CV ever! Made with Vue and LESS.

  • tailwind-config-viewer A local UI tool for visualizing your Tailwind CSS configuration file.

  • hoppscotch 👽 Open source API development ecosystem - https://hoppscotch.io

  • Daily-Question 互联网大厂内推及大厂面经整理,并且每天一道面试题推送。每天五分钟,半年大厂中

  • cssfx ✨ Beautifully simple click-to-copy CSS effects

  • echarts-map-demo echarts地图geoJson行政边界数据的实时获取与应用,省市区县多级联动下钻,真正意义的下钻至县级【附最新geoJson文件下载】

  • vue-cli-multi-page 在vue-cli + webpack的基础上修改为多页面应用,支持指定子项目打包

  • mpvue-weui 用 vue 写小程序,基于 mpvue 框架重写 weui。

  • rock-music 项目是学完慕课网课程《Vue.js高级实战—开发移动端音乐App》后,独立开发,也是第一个vue程序。

  • element A Vue.js 2.0 UI Toolkit for Web

  • vue-element-admin 🎉 A magical vue admin https://panjiachen.github.io/vue-element-admin

  • vue-issue-helper

  • jackblog-vue Jackblog vue 版, 个人博客系统, 使用 vue2, vuex, vue-resource, vue-router, vee-validate, vue-toast 等.

  • vux Mobile UI Components based on Vue & WeUI

Svelte

  • daisyui ⭐️ ⭐️ ⭐️ ⭐️ ⭐️  The most popular, free and open-source Tailwind CSS component library

Shell

  • docker.sh docker.sh 是用 Shell 写的一个简易的 docker,麻雀虽小,五脏俱全,可用于学习 Docker 原理。

  • distributions NodeSource Node.js Binary Distributions

  • acme.sh A pure Unix shell script implementing ACME client protocol

  • bitnami-docker-nginx Bitnami Docker Image for NGINX Open Source

  • gluster-kubernetes GlusterFS Native Storage Service for Kubernetes

  • build-image This is the build image used for running automated builds

  • Publish-Docker-Github-Action A Github Action used to build and publish Docker images

  • tmux-config Tmux configuration, that supercharges your tmux to build cozy and cool terminal environment

  • pure-bash-bible 📖 A collection of pure bash alternatives to external processes.

  • docker-elk The Elastic stack (ELK) powered by Docker and Compose.

  • perf-tools Performance analysis tools based on Linux perf_events (aka perf) and ftrace

  • realworld "The mother of all demo apps" — Exemplary fullstack Medium.com clone powered by React, Angular, Node, Django, and many more 🏅

  • self-hosted Sentry, feature-complete and packaged up for low-volume deployments and proofs-of-concept

  • kafka-docker Dockerfile for Apache Kafka

  • awesome-spark A curated list of awesome Apache Spark packages and resources.

  • notes Full-stack web development notes.

  • zsh-autosuggestions Fish-like autosuggestions for zsh

  • programmer-job-blacklist 🙈程序员找工作黑名单,换工作和当技术合伙人需谨慎啊 更新有赞

  • boot2docker DEPRECATED; see boot2docker/boot2docker#1408

  • docker-host A docker sidecar container to forward all traffic to local docker host or any other host

  • openvpn-install OpenVPN road warrior installer for Ubuntu, Debian, AlmaLinux, Rocky Linux, CentOS and Fedora

  • easy-rsa easy-rsa - Simple shell based CA utility

  • docker-ipsec-vpn-server Docker image to run an IPsec VPN server, with IPsec/L2TP, Cisco IPsec and IKEv2

  • gcr.io_mirror all of the gcr.io docker image mirror

  • docker-consul Dockerized Consul

  • autoenv Directory-based environments

  • graphql-spec GraphQL is a query language and execution engine tied to any backend service.

  • antigen The plugin manager for zsh.

  • gitflow Git extensions to provide high-level repository operations for Vincent Driessen's branching model.

  • docker-gitlab Dockerized GitLab

  • wifi-password Get the password of the wifi you're on (bash)

  • git-extras GIT utilities -- repo summary, repl, changelog population, author commit percentages and more

  • docker-openvpn 🔒 OpenVPN server in a Docker container complete with an EasyRSA PKI CA

  • ansible-examples A few starter examples of ansible playbooks, to show features and how they work together. See http://galaxy.ansible.com for example roles from the Ansible community for deploying many popular applications.

  • tmux-resurrect Persists tmux environment across system restarts.

  • tmux-config 📗 Example tmux configuration - screen + vim key-bindings, system stat, cpu load bar.

  • gnome-terminal-colors-solarized Solarized Gnome Terminal colors, based on http://ethanschoonover.com/solarized

  • nvm Node Version Manager - POSIX-compliant bash script to manage multiple active node.js versions

  • docs Documentation for Docker Official Images in docker-library

  • ohmyzsh 🙃 A delightful community-driven (with 2,000+ contributors) framework for managing your zsh configuration. Includes 300+ optional plugins (rails, git, macOS, hub, docker, homebrew, node, php, python, etc), 140+ themes to spice up your morning, and an auto-update tool so that makes it easy to keep up with the latest updates from the community.

  • interview_python 关于Python的面试题

  • githug Git your game on!

Other

TypeScript

  • Reactive-Resume A one-of-a-kind resume builder that keeps your privacy in mind. Completely secure, customizable, portable, open-source and free forever. Try it out today!

  • core Online IDE powered by Visual Studio Code ⚡️

  • faker Generate massive amounts of fake data in the browser and node.js

  • case-police 🚨 Make the case correct, PLEASE!

  • headlessui Completely unstyled, fully accessible UI components, designed to integrate beautifully with Tailwind CSS.

  • react-color-palette 🎨 Lightweight Color Picker component for React.

  • react-colorful 🎨 A tiny (2,8 KB) color picker component for React and Preact apps

  • magic-bytes A library for detecting file types.

  • react-2048 A React implementation of 2048 game built with typescript and styled-components

  • tooling.one A collection of useful tools for developers.

  • httpsnippet HTTP Request snippet generator for many languages & libraries

  • Bilibili-Evolved 强大的哔哩哔哩增强脚本

  • core 🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • astro Build fast websites, faster. 🚀🧑‍🚀✨

  • emp EMP Micro FE Base on webpack 5 & module federation

  • code-server VS Code in the browser

  • regexlearn.com Learn RegEx step by step, from zero to advanced.

  • react-anime ✨ (ノ´ヮ´)ノ*:・゚✧ A super easy animation library for React!

  • javascript-obfuscator A powerful obfuscator for JavaScript and Node.js

  • ag-grid The best JavaScript Data Table for building Enterprise Applications. Supports React / Angular / Vue / Plain JavaScript.

  • react-freeze

  • WASM-ImageMagick Webassembly compilation of https://github.com/ImageMagick/ImageMagick & samples

  • npkill List any node_modules directories in your system, as well as the space they take up. You can then select which ones you want to erase to free up space.

  • patch-package Fix broken node modules instantly 🏃🏽‍♀️💨

  • play-esbuild esbuild online playground

  • react-dnd Drag and Drop for React

  • mdvideo Markdown To Video, 一个将markdown文档转为视频的便捷工具

  • xstate State machines and statecharts for the modern web.

  • code-debug Native debugging for VSCode

  • llparse Generating parsers in LLVM IR

  • llhttp Port of http_parser to llparse

  • snabbdom A virtual DOM library with focus on simplicity, modularity, powerful features and performance.

  • cac Simple yet powerful framework for building command-line apps.

  • china-region 根据国家标准《中华人民共和国行政区划代码》即 GB2260 标准制定,用以查看各个省地县的行政区划代码,并支持多级联动查询

  • helmet Help secure Express apps with various HTTP headers

  • use-localstorage access local storage with React hooks

  • react-spring ✌️ A spring physics based React animation library

  • gqless a GraphQL client without queries

  • pont 🌉数据服务层解决方案

  • ink 🌈 React for interactive command-line apps

  • query 🤖 Powerful asynchronous state management, server-state utilities and data fetching for TS/JS, React, Solid, Svelte and Vue.

  • clipboard-polyfill 📋 Simple copying on the web, with maximum browser compatibility. (You probably don't need this anymore!)

  • ant-design An enterprise-class UI design language and React UI library

  • htmr Simple and lightweight (< 2kB) HTML string to React element conversion library

  • slidev Presentation Slides for Developers

  • type-fest A collection of essential TypeScript types

  • react-spinners A collection of loading spinner components for react

  • use-clipboard-copy 📋 Lightweight copy to clipboard hook for React

  • vscode-icons Icons for Visual Studio Code

  • react-flow Highly customizable library for building interactive node-based UIs, editors, flow charts and diagrams

  • javascript-playgrounds An interactive JavaScript sandbox

  • use-debounce A debounce hook for react

  • react-select The Select Component for React.js

  • markmap Visualize your Markdown as mindmaps with Markmap.

  • use-local-storage-state React hook that persists data in localStorage

  • cheerio Fast, flexible, and lean implementation of core jQuery designed specifically for the server.

  • npm-search 🗿 npm ↔️ Algolia replication tool ⛷️ 🐌 🛰️

  • use-countdown ⏳ useCountdown hook

  • tinyhttp 🦄 0-legacy, tiny & fast web framework as a replacement of Express

  • LogicFlow A flow chart editing framework focusing on business customization. 专注于业务自定义的流程图编辑框架,支持实现脑图、ER图、UML、工作流等各种图编辑场景。

  • npm-trends NPM package comparison

  • react-hotkeys-hook React hook for using keyboard shortcuts in components.

  • react-hot-toast Smoking hot React Notifications 🔥

  • react-toastify React notification made easy 🚀 !

  • http-status-codes Constants enumerating the HTTP status codes. All status codes defined in RFC1945 (HTTP/1.0, RFC2616 (HTTP/1.1), and RFC2518 (WebDAV) are supported.

  • formik Build forms in React, without the tears 😭

  • curlconverter Generate code from cURL commands

  • regulex 🚧 Regular Expression Excited!

  • badgen.net Fast badge service

  • packagephobia ⚖️ Find the cost of adding a new dependency to your project

  • react-icons svg react icons of popular icon packs

  • web-clipper For Notion,OneNote,Bear,Yuque,Joplin。Clip anything to anywhere

  • markdown-read Read markdown from URL

  • svgr Transform SVGs into React components 🦁

  • image-size Node module for detecting image dimensions

  • squoosh Make images smaller using best-in-class codecs, right in the browser.

  • actions-gh-pages GitHub Actions for GitHub Pages 🚀 Deploy static files and publish your site easily. Static-Site-Generators-friendly.

  • github-rank 🕷️Github **和全球用户排名,全球仓库 Star 最多排名(Github Action自动日更)。

  • editor.js A block-styled editor with clean JSON output

  • bytemd Hackable Markdown Editor and Viewer

  • chakra-ui ⚡️ Simple, Modular & Accessible UI Components for your React Applications

  • promise-utils Useful utils about promise. (map, filter, retry, sleep

  • excalidraw Virtual whiteboard for sketching hand-drawn like diagrams

  • topology A diagram (topology, UML) framework uses canvas and typescript. 一个轻量(100k左右)、功能丰富的绘图工具(微服务架构图、拓扑图、流程图、类图等UML图、脑图,动画、视频支持)。 【在线使用】:

  • node-api-boilerplate DDD/Clean Architecture inspired boilerplate for Node web APIs

  • bit A tool for composable software development.

  • immutable-js Immutable persistent data collections for Javascript which increase efficiency and simplicity.

  • reselect Selector library for Redux

  • redux-persist persist and rehydrate a redux store

  • redux-thunk Thunk middleware for Redux

  • mobx-react React bindings for MobX

  • opentracing-javascript OpenTracing API for Javascript (both Node and browser). 🛑 This library is DEPRECATED! opentracing/specification#163

  • docz ✍ It has never been so easy to document your things!

  • wechaty-puppet-padplus DEPRECATED: One puppet based on iPad protocal for Wechaty

  • hox The next-generation state manager for React.

  • graphql-request Minimal GraphQL client supporting Node and browsers for scripts or simple apps

  • chrome The browserless Chrome service in Docker. Run on our cloud, or bring your own.

  • fork-ts-checker-webpack-plugin Webpack plugin that runs typescript type checker on a separate process.

  • oak A middleware framework for handling HTTP with Deno 🐿️ 🦕

  • memfs In-memory filesystem with Node's API

  • vscode-drawio This unofficial extension integrates Draw.io (also known as diagrams.net) into VS Code.

  • http-proxy-middleware ⚡ The one-liner node.js http-proxy middleware for connect, express, next.js and more

  • hyper A terminal built on web technologies

  • graphql-let A webpack loader / babel-plugin / babel-plugin-macros / CLI / generated file manager of GraphQL code generator.

  • react-image-crop A responsive image cropping tool for React

  • react-hook-form 📋 React Hooks for form state management and validation (Web + React Native)

  • serverless-next.js ⚡ Deploy your Next.js apps on AWS Lambda@Edge via Serverless Components

  • destiny Prettier for File Structures

  • vite Next generation frontend tooling. It's fast!

  • vugel Vue3 fast webgl/canvas renderer based on the tree2d framework

  • react-page Next-gen, highly customizable content editor for the browser - based on React and written in TypeScript. WYSIWYG on steroids.

  • koa-typeorm-starter Starter project for using koa with TS and TypeORM

  • grpc-node gRPC for Node.js

  • path-to-regexp Turn a path string such as /user/:name into a regular expression

  • react-codemirror2 Codemirror integrated components for React

  • midway 🍔 A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate with React and Vue. 🌈

  • source-map-explorer Analyze and debug space usage through source maps

  • zignis-plugin-read-extend-format-wechat A plugin of zignis-plugin-read to provide wechat editor format

  • react-use React Hooks — 👍

  • outline The fastest wiki and knowledge base for growing teams. Beautiful, feature rich, and markdown compatible.

  • tfjs A WebGL accelerated JavaScript library for training and deploying ML models.

  • vscode-restclient REST Client Extension for Visual Studio Code

  • wenyan 文言文編程語言 A programming language for the ancient Chinese.

  • routing-controllers Create structured, declarative and beautifully organized class-based controllers with heavy decorators usage in Express / Koa using TypeScript and Routing Controllers Framework.

  • graphql-editor 📺 Visual Editor & GraphQL IDE. Draw GraphQL schemas using visual 🔷 nodes and explore GraphQL API with beautiful UI. Even 🐒 can do that!

  • swr React Hooks for Data Fetching

  • svelte Cybernetically enhanced web apps

  • G6 ♾ A Graph Visualization Framework in JavaScript

  • checkout Action for checking out a repo

  • starter-workflows Accelerating new GitHub Actions workflows

  • artipub Article publishing platform that automatically distributes your articles to various media channels

  • star-history The missing star history graph of GitHub repos - https://star-history.com

  • remax 使用真正的 React 构建跨平台小程序

  • apollo-tooling ✏️ Tooling for development and production Apollo workflows

  • ant-design-mobile Essential UI blocks for building mobile web apps.

  • fluentui Fluent UI web represents a collection of utilities, React components, and web components for building web applications.

  • wechaty Conversational RPA SDK for Chatbot Makers

  • formily Alibaba Group Unified Form Solution -- Support React/ReactNative/Vue2/Vue3

  • react-apollo-hooks Use Apollo Client as React hooks

  • qiankun 📦 🚀 Blazing fast, simple and complete solution for micro frontends.

  • verdaccio 📦🔐 A lightweight Node.js private proxy registry

  • taro-ui 一款基于 Taro 框架开发的多端 UI 组件库

  • npm-check-updates Find newer versions of package dependencies than what your package.json allows

  • node-redlock A node.js redlock implementation for distributed, highly-available redis locks

  • cls-proxify Logging on steroids with CLS and Proxy. Integrated with express, koa, fastify.

  • apollo-link-persisted-queries Persisted Query support with Apollo Link

  • typeorm ORM for TypeScript and JavaScript (ES7, ES6, ES5). Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, SAP Hana, WebSQL databases. Works in NodeJS, Browser, Ionic, Cordova and Electron platforms.

  • formatjs The monorepo home to all of the FormatJS related libraries, most notably react-intl.

  • notadd A microservice development architecture based on nest.js. —— 基于 Nest.js 的微服务开发架构。

  • typescript-book 📚 The definitive guide to TypeScript and possibly the best TypeScript book 📖. Free and Open Source 🌹

  • recast JavaScript syntax tree transformer, nondestructive pretty-printer, and automatic source map generator

  • typescript-eslint ✨ Monorepo for all the tooling which enables ESLint to support TypeScript

  • graphql-compose Toolkit for generating complex GraphQL Schemas on Node.js

  • sequelize-typescript Decorators and some other features for sequelize

  • Typescript-restful-starter Node.js + ExpressJS + Joi + Typeorm + Typescript + JWT + ES2015 + Clustering + Tslint + Mocha + Chai

  • graphql-inspector 🕵️‍♀️ Validate schema, get schema change notifications, validate operations, find breaking changes, look for similar types, schema coverage

  • class-validator Decorator-based property validation for classes.

  • type-graphql Create GraphQL schema and resolvers with TypeScript, using classes and decorators!

  • react-avatar-editor Small avatar & profile picture component. Resize and crop uploaded images using a intuitive user interface.

  • ms Tiny millisecond conversion utility

  • ts-node TypeScript execution and REPL for node.js

  • ts-node-dev Compiles your TS app and restarts when files are modified.

  • taro 开放式跨端跨框架解决方案,支持使用 React/Vue/Nerv 等框架来开发微信/京东/百度/支付宝/字节跳动/ QQ 小程序/H5/React Native 等应用。 https://taro.zone/

  • graphql-rate-limit Add Rate Limiting To Your GraphQL Resolvers 💂‍♀️

  • firekylin A Simple & Fast Node.js Blogging Platform Base On ThinkJS3 & React & ES2015+

  • grafana The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.

  • react-styleguidist Isolated React component development environment with a living style guide

  • ajv The fastest JSON schema Validator. Supports JSON Schema draft-04/06/07/2019-09/2020-12 and JSON Type Definition (RFC8927)

  • qrcode.react A component for use with React.

  • ext-saladict 🥗 All-in-one professional pop-up dictionary and page translator which supports multiple search modes, page translations, new word notebook and PDF selection searching.

  • postgraphile Execute one command (or mount one Node.js middleware) and get an instant high-performance GraphQL API for your PostgreSQL database!

  • subscriptions-transport-ws 🔃 A WebSocket client + server for GraphQL subscriptions

  • playground Play with neural networks!

  • graphql-cli 📟 Command line tool for common GraphQL development workflows

  • node-decorators node-decorators

  • howtographql The Fullstack Tutorial for GraphQL

  • elasticsearch-js Official Elasticsearch client library for Node.js

  • graphiql GraphiQL & the GraphQL LSP Reference Ecosystem for building browser & IDE tools.

  • apollo-fetch 🐶 Lightweight GraphQL client that supports middleware and afterware

  • graphql-tag A JavaScript template literal tag that parses GraphQL queries

  • graphql-subscriptions 📰 A small module that implements GraphQL subscriptions for Node.js

  • electron-react-boilerplate A Foundation for Scalable Cross-Platform Apps

  • node-lru-cache

  • reactql Universal React+GraphQL starter kit: React 16, Apollo 2, MobX, Emotion, Webpack 4, GraphQL Code Generator, React Router 4, PostCSS, SSR

  • react-starter-kit The web's most popular Jamstack front-end template (boilerplate) for building web applications with React

  • apollo-link-rest Use existing REST endpoints with GraphQL

  • graphql-playground 🎮 GraphQL IDE for better development workflows (GraphQL Subscriptions, interactive docs & collaboration)

  • apollo-client-devtools Apollo Client browser developer tools.

  • apollo-cache-persist 🎏 Simple persistence for all Apollo Cache implementations

  • vercel Develop. Preview. Ship.

  • graphql-yoga 🧘 Rewrite of a fully-featured GraphQL Server with focus on easy setup, performance & great developer experience. The core of Yoga implements W3C Fetch API and can run/deploy on any JS environment.

  • graphql-prisma-typescript 🏡 GraphQL server reference implementation (Airbnb clone) in Typescript using Prisma & graphql-yoga

  • apollo-link-state ✨ Manage your application's state with Apollo!

  • graphql-relay-js A library to help construct a graphql-js server supporting react-relay.

  • graphql-lodash 🛠 Data manipulation for GraphQL queries with lodash syntax

  • graphdoc Static page generator for documenting GraphQL Schema

  • apollo-link 🔗 Interface for fetching and modifying control flow of GraphQL requests

  • sequelize-auto Automatically generate bare sequelize models from your database.

  • apollo-client 🚀 A fully-featured, production ready caching GraphQL client for every UI framework and GraphQL server.

  • kibana Your window into the Elastic Stack

  • koa-graphql Create a GraphQL HTTP server with Koa.

  • relay-starter-kit 💥 Monorepo template (seed project) pre-configured with GraphQL API, PostgreSQL, React, Relay, Material UI.

  • graphql-tools 🔧 Build, mock, and stitch a GraphQL schema using the schema language

  • apollo-server 🌍 Spec-compliant and production ready JavaScript GraphQL server that lets you develop in a schema-first way. Built for Express, Connect, Hapi, Koa, and more.

  • ali-mns The nodejs sdk for aliyun mqs service

  • sentry-javascript Official Sentry SDKs for JavaScript. We're hiring https://grnh.se/ca81c1701us

  • Steward A command launcher for Chrome

  • puppeteer Headless Chrome Node.js API

  • BizCharts Powerful data visualization library based on G2 and React.

  • graphql-js A reference implementation of GraphQL for JavaScript

  • G2 📊 A highly interactive data-driven visualization grammar for statistical charts.

  • bottender ⚡️ A framework for building conversational user interfaces.

  • node-xlsx NodeJS excel file parser & builder

  • socket.io Realtime application framework (Node.JS server)

  • html2canvas Screenshots with JavaScript

  • ant-design-pro 👨🏻‍💻👩🏻‍💻 Use Ant Design like a Pro!

  • Vim ⭐ Vim for Visual Studio Code

  • jest Delightful JavaScript Testing.

  • ioredis 🚀 A robust, performance-focused, and full-featured Redis client for Node.js.

  • fingerprintjs Browser fingerprinting library with the highest accuracy and stability.

  • ga-dev-tools A showcase of demos and tools built with the various Google Analytics APIs and Libraries.

  • recharts Redefined chart library built with React and D3

  • styled-components Visual primitives for the component age. Use the best bits of ES6 and CSS to style your apps without stress 💅

  • vue 🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • developer-roadmap Roadmap to becoming a developer in 2022

  • esprima ECMAScript parsing infrastructure for multipurpose analysis

  • create-react-native-app Create React Native apps that run on iOS, Android, and web

  • mitt 🥊 Tiny 200 byte functional event emitter / pubsub.

  • react-router Declarative routing for React

  • postcss Transforming styles with JS plugins

  • freeCodeCamp freeCodeCamp.org's open-source codebase and curriculum. Learn to code for free.

  • rxjs A reactive programming library for JavaScript

  • storybook 📓 The UI component explorer. Develop, document, & test React, Vue, Angular, Web Components, Ember, Svelte & more!

  • mobx Simple, scalable state management.

  • redux-devtools DevTools for Redux with hot reloading, action replay, and customizable UI

  • redux Predictable state container for JavaScript apps

  • ionic-framework A powerful cross-platform UI toolkit for building native-quality iOS, Android, and Progressive Web Apps with HTML, CSS, and JavaScript.

  • zrender A lightweight graphic library providing 2d draw for Apache ECharts

  • echarts Apache ECharts is a powerful, interactive charting and data visualization library for browser

  • highcharts Highcharts JS, the JavaScript charting framework

CSS

PHP

  • web-frameworks Which is the fastest web framework?

  • howto-make-more-money 程序员如何优雅的挣零花钱,2.0版,升级为小书了。Most of this not work outside China , so no English translate

  • flarum Simple forum software for building great communities.

  • recaptcha PHP client library for reCAPTCHA, a free service to protect your website from spam and abuse.

Swift

  • uPic 📤uPic is a native, powerful, beautiful and simple picture and file upload tool for macOS.

  • Material A UI/UX framework for creating beautiful applications.

Java

  • hutool 🍬A set of tools that keep Java sweet.

  • closure-compiler A JavaScript checker and optimizer.

  • npm-stat.com download statistics for npm packages

  • Auto.js A UiAutomator on android, does not need root access(安卓平台上的JavaScript自动化工具)

  • system-design-interviews

  • vhr 微人事是一个前后端分离的人力资源管理系统,项目采用SpringBoot+Vue开发。

  • miaosha ⭐⭐⭐⭐秒杀系统设计与实现.互联网工程师进阶与分析🙋🐓

  • redisson Redisson - Redis Java client with features of In-Memory Data Grid. Over 50 Redis based Java objects and services: Set, Multimap, SortedSet, Map, List, Queue, Deque, Semaphore, Lock, AtomicLong, Map Reduce, Publish / Subscribe, Bloom filter, Spring Cache, Tomcat, Scheduler, JCache API, Hibernate, MyBatis, RPC, local cache ...

  • advanced-java 😮 Core Interview Questions & Answers For Experienced Java(Backend) Developers | 互联网 Java 工程师进阶知识完全扫盲:涵盖高并发、分布式、高可用、微服务、海量数据处理等领域知识

  • mysql-binlog-connector-java MySQL Binary Log connector

  • learning-spark Example code from Learning Spark book

  • kafka Mirror of Apache Kafka

  • canal 阿里巴巴 MySQL binlog 增量订阅&消费组件

  • UsageStatsSample Sample of Android's UsageStats Manager.

  • elasticsearch Free and Open, Distributed, RESTful Search Engine

  • graphql-java GraphQL Java implementation

  • android-demos Examples of Android applications

  • wechat_jump_hack 腾讯微信跳一跳破解(目前最高19844分)

  • APIJSON 🚀 零代码、全功能、强安全 ORM 库,后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构。 🚀 A JSON Transmission Protocol and an ORM Library for providing APIs and Docs without writing any code.

  • Android Android related examples

  • rest-assured Java DSL for easy testing of REST services

  • material A library to bring fully animated Material Design components to pre-Lolipop Android.

  • volley

  • retrofit A type-safe HTTP client for Android and the JVM

  • android-interview-questions Your Cheat Sheet For Android Interview - Android Interview Questions

  • AndroidStudio AndroidStudio中文入门指南

  • zipkin Zipkin is a distributed tracing system

  • interviews Everything you need to know to get the job.

  • interviews Everything you need to know to get the job.

  • banya An open resource for Douban API with NETEASY Music's UI.

Ruby

  • jekyll 🌐 Jekyll is a blog-aware static site generator in Ruby

  • nodejs-learning-guide Nodejs学习笔记以及经验总结,公众号"程序猿小卡"

  • graphql-docs Easily generate beautiful documentation from your GraphQL schema.

  • devdocs API Documentation Browser

  • gitlabhq GitLab CE Mirror | Please open new issues in our issue tracker on GitLab.com

  • Home-Network-Note 🚧 持续更新 🚧 记录搭建兼顾学习娱乐的家用网络环境的过程,折腾过的一些软硬件小经验。

  • logstash Logstash - transport and process your logs, events, or other data

  • puppet Server automation framework and application

  • dotenv A Ruby gem to load environment variables from .env.

  • brew 🍺 The missing package manager for macOS (or Linux)

  • linguist Language Savant. If your repository's language is being reported incorrectly, send us a pull request!

Jupyter Notebook

HTML

Jinja

Emacs Lisp

  • know-your-http-well HTTP headers, media-types, methods, relations and status codes, all summarized and linking to their specification.

SCSS

  • cheatsheets My cheatsheets

  • hacker101 Source code for Hacker101.com - a free online web and mobile security class.

Markdown

Elixir

  • phoenix Peace of mind from prototype to production

C#

  • BiliBiliTool 基于 .Net 5 的B站(哔哩哔哩)任务工具,实现每日自动运行任务:如每日自动登录、观看、分享、投币视频,获取每日任务的满额经验,轻松升级Level 6;如定时自动领取大会员权益、月底自动为自己充电;如天选时刻抽奖等功能。

Kotlin

  • LibChecker An app to view libraries used in apps in your device.

  • gitstar-ranking GitHub star ranking for users, organizations and repositories

  • apollo-kotlin 🤖 A strongly-typed, caching GraphQL client for the JVM, Android, and Kotlin multiplatform.

  • kotlin-playground Kotlin practice

Haskell

  • graphql-engine Blazing fast, instant realtime GraphQL APIs on your DB with fine grained access control, also trigger webhooks on database events.

CoffeeScript

  • yaml.js Standalone JavaScript YAML 1.2 Parser & Encoder. Works under node.js and all major browsers. Also brings command line YAML/JSON conversion tools.

  • camo 🔒 an http proxy to route images through SSL

  • gulp-connect Gulp plugin to run a webserver (with LiveReload)

CMake

Dockerfile

  • redis Docker Official Image packaging for Redis

  • ssh-action GitHub Actions for executing remote ssh commands.

  • docker-node Official Docker Image for Node.js 🐳 🐢 🚀

  • nocode The best way to write secure and reliable applications. Write nothing; deploy nowhere.

  • docker-scrapy Docker image for Scrapy

  • python Docker Official Image packaging for Python

Vim script

Rich Text Format

  • mqtt MQTT协议3.1.1中文翻译版,IoT,物联网

Mustache

  • harbor-helm The helm chart to deploy Harbor

  • charts Bitnami Helm Charts

  • charts Localized Helm charts from Helm Hub to China

Dart

  • flutter-todos 📝 one day list app created by flutter!

  • developer_quest Respository for the I/O 2019 demo: Become a tech lead, slay bugs, and don't get fired.

  • flutter_wanandroid 🔥🔥🔥 基于Google Flutter的WanAndroid客户端,支持Android和iOS。包括BLoC、RxDart 、国际化、主题色、启动页、引导页!

  • flutter-plugins A collection of Flutter plugins developed by CACHET

  • fluro Fluro is a Flutter routing library that adds flexible routing options like wildcards, named parameters and clear route definitions.

  • judou judou app implement by flutter

  • awesome-flutter An awesome list that curates the best Flutter libraries, tools, tutorials, articles and more.

  • graphql-flutter A GraphQL client for Flutter, bringing all the features from a modern GraphQL client to one easy to use package.

  • fish-redux An assembled flutter application framework.

  • flutter-go flutter 开发者帮助 APP,包含 flutter 常用 140+ 组件的demo 演示与中文文档

  • flutter Flutter makes it easy and fast to build beautiful apps for mobile and beyond

Assembly

  • Apollo-11 Original Apollo 11 Guidance Computer (AGC) source code for the command and lunar modules.

Scala

  • akka Build highly concurrent, distributed, and resilient message-driven applications on the JVM

  • BinlogUpdatetoHive mysql数据实时增量导入hive

  • spark Apache Spark - A unified analytics engine for large-scale data processing

  • prisma1 💾 Database Tools incl. ORM, Migrations and Admin UI (Postgres, MySQL & MongoDB)

TeX

Perl

  • MySQLTuner-perl MySQLTuner is a script written in Perl that will assist you with your MySQL configuration and make recommendations for increased performance and stability.

OCaml

  • reason Simple, fast & type safe code that leverages the JavaScript & OCaml ecosystems

Objective-C

  • expo An open-source platform for making universal native apps with React. Expo runs on Android, iOS, and the web.

  • StarApp

  • iTerm2 iTerm2 is a terminal emulator for Mac OS X that does amazing things.

Makefile

VimL

  • ctrlp.vim Fuzzy file, buffer, mru, tag, etc finder.

  • spf13-vim The ultimate vim distribution

Nunjucks

Rascal

  • hosts 🗽最新可用的google hosts文件。国内镜像:

Batchfile

  • python-guide Python best practices guidebook, written for humans.

License

MIT

leetcode's People

Contributors

huangguangda avatar webvueblog avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

leetcode's Issues

30. javascript中的作用域是什么

范围是在运行时代码的某些特定部分中的变量、函数和对象的可访问性。换句话说,范围决定了代码区域中变量和其他资源的可见性。

【深入理解JS核心技术】1.在 JavaScript 中创建对象的可能方式有哪些?

创建对象的方式:

  1. 创建空对象,可以使用Object构造函数。(对象构造函数)
var object = new Object();
  1. 可以使用Object的create方法通过将原型对象作为参数来创建一个新对象
var object = Object.create(null);
  1. 可以使用对象字面量语法。(这是创建对象最简单的方法)
var object = {}
  1. 函数构造函数,创建任何函数并使用new运算符来创建对象实例
function Person (name) {
 this.name = name;
 this.age = 18;
}
var object = new Person('哪吒');
  1. 带有原型的函数构造函数,类似于函数构造函数,但它使用原型作为它们的属性和方法
function Person() {
}
Person.prototype.name = '哪吒';
var object = new Person();
  1. es6语法:类特性来创建对象
class Person {
 constructor(name) {
  this.name = name;
 }
}

var object = new Person('哪吒');
  1. 单例模式

Singleton 是一个只能被实例化一次的对象。对其构造函数的重复调用返回相同的实例,这样可以确保它们不会意外创建多个实例。

var object = new (function() {
 this.name = '哪吒';
})();

未完结!更多内容尽情期待下一节~

【深入理解JS核心技术】欢迎各位观众老爷,求点赞,求关注,求转发~

206. 反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
 
示例 1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

示例 2:
输入:head = [1,2]
输出:[2,1]

示例 3:
输入:head = []
输出:[]
 
提示:

链表中节点的数目范围是 [0, 5000]
-5000 <= Node.val <= 5000

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 206. 反转链表
 迭代的方法

      pre = null
      cur = head
(指向空节点)
 pre  cur(指向当前的头节点)
 null  1 -> 2 -> 3 -> 4 -> 5 -> null
 pre = null
 cur = head
 当当前节点不为空的时候

 pre    cur  next
 null <- 1   2     ->  3 -> 4 -> 5 -> null
 next = current.next
 cur.next = pre

            cur
       pre  next
null <- 1    2   ->  3 -> 4 -> 5 -> null
 pre = cur
 cur = next
 next = current.next
 cur.next = pre
 
 直到cur节点为空 return pre
 */
var reverseList = function(head) {
    let pre = null, cur = head, next
    // 判断当前指针
    while(cur) {
        next = cur.next
        cur.next = pre
        pre = cur
        cur = next
    }
    return pre
};

【深入理解JS核心技术】14. 什么是一元函数

一元函数(即monadic)是一个只接受一个参数的函数。它代表函数接受的单个参数。

const unaryFunction = (a) => console.log(a + 10); // Add 10 to the given argument and display the value

参数扩展与收集

ECMAScript 6 新增了扩展操作符,使用它可以非常简洁地操作和组合集合数据。扩展操作符最有用的场景就是函数定义中的参数列表,在这里它可以充分利用这门语言的弱类型及参数长度可变的特点。扩展操作符既可以用于调用函数时传参,也可以用于定义函数参数。

  1. 扩展参数
function getProduct(a, b, c = 1) { 
 return a * b * c; 
} 

let getSum = (a, b, c = 0) => { 
 return a + b + c; 
} 

console.log(getProduct(...[1,2])); // 2 
console.log(getProduct(...[1,2,3])); // 6 
console.log(getProduct(...[1,2,3,4])); // 6 

console.log(getSum(...[0,1])); // 1 
console.log(getSum(...[0,1,2])); // 3 
console.log(getSum(...[0,1,2,3])); // 3
  1. 收集参数

在构思函数定义时,可以使用扩展操作法把不同长度的独立参数组合为一个数组。

function getSum(...values) {
 // 顺序累加values中的所有值
 // 初始值的总和为0
 return values.reduce((x,y) => x + y, 0);
}
console.log(getSum(1,2,3)); // 6
// 不可以
function getProduct(...values, lastValue) {} 

// 可以
function ignoreFirst(firstValue, ...values) { 
 console.log(values); 
} 

ignoreFirst(); // [] 
ignoreFirst(1); // [] 
ignoreFirst(1,2); // [2] 
ignoreFirst(1,2,3); // [2, 3]
let getSum = (...values) => { 
 return values.reduce((x, y) => x + y, 0); 
} 

console.log(getSum(1,2,3)); // 6
function getSum(...values) { 
 console.log(arguments.length); // 3 
 console.log(arguments); // [1, 2, 3] 
 console.log(values); // [1, 2, 3] 
} 

console.log(getSum(1,2,3));

未完结!更多内容尽情期待下一节~

【深入理解JS核心技术】欢迎各位观众老爷,求点赞,求关注,求转发~

29. 为什么需要模块

以下是在 javascript 生态系统中使用模块的好处列表

可维护性

可重用性

命名空间

【深入理解JS核心技术】21. 什么是暂时性死区

暂时性死区是 JavaScript 中的一种行为,当使用 let 和 const 关键字声明变量时发生,但不使用 var。在 ECMAScript 6 中,在声明之前(在其范围内)访问letor变量会导致 ReferenceError。

function somemethod() {
  console.log(counter1); // undefined
  console.log(counter2); // ReferenceError
  var counter1 = 1;
  let counter2 = 2;
}

复习内容:

数组的解构赋值

ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。

ES6 允许写成下面这样。

let [a, b, c] = [1, 2, 3];

本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。

let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3

let [ , , third] = ["foo", "bar", "baz"];
third // "baz"

let [x, , y] = [1, 2, 3];
x // 1
y // 3

let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]

let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []

如果解构不成功,变量的值就等于undefined。

let [foo] = [];
let [bar, foo] = [1];

以上两种情况都属于解构不成功,foo的值都会等于undefined。

另一种情况是不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。

let [x, y] = [1, 2, 3];
x // 1
y // 2

let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4

上面两个例子,都属于不完全解构,但是可以成功。

如果等号的右边不是数组,那么将会报错。

// 报错
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};

上面的语句都会报错,因为等号右边的值,要么转为对象以后不具备 Iterator 接口,要么本身就不具备 Iterator 接口。

对于 Set 结构,也可以使用数组的解构赋值。

let [x, y, z] = new Set(['a', 'b', 'c']);
x // "a"

事实上,只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值。

数组的默认值

解构赋值允许指定默认值。

let [foo = true] = [];
foo // true

let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'

注意,ES6 内部使用严格相等运算符(===),判断一个位置是否有值。所以,只有当一个数组成员严格等于undefined,默认值才会生效。

let [x = 1] = [undefined];
x // 1

let [x = 1] = [null];
x // null

上面代码中,如果一个数组成员是null,默认值就不会生效,因为null不严格等于undefined。

如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值。

function f() {
  console.log('aaa');
}

let [x = f()] = [1];

上面代码中,因为x能取到值,所以函数f根本不会执行。上面的代码其实等价于下面的代码。

let x;
if ([1][0] === undefined) {
  x = f();
} else {
  x = [1][0];
}

默认值可以引用解构赋值的其他变量,但该变量必须已经声明。

let [x = 1, y = x] = [];     // x=1; y=1
let [x = 1, y = x] = [2];    // x=2; y=2
let [x = 1, y = x] = [1, 2]; // x=1; y=2
let [x = y, y = 1] = [];     // ReferenceError: y is not defined

上面最后一个表达式之所以会报错,是因为x用y做默认值时,y还没有声明。

未完结!更多内容尽情期待下一节~

【深入理解JS核心技术】欢迎各位观众老爷,求点赞,求关注,求转发~

202. 快乐数

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」 定义为:

对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。

示例 1:
输入:n = 19
输出:true

解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1

示例 2:
输入:n = 2
输出:false
 
提示:

1 <= n <= 231 - 1

/**
 * @param {number} n
 * @return {boolean}
 202. 快乐数
 当它最后是快乐数,最后是能够得到1这个值的
 1这个值就相当链表的尾节点就一个空节点
 如果不是快乐数 说明它会一直的去 在某一个数
 通过计算之后会等于前面的某一个数
 这样的话它才会 一直的循环下去形成闭环
 */
var isHappy = function(n) {
    // 1 <= n so 等于1的话它就是一个快乐数
    if(n === 1) return true
    // 首先我们定义一个map结构
    let mapper = new Map()
    let p = getNext(n)
    // 如果p不等于1的话
    while(p !== 1) {
        if(!mapper.has(p)) {
            mapper.set(p, p)
            p = getNext(p)
        } else {
            return false
        }
    }
    // 跳出循环的时候
    // 走到了最后一个节点也就是为1
    return true
};

// 获取它的下一个节点
var getNext = function(n) {
    let sum = 0
    while(n) {
        // 计算的话我们从它的末位开始去 平方和相加
        sum += (n % 10) ** 2 // 9
        // 把n赋值为就是把它的个位数砍掉
        n = Math.floor(n / 10) // 1
    }
    // 计算完成之后,我们再把和返回出来
    return sum
}

【深入理解JS核心技术】15. 什么是柯里化函数

柯里化是将具有多个参数的函数转换为一系列函数的过程,每个函数只有一个参数。Currying 以数学家Haskell Curry的名字命名。通过应用柯里化,n 元函数将其转换为一元函数。

让我们举一个 n 元函数的例子,以及它是如何变成柯里化函数的,

const multiArgFunction = (a, b, c) => a + b + c;
console.log(multiArgFunction(1, 2, 3)); // 6

const curryUnaryFunction = (a) => (b) => (c) => a + b + c;
curryUnaryFunction(1); // returns a function: b => c =>  1 + b + c
curryUnaryFunction(1)(2); // returns a function: c => 3 + c
curryUnaryFunction(1)(2)(3); // returns the number 6

Curried 函数对于提高代码的可重用性函数组合非常有用。

函数声明与函数表达式

JavaScript引擎在任何代码执行之前,会先读取函数声明,并在执行上下文中生成函数定义。而函数表达式必须等到代码执行到它那一行,才会在执行上下文中生成函数定义。

// 没问题
console.log(sum(10, 10));
function sum(num1, num2) {
 return num1 + num2;
}

代码可以正常运行,因为函数声明会在任何代码执行之前先被读取并添加到执行上下文。这个过程叫作函数声明提升

在执行代码时,JavaScript引擎会先执行一遍扫描,把发现的函数声明提升到源代码树的顶部。因此即使函数定义出现在调用它们的代码之后,引擎也会把函数声明提升到顶部。

改为函数表达式就回出错:

// 会出错
console.log(sum(10, 10));
let sum = function(num1, num2) {
 return num1 + num2;
};

函数作为值

因为函数名在ECMAScript中就是变量,所以函数可以用在任何可以使用变量的地方。这意味着不仅可以把函数作为参数传给另一个函数,而且还可以在一个函数中返回另一个函数。

函数表达式

定义函数有两种方式:函数声明和函数表达式。

函数声明:

function functionName(arg0, arg1, arg2) {
 // 函数体
}

函数声明的关键特点是函数声明提升,即函数声明会在代码执行之前获得定义。这意味着函数声明可以出现在调用它的代码之后:

sayHi();
function sayHi() {
 console.log('Hi!');
}

因为 JavaScript 引擎会先读取函数声明,然后再执行代码。

第二种创建函数的方式就是函数表达式。

let functionName = function(arg0, arg1, arg2) {
 // 函数体
}

函数表达式看起来就像一个普通的变量定义和赋值,即创建一个函数再把它赋值给一个变量functionName。

这样创建的函数叫作匿名函数(anonymous funtion),因为 function 关键字后面没有标识符。(匿名函数有也时候也被称为兰姆达函数)。

任何时候,只要函数被当作值来使用,它就是一个函数表达式。

未完结!更多内容尽情期待下一节~

【深入理解JS核心技术】欢迎各位观众老爷,求点赞,求关注,求转发~

【深入理解JS核心技术】2. 什么是原型链?

原型链是用于在现有对象的基础上构建新类型的对象。它类似于基于类的语言中的继承。

对象实例的原型可以通过 Obeject.getPrototypeOf(object) 或 __proto__ 属性获得,而构造函数的原型可通过 Object.prototype 获得。

构造函数,原型,实例的关系:

每个构造函数都有一个原型对象,原型有一个属性指回构造函数,而实例有一个内部指针指向原型。

原型链的基本**:(如果原型是另一个类型的实例?)原型当成实例。

意味着这个原型本身有一个内部指针指向另一个原型,相应地另一个原型也有一个指针指向另一个构造函数。这样就在实例和原型之间构造了一条原型链。

function SuperType() {
 this.property = true;
}

SuperType.prototype.getSuperValue = function() {
 return this.property;
};

function SubType() {
 this.subproperty = false;
}

// 继承SuperType
SubType.prototype = new SuperType();

SubType.prototype.getSubValue = function() {
 return this.subproperty;
};

let instance = new SubType();
console.log(instance.getSuperValue()); // true
  1. 默认原型

默认情况下,所有引用类型都继承自Object,这也是通过原型链实现的。任何函数的默认原型都是一个Object的实例,这意味着这个实例有一个内部指针指向Object.prototype。

  1. 原型与继承关系

原型与实例的关系可以通过两种方式来确定。第一种方式是使用instanceof操作符,如果一个实例的原型链中出现过相应的构造函数,则instanceof返回true。

确定关系的第二种方式是使用 isPrototypeOf() 方法。原型链中的每个原型都可以调用这个方法。

// 只要原型链中包含这个原型,这个方法就返回true
console.log(Object.prototype.isPrototypeOf(instance)); // true
  1. 关于方法

子类有时候需要覆盖父类的方法,或者增加父类没有的方法。这些方法必须在原型赋值之后再添加到原型上。

function SuperType() {
 this.property = true;
}

SuperType.prototype.getSuperValue = function() {
 return this.property;
}

function SubType() {
 this.subproperty = false;
}

// 继承SuperType
SubType.prototype = new SuperType();

// 新方法
SubType.prototype.getSubValue = function() {
 return this.subproperty;
};

// 覆盖已有的方法
SubType.prototype.getSuperValue = function() {
 return false;
};

let instance = new SubType();
console.log(instance.getSuperValue()); // false

重点:如果以对象字面量方式创建原型方法会破坏之前的原型链,因为这相当于重写了原型链。

function SuperType() {
 this.property = true;
}

SuperType.prototype.getSuperValue = function() {
 return this.property;
};

function SubType() {
 this.subproperty = false;
};

// 继承SuperType
SubType.prototype = new SuperType();

// 通过对象字面量添加新方法,这会导致上一行无效
SubType.prototype = {
 getSubValue() {
  return this.subproperty;
 },
 someOtherMethod() {
  return false;
 }
};

let instance = new SubType();
console.log(instance.getSuperValue()); // 出错
  1. 原型链的问题

原型中包含的引用值会在所有实例间共享,这也是为什么属性通常会在构造函数中定义而不会在原型上的原因。

在使用原型实现继承时,原型实际上变成了另一个类型的实例。(原先的实例属性变成了原型属性)

function SuperType() {
 this.colors = ["red", "blue", "green"];
}

function SubType() { }

// 继承SuperType
SubType.prototype = new SuperType();

let instance1 = new SubType();
instance1.colors.push('black');
console.log(instance1.colors); // "red,blue,green,black"

let instance2 = new SubType();
console.log(instance2.colors); // "red,blue,green,black"

原型链的第二个问题:子类型在实例化时不能给父类型的构造函数传参。

未完结!更多内容尽情期待下一节~

【深入理解JS核心技术】欢迎各位观众老爷,求点赞,求关注,求转发~

33. 如何在 service worker 重启时重用信息

Service Worker 的问题在于它在不使用时被终止,并在下次需要时重新启动,因此您不能依赖 Service Workeronfetch和onmessage处理程序中的全局状态。在这种情况下,服务工作者将有权访问 IndexedDB API,以便在重新启动时保持和重用。

【深入理解JS核心技术】3. 调用、应用和绑定有什么区别

call, apply, bind之间的区别:

  1. call()方法调用一个给定this值和参数一一提供的函数。
var person = { lastName: '哪吒' };

function invite(greeting1, greeting2) {
 console.log(
  greeting1 + " " + this.lastName + " " + greeting2
 );
}

invite.call(person, "Hello", "How are you"); // Hello  哪吒 How are you
  1. apply()使用给定值调用函数this并允许您将参数座位数组传递

apply()方法会接收两个参数:函数内this的值和一个参数数组。第二个参数可以是Array的实例,也可以是arguments对象。

function sum(num1, num2) {
 return num1 + num2;
}

function callSum1(num1, num2) {
 return sum.apply(this, arguments); // 传入arguments对象
}

function callSum2(num1, num2) {
 return sum.apply(this, [num1, num2]); // 传入数组
}

console.log(callSum1(10, 10)); // 20
console.log(callSum2(10, 10)); // 20
var person = { lastName: '哪吒' };

function invite(greeting1, greeting2) {
 console.log(
  greeting1 + " " + this.lastName + " " + greeting2
 );
}

invite.apply(person, ["Hello", "How are you"]); // Hello  哪吒 How are you
  1. bind()返回一个新函数,允许您传递任何数量的参数

ES5定义一个新方法:bind()。(bind方法会创建一个新的函数实例,其this值会被绑定到传给bind()的对象)

var employee1 = { firstName: "a", lastName: "aa" };
var employee2 = { firstName: "d", lastName: "dd" };

function invite(greeting1, greeting2) {
  console.log(
    greeting1 + " " + this.firstName + " " + this.lastName + ", " + greeting2
  );
}

var inviteEmployee1 = invite.bind(employee1); // 创建新函数并绑定对象
var inviteEmployee2 = invite.bind(employee2);
inviteEmployee1("Hello", "How are you?"); // Hello a aa, How are you?
inviteEmployee2("Hello", "How are you?"); // Hello d dd, How are you?

call和apply是可以互换的,两者都立即执行当前函数。您需要决定是否更容易发送数组或逗号分隔的参数列表。而bind创建一个新的函数,该函数将this设置为传递给bind()的第一个参数。

call()和apply()方法都会以指定的this值来调用函数,即会设置调用函数时函数体内this对象的值。call()和apply()真正强大的地方并不是给函数传参,而是控制函数 调用上下文 即函数体内this值的能力。

window.color = "red";
let o = {
 color: 'blue'
};

function sayColor() {
 console.log(this.color);
}

sayColor(); // red

sayColor.call(this); // red
sayColor.call(window); // red
sayColor.call(o); // blue

使用call()和apply()的好处是可以将任意对象设置为任意函数的作用域,这样对象可以不用关心方法。

未完结!更多内容尽情期待下一节~

【深入理解JS核心技术】欢迎各位观众老爷,求点赞,求关注,求转发~

31. 什么是service worker?

Service Worker 基本上是一个在后台运行的脚本(JavaScript 文件),独立于网页并提供不需要网页或用户交互的功能。Service Worker 的一些主要功能是丰富的离线体验(离线优先 Web 应用程序开发)、定期后台同步、推送通知、拦截和处理网络请求以及以编程方式管理响应缓存。

24.什么是memoization

记忆化是一种编程技术,它试图通过缓存以前计算的结果来提高函数的性能。每次调用 memoized 函数时,它的参数都用于索引缓存。如果数据存在,则可以将其返回,而无需执行整个函数。否则执行该函数,然后将结果添加到缓存中。让我们举一个使用 memoization 添加函数的例子

const memoizAddition = () => {
  let cache = {};
  return (value) => {
    if (value in cache) {
      console.log("Fetching from cache");
      return cache[value]; // Here, cache.value cannot be used as property name starts with the number which is not a valid JavaScript  identifier. Hence, can only be accessed using the square bracket notation.
    } else {
      console.log("Calculating result");
      let result = value + 20;
      cache[value] = result;
      return result;
    }
  };
};
// returned function from memoizAddition
const addition = memoizAddition();
console.log(addition(20)); //output: 40 calculated
console.log(addition(20)); //output: 40 cached

141. 环形链表

  1. 环形链表

给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

如果链表中存在环 ,则返回 true 。 否则,返回 false 。

示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。

示例 2:
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。

示例 3:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。

提示:

链表中节点的数目范围是 [0, 104]
-105 <= Node.val <= 105
pos 为 -1 或者链表中的一个 有效索引 。

思路:暴力破解法:

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }

 */

/**
 * @param {ListNode} head
 * @return {boolean}
 */
var hasCycle = function(head) {
    // 首先判断一下这个链表是不是空的链表或者是这个链表是不是只有一个节点
    // 如果是的话那就说明他不可能构成环形链表
    // 我们直接给他返回false
    if(!head || head.next === null) return false
    // 否则的话呢,我们就从头指针开始然后遍历一下 遍历一下这个链表
    let p = head
    const arr = []
    while(p !== null) {
        // 我们判断一下这个数组里面是否已经 包含了这个节点
        if(!arr.includes(p)) {
            // 如果不包含的话呢我们就 把这个节点直接push到这个数组里面
            arr.push(p)
            // 然后把 p 指针指向下一个节点
            p = p.next
        } else {
            // 否则如果这个节点已经在数组里面出现了
            // 就说明他是第二次走到了这里
            // 那么我们返回true
            // 说明他有 他是一个环形链表
            return true;
        }
    }
    // 否则的话呢
    // 就是说明他走到了末尾的空节点 
    // 那么我们就返回了false
    return false
};

期约与异步函数

  1. 异步编程
  2. 期约
  3. 异步函数

ECMAScript 6 新增了正式的 Promise(期约)引用类型

异步编程

异步行为是为了优化因计算量大而时间长的操作。

同步与异步

同步行为对应内存中顺序执行的处理器指令。每条指令都会严格按照它们出现的顺序来执行,而每条指令执行后也能立即获得存储在系统本地(如寄存器或系统内存)的信息。这样的执行流程容易分析程序在执行到代码任意位置时的状态(比如变量的值)。

以往的异步编程模式

俗称“回调地狱”

function double(value) {
 setTimeout(() => setTimeout(console.log, 0, value * 2), 1000);
}
double(3);
// 6(大约 1000 毫秒之后)
  1. 异步返回值

给异步操作提供一个回调,这个回调中包含要使用异步返回值的代码(作为回调的参数)

function double(value, callback) {
 setTimeout(() => callback(value * 2), 1000);
}
double(3, (x) => console.log(`I was given: ${x}`));
// I was given: 6(大约 1000 毫秒之后)
  1. 失败处理
  2. 嵌套异步回调

期约

Promises/A+规范

期约基础

ECMAScript 6 新增的引用类型 Promise,可以通过 new 操作符来实例化。

let p = new Promise(() => {});
setTimeout(console.log, 0, p); // Promise <pending>

之所以说是应付解释器,是因为如果不提供执行器函数,就会抛出 SyntaxError。

  1. 期约状态机

期约是一个有状态的对象,可能处于如下 3 种状态之一:

  1. 待定(pending)
  2. 兑现(fulfilled,有时候也称为“解决”,resolved)
  3. 拒绝(rejected)

待定(pending)是期约的最初始状态。在待定状态下,期约可以落定(settled)为代表成功的兑现(fulfilled)状态,或者代表失败的拒绝(rejected)状态。无论落定为哪种状态都是不可逆的。只要从待定转换为兑现或拒绝,期约的状态就不再改变。而且,也不能保证期约必然会脱离待定状态。因此,组织合理的代码无论期约解决(resolve)还是拒绝(reject),甚至永远处于待定(pending)状态,都应该具有恰当的行为。

期约的状态是私有的,不能直接通过 JavaScript 检测到。这主要是为了避免根据读取到的期约状态,以同步方式处理期约对象。另外,期约的状态也不能被外部 JavaScript 代码修改。

  1. 解决值、拒绝理由及期约用例

期约主要有两大用途。首先是抽象地表示一个异步操作。期约的状态代表期约是否完成。“待定”表示尚未开始或者正在执行中。“兑现”表示已经成功完成,而“拒绝”则表示没有成功完成。

每个期约只要状态切换为兑现,就会有一个私有的内部值(value)。类似地,每个期约只要状态切换为拒绝,就会有一个私有的内部理由(reason)。无论是值还是理由,都是包含原始值或对象的不可修改的引用。

  1. 通过执行函数控制期约状态

内部操作在期约的执行器函数中完成。

主要有两项职责:初始化期约的异步行为和控制状态的最终转换。

控制期约状态的转换是通过调用它的两个函数参数实现的。这两个函数参数通常都命名为 resolve()和 reject()。调用resolve()会把状态切换为兑现,调用 reject()会把状态切换为拒绝。另外,调用 reject()也会抛出错误

let p1 = new Promise((resolve, reject) => resolve());
setTimeout(console.log, 0, p1); // Promise <resolved>
let p2 = new Promise((resolve, reject) => reject());
setTimeout(console.log, 0, p2); // Promise <rejected>
// Uncaught error (in promise)

Promise {<fulfilled>: undefined}

执行器函数是同步执行的。这是因为执行器函数是期约的初始化程序

new Promise(() => setTimeout(console.log, 0, 'executor'));
setTimeout(console.log, 0, 'promise initialized');
// executor
// promise initialized

添加 setTimeout 可以推迟切换状态

let p = new Promise((resolve, reject) => setTimeout(resolve, 1000));
// 在 console.log 打印期约实例的时候,还不会执行超时回调(即 resolve())
setTimeout(console.log, 0, p); // Promise <pending> 
let p = new Promise((resolve, reject) => setTimeout(resolve, 1000));
// 在 console.log 打印期约实例的时候,还不会执行超时回调(即 resolve())
setTimeout(console.log, 0, p); // Promise <pending> 

Promise {<pending>}[[Prototype]]: Promise[[PromiseState]]: "fulfilled"[[PromiseResult]]: undefined

为避免期约卡在待定状态,可以添加一个定时退出功能。

let p = new Promise((resolve, reject) => {
 setTimeout(reject, 10000); // 10 秒后调用 reject()
 // 执行函数的逻辑
});
setTimeout(console.log, 0, p); // Promise <pending>
setTimeout(console.log, 11000, p); // 11 秒后再检查状态
// (After 10 seconds) Uncaught error
// (After 11 seconds) Promise <rejected>
  1. Promise.resolve()
let p1 = new Promise((resolve, reject) => resolve());
let p2 = Promise.resolve();
setTimeout(console.log, 0, Promise.resolve());
// Promise <resolved>: undefined
setTimeout(console.log, 0, Promise.resolve(3));
// Promise <resolved>: 3
// 多余的参数会忽略
setTimeout(console.log, 0, Promise.resolve(4, 5, 6));
// Promise <resolved>: 4 

对这个静态方法而言,如果传入的参数本身是一个期约,那它的行为就类似于一个空包装。因此,Promise.resolve()可以说是一个幂等方法

let p = Promise.resolve(7);
setTimeout(console.log, 0, p === Promise.resolve(p));
// true
setTimeout(console.log, 0, p === Promise.resolve(Promise.resolve(p)));
// true

这个幂等性会保留传入期约的状态:

let p = new Promise(() => {});
setTimeout(console.log, 0, p); // Promise <pending>
setTimeout(console.log, 0, Promise.resolve(p)); // Promise <pending>
setTimeout(console.log, 0, p === Promise.resolve(p)); // true
let p = Promise.resolve(new Error('foo'));
setTimeout(console.log, 0, p);
// Promise <resolved>: Error: foo
  1. Promise.reject()
let p1 = new Promise((resolve, reject) => reject());
let p2 = Promise.reject();
let p = Promise.reject(3);
setTimeout(console.log, 0, p); // Promise <rejected>: 3
p.then(null, (e) => setTimeout(console.log, 0, e)); // 3 

如果给它传一个期约对象,则这个期约会成为它返回的拒绝期约的理由:

setTimeout(console.log, 0, Promise.reject(Promise.resolve()));
// Promise <rejected>: Promise <resolved> 
  1. 同步/异步执行的二元性
try {
 throw new Error('foo');
} catch(e) {
 console.log(e); // Error: foo
}
try {
 Promise.reject(new Error('bar'));
} catch(e) {
 console.log(e);
}
// Uncaught (in promise) Error: bar

第一个 try/catch 抛出并捕获了错误,第二个 try/catch 抛出错误却没有捕获到。

期约的实例方法

期约实例的方法是连接外部同步代码与内部异步代码之间的桥梁。

  1. 实现 Thenable 接口

在 ECMAScript 暴露的异步结构中,任何对象都有一个 then()方法。这个方法被认为实现了Thenable 接口。

class MyThenable {
 then() {}
}

ECMAScript 的 Promise 类型实现了 Thenable 接口。

  1. Promise.prototype.then()

Promise.prototype.then()是为期约实例添加处理程序的主要方法。这个 then()方法接收最多两个参数:onResolved 处理程序和 onRejected 处理程序。

function onResolved(id) {
 setTimeout(console.log, 0, id, 'resolved');
}
function onRejected(id) {
 setTimeout(console.log, 0, id, 'rejected');
}
let p1 = new Promise((resolve, reject) => setTimeout(resolve, 3000));
let p2 = new Promise((resolve, reject) => setTimeout(reject, 3000));
p1.then(() => onResolved('p1'),
 () => onRejected('p1'));
p2.then(() => onResolved('p2'),
 () => onRejected('p2'));
//(3 秒后)
// p1 resolved
// p2 rejected
function onResolved(id) {
 setTimeout(console.log, 0, id, 'resolved');
}
function onRejected(id) {
 setTimeout(console.log, 0, id, 'rejected');
}
let p1 = new Promise((resolve, reject) => setTimeout(resolve, 3000));
let p2 = new Promise((resolve, reject) => setTimeout(reject, 3000));
// 非函数处理程序会被静默忽略,不推荐
p1.then('gobbeltygook');
// 不传 onResolved 处理程序的规范写法
p2.then(null, () => onRejected('p2'));
// p2 rejected(3 秒后)

Promise.prototype.then()方法返回一个新的期约实例:

let p1 = new Promise(() => {});
let p2 = p1.then();
setTimeout(console.log, 0, p1); // Promise <pending>
setTimeout(console.log, 0, p2); // Promise <pending>
setTimeout(console.log, 0, p1 === p2); // false 
let p1 = Promise.resolve('foo');
// 若调用 then()时不传处理程序,则原样向后传
let p2 = p1.then(); 
setTimeout(console.log, 0, p2); // Promise <resolved>: foo
// 这些都一样
let p3 = p1.then(() => undefined);
let p4 = p1.then(() => {});
let p5 = p1.then(() => Promise.resolve());
setTimeout(console.log, 0, p3); // Promise <resolved>: undefined
setTimeout(console.log, 0, p4); // Promise <resolved>: undefined
setTimeout(console.log, 0, p5); // Promise <resolved>: undefined 
// 这些都一样
let p6 = p1.then(() => 'bar');
let p7 = p1.then(() => Promise.resolve('bar'));
setTimeout(console.log, 0, p6); // Promise <resolved>: bar
setTimeout(console.log, 0, p7); // Promise <resolved>: bar
// Promise.resolve()保留返回的期约
let p8 = p1.then(() => new Promise(() => {}));
let p9 = p1.then(() => Promise.reject());
// Uncaught (in promise): undefined
setTimeout(console.log, 0, p8); // Promise <pending>
setTimeout(console.log, 0, p9); // Promise <rejected>: undefined

抛出异常会返回拒绝的期约

let p10 = p1.then(() => { throw 'baz'; });
// Uncaught (in promise) baz
setTimeout(console.log, 0, p10); // Promise <rejected> baz

注意,返回错误值不会触发上面的拒绝行为,而会把错误对象包装在一个解决的期约中

let p11 = p1.then(() => Error('qux'));
setTimeout(console.log, 0, p11); // Promise <resolved>: Error: qux

onRejected 处理程序的任务不就是捕获异步错误吗?因此,拒绝处理程序在捕获错误后不抛出异常是符合期约的行为,应该返回一个解决期约。

let p1 = Promise.reject('foo');
// 调用 then()时不传处理程序则原样向后传
let p2 = p1.then();
// Uncaught (in promise) foo

setTimeout(console.log, 0, p2); // Promise <rejected>: foo
// 这些都一样
let p3 = p1.then(null, () => undefined);
let p4 = p1.then(null, () => {});
let p5 = p1.then(null, () => Promise.resolve());
setTimeout(console.log, 0, p3); // Promise <resolved>: undefined
setTimeout(console.log, 0, p4); // Promise <resolved>: undefined
setTimeout(console.log, 0, p5); // Promise <resolved>: undefined
// 这些都一样
let p6 = p1.then(null, () => 'bar');
let p7 = p1.then(null, () => Promise.resolve('bar'));
setTimeout(console.log, 0, p6); // Promise <resolved>: bar
setTimeout(console.log, 0, p7); // Promise <resolved>: bar
// Promise.resolve()保留返回的期约
let p8 = p1.then(null, () => new Promise(() => {}));
let p9 = p1.then(null, () => Promise.reject());
// Uncaught (in promise): undefined
setTimeout(console.log, 0, p8); // Promise <pending>
setTimeout(console.log, 0, p9); // Promise <rejected>: undefined
let p10 = p1.then(null, () => { throw 'baz'; });
// Uncaught (in promise) baz
setTimeout(console.log, 0, p10); // Promise <rejected>: baz
let p11 = p1.then(null, () => Error('qux'));
setTimeout(console.log, 0, p11); // Promise <resolved>: Error: qux 
  1. Promise.prototype.catch()

Promise.prototype.catch()方法用于给期约添加拒绝处理程序。这个方法只接收一个参数:onRejected 处理程序。事实上,这个方法就是一个语法糖,调用它就相当于调用 Promise.prototype.then(null, onRejected)。

let p = Promise.reject();
let onRejected = function(e) {
 setTimeout(console.log, 0, 'rejected');
};
// 这两种添加拒绝处理程序的方式是一样的:
p.then(null, onRejected); // rejected
p.catch(onRejected); // rejected

Promise.prototype.catch()返回一个新的期约实例:

let p1 = new Promise(() => {});
let p2 = p1.catch();
setTimeout(console.log, 0, p1); // Promise <pending>
setTimeout(console.log, 0, p2); // Promise <pending>
setTimeout(console.log, 0, p1 === p2); // false
  1. Promise.prototype.finally()

Promise.prototype.finally()方法用于给期约添加 onFinally 处理程序,这个处理程序在期约转换为解决或拒绝状态时都会执行。这个方法可以避免 onResolved 和 onRejected 处理程序中出现冗余代码。但 onFinally 处理程序没有办法知道期约的状态是解决还是拒绝,所以这个方法主要用于添加清理代码。

let p1 = Promise.resolve();
let p2 = Promise.reject();
let onFinally = function() {
 setTimeout(console.log, 0, 'Finally!')
}
p1.finally(onFinally); // Finally
p2.finally(onFinally); // Finally

Promise.prototype.finally()方法返回一个新的期约实例:

let p1 = new Promise(() => {});
let p2 = p1.finally();
setTimeout(console.log, 0, p1); // Promise <pending>
setTimeout(console.log, 0, p2); // Promise <pending>
setTimeout(console.log, 0, p1 === p2); // false 

在大多数情况下它将表现为父期约的传递。对于已解决状态和被拒绝状态都是如此。

let p1 = Promise.resolve('foo');
// 这里都会原样后传
let p2 = p1.finally();
let p3 = p1.finally(() => undefined);
let p4 = p1.finally(() => {});
let p5 = p1.finally(() => Promise.resolve());
let p6 = p1.finally(() => 'bar');
let p7 = p1.finally(() => Promise.resolve('bar'));
let p8 = p1.finally(() => Error('qux'));
setTimeout(console.log, 0, p2); // Promise <resolved>: foo
setTimeout(console.log, 0, p3); // Promise <resolved>: foo
setTimeout(console.log, 0, p4); // Promise <resolved>: foo
setTimeout(console.log, 0, p5); // Promise <resolved>: foo
setTimeout(console.log, 0, p6); // Promise <resolved>: foo
setTimeout(console.log, 0, p7); // Promise <resolved>: foo
setTimeout(console.log, 0, p8); // Promise <resolved>: foo
// Promise.resolve()保留返回的期约
let p9 = p1.finally(() => new Promise(() => {}));
let p10 = p1.finally(() => Promise.reject());
// Uncaught (in promise): undefined
setTimeout(console.log, 0, p9); // Promise <pending>
setTimeout(console.log, 0, p10); // Promise <rejected>: undefined
let p11 = p1.finally(() => { throw 'baz'; });
// Uncaught (in promise) baz
setTimeout(console.log, 0, p11); // Promise <rejected>: baz

返回待定期约的情形并不常见,这是因为只要期约一解决,新期约仍然会原样后传初始的期约:

let p1 = Promise.resolve('foo');
// 忽略解决的值
let p2 = p1.finally(
 () => new Promise((resolve, reject) => setTimeout(() => resolve('bar'), 100)));
setTimeout(console.log, 0, p2); // Promise <pending>
setTimeout(() => setTimeout(console.log, 0, p2), 200);
// 200 毫秒后:
// Promise <resolved>: foo
  1. 非重入期约方法

当期约进入落定状态时,与该状态相关的处理程序仅仅会被排期,而非立即执行。跟在添加这个处
理程序的代码之后的同步代码一定会在处理程序之前先执行。即使期约一开始就是与附加处理程序关联
的状态,执行顺序也是这样的。这个特性由 JavaScript 运行时保证,被称为“非重入”(non-reentrancy)
特性。

// 创建解决的期约
let p = Promise.resolve();
// 添加解决处理程序
// 直觉上,这个处理程序会等期约一解决就执行
p.then(() => console.log('onResolved handler'));
// 同步输出,证明 then()已经返回
console.log('then() returns');
// 实际的输出:
// then() returns
// onResolved handler
let synchronousResolve;
// 创建一个期约并将解决函数保存在一个局部变量中
let p = new Promise((resolve) => {
 synchronousResolve = function() {
 console.log('1: invoking resolve()');
 resolve();
 console.log('2: resolve() returns');
 };
});
p.then(() => console.log('4: then() handler executes'));
synchronousResolve();
console.log('3: synchronousResolve() returns');
// 实际的输出:
// 1: invoking resolve()
// 2: resolve() returns
// 3: synchronousResolve() returns
// 4: then() handler executes

非重入适用于 onResolved/onRejected 处理程序、catch()处理程序和 finally()处理程序。

let p1 = Promise.resolve();
p1.then(() => console.log('p1.then() onResolved'));
console.log('p1.then() returns');
let p2 = Promise.reject();
p2.then(null, () => console.log('p2.then() onRejected'));
console.log('p2.then() returns');
let p3 = Promise.reject();
p3.catch(() => console.log('p3.catch() onRejected'));
console.log('p3.catch() returns');
let p4 = Promise.resolve();
p4.finally(() => console.log('p4.finally() onFinally'));
console.log('p4.finally() returns');
// p1.then() returns
// p2.then() returns
// p3.catch() returns
// p4.finally() returns
// p1.then() onResolved
// p2.then() onRejected
// p3.catch() onRejected
// p4.finally() onFinally 
  1. 邻近处理程序的执行顺序
let p1 = Promise.resolve();
let p2 = Promise.reject();
p1.then(() => setTimeout(console.log, 0, 1));
p1.then(() => setTimeout(console.log, 0, 2));
// 1
// 2
p2.then(null, () => setTimeout(console.log, 0, 3));
p2.then(null, () => setTimeout(console.log, 0, 4));
// 3
// 4
p2.catch(() => setTimeout(console.log, 0, 5));
p2.catch(() => setTimeout(console.log, 0, 6));
// 5
// 6
p1.finally(() => setTimeout(console.log, 0, 7));
p1.finally(() => setTimeout(console.log, 0, 8));
// 7
// 8 
  1. 传递解决值和拒绝理由

在执行函数中,解决的值和拒绝的理由是分别作为 resolve()和 reject()的第一个参数往后传的

这些值又会传给它们各自的处理程序,作为 onResolved 或 onRejected 处理程序的唯一参数。

let p1 = new Promise((resolve, reject) => resolve('foo'));
p1.then((value) => console.log(value)); // foo
let p2 = new Promise((resolve, reject) => reject('bar'));
p2.catch((reason) => console.log(reason)); // bar 

Promise.resolve()和 Promise.reject()在被调用时就会接收解决值和拒绝理由。它们返回的期约也会像执行器一样把这些值传给 onResolved 或 onRejected 处理程序

let p1 = Promise.resolve('foo');
p1.then((value) => console.log(value)); // foo
let p2 = Promise.reject('bar');
p2.catch((reason) => console.log(reason)); // bar
  1. 拒绝期约与拒绝错误处理

拒绝期约类似于 throw()表达式,因为它们都代表一种程序状态,即需要中断或者特殊处理

let p1 = new Promise((resolve, reject) => reject(Error('foo')));
let p2 = new Promise((resolve, reject) => { throw Error('foo'); });
let p3 = Promise.resolve().then(() => { throw Error('foo'); });
let p4 = Promise.reject(Error('foo'));
setTimeout(console.log, 0, p1); // Promise <rejected>: Error: foo
setTimeout(console.log, 0, p2); // Promise <rejected>: Error: foo
setTimeout(console.log, 0, p3); // Promise <rejected>: Error: foo
setTimeout(console.log, 0, p4); // Promise <rejected>: Error: foo
// 也会抛出 4 个未捕获错误

抛出的 4 个错误的栈追踪信息如下:

Uncaught (in promise) Error: foo
 at Promise (test.html:5)
 at new Promise (<anonymous>)
 at test.html:5
Uncaught (in promise) Error: foo
 at Promise (test.html:6)
 at new Promise (<anonymous>)
 at test.html:6
Uncaught (in promise) Error: foo
 at test.html:8
Uncaught (in promise) Error: foo
 at Promise.resolve.then (test.html:7)

所有错误都是异步抛出且未处理的,通过错误对象捕获的栈追踪信息展示了错误发生的路径。

throw Error('foo');
console.log('bar'); // 这一行不会执行
// Uncaught Error: foo

但是,在期约中抛出错误时,因为错误实际上是从消息队列中异步抛出的,所以并不会阻止运行时
继续执行同步指令:

Promise.reject(Error('foo'));
console.log('bar');
// bar
// Uncaught (in promise) Error: foo 

异步错误只能通过异步的 onRejected 处理程序
捕获:

// 正确
Promise.reject(Error('foo')).catch((e) => {});
// 不正确

try {
 Promise.reject(Error('foo'));
} catch(e) {}

可以使用 try/catch 在执行函数
中捕获错误:

let p = new Promise((resolve, reject) => {
 try {
 throw Error('foo');
 } catch(e) {}
 resolve('bar');
});
setTimeout(console.log, 0, p); // Promise <resolved>: bar

then()和 catch()的 onRejected 处理程序在语义上相当于 try/catch。

console.log('begin synchronous execution');
try {
 throw Error('foo');
} catch(e) {
 console.log('caught error', e);
}
console.log('continue synchronous execution');
// begin synchronous execution
// caught error Error: foo
// continue synchronous execution
new Promise((resolve, reject) => {
 console.log('begin asynchronous execution');
 reject(Error('bar'));
}).catch((e) => {
 console.log('caught error', e);
}).then(() => {
 console.log('continue asynchronous execution');
});
// begin asynchronous execution
// caught error Error: bar
// continue asynchronous execution

期约连锁与期约合成

前者就是一个期约接一个期约地拼接,后者则是将多个期约组合为一个期约。

  1. 期约连锁

把期约逐个地串联起来是一种非常有用的编程模式

“期约连锁”

let p = new Promise((resolve, reject) => {
 console.log('first');
 resolve();
});
p.then(() => console.log('second'))
 .then(() => console.log('third'))
 .then(() => console.log('fourth'));
// first
// second
// third
// fourth

使用 4 个同步函数也可以做到:

(() => console.log('first'))();
(() => console.log('second'))();
(() => console.log('third'))();
(() => console.log('fourth'))(); 

串行化异步任务

let p1 = new Promise((resolve, reject) => {
 console.log('p1 executor');
 setTimeout(resolve, 1000);
});
p1.then(() => new Promise((resolve, reject) => {
 console.log('p2 executor');
 setTimeout(resolve, 1000);
 }))
 .then(() => new Promise((resolve, reject) => {
 console.log('p3 executor');
 setTimeout(resolve, 1000);
 }))
 .then(() => new Promise((resolve, reject) => {
 console.log('p4 executor');
 setTimeout(resolve, 1000);
 }));
// p1 executor(1 秒后)
// p2 executor(2 秒后)
// p3 executor(3 秒后)
// p4 executor(4 秒后)

把生成期约的代码提取到一个工厂函数中

function delayedResolve(str) {
 return new Promise((resolve, reject) => {
 console.log(str);
 setTimeout(resolve, 1000);
 });
}

delayedResolve('p1 executor')
 .then(() => delayedResolve('p2 executor'))
 .then(() => delayedResolve('p3 executor'))
 .then(() => delayedResolve('p4 executor'))
// p1 executor(1 秒后)
// p2 executor(2 秒后)
// p3 executor(3 秒后)
// p4 executor(4 秒后)

每个后续的处理程序都会等待前一个期约解决,然后实例化一个新期约并返回它。

因为 then()、catch()和 finally()都返回期约

let p = new Promise((resolve, reject) => {
 console.log('initial promise rejects');
 reject();
});
p.catch(() => console.log('reject handler'))
 .then(() => console.log('resolve handler'))
 .finally(() => console.log('finally handler'));
// initial promise rejects
// reject handler
// resolve handler
// finally handler
  1. 期约图

因为一个期约可以有任意多个处理程序,所以期约连锁可以构建有向非循环图的结构。

这样,每个期约都是图中的一个节点,而使用实例方法添加的处理程序则是有向顶点。因为图中的每个节点都会等待前一个节点落定,所以图的方向就是期约的解决或拒绝顺序。

展示了一种期约有向图,也就是二叉树:

// A
// / \
// B C
// /\ /\
// D E F G
let A = new Promise((resolve, reject) => {
 console.log('A');
 resolve();
});
let B = A.then(() => console.log('B'));
let C = A.then(() => console.log('C'));
B.then(() => console.log('D'));
B.then(() => console.log('E'));
C.then(() => console.log('F'));
C.then(() => console.log('G'));
// A
// B
// C
// D
// E
// F
// G

树只是期约图的一种形式。考虑到根节点不一定唯一,且多个期约也可以组合成一个期约

  1. Promise.all()和 Promise.race()

Promise 类提供两个将多个期约实例组合成一个期约的静态方法:Promise.all()和 Promise.race()。而合成后期约的行为取决于内部期约的行为。

Promise.all()

Promise.all()静态方法创建的期约会在一组期约全部解决之后再解决。这个静态方法接收一个
可迭代对象,返回一个新期约:

let p1 = Promise.all([
 Promise.resolve(),
 Promise.resolve()
]);
// 可迭代对象中的元素会通过 Promise.resolve()转换为期约
let p2 = Promise.all([3, 4]);
// 空的可迭代对象等价于 Promise.resolve()
let p3 = Promise.all([]);
// 无效的语法
let p4 = Promise.all();
// TypeError: cannot read Symbol.iterator of undefined

合成的期约只会在每个包含的期约都解决之后才解决

let p = Promise.all([
 Promise.resolve(),
 new Promise((resolve, reject) => setTimeout(resolve, 1000))
]);
setTimeout(console.log, 0, p); // Promise <pending>
p.then(() => setTimeout(console.log, 0, 'all() resolved!'));
// all() resolved!(大约 1 秒后)

如果至少有一个包含的期约待定,则合成的期约也会待定。如果有一个包含的期约拒绝,则合成的期约也会拒绝:

// 永远待定
let p1 = Promise.all([new Promise(() => {})]);
setTimeout(console.log, 0, p1); // Promise <pending>
// 一次拒绝会导致最终期约拒绝
let p2 = Promise.all([
 Promise.resolve(),
 Promise.reject(),
 Promise.resolve()
]);
setTimeout(console.log, 0, p2); // Promise <rejected>
// Uncaught (in promise) undefined 

如果所有期约都成功解决,则合成期约的解决值就是所有包含期约解决值的数组,按照迭代器顺序

let p = Promise.all([
 Promise.resolve(3),
 Promise.resolve(),
 Promise.resolve(4)
]);
p.then((values) => setTimeout(console.log, 0, values)); // [3, undefined, 4]

如果有期约拒绝,则第一个拒绝的期约会将自己的理由作为合成期约的拒绝理由。之后再拒绝的期
约不会影响最终期约的拒绝理由。不过,这并不影响所有包含期约正常的拒绝操作。合成的期约会静默
处理所有包含期约的拒绝操作,如下所示:

// 虽然只有第一个期约的拒绝理由会进入
// 拒绝处理程序,第二个期约的拒绝也
// 会被静默处理,不会有错误跑掉
let p = Promise.all([
 Promise.reject(3),
 new Promise((resolve, reject) => setTimeout(reject, 1000))
]);
p.catch((reason) => setTimeout(console.log, 0, reason)); // 3
// 没有未处理的错误

Promise.race()

Promise.race()静态方法返回一个包装期约,是一组集合中最先解决或拒绝的期约的镜像。这个
方法接收一个可迭代对象,返回一个新期约:

let p1 = Promise.race([
 Promise.resolve(),
 Promise.resolve()
]);
// 可迭代对象中的元素会通过 Promise.resolve()转换为期约
let p2 = Promise.race([3, 4]);
// 空的可迭代对象等价于 new Promise(() => {})
let p3 = Promise.race([]);
// 无效的语法
let p4 = Promise.race();
// TypeError: cannot read Symbol.iterator of undefined

Promise.race()不会对解决或拒绝的期约区别对待。无论是解决还是拒绝,只要是第一个落定的
期约,Promise.race()就会包装其解决值或拒绝理由并返回新期约:

// 解决先发生,超时后的拒绝被忽略
let p1 = Promise.race([
 Promise.resolve(3),
 new Promise((resolve, reject) => setTimeout(reject, 1000))
]);
setTimeout(console.log, 0, p1); // Promise <resolved>: 3
// 拒绝先发生,超时后的解决被忽略
let p2 = Promise.race([
 Promise.reject(4),
 new Promise((resolve, reject) => setTimeout(resolve, 1000))
]);
setTimeout(console.log, 0, p2); // Promise <rejected>: 4
// 迭代顺序决定了落定顺序
let p3 = Promise.race([
 Promise.resolve(5),
 Promise.resolve(6),
 Promise.resolve(7)
]);
setTimeout(console.log, 0, p3); // Promise <resolved>: 5 

如果有一个期约拒绝,只要它是第一个落定的,就会成为拒绝合成期约的理由。之后再拒绝的期约
不会影响最终期约的拒绝理由。不过,这并不影响所有包含期约正常的拒绝操作。与 Promise.all()
类似,合成的期约会静默处理所有包含期约的拒绝操作,如下所示:

// 虽然只有第一个期约的拒绝理由会进入
// 拒绝处理程序,第二个期约的拒绝也
// 会被静默处理,不会有错误跑掉
let p = Promise.race([
 Promise.reject(3),
 new Promise((resolve, reject) => setTimeout(reject, 1000))
]);
p.catch((reason) => setTimeout(console.log, 0, reason)); // 3
// 没有未处理的错误
  1. 串行期约合成

基于后续期约使用之前期约的返回值来串联期约是期约的基本功能。这

function addTwo(x) {return x + 2;}
function addThree(x) {return x + 3;}
function addFive(x) {return x + 5;}
function addTen(x) {
 return addFive(addTwo(addThree(x)));
}
console.log(addTen(7)); // 17
function addTwo(x) {return x + 2;}
function addThree(x) {return x + 3;}
function addFive(x) {return x + 5;}
function addTen(x) {
 return Promise.resolve(x)
 .then(addTwo)
 .then(addThree)
 .then(addFive);
}
addTen(8).then(console.log); // 18
使用 Array.prototype.reduce()可以写成更简洁的形式:
function addTwo(x) {return x + 2;}
function addThree(x) {return x + 3;}
function addFive(x) {return x + 5;}
function addTen(x) {
 return [addTwo, addThree, addFive]
 .reduce((promise, fn) => promise.then(fn), Promise.resolve(x));
}
addTen(8).then(console.log); // 18

使用 Array.prototype.reduce()

function addTwo(x) {return x + 2;}
function addThree(x) {return x + 3;}
function addFive(x) {return x + 5;}
function addTen(x) {
 return [addTwo, addThree, addFive]
 .reduce((promise, fn) => promise.then(fn), Promise.resolve(x));
}
addTen(8).then(console.log); // 18 
function addTwo(x) {return x + 2;}
function addThree(x) {return x + 3;}
function addFive(x) {return x + 5;}
function compose(...fns) {
 return (x) => fns.reduce((promise, fn) => promise.then(fn), Promise.resolve(x))
}
let addTen = compose(addTwo, addThree, addFive);
addTen(8).then(console.log); // 18

期约扩展

实际上,可以在现有实现基础上提供一种临时性的封装,以实现取消期约的功能。

class CancelToken {
 constructor(cancelFn) {
 this.promise = new Promise((resolve, reject) => {
 cancelFn(resolve);
 });
 }
} 

这个类包装了一个期约,把解决方法暴露给了 cancelFn 参数。这样,外部代码就可以向构造函数
中传入一个函数,从而控制什么情况下可以取消期约。这里期约是令牌类的公共成员,因此可以给它添
加处理程序以取消期约。

<button id="start">Start</button>
<button id="cancel">Cancel</button>
<script>
class CancelToken {
 constructor(cancelFn) {
 this.promise = new Promise((resolve, reject) => {
 cancelFn(() => {
 setTimeout(console.log, 0, "delay cancelled");
 resolve();
 });
 });
 }
}
const startButton = document.querySelector('#start');
const cancelButton = document.querySelector('#cancel');
function cancellableDelayedResolve(delay) {
 setTimeout(console.log, 0, "set delay");
 return new Promise((resolve, reject) => {
 const id = setTimeout((() => {
 setTimeout(console.log, 0, "delayed resolve");
 resolve();
 }), delay); 
const cancelToken = new CancelToken((cancelCallback) =>
 cancelButton.addEventListener("click", cancelCallback));
 cancelToken.promise.then(() => clearTimeout(id));
 });
}
startButton.addEventListener("click", () => cancellableDelayedResolve(1000));
</script>

每次单击“Start”按钮都会开始计时,并实例化一个新的 CancelToken 的实例。此时,“Cancel”
按钮一旦被点击,就会触发令牌实例中的期约解决。而解决之后,单击“Start”按钮设置的超时也会被
取消。

  1. 期约进度通知

执行中的期约可能会有不少离散的“阶段”,在最终解决之前必须依次经过。某些情况下,监控期
约的执行进度会很有用。ECMAScript 6 期约并不支持进度追踪,但是可以通过扩展来实现。
一种实现方式是扩展 Promise 类,为它添加 notify()方法,如下所示:

class TrackablePromise extends Promise {
 constructor(executor) {
 const notifyHandlers = [];
 super((resolve, reject) => {
 return executor(resolve, reject, (status) => {
 notifyHandlers.map((handler) => handler(status));
 });
 });
 this.notifyHandlers = notifyHandlers;
 }
 notify(notifyHandler) {
 this.notifyHandlers.push(notifyHandler);
 return this;
 }
} 

这样,TrackablePromise 就可以在执行函数中使用 notify()函数了。可以像下面这样使用这个
函数来实例化一个期约:

let p = new TrackablePromise((resolve, reject, notify) => {
 function countdown(x) {
 if (x > 0) {
 notify(`${20 * x}% remaining`);
 setTimeout(() => countdown(x - 1), 1000);
 } else {
 resolve();
 }
 }
 countdown(5);
});

这个期约会连续5次递归地设置1000毫秒的超时。每个超时回调都会调用notify()并传入状态值。
假设通知处理程序简单地这样写:

let p = new TrackablePromise((resolve, reject, notify) => {
 function countdown(x) {
 if (x > 0) {
 notify(`${20 * x}% remaining`);
 setTimeout(() => countdown(x - 1), 1000);
 } else {
 resolve();
 }
 }
 countdown(5);
});
p.notify((x) => setTimeout(console.log, 0, 'progress:', x));
p.then(() => setTimeout(console.log, 0, 'completed'));
// (约 1 秒后)80% remaining
// (约 2 秒后)60% remaining
// (约 3 秒后)40% remaining
// (约 4 秒后)20% remaining
// (约 5 秒后)completed 

notify()函数会返回期约,所以可以连缀调用,连续添加处理程序。多个处理程序会针对收到的
每条消息分别执行一遍,如下所示:

p.notify((x) => setTimeout(console.log, 0, 'a:', x))
.notify((x) => setTimeout(console.log, 0, 'b:', x));
p.then(() => setTimeout(console.log, 0, 'completed'));
// (约 1 秒后) a: 80% remaining
// (约 1 秒后) b: 80% remaining
// (约 2 秒后) a: 60% remaining
// (约 2 秒后) b: 60% remaining
// (约 3 秒后) a: 40% remaining
// (约 3 秒后) b: 40% remaining
// (约 4 秒后) a: 20% remaining
// (约 4 秒后) b: 20% remaining
// (约 5 秒后) completed

总体来看,这还是一个比较粗糙的实现,但应该可以演示出如何使用通知报告进度了。

异步函数

异步函数,也称为“async/await”(语法关键字),是 ES6 期约模式在 ECMAScript 函数中的应用。
async/await 是 ES8 规范新增的。这个特性从行为和语法上都增强了 JavaScript,让以同步方式写的代码
能够异步执行

let p = new Promise((resolve, reject) => setTimeout(resolve, 1000, 3));

let p = new Promise((resolve, reject) => setTimeout(resolve, 1000, 3));
p.then((x) => console.log(x)); // 3

function handler(x) { console.log(x); }
let p = new Promise((resolve, reject) => setTimeout(resolve, 1000, 3));
p.then(handler); // 3

异步函数

ES8 的 async/await 旨在解决利用异步结构组织代码的问题。

  1. async

async 关键字用于声明异步函数。这个关键字可以用在函数声明、函数表达式、箭头函数和方法上:

async function foo() {}
let bar = async function() {};
let baz = async () => {};
class Qux {
 async qux() {}
}

使用 async 关键字可以让函数具有异步特征,但总体上其代码仍然是同步求值的。而在参数或闭
包方面,异步函数仍然具有普通 JavaScript 函数的正常行为。正如下面的例子所示,foo()函数仍然会
在后面的指令之前被求值:

async function foo() {
 console.log(1);
}
foo();
console.log(2);
// 1
// 2

不过,异步函数如果使用 return 关键字返回了值(如果没有 return 则会返回 undefined),这
个值会被 Promise.resolve()包装成一个期约对象。异步函数始终返回期约对象。

async function foo() {
 console.log(1);
 return 3;
}
// 给返回的期约添加一个解决处理程序
foo().then(console.log);
console.log(2);
// 1
// 2
// 3
当然,直接返回一个期约对象也是一样的:
async function foo() {
 console.log(1);
 return Promise.resolve(3);
}
// 给返回的期约添加一个解决处理程序
foo().then(console.log);
console.log(2);
// 1
// 2
// 3 

异步函数的返回值期待(但实际上并不要求)一个实现 thenable 接口的对象,但常规的值也可以。
如果返回的是实现 thenable 接口的对象,则这个对象可以由提供给 then()的处理程序“解包”。如果
不是,则返回值就被当作已经解决的期约。

// 返回一个原始值
async function foo() {
 return 'foo';
}
foo().then(console.log);
// foo
// 返回一个没有实现 thenable 接口的对象
async function bar() {
 return ['bar'];
}
bar().then(console.log);
// ['bar']
// 返回一个实现了 thenable 接口的非期约对象
async function baz() {
 const thenable = {
 then(callback) { callback('baz'); }
 };
 return thenable;
}
baz().then(console.log);
// baz
// 返回一个期约
async function qux() {
 return Promise.resolve('qux');
}
qux().then(console.log);
// qux
与在期约处理程序中一样,在异步函数中抛出错误会返回拒绝的期约:
async function foo() {
 console.log(1);
 throw 3;
}
// 给返回的期约添加一个拒绝处理程序
foo().catch(console.log);
console.log(2);
// 1
// 2
// 3
不过,拒绝期约的错误不会被异步函数捕获:
async function foo() {
 console.log(1);
 Promise.reject(3);
}
// Attach a rejected handler to the returned promise
foo().catch(console.log);
console.log(2);
// 1
// 2
// Uncaught (in promise): 3 
  1. await

因为异步函数主要针对不会马上完成的任务,所以自然需要一种暂停和恢复执行的能力。使用 await
关键字可以暂停异步函数代码的执行,等待期约解决

let p = new Promise((resolve, reject) => setTimeout(resolve, 1000, 3));
p.then((x) => console.log(x)); // 3
使用 async/await 可以写成这样:
async function foo() {
 let p = new Promise((resolve, reject) => setTimeout(resolve, 1000, 3));
 console.log(await p);
}
foo();
// 3

注意,await 关键字会暂停执行异步函数后面的代码,让出 JavaScript 运行时的执行线程。这个行
为与生成器函数中的 yield 关键字是一样的。await 关键字同样是尝试“解包”对象的值,然后将这
个值传给表达式,再异步恢复异步函数的执行。

await 关键字的用法与 JavaScript 的一元操作一样。它可以单独使用,也可以在表达式中使用

// 异步打印"foo"
async function foo() {
 console.log(await Promise.resolve('foo'));
}
foo();
// foo
// 异步打印"bar"
async function bar() {
 return await Promise.resolve('bar');
}
bar().then(console.log);
// bar
// 1000 毫秒后异步打印"baz"
async function baz() {
 await new Promise((resolve, reject) => setTimeout(resolve, 1000));
 console.log('baz');
}
baz();
// baz(1000 毫秒后)

await 关键字期待(但实际上并不要求)一个实现 thenable 接口的对象,但常规的值也可以。如
果是实现 thenable 接口的对象,则这个对象可以由 await 来“解包”。

// 等待一个原始值
async function foo() {
 console.log(await 'foo');
}
foo();
// foo
// 等待一个没有实现 thenable 接口的对象
async function bar() {
 console.log(await ['bar']);
}
bar();
// ['bar']
// 等待一个实现了 thenable 接口的非期约对象
async function baz() {
 const thenable = {
 then(callback) { callback('baz'); }
 };
 console.log(await thenable);
}
baz();
// baz
// 等待一个期约
async function qux() {
 console.log(await Promise.resolve('qux'));
}
qux();
// qux
等待会抛出错误的同步操作,会返回拒绝的期约:
async function foo() {
 console.log(1);
 await (() => { throw 3; })();
}
// 给返回的期约添加一个拒绝处理程序
foo().catch(console.log);
console.log(2);
// 1
// 2
// 3

单独的 Promise.reject()不会被异步函数捕获,而会抛出未捕获错误

async function foo() {
 console.log(1);
 await Promise.reject(3);
 console.log(4); // 这行代码不会执行
}
// 给返回的期约添加一个拒绝处理程序
foo().catch(console.log);
console.log(2);
// 1
// 2
// 3
  1. await 的限制

await 关键字必须在异步函数中使用,不能在顶级上下文如<script>标签或模块中使用。不过,
定义并立即调用异步函数是没问题的。

async function foo() {
 console.log(await Promise.resolve(3));
}
foo();
// 3
// 立即调用的异步函数表达式
(async function() {
 console.log(await Promise.resolve(3));
})();
// 3 

await 关键字也只能直接出现在异步函数的定
义中。在同步函数内部使用 await 会抛出 SyntaxError。

// 不允许:await 出现在了箭头函数中
function foo() {
 const syncFn = () => {
 return await Promise.resolve('foo');
 };
 console.log(syncFn());
}
// 不允许:await 出现在了同步函数声明中
function bar() {
 function syncFn() {
 return await Promise.resolve('bar');
 }
 console.log(syncFn());
}
// 不允许:await 出现在了同步函数表达式中
function baz() {
 const syncFn = function() {
 return await Promise.resolve('baz');
 };
 console.log(syncFn());
}
// 不允许:IIFE 使用同步函数表达式或箭头函数
function qux() {
 (function () { console.log(await Promise.resolve('qux')); })();
 (() => console.log(await Promise.resolve('qux')))();

停止和恢复执行

async function foo() {
 console.log(await Promise.resolve('foo'));
}
async function bar() {
 console.log(await 'bar');
}
async function baz() {
 console.log('baz');
}
foo();
bar();
baz();
// baz
// bar
// foo

async/await 中真正起作用的是 await。async 关键字,无论从哪方面来看,都不过是一个标识符。

async function foo() {
 console.log(2);
}
console.log(1);
foo();
console.log(3);
// 1
// 2
// 3

JavaScript 运行时在碰到 await 关键字时,会记录在哪里暂停执行。等到 await 右边的值可用了,JavaScript 运行时会向消息队列中推送一个任务,这个任务会恢复异步函数的执行。

async function foo() {
 console.log(2);
 await null;
 console.log(4);
}
console.log(1);
foo();
console.log(3);
// 1
// 2
// 3
// 4
控制台中输出结果的顺序很好地解释了运行时的工作过程:
(1) 打印 1
(2) 调用异步函数 foo()
(3)(在 foo()中)打印 2
(4)(在 foo()中)await 关键字暂停执行,为立即可用的值 null 向消息队列中添加一个任务;
(5) foo()退出;
(6) 打印 3
(7) 同步线程的代码执行完毕;
(8) JavaScript 运行时从消息队列中取出任务,恢复异步函数执行;
(9)(在 foo()中)恢复执行,await 取得 null 值(这里并没有使用);
(10)(在 foo()中)打印 4
(11) foo()返回。
async function foo() {
 console.log(2);
 console.log(await Promise.resolve(8));
 console.log(9);
}
async function bar() {
 console.log(4);
 console.log(await 6);
 console.log(7);
}
console.log(1);
foo();
console.log(3);
bar();
console.log(5);
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 8
// 9
运行时会像这样执行上面的例子:
(1) 打印 1
(2) 调用异步函数 foo()
(3)(在 foo()中)打印 2
(4)(在 foo()中)await 关键字暂停执行,向消息队列中添加一个期约在落定之后执行的任务;
(5) 期约立即落定,把给 await 提供值的任务添加到消息队列;
(6) foo()退出;
(7) 打印 3
(8) 调用异步函数 bar()
(9)(在 bar()中)打印 4
(10)(在 bar()中)await 关键字暂停执行,为立即可用的值 6 向消息队列中添加一个任务;
(11) bar()退出;
(12) 打印 5
(13) 顶级线程执行完毕;
(14) JavaScript 运行时从消息队列中取出解决 await 期约的处理程序,并将解决的值 8 提供给它;
(15) JavaScript 运行时向消息队列中添加一个恢复执行 foo()函数的任务;
(16) JavaScript 运行时从消息队列中取出恢复执行 bar()的任务及值 6
(17)(在 bar()中)恢复执行,await 取得值 6
(18)(在 bar()中)打印 6
(19)(在 bar()中)打印 7
(20) bar()返回;
(21) 异步任务完成,JavaScript 从消息队列中取出恢复执行 foo()的任务及值 8
(22)(在 foo()中)打印 8
(23)(在 foo()中)打印 9
(24) foo()返回。

异步函数策略

  1. 实现 sleep()

很多人在刚开始学习 JavaScript 时,想找到一个类似 Java 中 Thread.sleep()之类的函数,好在程
序中加入非阻塞的暂停。以前,这个需求基本上都通过 setTimeout()利用 JavaScript 运行时的行为来
实现的。

有了异步函数之后,就不一样了。一个简单的箭头函数就可以实现 sleep():

async function sleep(delay) {
 return new Promise((resolve) => setTimeout(resolve, delay));
}
async function foo() {
 const t0 = Date.now();
 await sleep(1500); // 暂停约 1500 毫秒
 console.log(Date.now() - t0);
}
foo();
// 1502 
  1. 利用平行执行

如果使用 await 时不留心,则很可能错过平行加速的机会。来看下面的例子,其中顺序等待了 5
个随机的超时:

async function randomDelay(id) {
 // 延迟 0~1000 毫秒
 const delay = Math.random() * 1000;
 return new Promise((resolve) => setTimeout(() => {
 console.log(`${id} finished`);
 resolve();
 }, delay));
}
async function foo() {
 const t0 = Date.now();
 await randomDelay(0);
 await randomDelay(1);
 await randomDelay(2);
 await randomDelay(3);
 await randomDelay(4);
 console.log(`${Date.now() - t0}ms elapsed`);
}
foo();
// 0 finished
// 1 finished
// 2 finished
// 3 finished
// 4 finished
// 877ms elapsed

用一个 for 循环重写,就是:

async function randomDelay(id) {
 // 延迟 0~1000 毫秒
 const delay = Math.random() * 1000;
 return new Promise((resolve) => setTimeout(() => {
 console.log(`${id} finished`);
 resolve();
 }, delay));
}
async function foo() {
 const t0 = Date.now();
 for (let i = 0; i < 5; ++i) {
 await randomDelay(i);
 }
 console.log(`${Date.now() - t0}ms elapsed`);
}
foo();
// 0 finished
// 1 finished
// 2 finished
// 3 finished
// 4 finished
// 877ms elapsed

就算这些期约之间没有依赖,异步函数也会依次暂停,等待每个超时完成。这样可以保证执行顺序,
但总执行时间会变长。
如果顺序不是必需保证的,那么可以先一次性初始化所有期约,然后再分别等待它们的结果。比如:

async function randomDelay(id) {
 // 延迟 0~1000 毫秒
 const delay = Math.random() * 1000;
 return new Promise((resolve) => setTimeout(() => {
 setTimeout(console.log, 0, `${id} finished`);
 resolve();
 }, delay));
}
async function foo() {
 const t0 = Date.now();
 const p0 = randomDelay(0);
 const p1 = randomDelay(1);
 const p2 = randomDelay(2);
 const p3 = randomDelay(3);
 const p4 = randomDelay(4);
 await p0;
 await p1;
 await p2;
 await p3;
 await p4;
 setTimeout(console.log, 0, `${Date.now() - t0}ms elapsed`);
}
foo();
// 1 finished 
// 4 finished
// 3 finished
// 0 finished
// 2 finished
// 877ms elapsed 
用数组和 for 循环再包装一下就是:
async function randomDelay(id) {
 // 延迟 0~1000 毫秒
 const delay = Math.random() * 1000;
 return new Promise((resolve) => setTimeout(() => {
 console.log(`${id} finished`);
 resolve();
 }, delay));
}
async function foo() {
 const t0 = Date.now();
 const promises = Array(5).fill(null).map((_, i) => randomDelay(i));
 for (const p of promises) {
 await p;
 }
 console.log(`${Date.now() - t0}ms elapsed`);
}
foo();
// 4 finished
// 2 finished
// 1 finished
// 0 finished
// 3 finished
// 877ms elapsed
注意,虽然期约没有按照顺序执行,但 await 按顺序收到了每个期约的值:
async function randomDelay(id) {
 // 延迟 0~1000 毫秒
 const delay = Math.random() * 1000;
 return new Promise((resolve) => setTimeout(() => {
 console.log(`${id} finished`);
 resolve(id);
 }, delay));
}
async function foo() {
 const t0 = Date.now();
 const promises = Array(5).fill(null).map((_, i) => randomDelay(i));
 for (const p of promises) {
 console.log(`awaited ${await p}`);
 }
 console.log(`${Date.now() - t0}ms elapsed`);
}
foo(); 
// 1 finished
// 2 finished
// 4 finished
// 3 finished
// 0 finished
// awaited 0
// awaited 1
// awaited 2
// awaited 3
// awaited 4
// 645ms elapsed
  1. 串行执行期约

我们讨论过如何串行执行期约并把值传给后续的期约。使用 async/await,期约连锁会变
得很简单:

function addTwo(x) {return x + 2;}
function addThree(x) {return x + 3;}
function addFive(x) {return x + 5;}
async function addTen(x) {
 for (const fn of [addTwo, addThree, addFive]) {
 x = await fn(x);
 }
 return x;
}
addTen(9).then(console.log); // 19

这里,await 直接传递了每个函数的返回值,结果通过迭代产生。当然,这个例子并没有使用期约,
如果要使用期约,则可以把所有函数都改成异步函数。这样它们就都返回期约了:

async function addTwo(x) {return x + 2;}
async function addThree(x) {return x + 3;}
async function addFive(x) {return x + 5;}
async function addTen(x) {
 for (const fn of [addTwo, addThree, addFive]) {
 x = await fn(x);
 }
 return x;
}
addTen(9).then(console.log); // 19
  1. 栈追踪与内存管理

期约与异步函数的功能有相当程度的重叠,但它们在内存中的表示则差别很大。看看下面的例子,
它展示了拒绝期约的栈追踪信息:

function fooPromiseExecutor(resolve, reject) {
 setTimeout(reject, 1000, 'bar');
}
function foo() {
 new Promise(fooPromiseExecutor);
} 
foo();
// Uncaught (in promise) bar
// setTimeout
// setTimeout (async)
// fooPromiseExecutor
// foo

根据对期约的不同理解程度,以上栈追踪信息可能会让某些读者不解。栈追踪信息应该相当直接地
表现 JavaScript 引擎当前栈内存中函数调用之间的嵌套关系。在超时处理程序执行时和拒绝期约时,我
们看到的错误信息包含嵌套函数的标识符,那是被调用以创建最初期约实例的函数。可是,我们知道这
些函数已经返回了,因此栈追踪信息中不应该看到它们。

答案很简单,这是因为 JavaScript 引擎会在创建期约时尽可能保留完整的调用栈。在抛出错误时,
调用栈可以由运行时的错误处理逻辑获取,因而就会出现在栈追踪信息中。当然,这意味着栈追踪信息
会占用内存,从而带来一些计算和存储成本。

如果在前面的例子中使用的是异步函数,那又会怎样呢?比如:

function fooPromiseExecutor(resolve, reject) {
 setTimeout(reject, 1000, 'bar');
}
async function foo() {
 await new Promise(fooPromiseExecutor);
}
foo();
// Uncaught (in promise) bar
// foo
// async function (async)
// foo

这样一改,栈追踪信息就准确地反映了当前的调用栈。fooPromiseExecutor()已经返回,所以
它不在错误信息中。但 foo()此时被挂起了,并没有退出。JavaScript 运行时可以简单地在嵌套函数中
存储指向包含函数的指针,就跟对待同步函数调用栈一样。这个指针实际上存储在内存中,可用于在出
错时生成栈追踪信息。这样就不会像之前的例子那样带来额外的消耗,因此在重视性能的应用中是可以
优先考虑的。

🆗

28. 什么是模块

模块是指独立的、可重用的代码的小单元,也是许多 JavaScript 设计模式的基础。大多数 JavaScript 模块都导出对象字面量、函数或构造函数

27. 什么是闭包

闭包是函数和声明该函数的词法环境的组合。即,它是一个内部函数,可以访问外部或封闭函数的变量。闭包具有三个作用域链

自己的范围,其中在其大括号之间定义的变量

外部函数的变量

全局变量

让我们以闭包概念为例,

function Welcome(name) {
  var greetingInfo = function (message) {
    console.log(message + " " + name);
  };
  return greetingInfo;
}
var myFunction = Welcome("John");
myFunction("Welcome "); //Output: Welcome John
myFunction("Hello Mr."); //output: Hello Mr.John

根据上面的代码,即使在外部函数返回之后,内部函数(即 greetingInfo)也可以访问外部函数作用域(即 Welcome)中的变量。

【深入理解JS核心技术】10. 什么是 lambda 或箭头函数

箭头函数是函数表达式的较短语法,没有自己的this、arguments、super 或 new.target。这些函数最适合非方法函数,它们不能用作构造函数。此外,箭头函数也没有 prototype 属性。

ECMAScript 6 新增了使用胖箭头(=>)语法定义函数表达式的能力。

箭头函数实例化的函数对象与正式的函数表达式创建的函数对象行为是相同的。

箭头函数也可以不用大括号,但这样会改变函数的行为。使用大括号就说明包含“函数体”。

  1. 没有参数需要括号
  2. 多个参数需要括号

未完结!更多内容尽情期待下一节~

【深入理解JS核心技术】欢迎各位观众老爷,求点赞,求关注,求转发~

【深入理解JS核心技术】22. 什么是 IIFE(立即调用函数表达式)

IIFE(立即调用函数表达式)是一个 JavaScript 函数,一旦定义就会运行。

(function() {
 // 
})();

使用 IIFE 的主要原因是为了获得数据隐私,因为在 IIFE 中声明的任何变量都不能被外界访问。即,如果您尝试使用 IIFE 访问变量,则会引发如下错误

(function () {
  var message = "IIFE";
  console.log(message);
})();
console.log(message); //Error: message is not defined

附加:

对象的解构赋值

解构不仅可以用于数组,还可以用于对象。

let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"

对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。

let { bar, foo } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"

let { baz } = { foo: 'aaa', bar: 'bbb' };
baz // undefined

上面代码的第一个例子,等号左边的两个变量的次序,与等号右边两个同名属性的次序不一致,但是对取值完全没有影响。第二个例子的变量没有对应的同名属性,导致取不到值,最后等于undefined。

如果解构失败,变量的值等于undefined。

let {foo} = {bar: 'baz'};
foo // undefined

上面代码中,等号右边的对象没有foo属性,所以变量foo取不到值,所以等于undefined。

对象的解构赋值,可以很方便地将现有对象的方法,赋值到某个变量。

// 例一
let { log, sin, cos } = Math;

// 例二
const { log } = console;
log('hello') // hello

上面代码的例一将Math对象的对数、正弦、余弦三个方法,赋值到对应的变量上,使用起来就会方便很多。例二将console.log赋值到log变量。

如果变量名与属性名不一致,必须写成下面这样。

let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"

let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;
f // 'hello'
l // 'world'

这实际上说明,对象的解构赋值是下面形式的简写(参见《对象的扩展》一章)。

let { foo: foo, bar: bar } = { foo: 'aaa', bar: 'bbb' };

也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。

let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
foo // error: foo is not defined

上面代码中,foo是匹配的模式,baz才是变量。真正被赋值的是变量baz,而不是模式foo。

与数组一样,解构也可以用于嵌套结构的对象。

let obj = {
  p: [
    'Hello',
    { y: 'World' }
  ]
};

let { p: [x, { y }] } = obj;
x // "Hello"
y // "World"

注意,这时p是模式,不是变量,因此不会被赋值。如果p也要作为变量赋值,可以写成下面这样。

let obj = {
  p: [
    'Hello',
    { y: 'World' }
  ]
};

let { p, p: [x, { y }] } = obj;
x // "Hello"
y // "World"
p // ["Hello", {y: "World"}]

下面是另一个例子。

const node = {
  loc: {
    start: {
      line: 1,
      column: 5
    }
  }
};

let { loc, loc: { start }, loc: { start: { line }} } = node;
line // 1
loc  // Object {start: Object}
start // Object {line: 1, column: 5}

上面代码有三次解构赋值,分别是对loc、start、line三个属性的解构赋值。注意,最后一次对line属性的解构赋值之中,只有line是变量,loc和start都是模式,不是变量。

下面是嵌套赋值的例子。

let obj = {};
let arr = [];

({ foo: obj.prop, bar: arr[0] } = { foo: 123, bar: true });

obj // {prop:123}
arr // [true]

如果解构模式是嵌套的对象,而且子对象所在的父属性不存在,那么将会报错。

// 报错
let {foo: {bar}} = {baz: 'baz'};

上面代码中,等号左边对象的foo属性,对应一个子对象。该子对象的bar属性,解构时会报错。原因很简单,因为foo这时等于undefined,再取子属性就会报错。

注意,对象的解构赋值可以取到继承的属性。

const obj1 = {};
const obj2 = { foo: 'bar' };
Object.setPrototypeOf(obj1, obj2);

const { foo } = obj1;
foo // "bar"

上面代码中,对象obj1的原型对象是obj2。foo属性不是obj1自身的属性,而是继承自obj2的属性,解构赋值可以取到这个属性。

对象的解构赋值-默认值

对象的解构也可以指定默认值。

var {x = 3} = {};
x // 3

var {x, y = 5} = {x: 1};
x // 1
y // 5

var {x: y = 3} = {};
y // 3

var {x: y = 3} = {x: 5};
y // 5

var { message: msg = 'Something went wrong' } = {};
msg // "Something went wrong"

默认值生效的条件是,对象的属性值严格等于undefined。

var {x = 3} = {x: undefined};
x // 3

var {x = 3} = {x: null};
x // null

未完结!更多内容尽情期待下一节~

【深入理解JS核心技术】欢迎各位观众老爷,求点赞,求关注,求转发~

代理与反射

ECMAScript 6 新增的代理和反射

代理基础

代理是目标对象的抽象

创建空代理

最简单的代理是空代理,即除了作为一个抽象的目标对象,什么也不做。默认情况下,在代理对象上执行的所有操作都会无障碍地传播到目标对象。因此,在任何可以使用目标对象的地方,都可以通过同样的方式来使用与之关联的代理对象。

代理是使用 Proxy 构造函数创建的。这个构造函数接收两个参数:目标对象和处理程序对象。

const target = {
 id: 'target'
};
const handler = {};
const proxy = new Proxy(target, handler);

// id 属性会访问同一个值
console.log(target.id); // target
console.log(proxy.id); // target

// 给目标属性赋值会反映在两个对象上
// 因为两个对象访问的是同一个值
target.id = 'foo';
console.log(target.id); // foo
console.log(proxy.id); // foo

// 给代理属性赋值会反映在两个对象上
// 因为这个赋值会转移到目标对象
proxy.id = 'bar';
console.log(target.id); // bar
console.log(proxy.id); // bar

// hasOwnProperty()方法在两个地方
// 都会应用到目标对象
console.log(target.hasOwnProperty('id')); // true
console.log(proxy.hasOwnProperty('id')); // true

// Proxy.prototype 是 undefined
// 因此不能使用 instanceof 操作符
console.log(target instanceof Proxy); // TypeError: Function has non-object prototype
'undefined' in instanceof check
console.log(proxy instanceof Proxy); // TypeError: Function has non-object prototype
'undefined' in instanceof check

// 严格相等可以用来区分代理和目标
console.log(target === proxy); // false

定义捕获器

使用代理的主要目的是可以定义捕获器(trap)。捕获器就是在处理程序对象中定义的“基本操作的拦截器”。

注意 捕获器(trap)是从操作系统中借用的概念。在操作系统中,捕获器是程序流中的一个同步中断,可以暂停程序流,转而执行一段子例程,之后再返回原始程序流。

定义了一个 get()捕获器

const target = {
 foo: 'bar'
};
const handler = {
 // 捕获器在处理程序对象中以方法名为键
 get() {
 return 'handler override';
 }
};
const proxy = new Proxy(target, handler);

proxy[property]、proxy.property 或 Object.create(proxy)[property]等操作都会触发基本的 get()操作以获取属性。因此所有这些操作只要发生在代理对象上,就会触发 get()捕获器。注意,只有在代理对象上执行这些操作才会触发捕获器。在目标对象上执行这些操作仍然会产生正常的行为。

const target = {
 foo: 'bar'
};
const handler = {
 // 捕获器在处理程序对象中以方法名为键
 get() {
 return 'handler override';
 }
};
const proxy = new Proxy(target, handler);
console.log(target.foo); // bar
console.log(proxy.foo); // handler override
console.log(target['foo']); // bar
console.log(proxy['foo']); // handler override
console.log(Object.create(target)['foo']); // bar
console.log(Object.create(proxy)['foo']); // handler override

捕获器参数和反射 API

捕获器会接收到目标对象、要查询的属性和代理对象三个参数。

const target = {
 foo: 'bar'
};
const handler = {
get(trapTarget, property, receiver) {
 console.log(trapTarget === target);
 console.log(property);
 console.log(receiver === proxy);
 }
};
const proxy = new Proxy(target, handler);
proxy.foo;
// true
// foo
// true 
const target = {
 foo: 'bar'
};
const handler = {
 get(trapTarget, property, receiver) {
 return trapTarget[property];
 }
};
const proxy = new Proxy(target, handler);
console.log(proxy.foo); // bar
console.log(target.foo); // bar

通过调用全局 Reflect 对象上(封装了原始行为)的同名方法来轻松重建。

处理程序对象中所有可以捕获的方法都有对应的反射(Reflect)API 方法。

const target = {
 foo: 'bar'
};
const handler = {
 get() {
 return Reflect.get(...arguments);
 }
};
const proxy = new Proxy(target, handler);
console.log(proxy.foo); // bar
console.log(target.foo); // bar
const target = {
 foo: 'bar'
};

const handler = {
 get: Reflect.get
};
const proxy = new Proxy(target, handler);
console.log(proxy.foo); // bar
console.log(target.foo); // bar 

甚至不需要定义处理程序对象:

const target = {
 foo: 'bar'
};
const proxy = new Proxy(target, Reflect);
console.log(proxy.foo); // bar
console.log(target.foo); // bar
const target = {
 foo: 'bar',
 baz: 'qux'
};
const handler = {
 get(trapTarget, property, receiver) {
 let decoration = '';
 if (property === 'foo') {
 decoration = '!!!';
 }
 return Reflect.get(...arguments) + decoration;
 }
};
const proxy = new Proxy(target, handler);
console.log(proxy.foo); // bar!!!
console.log(target.foo); // bar
console.log(proxy.baz); // qux
console.log(target.baz); // qux

捕获器不变式

每个捕获的方法都知道目标对象上下文、捕获函数签名,而捕获处理程序的行为必须遵循“捕获器不变式”(trap invariant)。捕获器不变式因方法不同而异,但通常都会防止捕获器定义出现过于反常的行为。

const target = {};
Object.defineProperty(target, 'foo', {
 configurable: false,
 writable: false,
 value: 'bar'
});
const handler = {
 get() {
 return 'qux';
 }
};
const proxy = new Proxy(target, handler);
console.log(proxy.foo);
// TypeError 

可撤销代理

有时候可能需要中断代理对象与目标对象之间的联系。revocable

Proxy 也暴露了 revocable()方法,这个方法支持撤销代理对象与目标对象的关联。撤销代理的操作是不可逆的

撤销代理之后再调用代理会抛出 TypeError。

// 撤销函数和代理对象是在实例化时同时生成的
const target = {
 foo: 'bar'
};
const handler = {
 get() {
 return 'intercepted';
 }
};
const { proxy, revoke } = Proxy.revocable(target, handler);
console.log(proxy.foo); // intercepted
console.log(target.foo); // bar
revoke();
console.log(proxy.foo); // TypeError

实用反射 API

  1. 反射 API 与对象 API

在使用反射 API 时,要记住:

(1) 反射 API 并不限于捕获处理程序;

(2) 大多数反射 API 方法在 Object 类型上有对应的方法。

通常,Object 上的方法适用于通用程序,而反射方法适用于细粒度的对象控制与操作。

  1. 状态标记

很多反射方法返回称作“状态标记”的布尔值,表示意图执行的操作是否成功。有时候,状态标记比那些返回修改后的对象或者抛出错误(取决于方法)的反射 API 方法更有用。

// 初始代码
const o = {};
try {
 Object.defineProperty(o, 'foo', 'bar');
 console.log('success');
} catch(e) {
 console.log('failure');
} 

在定义新属性时如果发生问题,Reflect.defineProperty()会返回 false,而不是抛出错误。因此使用这个反射方法可以这样重构上面的代码:

// 重构后的代码
const o = {};
if(Reflect.defineProperty(o, 'foo', {value: 'bar'})) {
 console.log('success');
} else {
 console.log('failure');
}
  1. Reflect.defineProperty()

  2. Reflect.preventExtensions()

  3. Reflect.setPrototypeOf()

  4. Reflect.set()

  5. Reflect.deleteProperty()

  6. 用一等函数替代操作符

  7. Reflect.get():可以替代对象属性访问操作符。

  8. Reflect.set():可以替代=赋值操作符。

  9. Reflect.has():可以替代 in 操作符或 with()。

  10. Reflect.deleteProperty():可以替代 delete 操作符。

  11. Reflect.construct():可以替代 new 操作符。

  12. 安全地应用函数

在通过 apply 方法调用函数时,被调用的函数可能也定义了自己的 apply 属性(虽然可能性极小)。为绕过这个问题,可以使用定义在 Function 原型上的 apply 方法,比如:

Function.prototype.apply.call(myFunc, thisVal, argumentList);

这种可怕的代码完全可以使用 Reflect.apply 来避免:

Reflect.apply(myFunc, thisVal, argumentsList); 

代理另一个代理

在一个目标对象之上构建多层拦截网

const target = {
 foo: 'bar'
};
const firstProxy = new Proxy(target, {
 get() {
 console.log('first proxy');
 return Reflect.get(...arguments);
 }
});
const secondProxy = new Proxy(firstProxy, {
 get() {
 console.log('second proxy');
 return Reflect.get(...arguments);
 }
});
console.log(secondProxy.foo);
// second proxy
// first proxy
// bar 

代理的问题与不足

  1. 代理中的 this
const target = {
 thisValEqualsProxy() {
 return this === proxy;
 }
}
const proxy = new Proxy(target, {});
console.log(target.thisValEqualsProxy()); // false
console.log(proxy.thisValEqualsProxy()); // true

通过 WeakMap 保存私有变量

const wm = new WeakMap();
class User {
 constructor(userId) {
 wm.set(this, userId);
 }
 set id(userId) {
 wm.set(this, userId);
 }
 get id() {
 return wm.get(this);
 }
} 

这个实现依赖 User 实例的对象标识

const user = new User(123);
console.log(user.id); // 123
const userInstanceProxy = new Proxy(user, {});
console.log(userInstanceProxy.id); // undefined
const UserClassProxy = new Proxy(User, {});
const proxyUser = new UserClassProxy(456);
console.log(proxyUser.id);
  1. 代理与内部槽位

Date 类型方法的执行依赖 this 值上的内部槽位[[NumberDate]]。代理对象上不存在这个内部槽位,而且这个内部槽位的值也不能通过普通的 get()和 set()操作访问到,于是代理拦截后本应转发给目标对象的方法会抛出 TypeError

const target = new Date();
const proxy = new Proxy(target, {});
console.log(proxy instanceof Date); // true
proxy.getDate(); // TypeError: 'this' is not a Date object

代理捕获器与反射方法

代理可以捕获 13 种不同的基本操作

在代理对象上执行的任何一种操作,只会有一个捕获处理程序被调用。不会存在重复捕获的情况。

get()捕获器会在获取属性值的操作中被调用。对应的反射 API 方法为 Reflect.get()。

const myTarget = {};
const proxy = new Proxy(myTarget, {
 get(target, property, receiver) {
 console.log('get()');
 return Reflect.get(...arguments)
 }
});
proxy.foo;
// get()
  1. 返回值
    返回值无限制。

  2. 拦截的操作

  • proxy.property
  • proxy[property]
  • Object.create(proxy)[property]
  • Reflect.get(proxy, property, receiver)
  1. 捕获器处理程序参数
  • target:目标对象。
  • property:引用的目标对象上的字符串键属性。①
  • receiver:代理对象或继承代理对象的对象。
  1. 捕获器不变式

如果 target.property 不可写且不可配置,则处理程序返回的值必须与 target.property 匹配。

如果 target.property 不可配置且[[Get]]特性为 undefined,处理程序的返回值也必须是 undefined。

set()捕获器会在设置属性值的操作中被调用。对应的反射 API 方法为 Reflect.set()。

const myTarget = {};

const proxy = new Proxy(myTarget, {
 set(target, property, value, receiver) {
 console.log('set()');
 return Reflect.set(...arguments)
 }
});

proxy.foo = 'bar';
// set()
  1. 返回值

返回 true 表示成功;返回 false 表示失败,严格模式下会抛出 TypeError。

  1. 拦截的操作
  • proxy.property = value
  • proxy[property] = value
  • Object.create(proxy)[property] = value
  • Reflect.set(proxy, property, value, receiver)
  1. 捕获器处理程序参数
  • target:目标对象。
  • property:引用的目标对象上的字符串键属性。
  • value:要赋给属性的值。
  • receiver:接收最初赋值的对象。
  1. 捕获器不变式

如果 target.property 不可写且不可配置,则不能修改目标属性的值。

如果 target.property 不可配置且[[Set]]特性为 undefined,则不能修改目标属性的值。在严格模式下,处理程序中返回 false 会抛出 TypeError。

has()捕获器会在 in 操作符中被调用。对应的反射 API 方法为 Reflect.has()。

const myTarget = {};
const proxy = new Proxy(myTarget, {
 has(target, property) {
 console.log('has()');
 return Reflect.has(...arguments)
 }
});
'foo' in proxy;
// has()
  1. 返回值
    has()必须返回布尔值,表示属性是否存在。返回非布尔值会被转型为布尔值。

  2. 拦截的操作

  • property in proxy
  • property in Object.create(proxy)
  • with(proxy) {(property);}
  • Reflect.has(proxy, property)
  1. 捕获器处理程序参数
  • target:目标对象。
  • property:引用的目标对象上的字符串键属性。
  1. 捕获器不变式

如果 target.property 存在且不可配置,则处理程序必须返回 true。

如果 target.property 存在且目标对象不可扩展,则处理程序必须返回 true。

defineProperty()捕获器会在 Object.defineProperty()中被调用。对应的反射 API 方法为Reflect.defineProperty()。

const myTarget = {};
const proxy = new Proxy(myTarget, {
 defineProperty(target, property, descriptor) {
 console.log('defineProperty()');
 return Reflect.defineProperty(...arguments)
 }
});
Object.defineProperty(proxy, 'foo', { value: 'bar' });
// defineProperty() 
  1. 返回值
    defineProperty()必须返回布尔值,表示属性是否成功定义。返回非布尔值会被转型为布尔值。

  2. 拦截的操作

  • Object.defineProperty(proxy, property, descriptor)
  • Reflect.defineProperty(proxy, property, descriptor)
  1. 捕获器处理程序参数
  • target:目标对象。
  • property:引用的目标对象上的字符串键属性。
  • descriptor:包含可选的 enumerable、configurable、writable、value、get 和 set
    定义的对象。
  1. 捕获器不变式

如果目标对象不可扩展,则无法定义属性。

如果目标对象有一个可配置的属性,则不能添加同名的不可配置属性。

如果目标对象有一个不可配置的属性,则不能添加同名的可配置属性。

getOwnPropertyDescriptor()捕获器会在 Object.getOwnPropertyDescriptor()中被调用。对应的反射 API 方法为 Reflect.getOwnPropertyDescriptor()。

const myTarget = {};
const proxy = new Proxy(myTarget, {
 getOwnPropertyDescriptor(target, property) {
 console.log('getOwnPropertyDescriptor()');
 return Reflect.getOwnPropertyDescriptor(...arguments)
 }
});
Object.getOwnPropertyDescriptor(proxy, 'foo');
// getOwnPropertyDescriptor() 
  1. 返回值
    getOwnPropertyDescriptor()必须返回对象,或者在属性不存在时返回 undefined。

  2. 拦截的操作

  • Object.getOwnPropertyDescriptor(proxy, property)
  • Reflect.getOwnPropertyDescriptor(proxy, property)
  1. 捕获器处理程序参数
  • target:目标对象。
  • property:引用的目标对象上的字符串键属性。
  1. 捕获器不变式

如果自有的 target.property 存在且不可配置,则处理程序必须返回一个表示该属性存在的对象。

如果自有的 target.property 存在且可配置,则处理程序必须返回表示该属性可配置的对象。

如果自有的 target.property 存在且 target 不可扩展,则处理程序必须返回一个表示该属性存在的对象。

如果 target.property 不存在且 target 不可扩展,则处理程序必须返回 undefined 表示该属性不存在。

如果 target.property 不存在,则处理程序不能返回表示该属性可配置的对象。

deleteProperty()捕获器会在 delete 操作符中被调用。对应的反射 API 方法为 Reflect.deleteProperty()。

const myTarget = {};
const proxy = new Proxy(myTarget, {
 deleteProperty(target, property) {
 console.log('deleteProperty()');
 return Reflect.deleteProperty(...arguments)
 }
});
delete proxy.foo
// deleteProperty() 
  1. 返回值
    deleteProperty()必须返回布尔值,表示删除属性是否成功。返回非布尔值会被转型为布尔值。

  2. 拦截的操作

  • delete proxy.property
  • delete proxy[property]
  • Reflect.deleteProperty(proxy, property)
  1. 捕获器处理程序参数
  • target:目标对象。
  • property:引用的目标对象上的字符串键属性。
  1. 捕获器不变式

如果自有的 target.property 存在且不可配置,则处理程序不能删除这个属性。

ownKeys()捕获器会在 Object.keys()及类似方法中被调用。对应的反射 API 方法为 Reflect.ownKeys()。

const myTarget = {};
const proxy = new Proxy(myTarget, {
 ownKeys(target) {
 console.log('ownKeys()');
 return Reflect.ownKeys(...arguments)
 }
});
Object.keys(proxy);
// ownKeys() 
  1. 返回值
    ownKeys()必须返回包含字符串或符号的可枚举对象。

  2. 拦截的操作

  • Object.getOwnPropertyNames(proxy)
  • Object.getOwnPropertySymbols(proxy)
  • Object.keys(proxy)
  • Reflect.ownKeys(proxy)
  1. 捕获器处理程序参数
  • target:目标对象。
  1. 捕获器不变式

返回的可枚举对象必须包含 target 的所有不可配置的自有属性。
如果 target 不可扩展,则返回可枚举对象必须准确地包含自有属性键。

getPrototypeOf()捕获器会在 Object.getPrototypeOf()中被调用。对应的反射 API 方法为Reflect.getPrototypeOf()。

const myTarget = {};
const proxy = new Proxy(myTarget, {
 getPrototypeOf(target) {
 console.log('getPrototypeOf()');
 return Reflect.getPrototypeOf(...arguments)
 }
});
Object.getPrototypeOf(proxy);
// getPrototypeOf() 
  1. 返回值
    getPrototypeOf()必须返回对象或 null。

  2. 拦截的操作

  • Object.getPrototypeOf(proxy)
  • Reflect.getPrototypeOf(proxy)
  • proxy.proto
  • Object.prototype.isPrototypeOf(proxy)
  • proxy instanceof Object
  1. 捕获器处理程序参数
  • target:目标对象。
  1. 捕获器不变式

如果 target 不可扩展,则 Object.getPrototypeOf(proxy)唯一有效的返回值就是 Object.
getPrototypeOf(target)的返回值

setPrototypeOf()捕获器会在 Object.setPrototypeOf()中被调用。对应的反射 API 方法为Reflect.setPrototypeOf()。

const myTarget = {};
const proxy = new Proxy(myTarget, {
 setPrototypeOf(target, prototype) {
 console.log('setPrototypeOf()');
 return Reflect.setPrototypeOf(...arguments)
 }
});
Object.setPrototypeOf(proxy, Object);
// setPrototypeOf()
  1. 返回值
    setPrototypeOf()必须返回布尔值,表示原型赋值是否成功。返回非布尔值会被转型为布尔值。

  2. 拦截的操作

  • Object.setPrototypeOf(proxy)
  • Reflect.setPrototypeOf(proxy)
  1. 捕获器处理程序参数
  • target:目标对象。
  • prototype:target 的替代原型,如果是顶级原型则为 null。
  1. 捕获器不变式

如果 target 不可扩展,则唯一有效的 prototype 参数就是 Object.getPrototypeOf(target)
的返回值。

isExtensible()捕获器会在 Object.isExtensible()中被调用。对应的反射 API 方法为Reflect.isExtensible()。

const myTarget = {};
const proxy = new Proxy(myTarget, {
 isExtensible(target) {
 console.log('isExtensible()');
 return Reflect.isExtensible(...arguments)
 }
});
Object.isExtensible(proxy);
// isExtensible() 
  1. 返回值

isExtensible()必须返回布尔值,表示 target 是否可扩展。返回非布尔值会被转型为布尔值。

  1. 拦截的操作
  • Object.isExtensible(proxy)
  • Reflect.isExtensible(proxy)
  1. 捕获器处理程序参数
  • target:目标对象。
  1. 捕获器不变式

如果 target 可扩展,则处理程序必须返回 true。

如果 target 不可扩展,则处理程序必须返回 false。

preventExtensions()捕获器会在 Object.preventExtensions()中被调用。对应的反射 API方法为 Reflect.preventExtensions()。

const myTarget = {};
const proxy = new Proxy(myTarget, {
 preventExtensions(target) {
 console.log('preventExtensions()');
 return Reflect.preventExtensions(...arguments)
 }
});
Object.preventExtensions(proxy);
// preventExtensions() 
  1. 返回值

preventExtensions()必须返回布尔值,表示 target 是否已经不可扩展。返回非布尔值会被转
型为布尔值。

  1. 拦截的操作
  • Object.preventExtensions(proxy)
  • Reflect.preventExtensions(proxy)
  1. 捕获器处理程序参数
  • target:目标对象。
  1. 捕获器不变式

如果 Object.isExtensible(proxy)是 false,则处理程序必须返回 true。

apply()捕获器会在调用函数时中被调用。对应的反射 API 方法为 Reflect.apply()。

const myTarget = () => {};
const proxy = new Proxy(myTarget, {
 apply(target, thisArg, ...argumentsList) {
 console.log('apply()');
 return Reflect.apply(...arguments)
 }
});
proxy();
// apply()
  1. 返回值

返回值无限制。

  1. 拦截的操作
  • proxy(...argumentsList)
  • Function.prototype.apply(thisArg, argumentsList)
  • Function.prototype.call(thisArg, ...argumentsList)
  • Reflect.apply(target, thisArgument, argumentsList)
  1. 捕获器处理程序参数
  • target:目标对象。
  • thisArg:调用函数时的 this 参数。
  • argumentsList:调用函数时的参数列表
  1. 捕获器不变式

target 必须是一个函数对象。

construct()捕获器会在 new 操作符中被调用。对应的反射 API 方法为 Reflect.construct()。

const myTarget = function() {};
const proxy = new Proxy(myTarget, {
 construct(target, argumentsList, newTarget) {
 console.log('construct()');
 return Reflect.construct(...arguments)
 }
});
new proxy;
// construct()
  1. 返回值

construct()必须返回一个对象。

  1. 拦截的操作
  • new proxy(...argumentsList)
  • Reflect.construct(target, argumentsList, newTarget)
  1. 捕获器处理程序参数
  • target:目标构造函数。
  • argumentsList:传给目标构造函数的参数列表。
  • newTarget:最初被调用的构造函数。
  1. 捕获器不变式

target 必须可以用作构造函数。

代理模式

跟踪属性访问

通过捕获 get、set 和 has 等操作,可以知道对象属性什么时候被访问、被查询。

const user = {
 name: 'Jake'
};
const proxy = new Proxy(user, {
 get(target, property, receiver) {
 console.log(`Getting ${property}`);
 return Reflect.get(...arguments);
 },
 set(target, property, value, receiver) {
 console.log(`Setting ${property}=${value}`);
 return Reflect.set(...arguments);
 }
});
proxy.name; // Getting name
proxy.age = 27; // Setting age=27

隐藏属性

代理的内部实现对外部代码是不可见的,因此要隐藏目标对象上的属性也轻而易举

const hiddenProperties = ['foo', 'bar'];
const targetObject = {
 foo: 1,
 bar: 2,
 baz: 3
};
const proxy = new Proxy(targetObject, {
 get(target, property) {
 if (hiddenProperties.includes(property)) {
 return undefined;
 } else {
 return Reflect.get(...arguments);
 }
 },
 has(target, property) {
if (hiddenProperties.includes(property)) {
 return false;
 } else {
 return Reflect.has(...arguments);
 }
 }
});
// get()
console.log(proxy.foo); // undefined
console.log(proxy.bar); // undefined
console.log(proxy.baz); // 3
// has()
console.log('foo' in proxy); // false
console.log('bar' in proxy); // false
console.log('baz' in proxy); // true

属性验证

因为所有赋值操作都会触发 set()捕获器,所以可以根据所赋的值决定是允许还是拒绝赋值:

const target = {
 onlyNumbersGoHere: 0
};
const proxy = new Proxy(target, {
 set(target, property, value) {
 if (typeof value !== 'number') {
 return false;
 } else {
 return Reflect.set(...arguments);
 }
 }
});
proxy.onlyNumbersGoHere = 1;
console.log(proxy.onlyNumbersGoHere); // 1
proxy.onlyNumbersGoHere = '2';
console.log(proxy.onlyNumbersGoHere); // 1

函数与构造函数参数验证

可对函数和构造函数参数进行审查。

function median(...nums) {
 return nums.sort()[Math.floor(nums.length / 2)];
}
const proxy = new Proxy(median, {
 apply(target, thisArg, argumentsList) {
 for (const arg of argumentsList) {
 if (typeof arg !== 'number') {
 throw 'Non-number argument provided';
 }
 } 
return Reflect.apply(...arguments);
 }
});
console.log(proxy(4, 7, 1)); // 4
console.log(proxy(4, '7', 1));
// Error: Non-number argument provided

可以要求实例化时必须给构造函数传参

class User {
 constructor(id) {
 this.id_ = id;
 }
}
const proxy = new Proxy(User, {
 construct(target, argumentsList, newTarget) {
 if (argumentsList[0] === undefined) {
 throw 'User cannot be instantiated without id';
 } else {
 return Reflect.construct(...arguments);
 }
 }
});
new proxy(1);
new proxy();
// Error: User cannot be instantiated without id 

数据绑定与可观察对象

const userList = [];
class User {
 constructor(name) {
 this.name_ = name;
 }
}
const proxy = new Proxy(User, {
 construct() {
 const newUser = Reflect.construct(...arguments);
 userList.push(newUser);
 return newUser;
 }
});
new proxy('John');
new proxy('Jacob');
new proxy('Jingleheimerschmidt');
console.log(userList); // [User {}, User {}, User{}] 

可以把集合绑定到一个事件分派程序,每次插入新实例时都会发送消息:

const userList = [];
function emit(newValue) {
 console.log(newValue);
}
const proxy = new Proxy(userList, {
 set(target, property, value, receiver) {
 const result = Reflect.set(...arguments);
 if (result) {
 emit(Reflect.get(target, property, receiver));
 }
 return result;
 }
});
proxy.push('John');
// John
proxy.push('Jacob');
// Jacob 

代理可以定义包含捕获器的处理程序对象,而这些捕获器可以拦截绝大部分 JavaScript 的基本操作和方法。在这个捕获器处理程序中,可以修改任何基本操作的行为,当然前提是遵从捕获器不变式

代理的应用场景是不可限量的。

跟踪属性访问、隐藏属性、阻止修改或删除属性、函数参数验证、构造函数参数验证、数据绑定,以及可观察对象。

🆗

【深入理解JS核心技术】16. 什么是纯函数

纯函数是一个函数,其中返回值仅由其参数确定,没有任何副作用。即,如果您在应用程序中调用具有相同参数“n”次和“n”个位置的函数,那么它将始终返回相同的值。

我们举个例子来看看纯函数和非纯函数的区别

//Impure
let numberArray = [];
const impureAddNumber = (number) => numberArray.push(number);
//Pure
const pureAddNumber = (number) => (argNumberArray) =>
  argNumberArray.concat([number]);

//Display the results
console.log(impureAddNumber(6)); // returns 1
console.log(numberArray); // returns [6]
console.log(pureAddNumber(7)(numberArray)); // returns [6, 7]
console.log(numberArray); // returns [6]

请记住,纯函数很重要,因为它们简化了单元测试,没有任何副作用,也不需要依赖注入。它们还避免了紧密耦合,并且不会产生任何副作用,从而更难破坏您的应用程序。通过优先使用const而不是let使用,这些原则与 ES6 的不变性概念结合在一起。

函数内部

  1. arguments
  2. this
  3. caller
  4. new.target

函数内部,在ECMAScript5中,函数内部存在两个特殊的对象:arguments和this。ECMAScript6又新增了new.target属性。

arguments

arguments它是一个类数组对象,包含调用函数时传入的所有参数。这个对象只有以function关键字定义函数时才会有,箭头函数是没有的。

虽然主要用于包含函数参数,但arguments对象其实还有一个callee属性,是一个指向arguments对象所在函数的指针。

阶乘函数:

function factorial(num) {
 if (num <= 1) {
  return 1;
 } else {
  return num* factorial(num - 1);
 }
}

使用 arguments.callee 就可以让函数逻辑与函数名解耦:

function factorial(num) {
 if (num <= 1) {
  return 1;
 } else {
  return num * arguments.callee(num - 1);
 }
}

this

特殊的对象:this。在标准函数中,this引用的是把函数当成方法调用的上下文对象,这时候通常称其为this值(在网页的全局上下文中调用函数时,this指向windows)。

window.color = 'red'; 
let o = { 
 color: 'blue' 
}; 

function sayColor() { 
 console.log(this.color); 
} 

sayColor(); // 'red' 

o.sayColor = sayColor; 
o.sayColor(); // 'blue'

在箭头函数中,this引用的是定义箭头函数的上下文。

事件回调定时回调中调用某个函数时,this 值指向的并非想要的对象。使用箭头函数可以解决问题。

箭头函数中的this会保留定义该函数时的上下文。

function King() { 
 this.royaltyName = 'Henry'; 
 // this 引用 King 的实例
 setTimeout(() => console.log(this.royaltyName), 1000); 
}

function Queen() { 
 this.royaltyName = 'Elizabeth'; 
 // this 引用 window 对象
 setTimeout(function() { console.log(this.royaltyName); }, 1000); 
} 

new King(); // Henry 
new Queen(); // undefined

caller

ECMAScript5 也会给函数对象上添加一个属性:caller。虽然ECMAScript3中并没有定义,但所有浏览器除了早期版本的Opera都支持这个属性。

这个属性引用的是调用当前函数的函数,或者如果是在全局作用域中调用的则为null。

function outer() { 
 inner(); 
} 
function inner() { 
 console.log(inner.caller); 
} 
outer();

// 代码会显示 outer()函数的源代码
VM3252:5 ƒ outer() { 
 inner(); 
}

如果要降低耦合度,则可以通过 arguments.callee.caller 来引用同样的值。

function outer() { 
 inner(); 
} 

function inner() { 
console.log(arguments.callee.caller); 
} 

outer();

在严格模式下访问 arguments.callee 会报错。

ECMAScript 5 也定义了 arguments.caller,但在严格模式下访问它会报错,在非严格模式下则始终是 undefined。

严格模式下还有一个限制,就是不能给函数的 caller 属性赋值,否则会导致错误。

new.target

ECMAScript中的函数始终可以作为构造函数实例化一个新对象,也可以作为普通函数被调用。ECMAScript6新增了监测函数是否使用new关键字调用的new.target属性。

如果函数是正常的,则new.target的值是undefined;如果是使用new关键字调用的,则new.target将引用被调用的构造函数。

function King() { 
 if (!new.target) { 
 throw 'King must be instantiated using "new"' 
 } 
 console.log('King instantiated using "new"'); 
} 

new King(); // King instantiated using "new" 
King(); // Error: King must be instantiated using "new"

函数属性和方法

ECMAScript 中的函数是对象,因此有属性和方法。每个函数都有两个属性:length和 prototype。

prototype 是保存引用类型所有实例方法的地方,这意味着 toString()、valueOf()等方法实际上都保存在 prototype 上,进而由所有实例共享。

在 ECMAScript 5 中,prototype 属性是不可枚举的,因此使用 for-in 循环不会返回这个属性。

函数还有两个方法:apply()和 call()。

这两个方法都会以指定的 this 值来调用函数,即会设置调用函数时函数体内 this 对象的值。apply()方法接收两个参数:函数内 this 的值和一个参数数组。第二个参数可以是 Array 的实例,但也可以是 arguments 对象。通过 call()向函数传参时,必须将参数一个一个地列出来。

apply()和 call()真正强大的地方并不是给函数传参,而是控制函数调用上下文即函数体内 this 值的能力。

ECMAScript 5 出于同样的目的定义了一个新方法:bind()。bind()方法会创建一个新的函数实例,其 this 值会被绑定到传给 bind()的对象。

未完结!更多内容尽情期待下一节~

【深入理解JS核心技术】欢迎各位观众老爷,求点赞,求关注,求转发~

【深入手写JS原生API】1.手写call和apply

手写call和apply

call和apply是为了改变某个函数运行时的上下文而存在的,就是为了改变函数体内部this的指向。this是你想指定的上下文,call需要把参数按顺序传递进去,而apply则是把参数放在数组里。

call()apply()的区别在于,call()方法接受的是若干个参数的列表,而apply()方法接受的是一个包含多个参数的数组。

eg:

var func = function(arg1, arg2) {
 ...
};

func.call(this, arg1, arg2); // 使用 call,参数列表
func.apply(this, [arg1, arg2]); // 使用apply, 参数数组

使用场景:

高逼格合并两个数组:

var arr1 = ['a', 'b'];
var arr2 = ['c', 'd'];

// 将arr2放入到arr1
// 相当于arr1.push('c', 'd');
Array.prototype.push.apply(arr1, arr2);

arr1;
// ['a', 'b', 'c', 'd']

但是第二个数组太大时也不建议使用这个方法来合并数组,因一个函数能够接受的参数个数是有限制的,不同的引擎有不同的限制

怪异的使用来获取数组中的最大值和最小值

eg:

var nums = [2, 4, 5, 223, -34];
Math.max.apply(Math, nums); // 223
Math.max.call(Math, 2, 4, 5, 233, -34); // 223

// es6
Math.max.call(Math, ...nums); // 233

因数组nums没有max方法,使用Math上的,借助 call 和 apply 使用Math.max方法

判断是否是数组

function isArray(obj) {
 return Object.prototype.toString.call(obj) === '[object Array]'
}
isArray([1,2,3]);
// true

调用 父构造函数 实现继承

function SuperType() {
 this.color = ['red', 'green', 'blue'];
}
function SubType() {
 // 继承自SuperType
SuperType.call(this);
}

var instance1 = new SubType();
instance1.color.push('black');
console.log(instance1.color);
// ['red', 'green', 'blue', 'black']

var instance2 = new SubType();
console.log(instance2.color);
// ['red', 'green', 'blue']

在子构造函数中,通过调用父构造函数的call方法来实现继承,则SubType的每个实例都会将SuperType中的属性复制一份。

缺点:只能继承父类的实例属性和方法,不能继承原型属性和方法;无法实现复用,每个子类都有父类实例函数的副本,影响性能。

call

看个🌰 :

var value = 1;
var foo = {
 value: 2
};
function bar() {
 console.log(this.value);
}
bar.call(foo); // 2

通过上方知道,call()主要有两点:

  1. call() 改变了this的指向
  2. 函数bar执行了

模拟实现第一步:

上面改变如下:

bar.call(foo); ,foo为对象,bar()函数调用在这个对象里

var foo = {
 value: 2,
 bar: function() {
  console.log(this.value);
 }
};
foo.bar(); // 2

这样写有个坏处就是,给foo对象额外添加了一个属性,这是不需要的,就使用delete删除就好了

所以实现模拟就是:

  • 第一步:将函数设置为对象的属性,foo.fn = bar
  • 第二步:执行函数:foo.fn()
  • 第三步:删除函数:delete foo.fn

实现一下:

Function.prototype.myCall = function(context) {
 // 首先获取调用call的函数, this就是,用this可以获取
context.fn = this; // foo.fn = bar
context.fn(); // foo.fn()
delete context.fn; // delete foo.fn
}

// 测一下
var value = 0;
var foo = {
 value: 3
}
function bar() {
 console.log(this.value);
}
bar.myCall(foo); // 3

运行成功

有个问题是函数bar不能接受参数,我们了解可以使用arguments中获取参数,取出第二个到最后一个参数放到数组中,为什么要抛弃第一个参数,因为第一个参数是我们需要的this啊。

es3:

var args = [];
for (let i = 1, len = arguments.length; i < len; i++) {
 args.push(arguments[i]);
}

参数弄好了,接下来执行函数 context.fn()

改编一下:

Function.prototype.myCall2 = function(context) {
 context.fn = this;
 var args = [];
 for(let i = 1, len = arguments.length; i < len; i++) {
  args.push('arguments[' + i + ']');
 }
 // 采用 eval 方法来实现,拼成一个函数
 eval('context.fn(' + args + ')');
 delete context.fn;
}

var foo = {
 value: 1
};

function bar(name, age) {
 console.log(name);
 console.log(age);
 console.log(this.value);
}
bar.myCall2(foo, 'dada', 18);
// dada
// 18
// 1

这就完了吗,没那么简单。

  • 第一,this参数可以传null或者undefined,这个时候的this指向window
  • 第二,this参数可以传基本类型数据,原生的 call会主动用 Object() 转换
  • 第三,函数是可以有返回值的

如何理解呢???

// 实现步骤
Function.prototype.myCall3 = function(context) {
 // 判断context是否存在 存在主动转换Object()
 context = context ? Object(context) : window; // 第一, 第二
 context.fn = this;

 var args = [];
 for(let i = 1, len = arguments.length; i < len; i++) {
  args.push('arguments[' + i + ']');
 }

 let result = eval('context.fn(' + args + ')');
 delete context.fn
 return result; // 第三
}

// 测试
var value = 2;
var obj = {
 value: 1
}
function bar(name, age) {
 console.log(this.value);
 return {
  value: this.value,
  name: name,
  age: age
 }
}
function foo() {
 console.log(this);
}

bar.myCall3(null); // 2

foo.myCall3(123); // Number {123, fn: ƒ}

bar.myCall3(obj, 'dada', 18); // 1 {value: 1, name: 'dada', age: 18}

call模拟实现和apply模拟实现

call模拟es3版本:

Function.prototype.call = function (context) {
    // 判断是否存在,context 不为null或者undefined ,基本类型主动调用Object(context)
    // 不存在赋值 window
    context = context ? Object(context) : window; 
    // 属性fn 被赋值 this
    context.fn = this;
    // 收集参数
    var args = [];
    // 除掉第一个this,函数赋值给fn外,接收参数
    for(var i = 1, len = arguments.length; i < len; i++) {
        args.push('arguments[' + i + ']');
    }
    // 创建变量返回结果, eval执行函数
    var result = eval('context.fn(' + args +')');
    // 删除副作用,额外添加的去掉
    delete context.fn
    // 返回结果
    return result;
}

es6版本:

Function.prototype.call = function (context) {
  context = context ? Object(context) : window; 
  context.fn = this;

  let args = [...arguments].slice(1);
  let result = context.fn(...args);

  delete context.fn
  return result;
}

apply模拟实现:

es3版本:

Function.prototype.apply = function (context, arr) {
    context = context ? Object(context) : window; 
    context.fn = this;

    var result;
    // 判断是否存在第二个参数
    if (!arr) {
        result = context.fn();
    } else {
        var args = [];
        for (var i = 0, len = arr.length; i < len; i++) {
            args.push('arr[' + i + ']');
        }
        result = eval('context.fn(' + args + ')');
    }

    delete context.fn
    return result;
}

es6版本:

Function.prototype.apply = function (context, arr) {
    context = context ? Object(context) : window; 
    context.fn = this;
  
    let result;
    if (!arr) {
        result = context.fn();
    } else {
        result = context.fn(...arr);
    }
      
    delete context.fn
    return result;
}

未完结!更多内容尽情期待下一节~

【深入手写JS原生API】欢迎各位观众老爷,求点赞,求关注,求转发~

集合引用类型

  1. 对象
  2. 数组 和 定型数组
  3. Map,WeakMap,Set 和 WeakSet 类型

Object

Object 是 ECMAScript 中最常用的类型之一。

显示地 创建 Object的实例 有两种方式。

  1. 使用 new 操作符 和 Object 构造函数。
let person = new Object();
person.name = 'webVueBlog';
person.age = 12;
  1. 使用 对象字面量 表示法。(对象字面量 是 对象定义的 简写形式, 目的 为了简化包含大量属性的对象的创建)
let person = {
 name: 'webVueBlog',
 age: 29
};

在ECMAScript中,表达式上下文 指的是 期待返回值的上下文。

在对象字面量表示法中,属性名可以是字符串 或 数值。注意,数值属性会自动转换未字符串。

let person = {}; // 与new Object() 相同

注意 在使用对象字面量表示法定义对象时,并不会实际调用 Object 构造函数。

属性可以通过 点语法 来存取, 也可以通过 中括号 来存取属性。(使用中括号的主要优势是可以通过变量访问属性)

Array

创建数组:

  1. 使用Array构造函数: let colors = new Array();
  2. length属性会自动创建并设置为传入的这个数值:let colors = new Array(20);
  3. 可以给 Array 构造函数传入要保存的元素
  4. 使用 Array 构造函数时,可以省略 new 操作符

也可以使用 数组字面量 表示法。

如: let colors = ["red", "blue", "green"];

使用数组字面量表示法创建数组不会调用 Array 构造函数

ES6+

用于创建数组的静态方法: from() 和 of()

from() 用于将 类数组结构 转换 为 数组实例。

of() 用于将一组参数 转换为 数组实例。

Array.from() 的第一个参数时一个类数组对象,即为任何可迭代的结构,或者有一个 length 属性 和 可索引元素的结构。

// 字符串会拆分为单字符数组
console.log(Array.from('Matt')); // ['M', 'a', 't', 't'];

// 可以使用 from() 将集合和映射转换为一个新数组
const m = new Map().set(1,2).set(3,4);
const s = new Set().add(1).add(2).add(3).add(4);
console.log(Array.from(m)); // [[1,2], [3,4]];
console.log(Array.from(s)); // [1,2,3,4];

// Array.from()对现有数组执行浅复制
const a1 = [1,2,3,4];
const a2 = Array.from(a1);
console.log(a1); // [1,2,3,4]
alert(a1===a2); // false

// 可以使用任何可迭代对象
const iter = {
 *[Symbol.iterator]() {
   yield 1;
   yield 2;
   yield 3;
   yield 4;
 }
};
console.log(Array.from(iter)); // [1,2,3,4]

// arguments 对象可以被轻松地转换为数组
function getArgsArray() {
 return Array.from(arguments);
}
console.log(getArgsArray(1,2,3,4)); // [1,2,3,4]

// from() 也能转换带有必要属性的自定义对象
const arrayLikeObject = {
 0: 1,
 1: 2,
 2: 3,
 3: 4,
 length: 4
};
console.log(Array.from(arrayLikeObject)); // [1,2,3,4]

Array.from() 可以接收第二个 可选的映射函数 参数。就不用使用 Array.from().map() 这样了。

可以接收第三个可选参数, 用于指定 映射函数中 this 的值。(重写的 this 值在箭头函数中不适用)

const a1 = [1,2,3,4];
const a2 = Array.from(a1, x=>x*2);
const a3 = Array.from(a1, function(x) { return x*this.exponent }, {exponent:2});

Array.of()可以把一组参数转换为数组。

替换之前的 Array.prototype.slice.call(arguments)

console.log(Array.of(1,2,3,4)); // [1,2,3,4]
console.log(Array.of(undefined)); // [undefined];

数组空位

创建空位数组:

const options = [,,,,];

ES6 将空位当成存在的 undefined 的元素

const options = [1,,,,5];
for (const option of options) {
 console.log(option === undefined);
}
// false
// true
// true
// true
// false
const a = Array.from([,,,]); // 使用 ES6 的 Array.from()创建的包含 3 个空位的数组
for (const val of a) {
 alert(val === undefined);
}
// true
// true
// true
alert(Array.of(...[,,,])); // [undefined, undefined, undefined]
for (const [index, value] of options.entries()) {
 alert(value);
}
// 1
// undefined
// undefined
// undefined
// 5

ES6之前:

const options = [1,,,,5];
// map()会跳过空位置
console.log(options.map(() => 6)); // [6, undefined, undefined, undefined, 6]
// join()视空位置为空字符串
console.log(options.join('-')); // "1----5"

数组索引

检测数组

使用 instanceof 操作符:

if (value instanceof Array) {
 // 操作数组
}

使用 instanceof 的问题是假定只有一个全局执行上下文。

解决问题,提供了 Array.isArray() 方法,目的时确定 一个值 是否为数组,不用管它时哪个全局执行上下文中创建的。

if (Array.isArray(values)) {
 // 操作数组
}

迭代器方法

ES6 中,Array原型上 有 3 个用于检索数组内容的方法。

  1. keys() 返回数组索引的迭代器
  2. values() 返回数组元素的迭代器
  3. entries() 返回索引键值对的迭代器
const a = ["foo", "bar", "baz", "qux"];
// 因为这些方法都返回迭代器,所以可以将它们的内容
// 通过 Array.from()直接转换为数组实例
const aKeys = Array.from(a.keys());
const aValues = Array.from(a.values());
const aEntries = Array.from(a.entries());
console.log(aKeys); // [0, 1, 2, 3]
console.log(aValues); // ["foo", "bar", "baz", "qux"]
console.log(aEntries); // [[0, "foo"], [1, "bar"], [2, "baz"], [3, "qux"]]

使用 ES6 的解构

const a = ["foo", "bar", "baz", "qux"];
for (const [idx, element] of a.entries()) {
 alert(idx);
 alert(element);
}
// 0
// foo
// 1
// bar
// 2
// baz
// 3
// qux 

复制和填充方法

ES6 +

  1. 批量复制方法 copyWithin()
  2. 填充数组方法 fill()

都需要指定既有数组实例上的一个范围,包含开始索引,不包含结束索引。使用这个方法不会改变数组的大小

const zeroes = [0, 0, 0, 0, 0];
// 用 5 填充整个数组
zeroes.fill(5);
console.log(zeroes); // [5, 5, 5, 5, 5]
zeroes.fill(0); // 重置
// 用 6 填充索引大于等于 3 的元素
zeroes.fill(6, 3);
console.log(zeroes); // [0, 0, 0, 6, 6]
zeroes.fill(0); // 重置
// 用 7 填充索引大于等于 1 且小于 3 的元素
zeroes.fill(7, 1, 3);
console.log(zeroes); // [0, 7, 7, 0, 0];
zeroes.fill(0); // 重置
// 用 8 填充索引大于等于 1 且小于 4 的元素
// (-4 + zeroes.length = 1)
// (-1 + zeroes.length = 4)
zeroes.fill(8, -4, -1);
console.log(zeroes); // [0, 8, 8, 8, 0];

fill()静默忽略超出数组边界、零长度及方向相反的索引范围:

const zeroes = [0, 0, 0, 0, 0];
// 索引过低,忽略
zeroes.fill(1, -10, -6);
console.log(zeroes); // [0, 0, 0, 0, 0]
// 索引过高,忽略
zeroes.fill(1, 10, 15);
console.log(zeroes); // [0, 0, 0, 0, 0]
// 索引反向,忽略
zeroes.fill(2, 4, 2);
console.log(zeroes); // [0, 0, 0, 0, 0]
// 索引部分可用,填充可用部分
zeroes.fill(4, 3, 10)
console.log(zeroes); // [0, 0, 0, 4, 4]

copyWithin()会按照指定范围浅复制数组中的部分内容,然后将它们插入到指
定索引开始的位置。

let ints,
 reset = () => ints = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
reset();
// 从 ints 中复制索引 0 开始的内容,插入到索引 5 开始的位置
// 在源索引或目标索引到达数组边界时停止
ints.copyWithin(5);
console.log(ints); // [0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
reset();
// 从 ints 中复制索引 5 开始的内容,插入到索引 0 开始的位置
ints.copyWithin(0, 5);
console.log(ints); // [5, 6, 7, 8, 9, 5, 6, 7, 8, 9]
reset();
// 从 ints 中复制索引 0 开始到索引 3 结束的内容
// 插入到索引 4 开始的位置
ints.copyWithin(4, 0, 3);
alert(ints); // [0, 1, 2, 3, 0, 1, 2, 7, 8, 9]
reset();
// JavaScript 引擎在插值前会完整复制范围内的值
// 因此复制期间不存在重写的风险
ints.copyWithin(2, 0, 6);
alert(ints); // [0, 1, 0, 1, 2, 3, 4, 5, 8, 9]
reset();
// 支持负索引值,与 fill()相对于数组末尾计算正向索引的过程是一样的
ints.copyWithin(-4, -7, -3);
alert(ints); // [0, 1, 2, 3, 4, 5, 3, 4, 5, 6] 

copyWithin()静默忽略超出数组边界、零长度及方向相反的索引范围

let ints,
 reset = () => ints = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
reset();
// 索引过低,忽略
ints.copyWithin(1, -15, -12);
alert(ints); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
reset()
// 索引过高,忽略
ints.copyWithin(1, 12, 15);
alert(ints); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
reset();
// 索引反向,忽略
ints.copyWithin(2, 4, 2);
alert(ints); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
reset();
// 索引部分可用,复制、填充可用部分
ints.copyWithin(4, 7, 10)
alert(ints); // [0, 1, 2, 3, 7, 8, 9, 7, 8, 9];

数组中 转换方法

  1. valueOf() 返回的还是数组本身
  2. toString() 返回由数组中 每个值 的等效字符串拼接而成的一个逗号分隔的 字符串。(就是说,对数组的每个值都会调用其 toString() 方法,已得到最终的字符串。
  3. toLocaleString()

toLocaleString()方法也可能返回跟 toString()和 valueOf()相同的结果,但也不一定。

let person1 = {
 toLocaleString() {
 return "Nikolaos";
 },
 toString() {
 return "Nicholas";
 }
};
let person2 = {
 toLocaleString() {
 return "Grigorios";
 },
 toString() {
 return "Greg";
 }
};
let people = [person1, person2];
alert(people); // Nicholas,Greg
alert(people.toString()); // Nicholas,Greg
alert(people.toLocaleString()); // Nikolaos,Grigorios

join()方法接收一个参数,即字符串分隔符,返回包含所有项的字符串。

栈方法

栈是一种后进先出(LIFO,Last-In-First-Out)的结构

数据项的插入(称为推入,push)和删除(称为弹出,pop)只在栈的一个地方发生,即栈顶。ECMAScript 数组提供了 push()和 pop()方法,以实现类似栈的行为。

push()方法接收任意数量的参数,并将它们添加到数组末尾,返回数组的最新长度。pop()方法则用于删除数组的最后一项,同时减少数组的 length 值,返回被删除的项。

队列方法

队列以先进先出(FIFO,First-In-First-Out)形式限制访问。队列在列表末尾添加数据,但从列表开头获取数据。

使用 shift()和 push(),可以把数组当成队列来使用。

排序方法

对元素重新排序:reverse()和 sort()。

  1. reverse()方法就是将数组元素反向排列。
  2. 默认情况下,sort()会按照升序重新排列数组元素,即最小的值在前面,最大的值在后面。(sort()会在每一项上调用 String()转型函数,然后比较字符串来决定顺序。即使数组的元素都是数值,也会先把数组转换为字符串再比较、排序。)

sort()方法可以接收一个比较函数,用于判断哪个值应该排在前面。

比较函数接收两个参数,如果第一个参数应该排在第二个参数前面,就返回负值;如果两个参数相等,就返回 0;如果第一个参数应该排在第二个参数后面,就返回正值。

function compare(value1, value2) {
 if (value1 < value2) {
 return -1;
 } else if (value1 > value2) {
 return 1;
 } else {
 return 0;
 }
} 
let values = [0, 1, 5, 10, 15];
values.sort(compare);
alert(values); // 0,1,5,10,15 

操作方法

concat()方法可以在现有数组全部元素基础上创建一个新数组。

let colors = ["red", "green", "blue"];
let colors2 = colors.concat("yellow", ["black", "brown"]);
console.log(colors); // ["red", "green","blue"]
console.log(colors2); // ["red", "green", "blue", "yellow", "black", "brown"]

打平数组参数的行为可以重写,方法是在参数数组上指定一个特殊的符号:Symbol.isConcatSpreadable。这个符号能够阻止concat()打平参数数组。相反,把这个值设置为 true 可以强制打平类数组对象

let colors = ["red", "green", "blue"];
let newColors = ["black", "brown"];
let moreNewColors = {
 [Symbol.isConcatSpreadable]: true,
 length: 2,
 0: "pink",
 1: "cyan"
};
newColors[Symbol.isConcatSpreadable] = false;
// 强制不打平数组
let colors2 = colors.concat("yellow", newColors);
// 强制打平类数组对象
let colors3 = colors.concat(moreNewColors);
console.log(colors); // ["red", "green", "blue"]
console.log(colors2); // ["red", "green", "blue", "yellow", ["black", "brown"]]
console.log(colors3); // ["red", "green", "blue", "pink", "cyan"]

slice()用于创建一个包含原有数组中一个或多个元素的新数组。

slice()方法可以接收一个或两个参数:返回元素的开始索引和结束索引。

slice()返回从开始索引到结束索引对应的所有元素,其中不包含结束索引对应的元素。

不影响原始数组

let colors = ["red", "green", "blue", "yellow", "purple"];
let colors2 = colors.slice(1);
let colors3 = colors.slice(1, 4);
alert(colors2); // green,blue,yellow,purple
alert(colors3); // green,blue,yellow 

如果结束位置小于开始位置,则返回空数组。

最强大的数组方法就属 splice()了

splice()的主要目的是在数组中间插入元素,但有 3 种不同的方式使用这个方法。

  1. 删除。需要给 splice()传 2 个参数:要删除的第一个元素的位置和要删除的元素数量。
  2. 插入。需要给 splice()传 3 个参数:开始位置、0(要删除的元素数量)和要插入的元素
  3. 替换。splice()在删除元素的同时可以在指定位置插入新元素,同样要传入 3 个参数:开始位置、要删除元素的数量和要插入的任意多个元素。
let colors = ["red", "green", "blue"];
let removed = colors.splice(0,1); // 删除第一项
alert(colors); // green,blue
alert(removed); // red,只有一个元素的数组
removed = colors.splice(1, 0, "yellow", "orange"); // 在位置 1 插入两个元素
alert(colors); // green,yellow,orange,blue
alert(removed); // 空数组
removed = colors.splice(1, 1, "red", "purple"); // 插入两个值,删除一个元素
alert(colors); // green,red,purple,orange,blue
alert(removed); // yellow,只有一个元素的数组

搜索和位置方法

ECMAScript 提供两类搜索数组的方法:按 严格相等 搜索和按 断言函数 搜索。

提供了 3 个严格相等的搜索方法:indexOf()、lastIndexOf()和 includes()

ES7 + includes()方法

这些方法都接收两个参数:要查找的元素和一个可选的起始搜索位置。

  1. indexOf()和 includes()方法从数组前头(第一项)开始向后搜索
  2. lastIndexOf()从数组末尾(最后一项)开始向前搜索。
  3. indexOf()和 lastIndexOf()都返回要查找的元素在数组中的位置,如果没找到则返回-1。
  4. includes()返回布尔值,表示是否至少找到一个与指定元素匹配的项。
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
alert(numbers.indexOf(4)); // 3
alert(numbers.lastIndexOf(4)); // 5
alert(numbers.includes(4)); // true
alert(numbers.indexOf(4, 4)); // 5
alert(numbers.lastIndexOf(4, 4)); // 3
alert(numbers.includes(4, 7)); // false
let person = { name: "Nicholas" };
let people = [{ name: "Nicholas" }];
let morePeople = [person];
alert(people.indexOf(person)); // -1
alert(morePeople.indexOf(person)); // 0
alert(people.includes(person)); // false
alert(morePeople.includes(person)); // true 

断言函数

每个索引都会调用这个函数。断言函数的返回值决定了相应索引的元素是否被认为匹配。

断言函数接收 3 个参数:元素、索引和数组本身。

find()和 findIndex()方法使用了断言函数。这两个方法都从数组的最小索引开始。find()返回第一个匹配的元素,
findIndex()返回第一个匹配元素的索引。这两个方法也都接收第二个可选的参数,用于指定断言函数内部 this 的值。

const people = [
 {
 name: "Matt",
 age: 27
 },
 {
 name: "Nicholas",
 age: 29
 }
];
alert(people.find((element, index, array) => element.age < 28));
// {name: "Matt", age: 27}
alert(people.findIndex((element, index, array) => element.age < 28));
// 0

找到匹配项后,这两个方法都不再继续搜索。

const evens = [2, 4, 6];
// 找到匹配后,永远不会检查数组的最后一个元素
evens.find((element, index, array) => {
 console.log(element);
 console.log(index);
 console.log(array);
 return element === 4;
});
// 2
// 0
// [2, 4, 6]
// 4
// 1
// [2, 4, 6]

迭代方法

  1. every():对数组每一项都运行传入的函数,如果对每一项函数都返回 true,则这个方法返回 true。
  2. filter():对数组每一项都运行传入的函数,函数返回 true 的项会组成数组之后返回。
  3. forEach():对数组每一项都运行传入的函数,没有返回值。
  4. map():对数组每一项都运行传入的函数,返回由每次函数调用的结果构成的数组
  5. some():对数组每一项都运行传入的函数,如果有一项函数返回 true,则这个方法返回 true。

这些方法都不改变调用它们的数组。

let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let everyResult = numbers.every((item, index, array) => item > 2);
alert(everyResult); // false
let someResult = numbers.some((item, index, array) => item > 2);
alert(someResult); // true 

let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let filterResult = numbers.filter((item, index, array) => item > 2);
alert(filterResult); // 3,4,5,4,3 

let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let mapResult = numbers.map((item, index, array) => item * 2);
alert(mapResult); // 2,4,6,8,10,8,6,4,2

let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
numbers.forEach((item, index, array) => {
 // 执行某些操作
});

归并方法

数组提供了两个归并方法:reduce()和 reduceRight()

  1. reduce()方法从数组第一项开始遍历到最后一项。
  2. reduceRight()从最后一项开始遍历至第一项。

传给 reduce()和 reduceRight()的函数接收 4 个参数:上一个归并值、当前项、当前项的索引和数组本身。

let values = [1, 2, 3, 4, 5];
let sum = values.reduce((prev, cur, index, array) => prev + cur);
alert(sum); // 15 

定型数组

定型数组(typed array)是 ECMAScript 新增的结构,目的是提升向原生库传输数据的效率。

“TypedArray”类型,它所指的其实是一种特殊的包含数值类型的数组。

历史:CanvasFloatArray变成了 Float32Array,也就是今天定型数组中可用的第一个“类型”。

JavaScript 运行时使用这个类型可以分配、读取和写入数组。
这个数组可以直接传给底层图形驱动程序 API,也可以直接从底层获取到。

ArrayBuffer

Float32Array 实际上是一种“视图”,可以允许 JavaScript 运行时访问一块名为 ArrayBuffer 的预分配内存。ArrayBuffer 是所有定型数组及视图引用的基本单位。

ArrayBuffer()是一个普通的 JavaScript 构造函数,可用于在内存中分配特定数量的字节空间。

const buf = new ArrayBuffer(16); // 在内存中分配 16 字节
alert(buf.byteLength); // 16 

ArrayBuffer 一经创建就不能再调整大小。

可以使用 slice()复制其全部或部分到一个新实例中:

const buf1 = new ArrayBuffer(16);
const buf2 = buf1.slice(4, 12);
alert(buf2.byteLength); // 8

ArrayBuffer 某种程度上类似于 C++的 malloc(),但也有几个明显的区别。

  • malloc()在分配失败时会返回一个 null 指针。ArrayBuffer 在分配失败时会抛出错误。
  • malloc()可以利用虚拟内存,因此最大可分配尺寸只受可寻址系统内存限制。ArrayBuffer分配的内存不能超过 Number.MAX_SAFE_INTEGER(2的53次 - 1)字节。
  • malloc()调用成功不会初始化实际的地址。声明 ArrayBuffer 则会将所有二进制位初始化为 0。
  • 通过 malloc()分配的堆内存除非调用 free()或程序退出,否则系统不能再使用。而通过声明ArrayBuffer 分配的堆内存可以被当成垃圾回收,不用手动释放。

不能仅通过对 ArrayBuffer 的引用就读取或写入其内容。要读取或写入 ArrayBuffer,就必须通过视图。
视图有不同的类型,但引用的都是 ArrayBuffer 中存储的二进制数据。

DataView

第一种允许你读写 ArrayBuffer 的视图是 DataView。这个视图专为文件 I/O 和网络 I/O 设计,其API 支持对缓冲数据的高度控制

DataView 对缓冲内容没有任何预设,也不能迭代。

必须在对已有的 ArrayBuffer 读取或写入时才能创建 DataView 实例。这个实例可以使用全部或部分 ArrayBuffer,且维护着对该缓冲实例的引用,以及视图在缓冲中开始的位置。

const buf = new ArrayBuffer(16);
// DataView 默认使用整个 ArrayBuffer
const fullDataView = new DataView(buf);
alert(fullDataView.byteOffset); // 0
alert(fullDataView.byteLength); // 16
alert(fullDataView.buffer === buf); // true
// 构造函数接收一个可选的字节偏移量和字节长度
// byteOffset=0 表示视图从缓冲起点开始
// byteLength=8 限制视图为前 8 个字节
const firstHalfDataView = new DataView(buf, 0, 8);
alert(firstHalfDataView.byteOffset); // 0
alert(firstHalfDataView.byteLength); // 8
alert(firstHalfDataView.buffer === buf); // true

// 如果不指定,则 DataView 会使用剩余的缓冲
// byteOffset=8 表示视图从缓冲的第 9 个字节开始
// byteLength 未指定,默认为剩余缓冲
const secondHalfDataView = new DataView(buf, 8);
alert(secondHalfDataView.byteOffset); // 8
alert(secondHalfDataView.byteLength); // 8
alert(secondHalfDataView.buffer === buf); // true

要通过 DataView 读取缓冲,还需要几个组件。

  • 首先是要读或写的字节偏移量。可以看成 DataView 中的某种“地址”。
  • DataView 应该使用 ElementType 来实现 JavaScript 的 Number 类型到缓冲内二进制格式的转
    换。
  • 最后是内存中值的字节序。默认为大端字节序。
  1. ElementType

DataView 对存储在缓冲内的数据类型没有预设。它暴露的 API 强制开发者在读、写时指定一个ElementType,然后 DataView 就会忠实地为读、写而完成相应的转换。

ElementType 字节
Int8 1
Uint8 1
Int16 2
Uint16 2
Int32 4
Uint32 4
Float32 4
Float64 8

DataView 为上表中的每种类型都暴露了 get 和 set 方法,这些方法使用 byteOffset(字节偏移量)定位要读取或写入值的位置。

// 在内存中分配两个字节并声明一个 DataView
const buf = new ArrayBuffer(2);
const view = new DataView(buf);
// 说明整个缓冲确实所有二进制位都是 0
// 检查第一个和第二个字符
alert(view.getInt8(0)); // 0
alert(view.getInt8(1)); // 0
// 检查整个缓冲
alert(view.getInt16(0)); // 0
// 将整个缓冲都设置为 1
// 255 的二进制表示是 11111111(2^8 - 1)
view.setUint8(0, 255);
// DataView 会自动将数据转换为特定的 ElementType
// 255 的十六进制表示是 0xFF
view.setUint8(1, 0xFF);
// 现在,缓冲里都是 1 了
// 如果把它当成二补数的有符号整数,则应该是-1
alert(view.getInt16(0)); // -1

字节序

“字节序”指的是计算系统维护的一种字节顺序的约定。

DataView 只支持两种约定:大端字节序和小端字节序。

大端字节序也称为“网络字节序”,意思是最高有效位保存在第一个字节,而最低有效位保存在最后一个字节。小端字节序正好相反,即最低有效位保存在第一个字节,最高有效位保存在最后一个字节。

JavaScript 运行时所在系统的原生字节序决定了如何读取或写入字节,但 DataView 并不遵守这个约定。

对一段内存而言,DataView 是一个中立接口,它会遵循你指定的字节序。DataView 的所有 API 方法都以大端字节序作为默认值,但接收一个可选的布尔值参数,设置为 true 即可启用小端字节序。

// 在内存中分配两个字节并声明一个 DataView
const buf = new ArrayBuffer(2);
const view = new DataView(buf);
// 填充缓冲,让第一位和最后一位都是 1
view.setUint8(0, 0x80); // 设置最左边的位等于 1
view.setUint8(1, 0x01); // 设置最右边的位等于 1
// 缓冲内容(为方便阅读,人为加了空格)
// 0x8 0x0 0x0 0x1
// 1000 0000 0000 0001
// 按大端字节序读取 Uint16
// 0x80 是高字节,0x01 是低字节
// 0x8001 = 2^15 + 2^0 = 32768 + 1 = 32769
alert(view.getUint16(0)); // 32769
// 按小端字节序读取 Uint16
// 0x01 是高字节,0x80 是低字节
// 0x0180 = 2^8 + 2^7 = 256 + 128 = 384
alert(view.getUint16(0, true)); // 384
// 按大端字节序写入 Uint16
view.setUint16(0, 0x0004);
// 缓冲内容(为方便阅读,人为加了空格)
// 0x0 0x0 0x0 0x4
// 0000 0000 0000 0100
alert(view.getUint8(0)); // 0
alert(view.getUint8(1)); // 4
// 按小端字节序写入 Uint16
view.setUint16(0, 0x0002, true);
// 缓冲内容(为方便阅读,人为加了空格)
// 0x0 0x2 0x0 0x0
// 0000 0010 0000 0000
alert(view.getUint8(0)); // 2
alert(view.getUint8(1)); // 0 

边界情形

DataView 完成读、写操作的前提是必须有充足的缓冲区,否则就会抛出 RangeError:

const buf = new ArrayBuffer(6);
const view = new DataView(buf);
// 尝试读取部分超出缓冲范围的值
view.getInt32(4);
// RangeError
// 尝试读取超出缓冲范围的值
view.getInt32(8);
// RangeError
// 尝试读取超出缓冲范围的值
view.getInt32(-1);
// RangeError
// 尝试写入超出缓冲范围的值
view.setInt32(4, 123);
// RangeError 

DataView 在写入缓冲里会尽最大努力把一个值转换为适当的类型,后备为 0。如果无法转换,则抛出错误

const buf = new ArrayBuffer(1);
const view = new DataView(buf);

view.setInt8(0, 1.5);
alert(view.getInt8(0)); // 1

view.setInt8(0, [4]);
alert(view.getInt8(0)); // 4

view.setInt8(0, 'f');
alert(view.getInt8(0)); // 0

view.setInt8(0, Symbol());
// TypeError 

定型数组

定型数组是另一种形式的 ArrayBuffer 视图

设计定型数组的目的就是提高与 WebGL 等原生库交换二进制数据的效率。

创建定型数组的方式包括读取已有的缓冲、使用自有缓冲、填充可迭代结构,以及填充基于任意类型的定型数组。

通过.from()和.of()也可以创建定型数组

// 创建一个 12 字节的缓冲
const buf = new ArrayBuffer(12);

// 创建一个引用该缓冲的 Int32Array
const ints = new Int32Array(buf);

// 这个定型数组知道自己的每个元素需要 4 字节
// 因此长度为 3
alert(ints.length); // 3 
// 创建一个长度为 6 的 Int32Array
const ints2 = new Int32Array(6);

// 每个数值使用 4 字节,因此 ArrayBuffer 是 24 字节
alert(ints2.length); // 6

// 类似 DataView,定型数组也有一个指向关联缓冲的引用
alert(ints2.buffer.byteLength); // 24

// 创建一个包含[2, 4, 6, 8]的 Int32Array
const ints3 = new Int32Array([2, 4, 6, 8]);
alert(ints3.length); // 4

alert(ints3.buffer.byteLength); // 16
alert(ints3[2]); // 6

// 通过复制 ints3 的值创建一个 Int16Array
const ints4 = new Int16Array(ints3);
// 这个新类型数组会分配自己的缓冲
// 对应索引的每个值会相应地转换为新格式
alert(ints4.length); // 4

alert(ints4.buffer.byteLength); // 8

alert(ints4[2]); // 6

// 基于普通数组来创建一个 Int16Array
const ints5 = Int16Array.from([3, 5, 7, 9]);
alert(ints5.length); // 4
alert(ints5.buffer.byteLength); // 8
alert(ints5[2]); // 7

// 基于传入的参数创建一个 Float32Array
const floats = Float32Array.of(3.14, 2.718, 1.618);
alert(floats.length); // 3
alert(floats.buffer.byteLength); // 12
alert(floats[2]); // 1.6180000305175781

定型数组的构造函数和实例都有一个 BYTES_PER_ELEMENT 属性,返回该类型数组中每个元素的大小:

alert(Int16Array.BYTES_PER_ELEMENT); // 2
alert(Int32Array.BYTES_PER_ELEMENT); // 4

const ints = new Int32Array(1),
 floats = new Float64Array(1);
alert(ints.BYTES_PER_ELEMENT); // 4
alert(floats.BYTES_PER_ELEMENT); // 8

如果定型数组没有用任何值初始化,则其关联的缓冲会以 0 填充:

const ints = new Int32Array(4);
alert(ints[0]); // 0
alert(ints[1]); // 0
alert(ints[2]); // 0
alert(ints[3]); // 0 

定型数组行为

定型数组支持如下操作符、方法和属性:

[]
copyWithin()
entries()
every()
fill()
filter()
find()
findIndex()
forEach()
indexOf()
join()
keys()
lastIndexOf()
length
map()
reduce()
reduceRigth()
reverse()
slice()
some()
sort()
toLocaleString()
toString()
values()
const ints = new Int16Array([1, 2, 3]);
const doubleints = ints.map(x => 2*x);
alert(doubleints instanceof Int16Array); // true
const ints = new Int16Array([1, 2, 3]);
for (const int of ints) {
 alert(int);
}
// 1
// 2
// 3
alert(Math.max(...ints)); // 3

合并、复制和修改定型数组

定型数组同样使用数组缓冲来存储数据,而数组缓冲无法调整大小。因此,下列方法不适用于定型数组:

concat()
pop()
push()
shift()
spllice()
unshift()

定型数组也提供了两个新方法,可以快速向外或向内复制数据:set()和 subarray()。

set()从提供的数组或定型数组中把值复制到当前定型数组中指定的索引位置:

// 创建长度为 8 的 int16 数组
const container = new Int16Array(8);
// 把定型数组复制为前 4 个值
// 偏移量默认为索引 0
container.set(Int8Array.of(1, 2, 3, 4));
console.log(container); // [1,2,3,4,0,0,0,0]

// 把普通数组复制为后 4 个值
// 偏移量 4 表示从索引 4 开始插入
container.set([5,6,7,8], 4);
console.log(container); // [1,2,3,4,5,6,7,8]

// 溢出会抛出错误
container.set([5,6,7,8], 7);
// RangeError

subarray()执行与 set()相反的操作,它会基于从原始定型数组中复制的值返回一个新定型数组。

复制值时的开始索引和结束索引是可选的:

const source = Int16Array.of(2, 4, 6, 8);
// 把整个数组复制为一个同类型的新数组
const fullCopy = source.subarray();
console.log(fullCopy); // [2, 4, 6, 8]

// 从索引 2 开始复制数组
const halfCopy = source.subarray(2);
console.log(halfCopy); // [6, 8]

// 从索引 1 开始复制到索引 3
const partialCopy = source.subarray(1, 3);
console.log(partialCopy); // [4, 6] 

定型数组没有原生的拼接能力,但使用定型数组 API 提供的很多工具可以手动构建:

// 第一个参数是应该返回的数组类型
// 其余参数是应该拼接在一起的定型数组

function typedArrayConcat(typedArrayConstructor, ...typedArrays) {
 // 计算所有数组中包含的元素总数
 const numElements = typedArrays.reduce((x,y) => (x.length || x) + y.length);

 // 按照提供的类型创建一个数组,为所有元素留出空间
 const resultArray = new typedArrayConstructor(numElements);

 // 依次转移数组
 let currentOffset = 0;
 typedArrays.map(x => {
 resultArray.set(x, currentOffset);
 currentOffset += x.length;
 });

 return resultArray;
}

const concatArray = typedArrayConcat(Int32Array,
 Int8Array.of(1, 2, 3),
 Int16Array.of(4, 5, 6),
 Float32Array.of(7, 8, 9));
console.log(concatArray); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(concatArray instanceof Int32Array); // true 

下溢和上溢

定型数组中值的下溢和上溢不会影响到其他索引,但仍然需要考虑数组的元素应该是什么类型。

定型数组对于可以存储的每个索引只接受一个相关位,而不考虑它们对实际数值的影响。

以下代码演示了如何处理下溢和上溢:

// 长度为 2 的有符号整数数组
// 每个索引保存一个二补数形式的有符号整数
// 范围是-128(-1 * 2^7)~127(2^7 - 1)
const ints = new Int8Array(2);

// 长度为 2 的无符号整数数组
// 每个索引保存一个无符号整数
// 范围是 0~255(2^7 - 1)
const unsignedInts = new Uint8Array(2);

// 上溢的位不会影响相邻索引
// 索引只取最低有效位上的 8 位
unsignedInts[1] = 256; // 0x100
console.log(unsignedInts); // [0, 0]

unsignedInts[1] = 511; // 0x1FF
console.log(unsignedInts); // [0, 255]

// 下溢的位会被转换为其无符号的等价值
// 0xFF 是以二补数形式表示的-1(截取到 8 位),
// 但 255 是一个无符号整数
unsignedInts[1] = -1 // 0xFF (truncated to 8 bits)
console.log(unsignedInts); // [0, 255]

// 上溢自动变成二补数形式
// 0x80 是无符号整数的 128,是二补数形式的-128
ints[1] = 128; // 0x80
console.log(ints); // [0, -128]

// 下溢自动变成二补数形式
// 0xFF 是无符号整数的 255,是二补数形式的-1
ints[1] = 255; // 0xFF
console.log(ints); // [0, -1]

除了 8 种元素类型,还有一种“夹板”数组类型:Uint8ClampedArray,不允许任何方向溢出。

超出最大值 255 的值会被向下舍入为 255,而小于最小值 0 的值会被向上舍入为 0。

const clampedInts = new Uint8ClampedArray([-1, 0, 255, 256]);
console.log(clampedInts); // [0, 0, 255, 255]

按照 JavaScript 之父 Brendan Eich 的说法:“Uint8ClampedArray 完全是 HTML5canvas 元素的历史留存。除非真的做跟 canvas 相关的开发,否则不要使用它。”

Map

ECMAScript 6 的新增特性,Map 是一种新的集合类型

使用 new 关键字和 Map 构造函数可以创建一个空映射:

const m = new Map();
// 使用嵌套数组初始化映射
const m1 = new Map([ 
 ["key1", "val1"], 
 ["key2", "val2"], 
 ["key3", "val3"] 
]); 
alert(m1.size); // 3 

// 使用自定义迭代器初始化映射
const m2 = new Map({ 
 [Symbol.iterator]: function*() { 
 yield ["key1", "val1"]; 
 yield ["key2", "val2"]; 
 yield ["key3", "val3"]; 
 } 
}); 
alert(m2.size); // 3 

// 映射期待的键/值对,无论是否提供
const m3 = new Map([[]]); 
alert(m3.has(undefined)); // true 
alert(m3.get(undefined)); // undefined
  1. 使用 set()方法再添加键/值对
  2. 使用 get()和 has()进行查询
  3. 通过 size 属性获取映射中的键/值对的数量
  4. 使用 delete()和 clear()删除值
const m = new Map(); 
alert(m.has("firstName")); // false 
alert(m.get("firstName")); // undefined 
alert(m.size); // 0 

m.set("firstName", "Matt") 
 .set("lastName", "Frisbie"); 
alert(m.has("firstName")); // true 
alert(m.get("firstName")); // Matt 
alert(m.size); // 2 

m.delete("firstName"); // 只删除这一个键/值对
alert(m.has("firstName")); // false 
alert(m.has("lastName")); // true 
alert(m.size); // 1 

m.clear(); // 清除这个映射实例中的所有键/值对
alert(m.has("firstName")); // false 
alert(m.has("lastName")); // false 
alert(m.size); // 0
const m = new Map(); 

const functionKey = function() {}; 
const symbolKey = Symbol(); 
const objectKey = new Object(); 

m.set(functionKey, "functionValue"); 
m.set(symbolKey, "symbolValue"); 
m.set(objectKey, "objectValue"); 

alert(m.get(functionKey)); // functionValue 
alert(m.get(symbolKey)); // symbolValue 
alert(m.get(objectKey)); // objectValue 

// SameValueZero 比较意味着独立实例不冲突
alert(m.get(function() {})); // undefined
const m = new Map(); 

const objKey = {}, 
 objVal = {}, 
 arrKey = [], 
 arrVal = []; 

m.set(objKey, objVal); 
m.set(arrKey, arrVal); 

objKey.foo = "foo"; 
objVal.bar = "bar"; 
arrKey.push("foo"); 
arrVal.push("bar"); 

console.log(m.get(objKey)); // {bar: "bar"} 
console.log(m.get(arrKey)); // ["bar"]
const m = new Map(); 

const a = 0/"", // NaN 
 b = 0/"", // NaN 

 pz = +0, 
 nz = -0;

alert(a === b); // false 
alert(pz === nz); // true 

m.set(a, "foo"); 
m.set(pz, "bar"); 

alert(m.get(b)); // foo 
alert(m.get(nz)); // bar

顺序与迭代

映射实例可以提供一个迭代器(Iterator),能以插入顺序生成[key, value]形式的数组。可以通过 entries()方法(或者 Symbol.iterator 属性,它引用 entries())取得这个迭代器

const m = new Map([ 
 ["key1", "val1"], 
 ["key2", "val2"], 
 ["key3", "val3"] 
]); 

alert(m.entries === m[Symbol.iterator]); // true 

for (let pair of m.entries()) { 
 alert(pair); 
} 
// [key1,val1] 
// [key2,val2] 
// [key3,val3] 

for (let pair of m[Symbol.iterator]()) { 
 alert(pair); 
} 
// [key1,val1] 
// [key2,val2] 
// [key3,val3]

因为 entries()是默认迭代器,所以可以直接对映射实例使用扩展操作,把映射转换为数组

const m = new Map([ 
 ["key1", "val1"], 
 ["key2", "val2"], 
 ["key3", "val3"] 
]); 
console.log([...m]); // [[key1,val1],[key2,val2],[key3,val3]]

forEach(callback, opt_thisArg) 传入的回调接收可选的第二个参数,这个参数用于重写回调内部 this 的值

const m = new Map([ 
 ["key1", "val1"], 
 ["key2", "val2"], 
 ["key3", "val3"] 
]); 

m.forEach((val, key) => alert(`${key} -> ${val}`)); 
// key1 -> val1 
// key2 -> val2 
// key3 -> val3

keys()和 values()分别返回以插入顺序生成键和值的迭代器

const m = new Map([ 
 ["key1", "val1"], 
 ["key2", "val2"], 
 ["key3", "val3"] 
]); 

for (let key of m.keys()) { 
 alert(key); 
} 

// key1 
// key2 
// key3 

for (let key of m.values()) { 
 alert(key); 
} 

// value1 
// value2 
// value3
const m1 = new Map([ 
 ["key1", "val1"] 
]); 

// 作为键的字符串原始值是不能修改的
for (let key of m1.keys()) { 
 key = "newKey"; 
 alert(key); // newKey 
 alert(m1.get("key1")); // val1 
} 

const keyObj = {id: 1}; 

const m = new Map([ 
 [keyObj, "val1"] 
]); 

// 修改了作为键的对象的属性,但对象在映射内部仍然引用相同的值
for (let key of m.keys()) { 
 key.id = "newKey"; 
 alert(key); // {id: "newKey"} 
 alert(m.get(keyObj)); // val1 
} 

alert(keyObj); // {id: "newKey"}

选择 Object 还是 Map

  1. 内存占用: Map 大约可以比 Object 多存储 50%的键/值对。
  2. 插入性能: 如果代码涉及大量插入操作,那么显然 Map 的性能更佳。
  3. 查找速度: Object 更好一些
  4. 删除性能: 选择 Map

WeakMap

ECMAScript 6 新增的“弱映射”(WeakMap)是一种新的集合类型

WeakMap 中的“weak”(弱),描述的是 JavaScript 垃圾回收程序对待“弱映射”中键的方式。

可以使用 new 关键字实例化一个空的 WeakMap:

const wm = new WeakMap();

弱映射中的键只能是 Object 或者继承自 Object 的类型,尝试使用非对象设置键会抛出TypeError。值的类型没有限制。

const key1 = {id: 1}, 
 key2 = {id: 2},
key3 = {id: 3}; 

// 使用嵌套数组初始化弱映射
const wm1 = new WeakMap([ 
 [key1, "val1"], 
 [key2, "val2"], 
 [key3, "val3"] 
]); 

alert(wm1.get(key1)); // val1 
alert(wm1.get(key2)); // val2 
alert(wm1.get(key3)); // val3 

// 初始化是全有或全无的操作
// 只要有一个键无效就会抛出错误,导致整个初始化失败
const wm2 = new WeakMap([ 
 [key1, "val1"], 
 ["BADKEY", "val2"], 
 [key3, "val3"] 
]); 
// TypeError: Invalid value used as WeakMap key 

typeof wm2; 
// ReferenceError: wm2 is not defined 

// 原始值可以先包装成对象再用作键
const stringKey = new String("key1"); 
const wm3 = new WeakMap([ 
 stringKey, "val1" 
]); 
alert(wm3.get(stringKey)); // "val1"
  1. 使用 set()再添加键/值对
  2. 使用 get()和 has()查询
  3. 使用 delete()删除
const wm = new WeakMap(); 

const key1 = {id: 1}, 
 key2 = {id: 2}; 

alert(wm.has(key1)); // false 
alert(wm.get(key1)); // undefined 

wm.set(key1, "Matt") 
 .set(key2, "Frisbie"); 

alert(wm.has(key1)); // true 
alert(wm.get(key1)); // Matt 

wm.delete(key1); // 只删除这一个键/值对

alert(wm.has(key1)); // false 
alert(wm.has(key2)); // true

弱键

WeakMap 中“weak”表示弱映射的键是“弱弱地拿着”的。

只要键存在,键/值对就会存在于映射中,并被当作对值的引用,因此就不会被当作垃圾回收。

const wm = new WeakMap(); 
wm.set({}, "val");

因为没有指向这个对象的其他引用,所以当这行代码执行完成后,这个对象键就会被当作垃圾回收。

const wm = new WeakMap(); 
const container = { 
 key: {} 
}; 

wm.set(container.key, "val"); 
function removeReference() { 
 container.key = null; 
}

不可迭代键

因为 WeakMap 中的键/值对任何时候都可能被销毁,所以没必要提供迭代其键/值对的能力。当然,也用不着像 clear()这样一次性销毁所有键/值的方法。WeakMap 确实没有这个方法。因为不可能迭代,所以也不可能在不知道对象引用的情况下从弱映射中取得值。即便代码可以访问 WeakMap 实例,也没办法看到其中的内容。

WeakMap 实例之所以限制只能用对象作为键,是为了保证只有通过键对象的引用才能取得值。如果允许原始值,那就没办法区分初始化时使用的字符串字面量和初始化之后使用的一个相等的字符串了。

使用弱映射

  1. 私有变量
  2. DOM 节点元数据

前提很明确:私有变量会存储在弱映射中,以对象实例为键,以私有成员的字典为值。

const wm = new WeakMap();

class User { 
 constructor(id) { 
 this.idProperty = Symbol('id'); 
 this.setId(id); 
 } 
 setPrivate(property, value) { 
 const privateMembers = wm.get(this) || {}; 
 privateMembers[property] = value; 
 wm.set(this, privateMembers); 
 } 
 getPrivate(property) { 
 return wm.get(this)[property]; 
 } 
 setId(id) { 
 this.setPrivate(this.idProperty, id); 
 } 
 getId() { 
 return this.getPrivate(this.idProperty); 
 } 
}

const user = new User(123); 
alert(user.getId()); // 123 
user.setId(456); 
alert(user.getId()); // 456 

// 并不是真正私有的
alert(wm.get(user)[user.idProperty]); // 456

可以用一个闭包把 WeakMap 包装起来

const User = (() => { 
 const wm = new WeakMap(); 
 class User { 
 constructor(id) { 
 this.idProperty = Symbol('id');
this.setId(id); 
 } 
 setPrivate(property, value) { 
 const privateMembers = wm.get(this) || {}; 
 privateMembers[property] = value; 
 wm.set(this, privateMembers); 
 } 
 getPrivate(property) { 
 return wm.get(this)[property]; 
 } 
 setId(id) { 
 this.setPrivate(this.idProperty, id); 
 } 
 getId(id) { 
 return this.getPrivate(this.idProperty); 
 } 
 } 
 return User; 
})(); 

const user = new User(123); 
alert(user.getId()); // 123 
user.setId(456); 
alert(user.getId()); // 456
const m = new Map(); 
const loginButton = document.querySelector('#login'); 
// 给这个节点关联一些元数据
m.set(loginButton, {disabled: true});
const wm = new WeakMap(); 
const loginButton = document.querySelector('#login'); 
// 给这个节点关联一些元数据
wm.set(loginButton, {disabled: true});

Set

使用 new 关键字和 Set 构造函数可以创建一个空集合:

const m = new Set();
// 使用数组初始化集合
const s1 = new Set(["val1", "val2", "val3"]); 
alert(s1.size); // 3 

// 使用自定义迭代器初始化集合
const s2 = new Set({ 
 [Symbol.iterator]: function*() { 
 yield "val1"; 
 yield "val2"; 
 yield "val3"; 
 } 
}); 
alert(s2.size); // 3
  1. 使用 add()增加值
  2. 使用 has()查询
  3. 通过 size 取得元素数量
  4. 使用 delete()和 clear()删除元素
const s = new Set(); 
alert(s.has("Matt")); // false 

alert(s.size); // 0 

s.add("Matt") 
 .add("Frisbie"); 

alert(s.has("Matt")); // true 

alert(s.size); // 2 

s.delete("Matt"); 

alert(s.has("Matt")); // false 
alert(s.has("Frisbie")); // true 

alert(s.size); // 1 

s.clear(); // 销毁集合实例中的所有值

alert(s.has("Matt")); // false 
alert(s.has("Frisbie")); // false 
alert(s.size); // 0
const s = new Set(); 

const functionVal = function() {}; 
const symbolVal = Symbol(); 
const objectVal = new Object(); 

s.add(functionVal); 
s.add(symbolVal); 
s.add(objectVal); 

alert(s.has(functionVal)); // true 
alert(s.has(symbolVal)); // true 
alert(s.has(objectVal)); // true 

// SameValueZero 检查意味着独立的实例不会冲突
alert(s.has(function() {})); // false
const s = new Set(); 

const objVal = {}, 
 arrVal = []; 

s.add(objVal); 
s.add(arrVal); 

objVal.bar = "bar"; 
arrVal.push("bar"); 

alert(s.has(objVal)); // true 
alert(s.has(arrVal)); // true
const s = new Set(); 
s.add('foo'); 
alert(s.size); // 1 

s.add('foo'); 
alert(s.size); // 1 

// 集合里有这个值
alert(s.delete('foo')); // true 

// 集合里没有这个值
alert(s.delete('foo')); // false

顺序与迭代

集合实例可以提供一个迭代器(Iterator),能以插入顺序生成集合内容。可以通过 values()方法及其别名方法 keys()(或者 Symbol.iterator 属性,它引用 values())取得这个迭代器

const s = new Set(["val1", "val2", "val3"]); 
alert(s.values === s[Symbol.iterator]); // true 
alert(s.keys === s[Symbol.iterator]); // true 

for (let value of s.values()) { 
 alert(value); 
} 
// val1 
// val2 
// val3 

for (let value of s[Symbol.iterator]()) { 
 alert(value); 
} 
// val1 
// val2 
// val3

因为 values()是默认迭代器,所以可以直接对集合实例使用扩展操作,把集合转换为数组

const s = new Set(["val1", "val2", "val3"]); 
console.log([...s]); // ["val1", "val2", "val3"]
const s = new Set(["val1", "val2", "val3"]); 
for (let pair of s.entries()) { 
 console.log(pair); 
} 
// ["val1", "val1"] 
// ["val2", "val2"] 
// ["val3", "val3"]
const s = new Set(["val1", "val2", "val3"]); 
s.forEach((val, dupVal) => alert(`${val} -> ${dupVal}`)); 
// val1 -> val1 
// val2 -> val2 
// val3 -> val3

修改集合中值的属性不会影响其作为集合值的身份

const s1 = new Set(["val1"]); 

// 字符串原始值作为值不会被修改
for (let value of s1.values()) {
 value = "newVal"; 
 alert(value); // newVal 
 alert(s1.has("val1")); // true
}

const valObj = {id: 1}; 

const s2 = new Set([valObj]); 
// 修改值对象的属性,但对象仍然存在于集合中
for (let value of s2.values()) { 
 value.id = "newVal"; 
 alert(value); // {id: "newVal"} 
 alert(s2.has(valObj)); // true 
} 
alert(valObj); // {id: "newVal"}

定义正式集合操作

某些 Set 操作是有关联性的,因此最好让实现的方法能支持处理任意多个集合实例

Set 保留插入顺序,所有方法返回的集合必须保证顺序。

尽可能高效地使用内存。扩展操作符的语法很简洁,但尽可能避免集合和数组间的相互转换能够节省对象初始化成本。

不要修改已有的集合实例。union(a, b)或 a.union(b)应该返回包含结果的新集合实例。

class XSet extends Set { 
 union(...sets) { 
 return XSet.union(this, ...sets) 
 } 
 intersection(...sets) { 
 return XSet.intersection(this, ...sets); 
 } 
 difference(set) { 
 return XSet.difference(this, set); 
 } 
 symmetricDifference(set) { 
 return XSet.symmetricDifference(this, set); 
 } 
 cartesianProduct(set) { 
 return XSet.cartesianProduct(this, set); 
 } 
 powerSet() { 
 return XSet.powerSet(this); 
 }
// 返回两个或更多集合的并集
 static union(a, ...bSets) { 
 const unionSet = new XSet(a); 
 for (const b of bSets) { 
 for (const bValue of b) { 
 unionSet.add(bValue); 
 } 
 } 
 return unionSet; 
 } 
 // 返回两个或更多集合的交集
 static intersection(a, ...bSets) { 
 const intersectionSet = new XSet(a); 
 for (const aValue of intersectionSet) { 
 for (const b of bSets) { 
 if (!b.has(aValue)) { 
 intersectionSet.delete(aValue); 
 } 
 } 
 } 
 return intersectionSet; 
 } 
 // 返回两个集合的差集
 static difference(a, b) { 
 const differenceSet = new XSet(a); 
 for (const bValue of b) { 
 if (a.has(bValue)) { 
 differenceSet.delete(bValue); 
 } 
 } 
 return differenceSet; 
 } 
 // 返回两个集合的对称差集
 static symmetricDifference(a, b) { 
 // 按照定义,对称差集可以表达为
 return a.union(b).difference(a.intersection(b)); 
 } 
 // 返回两个集合(数组对形式)的笛卡儿积
 // 必须返回数组集合,因为笛卡儿积可能包含相同值的对
 static cartesianProduct(a, b) { 
 const cartesianProductSet = new XSet(); 
 for (const aValue of a) { 
 for (const bValue of b) { 
 cartesianProductSet.add([aValue, bValue]); 
 } 
 } 
 return cartesianProductSet; 
 } 
 // 返回一个集合的幂集
 static powerSet(a) { 
 const powerSet = new XSet().add(new XSet()); 
 for (const aValue of a) {
for (const set of new XSet(powerSet)) { 
 powerSet.add(new XSet(set).add(aValue)); 
 } 
 } 
 return powerSet; 
 } 
}

WeakSet

ECMAScript 6 新增的“弱集合”(WeakSet)是一种新的集合类型

WeakSet 中的“weak”(弱),描述的是 JavaScript 垃圾回收程序对待“弱集合”中值的方式。

可以使用 new 关键字实例化一个空的 WeakSet:

const ws = new WeakSet();

弱集合中的值只能是 Object 或者继承自 Object 的类型,尝试使用非对象设置值会抛出 TypeError。

const val1 = {id: 1}, 
 val2 = {id: 2}, 
 val3 = {id: 3}; 

// 使用数组初始化弱集合
const ws1 = new WeakSet([val1, val2, val3]); 
alert(ws1.has(val1)); // true 
alert(ws1.has(val2)); // true 
alert(ws1.has(val3)); // true 

// 初始化是全有或全无的操作
// 只要有一个值无效就会抛出错误,导致整个初始化失败
const ws2 = new WeakSet([val1, "BADVAL", val3]); 
// TypeError: Invalid value used in WeakSet 

typeof ws2; 
// ReferenceError: ws2 is not defined 

// 原始值可以先包装成对象再用作值
const stringVal = new String("val1"); 
const ws3 = new WeakSet([stringVal]); 
alert(ws3.has(stringVal)); // true
  1. 使用 add()再添加新值
  2. 使用 has()查询
  3. 使用 delete()删除
const ws = new WeakSet(); 
const val1 = {id: 1}, 
 val2 = {id: 2}; 

alert(ws.has(val1)); // false 
ws.add(val1)
 .add(val2); 

alert(ws.has(val1)); // true 
alert(ws.has(val2)); // true 

ws.delete(val1); // 只删除这一个值

alert(ws.has(val1)); // false 
alert(ws.has(val2)); // true 

add()方法返回弱集合实例,因此可以把多个操作连缀起来,包括初始化声明:
const val1 = {id: 1}, 
 val2 = {id: 2}, 
 val3 = {id: 3}; 

const ws = new WeakSet().add(val1); 
ws.add(val2) 
 .add(val3); 

alert(ws.has(val1)); // true 
alert(ws.has(val2)); // true 
alert(ws.has(val3)); // true

弱值

const ws = new WeakSet(); 
ws.add({});

const ws = new WeakSet(); 
const container = { 
 val: {} 
}; 
ws.add(container.val); 
function removeReference() { 
 container.val = null; 
}

不可迭代值

因为 WeakSet 中的值任何时候都可能被销毁,所以没必要提供迭代其值的能力。当然,也用不着像 clear()这样一次性销毁所有值的方法。WeakSet 确实没有这个方法。因为不可能迭代,所以也不可能在不知道对象引用的情况下从弱集合中取得值。即便代码可以访问 WeakSet 实例,也没办法看到其中的内容。

WeakSet 之所以限制只能用对象作为值,是为了保证只有通过值对象的引用才能取得值。如果允许原始值,那就没办法区分初始化时使用的字符串字面量和初始化之后使用的一个相等的字符串了。

使用弱集合

const disabledElements = new Set(); 
const loginButton = document.querySelector('#login'); 
// 通过加入对应集合,给这个节点打上“禁用”标签
disabledElements.add(loginButton);

const disabledElements = new WeakSet(); 
const loginButton = document.querySelector('#login'); 
// 通过加入对应集合,给这个节点打上“禁用”标签
disabledElements.add(loginButton);

迭代与扩展操作

  1. Array
  2. 所有定型数组
  3. Map
  4. Set
let iterableThings = [ 
 Array.of(1, 2), 
 typedArr = Int16Array.of(3, 4), 
 new Map([[5, 6], [7, 8]]), 
 new Set([9, 10]) 
]; 
for (const iterableThing of iterableThings) { 
 for (const x of iterableThing) { 
 console.log(x); 
 } 
} 
// 1 
// 2 
// 3 
// 4 
// [5, 6] 
// [7, 8] 
// 9 
// 10
let arr1 = [1, 2, 3]; 
let arr2 = [...arr1]; 
console.log(arr1); // [1, 2, 3] 
console.log(arr2); // [1, 2, 3] 
console.log(arr1 === arr2); // false
let map1 = new Map([[1, 2], [3, 4]]); 
let map2 = new Map(map1); 
console.log(map1); // Map {1 => 2, 3 => 4} 
console.log(map2); // Map {1 => 2, 3 => 4}
let arr1 = [1, 2, 3]; 
let arr2 = [0, ...arr1, 4, 5]; 
console.log(arr2); // [0, 1, 2, 3, 4, 5]

let arr1 = [{}]; 
let arr2 = [...arr1]; 
arr1[0].foo = 'bar'; 
console.log(arr2[0]); // { foo: 'bar' }
let arr1 = [1, 2, 3]; 
// 把数组复制到定型数组
let typedArr1 = Int16Array.of(...arr1); 
let typedArr2 = Int16Array.from(arr1); 
console.log(typedArr1); // Int16Array [1, 2, 3] 
console.log(typedArr2); // Int16Array [1, 2, 3] 

// 把数组复制到映射
let map = new Map(arr1.map((x) => [x, 'val' + x])); 
console.log(map); // Map {1 => 'val 1', 2 => 'val 2', 3 => 'val 3'} 

// 把数组复制到集合
let set = new Set(typedArr2); 
console.log(set); // Set {1, 2, 3} 

// 把集合复制回数组
let arr2 = [...set]; 
console.log(arr2); // [1, 2, 3]

👌

142. 环形链表 II

给定一个链表的头节点  head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

不允许修改 链表。

示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。

示例 2:
输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。

示例 3:
输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。
 

提示:

链表中节点的数目范围在范围 [0, 104] 内
-105 <= Node.val <= 105
pos 的值为 -1 或者链表中的一个有效索引
 

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} head
 * @return {ListNode}
 142. 环形链表 II
 */
 // 我们上节讲到第一种方法是 用一个数据结构去存储
 // 链表的每个节点 如果它走到了 最后的一个空节点就说明它没有环形
 // 如果它第二次走到一个相同的节点就说明 它具有环
 // 第二次走到的这个节点就是入环的节点
var detectCycle = function(head) {
    if(!head || head.next === null) return null
    // 我们使用Map结构来进行存储
    // 因为Map结构在查找的时候效率比较高
    const mapper = new Map()
    let p = head
    while(p) {
        // 我们看一下这个p节点是不是在Map里面
        // 如果不是的话我们就把它存到Map里面去
        if(!mapper.has(p)) {
            mapper.set(p, p)
            // 然后把p指向它的下一个节点
            p = p.next
        } else {
            // 否则的话就说明Map里面已经存在这个节点
            // 而且这个节点就是它的入环点
            return p
        }
    }
    // 如果跳出了循环呢 就说明它已经走到了空节点
    // 链表的末尾一个空节点
    // 所以就证明它是没有环的
    // 然后我们直接返回null就好了
    return null
};

26. ES6 中的类是什么

在 ES6 中,Javascript 类主要是 JavaScript 现有的基于原型的继承的语法糖。例如,用函数表达式编写的基于原型的继承,如下所示,

function Bike(model, color) {
  this.model = model;
  this.color = color;
}

Bike.prototype.getDetails = function () {
  return this.model + " bike has" + this.color + " color";
};

而 ES6 类可以定义为替代

class Bike {
  constructor(color, model) {
    this.color = color;
    this.model = model;
  }

  getDetails() {
    return this.model + " bike has" + this.color + " color";
  }
}

32. 如何使用 service worker 操作 DOM

Service Worker 不能直接访问 DOM。但是它可以通过响应通过接口发送的消息与它控制的页面进行通信postMessage,并且这些页面可以操作 DOM。

【深入理解JS核心技术】7. 切片和拼接有什么区别

数组切片 slice()方法将数组中的选定元素作为新数组对象返回。

数组拼接 splice()方法用于在数组中添加/删除项目,然后返回删除的项目。

注意: Slice 方法不会改变原始数组,但它会将子集作为新数组返回。

注意: Splice 方法修改原始数组并返回删除的数组。

slice() 不修改原始数组(不可变);返回原始数组的子集;用于从数组中挑选元素。

splice() 修改原始数组(可变);将删除的元素作为数组返回,用于在数组中插入或删除元素。

let arrayIntegers1 = arrayIntegers.slice(0, 2); 
// returns [1,2]

let arrayIntegers1 = arrayIntegersOriginal1.splice(0, 2); 
// returns [1, 2]; original array: [3, 4, 5]

未完结!更多内容尽情期待下一节~

【深入理解JS核心技术】欢迎各位观众老爷,求点赞,求关注,求转发~

【深入理解JS核心技术】18. let 和 var 有什么区别

  1. var: 从JavaScript开始就可以使用;它具有功能范围;变量将被提升。
  2. let: 作为ES6的一部分引入;它具有块范围;已提升但未初始化。
function userDetails(username) {
  if (username) {
    console.log(salary); // undefined due to hoisting
    console.log(age); // ReferenceError: Cannot access 'age' before initialization
    let age = 30;
    var salary = 10000;
  }
  console.log(salary); //10000 (accessible to due function scope)
  console.log(age); //error: age is not defined(due to block scope)
}
userDetails("John");

块级作用域与函数声明

函数能不能在块级作用域之中声明?

ES5 规定,函数只能在顶层作用域函数作用域之中声明,不能在块级作用域声明。

// 情况一
if (true) {
  function f() {}
}

// 情况二
try {
  function f() {}
} catch(e) {
  // ...
}

上面两种函数声明,根据 ES5 的规定都是非法的。

但是,浏览器没有遵守这个规定,为了兼容以前的旧代码,还是支持在块级作用域之中声明函数,因此上面两种情况实际都能运行,不会报错。

ES6引入了块级作用域,明确允许在块级作用域之中声明函数。ES6规定,块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用。

function f() { console.log('I am outside!'); }

(function () {
  if (false) {
    // 重复声明一次函数f
    function f() { console.log('I am inside!'); }
  }

  f();
}());

上面代码在 ES5 中运行,会得到“I am inside!”,因为在if内声明的函数f会被提升到函数头部,实际运行的代码如下。

// ES5 环境
function f() { console.log('I am outside!'); }

(function () {
  function f() { console.log('I am inside!'); }
  if (false) {
  }
  f();
}());

ES6 就完全不一样了,理论上会得到“I am outside!”。因为块级作用域内声明的函数类似于let,对作用域之外没有影响。但是,如果你真的在 ES6 浏览器中运行一下上面的代码,是会报错的,这是为什么呢?

// 浏览器的 ES6 环境
function f() { console.log('I am outside!'); }

(function () {
  if (false) {
    // 重复声明一次函数f
    function f() { console.log('I am inside!'); }
  }

  f();
}());
// Uncaught TypeError: f is not a function

上面的代码在 ES6 浏览器中,都会报错。

记住三条规则,只对ES6的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当作let处理。

  1. 允许在块级作用域内声明函数。
  2. 函数声明类似于var,即会提升到全局作用域或函数作用域的头部。
  3. 同时,函数声明还会提升到所在的块级作用域的头部。

根据这三条规则,浏览器的ES6环境中,块级作用域内声明的函数,行为类似于var声明的变量。上面的例子实际运行的代码如下。

// 浏览器的 ES6 环境
function f() { console.log('I am outside!'); }
(function () {
  var f = undefined;
  if (false) {
    function f() { console.log('I am inside!'); }
  }

  f();
}());
// Uncaught TypeError: f is not a function

考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。

// 块级作用域内部的函数声明语句,建议不要使用
{
  let a = 'secret';
  function f() {
    return a;
  }
}

// 块级作用域内部,优先使用函数表达式
{
  let a = 'secret';
  let f = function () {
    return a;
  };
}

另外,还有一个需要注意的地方。ES6 的块级作用域必须有大括号,如果没有大括号,JavaScript 引擎就认为不存在块级作用域。

// 第一种写法,报错
if (true) let x = 1;

// 第二种写法,不报错
if (true) {
  let x = 1;
}

上面代码中,第一种写法没有大括号,所以不存在块级作用域,而let只能出现在当前作用域的顶层,所以报错。第二种写法有大括号,所以块级作用域成立。

函数声明也是如此,严格模式下,函数只能声明在当前作用域的顶层。

// 不报错
'use strict';
if (true) {
  function f() {}
}

// 报错
'use strict';
if (true)
  function f() {}

未完结!更多内容尽情期待下一节~

【深入理解JS核心技术】欢迎各位观众老爷,求点赞,求关注,求转发~

【深入手写JS原生API】2. 手写new和bind

手写new和bind

我记得我之前上一次写什么来着~,上次写的时候还是上次呢?🤭 好像是call和apply的模拟实现呢!!!

我要复习一下下,别急

call, apply 想起来就两点:第一:改变了this的指向,第二:指向到指定的对象。

实现思路就是,第一,将函数设置为指定对象的属性;第二,执行函数;第三,删除该函数。

call()方法使用一个指定的this值和单独给出的一个或多个参数来调用一个函数。

记住:call()方法接受的是一个参数列表,而apply()方法接受的是一个包含多个参数的数组。

Function.prototype.call() : eg 🌰 如下:

function Product(name, price) {
 this.name = name;
 this.price = price;
}

function Food(name, price) {
 Product.call(this, name, price);
 this.category = 'food';
}

console.log(new Food('cheese', 5).name);
// cheese

语法:function.call(thisArg, arg1, arg2, ...)

参数:

  1. thisArg参数,在function函数运行时使用的this值。请注意,this可能不是该方法看到的实际值,如果这个函数处于非严格模式下,则指定为null或者undefined时会自动替换为指向全局对象,原始值会被包装。
  2. arg1, arg2...,为指定的参数列表。

返回值:使用调用者提供的this值和参数调用该函数的返回值。若该方法没有返回值,则返回undefined。

古灵精怪的call

是否还记得:🤔 使用call方法调用父构造函数

function Product(name, price) {
 this.name = name;
 this.price = price;
}
function Food(name, price) {
 Product.call(this, name, price);
 this.category = 'food';
}
function Toy(name, price) {
 Product.call(this, name, price);
 this.category = 'toy';
}

var cheese = new Food('feta', 5);
var fun = new Toy('robot', 40);

是否还记得:🤔 使用call方法调用匿名函数

var animals = [
 { species: 'Lion', name: 'King' },
 { species: 'Whale', name: 'Fail' }
];

for (var i = 0; i < animals.length; i++) {
 (function(i) {
  this.print = function() {
   console.log('#' + i + ' ' + this.species + ': ' + this.name);
  }
  this.print();
 }).call(animals[i], i);
}

是否还记得:🤔 使用call方法调用函数并且指定上下文的this

function greet() {
 var reply = [this.animal, 'typically sleep between', this.sleepDuration].join(' ');
 console.log(reply);
}

var obj = {
 animal: 'cats', sleepDuration: '12 and 16 hours'
}

greet.call(obj); // // cats typically sleep between 12 and 16 hours

是否还记得:🤔 使用call方法调用函数并且不指定第一个参数 argument

var sData = 'Wisen';

function display() {
  console.log('sData value is %s ', this.sData);
}

display.call();  // sData value is Wisen

严格模式下:

'use strict';

var sData = 'Wisen';

function display() {
  console.log('sData value is %s ', this.sData);
}

display.call(); // Cannot read the property of 'sData' of undefined

call手写

Function.prototype.myCall = function(thisArg, ...args) {
 // 在function函数运行时使用的this值。
 // 请注意 ⚠️ this可能不是该方法看到的实际值
 // 如果这个函数处于非严格模式下,则指定为null或undefined时会自动替换为指向全局对象
 if(thisArg === null || thisArg === null) {
  thisArg = typeof window === 'undefined' ? global : window;
 }
 // 当thisArg为原始值会被包装
 thisArg = Object(thisArg);
 // 避免覆盖thisArg上面同名的方法或属性,可以借用Symbol生成对应属性名
 let key = Symbol('fn');
 // 在指定的 thisArg 上添加方法
 thisArg[key] = this;
 // 运行函数返回结果默认值为undefined
 let result = thisArg[key](...args);
 // 删除添加的属性
 delete thisArg[key];
 // 返回结果
 return result ? result : undefined
}

可可爱爱的 apply

Function.prototype.apply() :apply() 方法调用一个具有给定 this 值的函数,以及一个数组或者类数组对象的形式提供的参数。

eg 🌰 如下:

const num = [ 23, 34, 434, 34, 3 ];
const max = Math.max.apply(null, num);
console.log(max); // 434
const min = Math.min.apply(null, num);
console.log(min); // 3

语法:func.apply(thisArg, [argsArray])

参数:

  1. thisArg参数,在 func 函数运行时使用的 this 值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。
  2. argsArray参数,是一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。如果该函数的值为null或者undefined,则表示不需要传入任何参数。

返回值:调用有指定this值和参数的函数的结果。

apply使用场景

是否还记得:🤔 用apply将数组各项添加到另一个数组

var arr = ['a', 'b'];
var val = [0, 1, 2];
arr.push.apply(arr, val);
console.log(arr); // ["a", "b", 0, 1, 2]

妈妈呀~目前只有写到这,如果你好有更多场景,请回复我哦~~~可以吗?写在下方让我知道呢

apply手写

Function.prototype.myApply = function(thisArg, args) {
 // thisArg参数,args数组或类数组参数
 if(thisArg === null || thisArg === undefined) {
  thisArg = typeof window === 'undefined' ? global : window;
 }
 thisArg = Object(thisArg);
 let key = Symbol('fn');
 thisArg[key] = this;
 let result = thisArg[key](...args);
 delete thisArg[key];
 return result ? result : undefined;
}

简写版本的call和apply手写 💯

Function.prototype.myCall = function(context) {
 var context = context || window;
 context.fn = this;
 var args = [];
 for(var i = 1, len = arguments.length; i < len; i++) {
  args.push('arguments[' + i + ']');
 }
 var result = eval('context.fn(' + args + ')');
 delete context.fn;
 return result;
}
Function.prototype.apply = function (context, arr) {
 var context = Object(context) || window;
 context.fn = this;
 var result;
 if(!arr) {
  result = context.fn();
 } else {
  var args = [];
  for(var i = 0; len = arr.length; i < len; i++) {
   args.push('arr[' + i + ']');
  }
  result = eval('context.fn(' + args + ')' );
 }
 delete context.fn;
 return result;
}

🐷 主题来啦!手写new和bind

话痨一下bind

前面 💺 复习🙏,先说说 Function.prototype.bind()因为它跟前面两个有点关系吧 👌,bind() 方法创建一个新的函数,在bind()被调用时,这个新函数的this被指定为bind()的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

让我们来🍊 不举 🌰 如下:

const module = {
  x: 42,
  getX: function() {
    return this.x;
  }
};

const unboundGetX = module.getX;
console.log(unboundGetX()); // undefined

const boundGetX = unboundGetX.bind(module);
console.log(boundGetX());
// 42

语法:function.bind(thisArg[, arg1[, arg2[, ...]]])

参数:

  1. thisArg参数,调用绑定函数时作为 this 参数传递给目标函数的值。如果使用new运算符构造绑定函数,则忽略该值。当使用bind在setTimeout中创建一个函数(作为回调提供)时,作为thisArg传递的任何原始值都将转换为 object。如果bind函数的参数列表为空,或者thisArg是null或undefined,执行作用域的this将被视为新函数的thisArg。
  2. arg1, arg2, ... ,当目标函数被调用时,被预置入绑定函数的参数列表中的参数。

返回值:返回一个原函数的拷贝,并拥有指定的this值和初始参数。

🐶 :bind()函数会创建一个新的绑定函数。绑定函数是一个怪异函数对象,它包装了原函数的对象。调用绑定函数通常会导致执行包装函数。

是否还记得:🤔 如何使用bind(),创建绑定函数

eg 🌰:

this.x = 9;    // 在浏览器中,this 指向全局的 "window" 对象
var module = {
  x: 81,
  getX: function() { return this.x; }
};

module.getX(); // 81

var retrieveX = module.getX;
retrieveX();
// 返回 9 - 因为函数是在全局作用域中调用的

// 创建一个新函数,把 'this' 绑定到 module 对象
// 新手可能会将全局变量 x 与 module 的属性 x 混淆
var boundGetX = retrieveX.bind(module);
boundGetX(); // 81

话痨一下new

new 运算符:创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例

举个🌰 :

function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}

const car1 = new Car('Eagle', 'Talon TSi', 1993);

console.log(car1.make);

语法:new constructor[([arguments])]

参数:

  1. constructor一个指定对象实例的类型的类或函数
  2. arguments一个用于被constructor调用的参数列表

new 关键字做了啥:

  1. 创建一个空的简单JavaScript对象(即{});
  2. 为步骤1新创建的对象添加属性__proto__,将该属性链接至构造函数的原型对象 ;
  3. 将步骤1新创建的对象作为this的上下文 ;
  4. 如果该函数没有返回对象,则返回this

进入new手写和bind手写

// new实现如下:
function create() {
 // 获取构造函数,同时删除arguments中第一个参数
 Con = [].shift.call(arguments);
 // 创建一个空的对象并链接到原型,obj可以访问构造函数原型中的属性
 var obj = Object.create(Con.prototype);
 // 绑定this实现继承,obj可以访问到构造函数中的属性
 var result = Con.apply(obj, arguments);
 // 优先返回构造函数返回的对象
 return result instanceof Object ? result : obj;
}
Function.prototype.bind2 = function (context) {

    if (typeof this !== "function") {
      throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fNOP = function () {};

    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    }

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;
}

未完结!更多内容尽情期待下一节~

【深入手写JS原生API】欢迎各位观众老爷,求点赞,求关注,求转发~

【深入理解JS核心技术】6. 数组拼接方法的目的是什么

splice()方法用于在数组中添加/删除项目,然后返回删除的项目。第一个参数指定插入或删除的数组位置,而可选的第二个参数指示要删除的元素数。每个附加参数都添加到数组中。

let arrayIntegersOriginal1 = [1, 2, 3, 4, 5];
let arrayIntegersOriginal2 = [1, 2, 3, 4, 5];
let arrayIntegersOriginal3 = [1, 2, 3, 4, 5];

let arrayIntegers1 = arrayIntegersOriginal1.splice(0, 2); 
// returns [1, 2]; original array: [3, 4, 5]

let arrayIntegers2 = arrayIntegersOriginal2.splice(3); 
// returns [4, 5]; original array: [1, 2, 3]

let arrayIntegers3 = arrayIntegersOriginal3.splice(3, 1, "a", "b", "c"); 
//returns [4]; original array: [1, 2, 3, "a", "b", "c", 5]

注意: Splice 方法修改原始数组并返回删除的数组。

splice()的主要目的是 在数组中间插入元素,但有3种不同的方式使用这个方法。

  1. 删除,需要给splice()传入2个参数。要删除的一个元素的位置和要删除的元素数量。可以从数组中删除任意多个元素,比如splice(0, 2)会删除前两个元素。
  2. 插入,需要给splice()传入3个参数。开始位置,0(要删除的元素数量)和要插入的元素,可以在数组中指定的位置插入元素。比如splice(2,0,'red','green')会从数组位置2开始插入字符串'red'和'green'
  3. 替换,需要给splice()传入3个参数。开始位置,要删除元素的数量和要插入的任意多个元素。

未完结!更多内容尽情期待下一节~

【深入理解JS核心技术】欢迎各位观众老爷,求点赞,求关注,求转发~

【深入理解JS核心技术】12. 什么是一阶函数

一阶函数是一个不接受另一个函数作为参数并且不返回函数作为其返回值的函数。

const firstOrder = () => console.log("I am a first order function!");

函数名

函数名就是指向函数的指针,一个函数可以有多个名称。

function sum(num1, num2) { 
 return num1 + num2; 
} 

console.log(sum(10, 10)); // 20 

let anotherSum = sum; 
console.log(anotherSum(10, 10)); // 20 

sum = null; 
console.log(anotherSum(10, 10)); // 20

ECMAScript6的所有函数对象都会暴露一个只读的name属性,其中包含关于函数的信息。多数情况下,这个属性中保存的就是一个函数标识符,或者说是一个字符串化的变量名。即使函数没有名称,也会如实显示成空字符串。如果它是使用Function构造函数创建的,则会标识成“anonymous".

function foo() {} 
let bar = function() {}; 
let baz = () => {}; 

console.log(foo.name); // foo 
console.log(bar.name); // bar 
console.log(baz.name); // baz 
console.log((() => {}).name); //(空字符串)
console.log((new Function()).name); // anonymous

如果函数是一个获取函数,设置函数,或者使用bind()实例化,那么标识符前面会加上一个前缀。

function foo() { }
console.log(foo.bind(null).name); // bound foo
let dog = {
 years: 1,
 get age() {
  return this.years;
 },
 set age(newAge) {
  this.years = newAge;
 }
}
let propertyDescriptor = Object.getOwnPropertyDescriptor(dog, 'age');
console.log(propertyDescriptor.get.name); // get age
console.log(propertyDescriptor.set.name); // set age
console.log(Object.getOwnPropertyDescriptor(dog, 'years'))

VM2829:1 
{value: 1, writable: true, enumerable: true, configurable: true}
configurable: true
enumerable: true
value: 1
writable: true

未完结!更多内容尽情期待下一节~

【深入理解JS核心技术】欢迎各位观众老爷,求点赞,求关注,求转发~

【深入理解JS核心技术】19. 选择名称let作为关键字的原因是什么

let是早期编程语言(如Scheme和Basic)采用的数学语句。let它是从已经尽可能接近传统关键字的数十种其他语言中借用的var。

与let相关就说说const命令,是ES6新增的内容。

const命令

const声明一个只读的常量。一旦声明,常量的值就不能改变。

const PI = 3.1415;
PI // 3.1415

PI = 3;
// TypeError: Assignment to constant variable.

const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。

对于const来说,只声明不赋值,就会报错。

const的作用域与let命令相同:只在声明所在的块级作用域内有效。

if (true) {
  const MAX = 5;
}

MAX // Uncaught ReferenceError: MAX is not defined

const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。

const声明的常量,也与let一样不可重复声明。

const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。

const foo = {};

// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123

// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only

上面代码中,常量foo储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把foo指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。

const a = [];
a.push('Hello'); // 可执行
a.length = 0;    // 可执行
a = ['Dave'];    // 报错

上面代码中,常量a是一个数组,这个数组本身是可写的,但是如果将另一个数组赋值给a,就会报错。

如果真的想将对象冻结,应该使用Object.freeze方法。

const foo = Object.freeze({});

// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;

上面代码中,常量foo指向一个冻结的对象,所以添加新属性不起作用,严格模式时还会报错。

下面是一个将对象彻底冻结的函数。

var constantize = (obj) => {
  Object.freeze(obj);
  Object.keys(obj).forEach( (key, i) => {
    if ( typeof obj[key] === 'object' ) {
      constantize( obj[key] );
    }
  });
};

ES6声明变量的六种方法:

ES5 只有两种声明变量的方法:var命令和function命令。ES6 除了添加let和const命令,另外两种声明变量的方法:import命令和class命令。所以,ES6 一共有 6 种声明变量的方法。

未完结!更多内容尽情期待下一节~

【深入理解JS核心技术】欢迎各位观众老爷,求点赞,求关注,求转发~

25. 什么是Hoisting

提升是一种 JavaScript 机制,其中变量、函数声明和类在代码执行之前被移动到其作用域的顶部。请记住,JavaScript 只提升声明,而不是初始化。我们举一个变量提升的简单例子,

console.log(message); //output : undefined
var message = "The variable Has been hoisted";

上面的代码对解释器来说如下所示,

var message;
console.log(message);
message = "The variable Has been hoisted";

以同样的方式,函数声明也被提升

message("Good morning"); //Good morning

function message(name) {
  console.log(name);
}

这种提升使函数在声明之前可以安全地在代码中使用。

92. 反转链表 II

给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。
 

示例 1:
输入:head = [1,2,3,4,5], left = 2, right = 4
输出:[1,4,3,2,5]

示例 2:
输入:head = [5], left = 1, right = 1
输出:[5]
 

提示:

链表中节点数目为 n
1 <= n <= 500
-500 <= Node.val <= 500
1 <= left <= right <= n
 

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} left
 * @param {number} right
 * @return {ListNode}
 92. 反转链表 II
left = 2 right = 4
定义一个虚拟的头节点

 -1 -> 1 -> 2 -> 3 -> 4 -> 5
 pre  cur
      pre  cur
      pre            cur

            2 -> 3 -> 4
            p    q
 */
var reverseBetween = function(head, left, right) {
    const newNode = new ListNode(-1, head)
    let pre = newNode, cur = head, next
    let n = right - left + 1
    while(--left) {
        pre = pre.next
        cur = cur.next
    }
    while(--n) {
        cur = cur.next
    }
    next = cur.next
    cur.next = null
    const reverseHead = reverse(pre.next, next)
    pre.next = reverseHead
    return newNode.next
};

var reverse = function (head, next) {
    let pre = head, cur = head.next
    while(head.next) {
        head.next = cur.next
        cur.next = pre
        pre = cur
        cur = head.next
    }
    head.next = next
    return pre
}

【深入理解JS核心技术】20. 如何在 switch 块中重新声明变量而不会出错

如果您尝试在 a 中重新声明变量,switch block则会导致错误,因为只有一个块。例如,下面的代码块会引发如下语法错误

let counter = 1;
switch (x) {
  case 0:
    let name;
    break;

  case 1:
    let name; // SyntaxError for redeclaration.
    break;
}

为避免此错误,您可以在 case 子句中创建一个嵌套块并创建一个新的块范围词法环境。

let counter = 1;
switch (x) {
  case 0: {
    let name;
    break;
  }
  case 1: {
    let name; // No SyntaxError for redeclaration.
    break;
  }
}

顶层对象的属性

顶层对象,在浏览器环境指的是window对象,在Node指的是global对象。ES5之中,顶层对象的属性与全局变量是等价的。

window.a = 1;
a // 1

a = 2;
window.a // 2

顶层对象的属性赋值与全局变量的赋值,是同一件事。

顶层对象的属性与全局变量挂钩,带来了几个很大的问题,首先是没法在编译时就报出变量未声明的错误,只有运行时才能知道(因为全局变量可能是顶层对象的属性创造的,而属性的创造是动态的);其次,程序员很容易不知不觉地就创建了全局变量(比如打字出错);最后,顶层对象的属性是到处可以读写的,这非常不利于模块化编程。

另一方面,window对象有实体含义,指的是浏览器的窗口对象,顶层对象是一个有实体含义的对象,也是不合适的。

ES6 为了改变这一点,一方面规定,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。

从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。

var a = 1;
// 如果在 Node 的 REPL 环境,可以写成 global.a
// 或者采用通用方法,写成 this.a
window.a // 1

let b = 1;
window.b // undefined

未完结!更多内容尽情期待下一节~

【深入理解JS核心技术】欢迎各位观众老爷,求点赞,求关注,求转发~

【深入理解JS核心技术】17. let 关键字的作用是什么

let 语句声明了一块范围的局部变量。因此,使用let关键字定义的变量的范围仅限于使用它的块,语句或 表达式。而使用var关键字声明的变量用于全局定义变量,或本地定义为整个函数,而不管块范围如何。

let counter = 12;
if (counter === 12) {
 let counter = 13;
 console.log(counter); // 13;
}
console.log(counter); // 12

let

ES6新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。

{
 let a = 10;
 var b = 1;
}

a // ReferenceError: a is not defined.
b // 1

代码块中分别用let和var声明了两个变量。然后在代码块之外调用这两个变量,结果let声明的变量报错,var声明的变量返回了正确的值。这表明,let声明的变量只在它所在的代码块有效。

for循环的计数器,就很适合用let命令。

for (let i = 0; i < 10; i++) {
  // ...
}

console.log(i);
// ReferenceError: i is not defined

代码中,计数器i只在for循环体内有效,在循环体外引用就回报错。

下面代码如果使用var,最后输出的是10。

var a = [];
for(var i = 0; i < 10; i++) {
 a[i] = function() {
  console.log(i);
 };
}
a[6](); // 10

代码中,变量i是var命令声明的,在全局范围内都有效,所以全局只有一个变量i。每一次循环,变量i的值都会发生 改变,而循环内被赋给数组a的函数内部的console.log(i),里面的i指向的就是全局的i。

所有数组a的成员里面的i,指向的都是同一个i,导致运行时输出的是最后一轮的i的值,也就是10。

如果使用let,声明的变量仅在块级作用域内有效,最后输出的是6.

var a = [];
for(let i = 0; i < 10; i++) {
 a[i] = function() {
  console.log(i);
 };
}

a[6](); // 6

上面代码中,变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6。

你可能会问,如果每一轮循环的变量i都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?

这是因为JavaScript引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。

另外,for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。

for (let i = 0; i < 3; i++) {
 let i = 'abc';
 console.log(i);
}
// abc
// abc
// abc

输出了3次abc。表明函数内部的变量i与循环变量i不在同一个作用域,有各自单独的作用域(同一个作用域不可使用let重复声明同一个变量)

不存在变量提升

var命令会发生“变量提升”现象,即变量可以在声明之前使用,值为undefined。本来按照常理来说,变量应用在声明语句之后才可以使用。

为了纠正这种现象,let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。

// var 的情况
console.log(foo); // 输出undefined
var foo = 2;

// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;

暂时性死区

只要块级作用域内存在let命令,它所声明的变量就“绑定”这个区域,不再受外部的影响。

var tmp = 123;
if (true) {
 tmp = 'abc'; // ReferenceError
 let tmp;
}

上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。

ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。

总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。

if (true) {
  // TDZ开始
  tmp = 'abc'; // ReferenceError
  console.log(tmp); // ReferenceError

  let tmp; // TDZ结束
  console.log(tmp); // undefined

  tmp = 123;
  console.log(tmp); // 123
}

“暂时性死区”也意味着typeof不再是一个百分之百安全的操作。

typeof x; // ReferenceError
let x;

上面代码中,变量x使用let命令声明,所以在声明之前,都属于x的“死区”,只要用到该变量就会报错。因此,typeof运行时就会抛出一个ReferenceError。

作为比较,如果一个变量根本没有被声明,使用typeof反而不会报错。

typeof undeclared_variable // "undefined"

变量一定要在声明之后使用,否则就报错。

ES6 规定暂时性死区和let、const语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。

不允许重复声明

let不允许在相同作用域内,重复声明同一个变量。

// 报错
function func() {
  let a = 10;
  var a = 1;
}

// 报错
function func() {
  let a = 10;
  let a = 1;
}

因此,不能在函数内部重新声明参数。

function func(arg) {
  let arg;
}
func() // 报错

function func(arg) {
  {
    let arg;
  }
}
func() // 不报错

ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。

为什么需要块级作用域

  1. 第一种场景,内层变量可能会覆盖外层变量。
  2. 第二种场景,用来计数的循环变量泄露为全局变量。

ES6的块级作用域

let实际上为 JavaScript 新增了块级作用域。

function f1() {
  let n = 5;
  if (true) {
    let n = 10;
  }
  console.log(n); // 5
}

ES6 允许块级作用域的任意嵌套。

{{{{
  {let insane = 'Hello World'}
  console.log(insane); // 报错
}}}};

内层作用域可以定义外层作用域的同名变量。

{{{{
  let insane = 'Hello World';
  {let insane = 'Hello World'}
}}}};

块级作用域的出现,实际上使得获得广泛应用的匿名立即执行函数表达式(匿名 IIFE)不再必要了。

// IIFE 写法
(function () {
  var tmp = ...;
  ...
}());

// 块级作用域写法
{
  let tmp = ...;
  ...
}

未完结!更多内容尽情期待下一节~

【深入理解JS核心技术】欢迎各位观众老爷,求点赞,求关注,求转发~

【深入理解JS核心技术】8. 你如何比较Object和Map

  1. 对象的键是字符串和符号; Map是任何值,包括函数、对象等。
  2. Map 中的键是有序的,而添加到 Object 中的键不是。因此,在对其进行迭代时, Map 对象会按插入顺序返回键。
  3. 使用 size 属性轻松获取 Map 的大小,而 Object 中的属性数量必须手动确定。
  4. Map 是可迭代的,因此可以直接迭代,而对 Object 进行迭代则需要以某种方式获取其键并对其进行迭代。
  5. 在涉及频繁添加和删除密钥对的场景中,Map 可能会表现得更好。

Object(大多数引用值的示例使用的是Object类型)

显示地创建Object的示例有两种方式:第一种使用new操作符和Object构造函数;另外一种使用对象字面量表示法。

对象字面量是对象定义的简写形式,目的是为了简化包含大量属性的对象的创建。

对象、类与面向对象编程

ECMA-262 将对象定义为一组属性的无序集合。创建自定义对象的通常方式是创建 Object 的一个新实例,然后再给它添加属性和方法。(目前流行使用对象字面量方式)

ECMA-262 使用一些内部特性来描述属性的特征。

为了将某个特性标识为内部特性,规范会用两个中括号把特性的名称括起来,比如[[Enumerable]]

属性分两种:数据属性和访问器属性。

  1. 数据属性

数据属性有 4 个特性描述它们的行为。

  • [[Configurable]]:表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特
    性,以及是否可以把它改为访问器属性。
  • [[Enumerable]]:表示属性是否可以通过 for-in 循环返回。
  • [[Writable]]:表示属性的值是否可以被修改。
  • [[Value]]:包含属性实际的值。默认值为 undefined。

要修改属性的默认特性,就必须使用 Object.defineProperty()方法。这个方法接收 3 个参数:要给其添加属性的对象、属性的名称和一个描述符对象。

一个属性被定义为不可配置之后,就不能再变回可配置的了。虽然可以对同一个属性多次调用 Object.defineProperty(),但在把 configurable 设置为 false 之后就会受限制了。

  1. 访问器属性

访问器属性不包含数据值。相反,它们包含一个获取(getter)函数和一个设置(setter)函数,不过这两个函数不是必需的。在读取访问器属性时,会调用获取函数,这个函数的责任就是返回一个有效的值。

访问器属性有 4 个特性描述它们的行为。

  • [[Configurable]]:表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特性,以及是否可以把它改为数据属性。
  • [[Enumerable]]:表示属性是否可以通过 for-in 循环返回。
  • [[Get]]:获取函数,在读取属性时调用。
  • [[Set]]:设置函数,在写入属性时调用。

访问器属性是不能直接定义的,必须使用 Object.defineProperty()。

定义多个属性

ECMAScript 提供了 Object.defineProperties()方法。这个方法可以通过多个描述符一次性定义多个属性。它接收两个参数:要为之添加或修改属性的对象和另一个描述符对象,其属性与要添加或修改的属性一一对应。

读取属性的特性

使用 Object.getOwnPropertyDescriptor()方法可以取得指定属性的属性描述符。这个方法接收两个参数:属性所在的对象和要取得其描述符的属性名。返回值是一个对象,对于访问器属性包含configurable、enumerable、get 和 set 属性,对于数据属性包含 configurable、enumerable、writable 和 value 属性。

ECMAScript 2017 新增了 Object.getOwnPropertyDescriptors()静态方法。

这个方法实际上会在每个自有属性上调用 Object.getOwnPropertyDescriptor()并在一个新对象中返回它们。

合并对象

ECMAScript 6为合并对象提供了 Object.assign()方法。这个方法接收一个目标对象和一个或多个源对象作为参数,然后将每个源对象中可枚举(Object.propertyIsEnumerable()返回 true)和自有(Object.hasOwnProperty()返回 true)属性复制到目标对象。

以字符串和符号为键的属性会被复制。对每个符合条件的属性,这个方法会使用源对象上的[[Get]]取得属性的值,然后使用目标对象上的[[Set]]设置属性的值。

let dest, src, result; 

/** 
 * 简单复制
 */ 

dest = {}; 
src = { id: 'src' }; 

result = Object.assign(dest, src); 

// Object.assign 修改目标对象
// 也会返回修改后的目标对象
console.log(dest === result); // true 
console.log(dest !== src); // true 
console.log(result); // { id: src } 
console.log(dest); // { id: src }
/** 
 * 多个源对象
 */ 
dest = {}; 
result = Object.assign(dest, { a: 'foo' }, { b: 'bar' }); 
console.log(result); // { a: foo, b: bar }

/** 
 * 获取函数与设置函数
 */ 
dest = { 
 set a(val) { 
 console.log(`Invoked dest setter with param ${val}`); 
 } 
}; 
src = { 
 get a() { 
 console.log('Invoked src getter'); 
 return 'foo'; 
 } 
};

/** 
 * 获取函数与设置函数
 */ 
dest = { 
 set a(val) { 
 console.log(`Invoked dest setter with param ${val}`); 
 } 
}; 
src = { 
 get a() { 
 console.log('Invoked src getter'); 
 return 'foo'; 
 } 
}; 
Object.assign(dest, src); 
// 调用 src 的获取方法
// 调用 dest 的设置方法并传入参数"foo" 
// 因为这里的设置函数不执行赋值操作
// 所以实际上并没有把值转移过来
console.log(dest); // { set a(val) {...} }

Object.assign()实际上对每个源对象执行的是浅复制。

对象标识及相等判定

在 ECMAScript 6 之前

// 这些是===符合预期的情况
console.log(true === 1); // false 
console.log({} === {}); // false 
console.log("2" === 2); // false

// 这些情况在不同 JavaScript 引擎中表现不同,但仍被认为相等
console.log(+0 === -0); // true 
console.log(+0 === 0); // true 
console.log(-0 === 0); // true

// 要确定 NaN 的相等性,必须使用极为讨厌的 isNaN() 
console.log(NaN === NaN); // false 
console.log(isNaN(NaN)); // true

ECMAScript 6 规范新增了 Object.is()

console.log(Object.is(true, 1)); // false 
console.log(Object.is({}, {})); // false 
console.log(Object.is("2", 2)); // false

// 正确的 0、-0、+0 相等/不等判定
console.log(Object.is(+0, -0)); // false 
console.log(Object.is(+0, 0)); // true 
console.log(Object.is(-0, 0)); // false

// 正确的 NaN 相等判定
console.log(Object.is(NaN, NaN)); // true

增强的对象语法

  1. 属性值简写
  2. 可计算属性
  3. 简写方法名

对象解构

ECMAScript 6 新增了对象解构语法

  1. 嵌套解构
  2. 部分解构
  3. 参数上下文匹配

Map

作为 ECMAScript 6 的新增特性,Map 是一种新的集合类型.

const m = new Map();

// 使用嵌套数组初始化映射
const m1 = new Map([ 
 ["key1", "val1"], 
 ["key2", "val2"], 
 ["key3", "val3"] 
]); 
alert(m1.size); // 3

// 使用自定义迭代器初始化映射
const m2 = new Map({ 
 [Symbol.iterator]: function*() { 
 yield ["key1", "val1"]; 
 yield ["key2", "val2"]; 
 yield ["key3", "val3"]; 
 } 
}); 
alert(m2.size); // 3

// 映射期待的键/值对,无论是否提供
const m3 = new Map([[]]); 
alert(m3.has(undefined)); // true 
alert(m3.get(undefined)); // undefined

初始化之后,可以使用 set()方法再添加键/值对。另外,可以使用 get()和 has()进行查询,可以通过 size 属性获取映射中的键/值对的数量,还可以使用 delete()和 clear()删除值。

set()方法返回映射实例,因此可以把多个操作连缀起来。

const m = new Map(); 

const functionKey = function() {}; 
const symbolKey = Symbol(); 
const objectKey = new Object(); 

m.set(functionKey, "functionValue"); 
m.set(symbolKey, "symbolValue"); 
m.set(objectKey, "objectValue"); 

alert(m.get(functionKey)); // functionValue 
alert(m.get(symbolKey)); // symbolValue 
alert(m.get(objectKey)); // objectValue 

// SameValueZero 比较意味着独立实例不冲突
alert(m.get(function() {})); // undefined


const m = new Map(); 

const objKey = {}, 
 objVal = {}, 
 arrKey = [], 
 arrVal = []; 

m.set(objKey, objVal); 
m.set(arrKey, arrVal); 

objKey.foo = "foo"; 
objVal.bar = "bar"; 
arrKey.push("foo"); 
arrVal.push("bar"); 

console.log(m.get(objKey)); // {bar: "bar"} 
console.log(m.get(arrKey)); // ["bar"]

顺序与迭代

映射实例可以提供一个迭代器(Iterator),能以插入顺序生成[key, value]形式的数组。可以通过 entries()方法(或者 Symbol.iterator 属性,它引用 entries())取得这个迭代器。

const m = new Map([ 
 ["key1", "val1"], 
 ["key2", "val2"], 
 ["key3", "val3"] 
]); 
alert(m.entries === m[Symbol.iterator]); // true

for (let pair of m.entries()) { 
 alert(pair); 
} 
// [key1,val1] 
// [key2,val2] 
// [key3,val3]

for (let pair of m[Symbol.iterator]()) { 
 alert(pair); 
} 
// [key1,val1] 
// [key2,val2] 
// [key3,val3]
const m = new Map([ 
 ["key1", "val1"], 
 ["key2", "val2"], 
 ["key3", "val3"] 
]); 
console.log([...m]); // [[key1,val1],[key2,val2],[key3,val3]]

m.forEach((val, key) => alert(`${key} -> ${val}`)); 
// key1 -> val1 
// key2 -> val2 
// key3 -> val3
const m1 = new Map([ 
 ["key1", "val1"] 
]); 

// 作为键的字符串原始值是不能修改的
for (let key of m1.keys()) { 
 key = "newKey"; 
 alert(key); // newKey 
 alert(m1.get("key1")); // val1 
} 

const keyObj = {id: 1}; 

const m = new Map([ 
 [keyObj, "val1"] 
]); 

// 修改了作为键的对象的属性,但对象在映射内部仍然引用相同的值
for (let key of m.keys()) { 
 key.id = "newKey"; 
 alert(key); // {id: "newKey"} 
 alert(m.get(keyObj)); // val1 
} 
alert(keyObj); // {id: "newKey"}

ECMAScript 6 新增的“弱映射”(WeakMap)是一种新的集合类型。不可迭代键。

弱映射中的键只能是 Object 或者继承自 Object 的类型,尝试使用非对象设置键会抛出TypeError。值的类型没有限制。

WeakMap 中“weak”表示弱映射的键是“弱弱地拿着”的。

使用弱映射: 1. 私有变量 ; 2. DOM 节点元数据

未完结!更多内容尽情期待下一节~

【深入理解JS核心技术】欢迎各位观众老爷,求点赞,求关注,求转发~

【深入理解JS核心技术】5. 数组切片方法的目的是什么

slice()方法将数组中的选定元素作为新数组对象返回。它选择从给定的 start 参数开始的元素,并在给定的可选 end 参数处结束,不包括最后一个元素。如果您省略第二个参数,那么它会一直选择到最后。

let arrayIntegers = [1, 2, 3, 4, 5];
let arrayIntegers1 = arrayIntegers.slice(0, 2); // returns [1,2]
let arrayIntegers2 = arrayIntegers.slice(2, 3); // returns [3]
let arrayIntegers3 = arrayIntegers.slice(4); //returns [5]

注意: Slice 方法不会改变原始数组,但它会将子集作为新数组返回。

未完结!更多内容尽情期待下一节~

【深入理解JS核心技术】欢迎各位观众老爷,求点赞,求关注,求转发~

【深入理解JS核心技术】23. 您如何在 JavaScript 中解码或编码 URL

  • encodeURI()函数用于对 URL 进行编码。此函数需要一个 URL 字符串作为参数并返回该编码字符串。
  • decodeURI()函数用于解码 URL。此函数需要一个编码的 URL 字符串作为参数并返回该解码的字符串。

注意:如果要对字符进行编码,例如/ ? : @ & = + $ #then 您需要使用encodeURIComponent().

复习

注意点:如果要将一个已经声明的变量用于解构赋值,必须非常小心。

// 错误的写法
let x;
{x} = {x: 1};
// SyntaxError: syntax error

上面代码的写法会报错,因为 JavaScript 引擎会将{x}理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免 JavaScript 将其解释为代码块,才能解决这个问题。

// 正确的写法
let x;
({x} = {x: 1});
({} = [true, false]);
({} = 'abc');
({} = []);

上面的表达式虽然毫无意义,但是语法是合法的,可以执行。

由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构。

let arr = [1, 2, 3];
let {0 : first, [arr.length - 1] : last} = arr;
first // 1
last // 3

字符串的解构赋值

字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。

const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"

类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。

let {length : len} = 'hello';
len // 5

数值和布尔值的解构赋值

解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。

let {toString: s} = 123;
s === Number.prototype.toString // true

let {toString: s} = true;
s === Boolean.prototype.toString // true

上面代码中,数值和布尔值的包装对象都有toString属性,因此变量s都能取到值。

解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。

let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError

函数参数的解构赋值

函数的参数也可以使用解构赋值。

function add([x, y]){
  return x + y;
}

add([1, 2]); // 3

上面代码中,函数add的参数表面上是一个数组,但在传入参数的那一刻,数组参数就被解构成变量x和y。对于函数内部的代码来说,它们能感受到的参数就是x和y。

下面是另一个例子。

[[1, 2], [3, 4]].map(([a, b]) => a + b);
// [ 3, 7 ]

函数参数的解构也可以使用默认值。

function move({x = 0, y = 0} = {}) {
  return [x, y];
}

move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]

上面代码中,函数move的参数是一个对象,通过对这个对象进行解构,得到变量x和y的值。如果解构失败,x和y等于默认值。

注意,下面的写法会得到不一样的结果。

function move({x, y} = { x: 0, y: 0 }) {
  return [x, y];
}

move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]

上面代码是为函数move的参数指定默认值,而不是为变量x和y指定默认值,所以会得到与前一种写法不同的结果。

undefined就会触发函数参数的默认值。

[1, undefined, 3].map((x = 'yes') => x);
// [ 1, 'yes', 3 ]

不能使用圆括号的情况

以下三种解构赋值不得使用圆括号。

(1)变量声明语句

// 全部报错
let [(a)] = [1];

let {x: (c)} = {};
let ({x: c}) = {};
let {(x: c)} = {};
let {(x): c} = {};

let { o: ({ p: p }) } = { o: { p: 2 } };

上面 6 个语句都会报错,因为它们都是变量声明语句,模式不能使用圆括号。

(2)函数参数

函数参数也属于变量声明,因此不能带有圆括号。

// 报错
function f([(z)]) { return z; }
// 报错
function f([z,(x)]) { return x; }
(3)赋值语句的模式

// 全部报错
({ p: a }) = { p: 42 };
([a]) = [5];

上面代码将整个模式放在圆括号之中,导致报错。

// 报错
[({ p: a }), { x: c }] = [{}, {}];

上面代码将一部分模式放在圆括号之中,导致报错。

可以使用圆括号的情况

可以使用圆括号的情况只有一种:赋值语句的非模式部分,可以使用圆括号。

[(b)] = [3]; // 正确
({ p: (d) } = {}); // 正确
[(parseInt.prop)] = [3]; // 正确

交换变量的值

let x = 1;
let y = 2;

[x, y] = [y, x];

从函数返回多个值

函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便。

// 返回一个数组

function example() {
  return [1, 2, 3];
}
let [a, b, c] = example();

// 返回一个对象

function example() {
  return {
    foo: 1,
    bar: 2
  };
}
let { foo, bar } = example();

函数参数的定义

解构赋值可以方便地将一组参数与变量名对应起来。

// 参数是一组有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);

// 参数是一组无次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});

提取 JSON 数据

解构赋值对提取 JSON 对象中的数据,尤其有用。

let jsonData = {
  id: 42,
  status: "OK",
  data: [867, 5309]
};

let { id, status, data: number } = jsonData;

console.log(id, status, number);
// 42, "OK", [867, 5309]

遍历 Map 结构

任何部署了 Iterator 接口的对象,都可以用for...of循环遍历。Map 结构原生支持 Iterator 接口,配合变量的解构赋值,获取键名和键值就非常方便。

const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');

for (let [key, value] of map) {
  console.log(key + " is " + value);
}
// first is hello
// second is world

如果只想获取键名,或者只想获取键值,可以写成下面这样。

// 获取键名
for (let [key] of map) {
  // ...
}

// 获取键值
for (let [,value] of map) {
  // ...
}

输入模块的指定方法

加载模块时,往往需要指定输入哪些方法。解构赋值使得输入语句非常清晰。

const { SourceMapConsumer, SourceNode } = require("source-map");

未完结!更多内容尽情期待下一节~

【深入理解JS核心技术】欢迎各位观众老爷,求点赞,求关注,求转发~

【深入理解JS核心技术】11. 什么是一级函数

在 Javascript 中,函数是第一类对象。一等函数意味着当该语言中的函数被视为任何其他变量时。

例如,在这种语言中,一个函数可以作为参数传递给其他函数,可以由另一个函数返回,也可以作为一个值分配给变量。例如,在下面的示例中,分配给侦听器的处理函数

const handler = () => console.log('This is a click handler function');
document.addEventListener('click', handler);

函数实际上是对象,每个函数都是Function类型的实例,而Function也有属性和方法,跟其他引用类型一样。因为函数是对象,所有函数名就是指向函数对象的指针,而且不一定与函数本身紧密绑定。

函数通常以函数声明的方式定义;定义函数的语法是函数表达式。注意这里的函数末尾是有分号的,与任何变量初始化语句一样。不推荐使用Function构造函数来定义函数,因为代码会被解释两次:

  1. 第一次是将它当作常规ECMAScript代码;
  2. 第二次是解释传给构造函数的字符串。

小结:

  1. 一个函数可以作为参数传递给其他函数;
  2. 一个函数可以由另一个函数返回;
  3. 一个函数可以作为一个值分配给变量。

未完结!更多内容尽情期待下一节~

【深入理解JS核心技术】欢迎各位观众老爷,求点赞,求关注,求转发~

【深入理解JS核心技术】13. 什么是高阶函数

高阶函数是接受另一个函数作为参数或返回一个函数作为返回值或两者兼有的函数。

const firstOrderFunc = () => console.log('hello, i am a first order function');
const higherOrder = (ReturnFirstOrderFunc) => ReturnFirstOrderFunc();
higherOrder(firstOrderFunc);

理解参数

在ECMAScript函数的参数在内部表现为一个数组。函数被调用时总会接收一个数组,但函数并不关心这个数组中包含什么。如果数组中什么也没有,那没问题;如果数组的元素超出了要求,那也没问题。

如果使用function关键字定义(非箭头)函数时,可以在函数内部访问arguments对象,从中取得传进来的每个参数值。

arguments对象时一个类数组对象,因此可以使用中括号语法访问其中的元素。而确定传进来多少个参数,可以访问arguments.length属性。(通过arguments对象的length属性传入的参数个数)

箭头函数中的参数

如果函数是使用箭头语法定义的,那么传给函数的参数将不能使用arguments关键字访问,而只能通过定义的命名参数访问。

虽然箭头函数中没有arguments对象,但可以在包装函数中把它提供给箭头函数。

function foo() {
 let bar = () => {
  console.log(arguments[0]); // 5
 };
 bar();
}

注意:ECMAScript中的所有参数都按值传递的。不可能按引用传递参数。如果把对象作为参数传递,那么传递的值就是 这个对象的引用。

没有重载

ECMAScript函数不能像传统那样重载。一个函数可以有两个定义,只要签名不同就行。ECMAScript函数没有签名,因为参数是由包含零个或多个值的数组表示的。没有函数签名,自然也就没有重载。

定义同名函数,后定义的会覆盖先定义的。

默认参数值

在 ECMAScript5.1 及以前,实现默认参数的一种常用方式就是检测某个参数是否等于 undefined,如果是则意味着没有传这个参数,那就给它赋一个值:

function makeKing(name) { 
 name = (typeof name !== 'undefined') ? name : 'da'; 
 return `xx ${name} xx`; 
} 
console.log(makeKing()); // 'xx da xx' 
console.log(makeKing('ff')); // 'xx ff xx'

ES6 写法,只要在函数定义中的参数后面用=就可以为参数赋一个默认值

在使用默认参数时,arguments 对象的值不反映参数的默认值,只反映传给函数的参数,它始终以调用函数时传入的值为准。给参数传 undefined 相当于没有传值。

function make(name = 'aa') { 
 name = 'bb'; 
 return `d ${arguments[0]}`; 
} 
console.log(make()); // 'd undefined' 
console.log(make('da')); // 'd da'

函数的默认参数只有在函数被调用时才会求值,不会在函数定义时求值。而且,计算默认值的函数只有在调用函数但未传相应参数时才会被调用。

默认参数作用域与暂时性死区

因为参数是按顺序初始化的,所以后定义默认值的参数可以引用先定义的参数。

参数也存在于自己的作用域中,它们不能引用函数体的作用域。

function makeKing(name = 'dada', numerals = name) { 
 return `King ${name} ${numerals}`; 
} 
console.log(makeKing()); // King dada dada

参数初始化顺序遵循“暂时性死区”规则,即前面定义的参数不能引用后面定义的。

function makeKing(name = 'Henry', numerals = defaultNumeral) { 
 let defaultNumeral = 'VIII'; 
 return `King ${name} ${numerals}`; 
}

makeKing();
// Uncaught ReferenceError: defaultNumeral is not defined

未完结!更多内容尽情期待下一节~

【深入理解JS核心技术】欢迎各位观众老爷,求点赞,求关注,求转发~

【深入理解JS核心技术】9. == 和 === 运算符有什么区别

JavaScript 提供了严格(===, !==) 和类型转换(==, !=) 相等比较。严格运算符考虑变量的类型,而非严格运算符根据变量的值进行类型校正/转换。严格的运算符遵循以下不同类型的条件,

  1. 当两个字符串具有相同的字符序列、相同的长度和对应位置的相同字符时,它们是严格相等的。
  2. 当两个数字在数值上相等时,它们是严格相等的。即,具有相同的数值。这里面有两种特殊情况,
    • NaN 不等于任何东西,包括 NaN。
    • 正零和负零彼此相等。
  3. 如果两个布尔操作数都为真或都为假,则两个布尔操作数严格相等。
  4. 如果两个对象引用同一个对象,则它们是严格相等的。
  5. Null 和 Undefined 类型不等于 ===,但等于 ==。即,null===undefined --> false 但 null==undefined --> true
0 == false   // true
0 === false  // false
1 == "1"     // true
1 === "1"    // false
null == undefined // true
null === undefined // false
'0' == false // true
'0' === false // false
[]==[] or []===[] //false, refer different objects in memory
{}=={} or {}==={} //false, refer different objects in memory

未完结!更多内容尽情期待下一节~

【深入理解JS核心技术】欢迎各位观众老爷,求点赞,求关注,求转发~

迭代器与生成器

理解迭代

  1. ES5 新增了 Array.prototype.forEach()方法
  2. 迭代会在一个有序集合上进行。(“有序”可以理解为集合中所有项都可以按照既定的顺序被遍历到,特别是开始和结束项有明确的定义。)数组是 JavaScript 中有序集合的最典型例子
let collection = ['foo', 'bar', 'baz'];
collection.forEach((item) => console.log(item));

迭代器模式

任何实现 Iterable 接口的数据结构都可以被实现 Iterator 接口的结构“消费”(consume)。迭代器(iterator)是按需创建的一次性对象。每个迭代器都会关联一个可迭代对象,而迭代器会暴露迭代其关联可迭代对象的 API。

  1. 字符串
  2. 数组
  3. 映射
  4. 集合
  5. arguments对象
  6. NodeList等DOM集合类型
let num = 1;
let obj = {};

// 这两种类型没有实现迭代器工厂函数
console.log(num[Symbol.iterator]); // undefined
console.log(obj[Symbol.iterator]); // undefined

let str = 'abc';
let arr = ['a', 'b', 'c'];
let map = new Map().set('a', 1).set('b', 2).set('c', 3);
let set = new Set().add('a').add('b').add('c');
let els = document.querySelectorAll('div');

// 这些类型都实现了迭代器工厂函数
console.log(str[Symbol.iterator]); // f values() { [native code] }
console.log(arr[Symbol.iterator]); // f values() { [native code] }
console.log(map[Symbol.iterator]); // f values() { [native code] }
console.log(set[Symbol.iterator]); // f values() { [native code] }
console.log(els[Symbol.iterator]); // f values() { [native code] }

// 调用这个工厂函数会生成一个迭代器
console.log(str[Symbol.iterator]()); // StringIterator {}
console.log(arr[Symbol.iterator]()); // ArrayIterator {}
console.log(map[Symbol.iterator]()); // MapIterator {}
console.log(set[Symbol.iterator]()); // SetIterator {}
console.log(els[Symbol.iterator]()); // ArrayIterator {}

接收可迭代对象的原生语言特性包括:

  1. for-of循环
  2. 数组解构
  3. 扩展操作符
  4. Array.from()
  5. 创建集合
  6. 创建映射
  7. Promise.all()接收由期约组成的可迭代对象
  8. Promise.race()接收由期约组成的可迭代对象
  9. yield*操作符,在生成器中使用
let arr = ['foo', 'bar', 'baz'];
// for-of 循环
for (let el of arr) {
 console.log(el);
} 
// foo
// bar
// baz

// 数组解构
let [a, b, c] = arr;
console.log(a, b, c); // foo, bar, baz

// 扩展操作符
let arr2 = [...arr];
console.log(arr2); // ['foo', 'bar', 'baz']
// Array.from()
let arr3 = Array.from(arr);
console.log(arr3); // ['foo', 'bar', 'baz']

// Set 构造函数
let set = new Set(arr);
console.log(set); // Set(3) {'foo', 'bar', 'baz'}

// Map 构造函数
let pairs = arr.map((x, i) => [x, i]);
console.log(pairs); // [['foo', 0], ['bar', 1], ['baz', 2]]
let map = new Map(pairs);
console.log(map); // Map(3) { 'foo'=>0, 'bar'=>1, 'baz'=>2 }

如果对象原型链上的父类实现了 Iterable 接口,那这个对象也就实现了这个接口:
class FooArray extends Array {}
let fooArr = new FooArray('foo', 'bar', 'baz');
for (let el of fooArr) {
 console.log(el);
}
// foo
// bar
// baz

迭代器协议

next()方法返回的迭代器对象 IteratorResult 包含两个属性:done 和 value。done 是一个布尔值,表示是否还可以再次调用 next()取得下一个值;value 包含可迭代对象的下一个值(done 为false),或者 undefined(done 为 true)。done: true 状态称为“耗尽”

// 可迭代对象
let arr = ['foo', 'bar'];
// 迭代器工厂函数
console.log(arr[Symbol.iterator]); // f values() { [native code] }
// 迭代器
let iter = arr[Symbol.iterator]();
console.log(iter); // ArrayIterator {}
// 执行迭代
console.log(iter.next()); // { done: false, value: 'foo' }
console.log(iter.next()); // { done: false, value: 'bar' }
console.log(iter.next()); // { done: true, value: undefined }
let arr = ['foo'];
let iter = arr[Symbol.iterator]();
console.log(iter.next()); // { done: false, value: 'foo' }
console.log(iter.next()); // { done: true, value: undefined }
console.log(iter.next()); // { done: true, value: undefined }
console.log(iter.next()); // { done: true, value: undefined } 
let arr = ['foo', 'bar'];
let iter1 = arr[Symbol.iterator]();
let iter2 = arr[Symbol.iterator]();
console.log(iter1.next()); // { done: false, value: 'foo' }
console.log(iter2.next()); // { done: false, value: 'foo' }
console.log(iter2.next()); // { done: false, value: 'bar' }
console.log(iter1.next()); // { done: false, value: 'bar' }
let arr = ['foo', 'baz'];
let iter = arr[Symbol.iterator]();
console.log(iter.next()); // { done: false, value: 'foo' }
// 在数组中间插入值
arr.splice(1, 0, 'bar');
console.log(iter.next()); // { done: false, value: 'bar' }
console.log(iter.next()); // { done: false, value: 'baz' }
console.log(iter.next()); // { done: true, value: undefined }
// 这个类实现了可迭代接口(Iterable)
// 调用默认的迭代器工厂函数会返回
// 一个实现迭代器接口(Iterator)的迭代器对象
class Foo {
 [Symbol.iterator]() {
 return {
 next() {
 return { done: false, value: 'foo' };
 }
 }
 }
}
let f = new Foo();

// Array 类型实现了可迭代接口(Iterable)
// 调用 Array 类型的默认迭代器工厂函数
// 会创建一个 ArrayIterator 的实例
let a = new Array();
// 打印出 ArrayIterator 的实例
console.log(a[Symbol.iterator]()); // Array Iterator {}

自定义迭代器

class Counter {
 // Counter 的实例应该迭代 limit 次
 constructor(limit) {
 this.count = 1;
 this.limit = limit;
 }
 next() {
 if (this.count <= this.limit) {
 return { done: false, value: this.count++ };
 } else {
 return { done: true, value: undefined };
 }
 }
 [Symbol.iterator]() {
 return this;
 }
}
let counter = new Counter(3);
for (let i of counter) {
 console.log(i);
}
// 1
// 2
// 3

这个类实现了 Iterator 接口,但不理想。这是因为它的每个实例只能被迭代一次

class Counter {
 constructor(limit) {
 this.limit = limit;
 }
 [Symbol.iterator]() {
 let count = 1,
 limit = this.limit;
 return {
 next() {
 if (count <= limit) {
 return { done: false, value: count++ };
 } else {
 return { done: true, value: undefined };
 }
 }
 };
 }
}

let counter = new Counter(3);
for (let i of counter) { console.log(i); }
// 1
// 2
// 3
for (let i of counter) { console.log(i); }
// 1
// 2
// 3 
let arr = ['foo', 'bar', 'baz'];
let iter1 = arr[Symbol.iterator]();
console.log(iter1[Symbol.iterator]); // f values() { [native code] }
let iter2 = iter1[Symbol.iterator]();
console.log(iter1 === iter2); // true
let arr = [3, 1, 4];
let iter = arr[Symbol.iterator]();

for (let item of arr ) { console.log(item); }
// 3
// 1
// 4
for (let item of iter ) { console.log(item); }
// 3
// 1
// 4 

提前终止迭代器

  1. return()方法用于指定在迭代器提前关闭时执行的逻辑
  2. for-of 循环通过 break、continue、return 或 throw 提前退出
  3. 解构操作并未消费所有值。
class Counter {
 constructor(limit) {
 this.limit = limit;
 }
 [Symbol.iterator]() {
 let count = 1,
 limit = this.limit;
 return {
 next() {
 if (count <= limit) {
 return { done: false, value: count++ };
 } else {
 return { done: true };
 }
 },
 return() {
 console.log('Exiting early');
 return { done: true };
 }
 };
 }
}

let counter1 = new Counter(5);

for (let i of counter1) {
 if (i > 2) {
 break;
 }
 console.log(i);
} 

// 1
// 2
// Exiting early
let counter2 = new Counter(5);
try {
 for (let i of counter2) {
 if (i > 2) {
 throw 'err';
 }
 console.log(i);
 }
} catch(e) {}
// 1
// 2
// Exiting early
let counter3 = new Counter(5);
let [a, b] = counter3;
// Exiting early 
let a = [1, 2, 3, 4, 5];
let iter = a[Symbol.iterator]();
for (let i of iter) {
 console.log(i);
 if (i > 2) {
 break
 }
}
// 1
// 2
// 3
for (let i of iter) {
 console.log(i);
}
// 4
// 5
let a = [1, 2, 3, 4, 5];
let iter = a[Symbol.iterator]();
iter.return = function() {
 console.log('Exiting early');
 return { done: true }; 
};
for (let i of iter) {
 console.log(i);
 if (i > 2) {
 break
 }
}
// 1
// 2
// 3
// 提前退出
for (let i of iter) {
 console.log(i);
}
// 4
// 5 

生成器

生成器是 ECMAScript 6 新增的一个极为灵活的结构,拥有在一个函数块内暂停和恢复代码执行的能力

生成器的形式是一个函数,函数名称前面加一个星号(*)表示它是一个生成器。

// 生成器函数声明
function* generatorFn() {}

// 生成器函数表达式
let generatorFn = function* () {}

// 作为对象字面量方法的生成器函数
let foo = {
 * generatorFn() {}
}

// 作为类实例方法的生成器函数
class Foo {
 * generatorFn() {}
}

// 作为类静态方法的生成器函数
class Bar {
 static * generatorFn() {}
} 

注意 箭头函数不能用来定义生成器函数。

// 等价的生成器函数:
function* generatorFnA() {}
function *generatorFnB() {}
function * generatorFnC() {}

// 等价的生成器方法:
class Foo {
 *generatorFnD() {}
 * generatorFnE() {}
} 

调用生成器函数会产生一个生成器对象。生成器对象一开始处于暂停执行(suspended)的状态。与迭代器相似,生成器对象也实现了 Iterator 接口,因此具有 next()方法。调用这个方法会让生成器开始或恢复执行。

function* generatorFn() {}
const g = generatorFn();
console.log(g); // generatorFn {<suspended>}
console.log(g.next); // f next() { [native code] } 

函数体为空的生成器函数中间不会停留,调用一次 next()就会让生成器到达 done: true 状态。

function* generatorFn() {}
let generatorObject = generatorFn();
console.log(generatorObject); // generatorFn {<suspended>}
console.log(generatorObject.next()); // { done: true, value: undefined }
function* generatorFn() {
 return 'foo';
}
let generatorObject = generatorFn();
console.log(generatorObject); // generatorFn {<suspended>}
console.log(generatorObject.next()); // { done: true, value: 'foo' }

生成器函数只会在初次调用 next()方法后开始执行

function* generatorFn() {
 console.log('foobar');
}
// 初次调用生成器函数并不会打印日志
let generatorObject = generatorFn();
generatorObject.next(); // foobar
function* generatorFn() {}
console.log(generatorFn);
// f* generatorFn() {}
console.log(generatorFn()[Symbol.iterator]);

// f [Symbol.iterator]() {native code}
console.log(generatorFn());
// generatorFn {<suspended>}
console.log(generatorFn()[Symbol.iterator]());
// generatorFn {<suspended>}
const g = generatorFn();
console.log(g === g[Symbol.iterator]());
// true 

通过 yield 中断执行

yield 关键字可以让生成器停止和开始执行,也是生成器最有用的地方。生成器函数在遇到 yield关键字之前会正常执行。遇到这个关键字后,执行会停止,函数作用域的状态会被保留。停止执行的生成器函数只能通过在生成器对象上调用 next()方法来恢复执行

function* generatorFn() {
 yield;
}
let generatorObject = generatorFn();
console.log(generatorObject.next()); // { done: false, value: undefined }
console.log(generatorObject.next()); // { done: true, value: undefined } 

通过 yield 关键字退出的生成器函数会处在 done: false 状态;通过 return 关键字退出的生成器函数会处于 done: true 状态

function* generatorFn() {
 yield 'foo';
 yield 'bar';
 return 'baz';
}
let generatorObject = generatorFn();
console.log(generatorObject.next()); // { done: false, value: 'foo' }
console.log(generatorObject.next()); // { done: false, value: 'bar' }
console.log(generatorObject.next()); // { done: true, value: 'baz' }

生成器函数内部的执行流程会针对每个生成器对象区分作用域。

function* generatorFn() {
 yield 'foo';
 yield 'bar';
 return 'baz';
}
let generatorObject1 = generatorFn();
let generatorObject2 = generatorFn();
console.log(generatorObject1.next()); // { done: false, value: 'foo' }
console.log(generatorObject2.next()); // { done: false, value: 'foo' } 
console.log(generatorObject2.next()); // { done: false, value: 'bar' }
console.log(generatorObject1.next()); // { done: false, value: 'bar' } 
// 有效
function* validGeneratorFn() {
 yield;
}

// 无效
function* invalidGeneratorFnA() {
 function a() {
 yield;
 }
}

// 无效
function* invalidGeneratorFnB() {
 const b = () => {
 yield;
 }
}

// 无效
function* invalidGeneratorFnC() {
 (() => {
 yield;
 })();
} 
  1. 生成器对象作为可迭代对象

  2. 使用 yield 实现输入和输出

  3. 产生可迭代对象

  4. 使用 yield*实现递归算法

  5. 生成器对象作为可迭代对象

function* generatorFn() {
 yield 1;
 yield 2;
 yield 3;
}
for (const x of generatorFn()) {
 console.log(x);
}
// 1
// 2
// 3 

通过一个简单的循环来实现

function* nTimes(n) {
 while(n--) {
 yield;
 }
}

for (let _ of nTimes(3)) {
 console.log('foo');
}
// foo
// foo
// foo
  1. 使用 yield 实现输入和输出

第一次调用 next()传入的值不会被使用,因为这一次调用是为了开始执行生成器函数

function* generatorFn(initial) {
 console.log(initial);
 console.log(yield);
 console.log(yield);
}
let generatorObject = generatorFn('foo');
generatorObject.next('bar'); // foo
generatorObject.next('baz'); // baz
generatorObject.next('qux'); // qux

yield 关键字可以同时用于输入和输出

function* generatorFn() {
 return yield 'foo';
}
let generatorObject = generatorFn();
console.log(generatorObject.next()); // { done: false, value: 'foo' }
console.log(generatorObject.next('bar')); // { done: true, value: 'bar' } 

定义了一个无穷计数生成器函数

function* generatorFn() {
 for (let i = 0;;++i) {
 yield i;
 }
}
let generatorObject = generatorFn();
console.log(generatorObject.next().value); // 0
console.log(generatorObject.next().value); // 1
console.log(generatorObject.next().value); // 2
console.log(generatorObject.next().value); // 3
console.log(generatorObject.next().value); // 4
console.log(generatorObject.next().value); // 5 
function* nTimes(n) {
 for (let i = 0; i < n; ++i) {
 yield i;
 }
}
for (let x of nTimes(3)) {
 console.log(x);
}
// 0
// 1
// 2
function* nTimes(n) {
 let i = 0;
 while(n--) {
 yield i++;
 }
}
for (let x of nTimes(3)) {
 console.log(x);
}
// 0
// 1
// 2 

这样使用生成器也可以实现范围和填充数组:

function* range(start, end) {
 while(end > start) {
 yield start++;
 }
}
for (const x of range(4, 7)) {
 console.log(x);
}
// 4
// 5
// 6

function* zeroes(n) {
 while(n--) {
 yield 0;
 }
}
console.log(Array.from(zeroes(8))); // [0, 0, 0, 0, 0, 0, 0, 0]
  1. 产生可迭代对象
// 等价的 generatorFn:
// function* generatorFn() {
// for (const x of [1, 2, 3]) {
// yield x;
// }
// }
function* generatorFn() {
 yield* [1, 2, 3];
}
let generatorObject = generatorFn();
for (const x of generatorFn()) {
 console.log(x);
}
// 1
// 2
// 3
function* generatorFn() {
 yield* [1, 2];
 yield *[3, 4];
 yield * [5, 6];
}
for (const x of generatorFn()) {
 console.log(x);
}
// 1
// 2
// 3
// 4
// 5
// 6 

因为 yield*实际上只是将一个可迭代对象序列化为一连串可以单独产出的值

function* generatorFnA() {
 for (const x of [1, 2, 3]) {
 yield x;
 }
}
for (const x of generatorFnA()) {
 console.log(x);
}
// 1
// 2
// 3
function* generatorFnB() {
 yield* [1, 2, 3];
}
for (const x of generatorFnB()) {
 console.log(x);
} 
// 1
// 2
// 3

yield*的值是关联迭代器返回 done: true 时的 value 属性。对于普通迭代器来说,这个值是undefined:

function* generatorFn() {
 console.log('iter value:', yield* [1, 2, 3]);
}
for (const x of generatorFn()) {
 console.log('value:', x);
}
// value: 1
// value: 2
// value: 3
// iter value: undefined

对于生成器函数产生的迭代器来说,这个值就是生成器函数返回的值:

function* innerGeneratorFn() {
 yield 'foo';
 return 'bar';
}
function* outerGeneratorFn(genObj) {
 console.log('iter value:', yield* innerGeneratorFn());
}
for (const x of outerGeneratorFn()) {
 console.log('value:', x);
}
// value: foo
// iter value: bar
  1. 使用 yield*实现递归算法
function* nTimes(n) {
 if (n > 0) {
 yield* nTimes(n - 1);
 yield n - 1;
 }
}
for (const x of nTimes(3)) {
 console.log(x);
}
// 0
// 1
// 2

yield*最有用的地方是实现递归操作,此时生成器可以产生自身。

使用递归生成器结构和 yield*可以优雅地表达递归算法。下面是一个图的实现,用于生成一个随机的双向图

class Node {
 constructor(id) {
 this.id = id;
 this.neighbors = new Set();
 }
 connect(node) {
 if (node !== this) {
 this.neighbors.add(node);
 node.neighbors.add(this);
 }
 }
}
class RandomGraph {
 constructor(size) {
 this.nodes = new Set();

 // 创建节点
 for (let i = 0; i < size; ++i) {
 this.nodes.add(new Node(i));
 }

 // 随机连接节点
 const threshold = 1 / size;
 for (const x of this.nodes) {
 for (const y of this.nodes) {
 if (Math.random() < threshold) {
 x.connect(y);
 }
 }
 }
 }

 // 这个方法仅用于调试
 print() {
 for (const node of this.nodes) {
 const ids = [...node.neighbors]
 .map((n) => n.id)
 .join(',');
 console.log(`${node.id}: ${ids}`);
 }
 }
}
const g = new RandomGraph(6);
g.print();

// 示例输出:
// 0: 2,3,5
// 1: 2,3,4,5
// 2: 1,3
// 3: 0,1,2,4
// 4: 2,3
// 5: 0,4 

图数据结构非常适合递归遍历,而递归生成器恰好非常合用。为此,生成器函数必须接收一个可迭代对象,产出该对象中的每一个值,并且对每个值进行递归。

这个实现可以用来测试某个图是否连通,即是否没有不可到达的节点。只要从一个节点开始,然后尽力访问每个节点就可以了。
结果就得到了一个非常简洁的深度优先遍历

class Node {
 constructor(id) {
 ...
 }
 connect(node) {
 ...
 }
}
class RandomGraph {
 constructor(size) {
 ...
 }
 print() {
 ...
 }
 isConnected() {
 const visitedNodes = new Set();
 function* traverse(nodes) {
 for (const node of nodes) {
 if (!visitedNodes.has(node)) {
 yield node;
 yield* traverse(node.neighbors);
 }
 }
 }
 // 取得集合中的第一个节点
 const firstNode = this.nodes[Symbol.iterator]().next().value;
 // 使用递归生成器迭代每个节点
 for (const node of traverse([firstNode])) {
 visitedNodes.add(node);
 }
 return visitedNodes.size === this.nodes.size;
 }
} 

生成器作为默认迭代器

因为生成器对象实现了 Iterable 接口,而且生成器函数和默认迭代器被调用之后都产生迭代器,所以生成器格外适合作为默认迭代器。下面是一个简单的例子,这个类的默认迭代器可以用一行代码产出类的内容:

class Foo {
 constructor() {
 this.values = [1, 2, 3];
 } 
* [Symbol.iterator]() {
 yield* this.values;
 }
}
const f = new Foo();
for (const x of f) {
 console.log(x);
}
// 1
// 2
// 3 

提前终止生成器

一个实现 Iterator 接口的对象一定有 next()方法,还有一个可选的 return()方法用于提前终止迭代器

还有第三个方法:throw()。

function* generatorFn() {}
const g = generatorFn();
console.log(g); // generatorFn {<suspended>}
console.log(g.next); // f next() { [native code] }
console.log(g.return); // f return() { [native code] }
console.log(g.throw); // f throw() { [native code] }

return()和 throw()方法都可以用于强制生成器进入关闭状态。

return()方法会强制生成器进入关闭状态。提供给 return()方法的值,就是终止迭代器对象的值

function* generatorFn() {
 for (const x of [1, 2, 3]) {
 yield x;
 }
}
const g = generatorFn();
console.log(g); // generatorFn {<suspended>}
console.log(g.return(4)); // { done: true, value: 4 }
console.log(g); // generatorFn {<closed>}

与迭代器不同,所有生成器对象都有 return()方法,只要通过它进入关闭状态,就无法恢复了。后续调用 next()会显示 done: true 状态,而提供的任何返回值都不会被存储或传播

function* generatorFn() {
 for (const x of [1, 2, 3]) {
 yield x;
 }
} 
const g = generatorFn();
console.log(g.next()); // { done: false, value: 1 }
console.log(g.return(4)); // { done: true, value: 4 }
console.log(g.next()); // { done: true, value: undefined }
console.log(g.next()); // { done: true, value: undefined }
console.log(g.next()); // { done: true, value: undefined } 
function* generatorFn() {
 for (const x of [1, 2, 3]) {
 yield x;
 }
}
const g = generatorFn();
for (const x of g) {
 if (x > 1) {
 g.return(4);
 }
 console.log(x);
}
// 1
// 2

throw()方法会在暂停的时候将一个提供的错误注入到生成器对象中。

function* generatorFn() {
 for (const x of [1, 2, 3]) {
 yield x;
 }
}
const g = generatorFn();
console.log(g); // generatorFn {<suspended>}
try {
 g.throw('foo');
} catch (e) {
 console.log(e); // foo
}
console.log(g); // generatorFn {<closed>} 

不过,假如生成器函数内部处理了这个错误,那么生成器就不会关闭,而且还可以恢复执行。错误处理会跳过对应的 yield

function* generatorFn() {
 for (const x of [1, 2, 3]) {
 try {
 yield x;
 } catch(e) {}
 }
}
const g = generatorFn();
console.log(g.next()); // { done: false, value: 1}
g.throw('foo');
console.log(g.next()); // { done: false, value: 3}

生成器在 try/catch 块中的 yield 关键字处暂停执行。

在暂停期间,throw()方法向生成器对象内部注入了一个错误:字符串"foo"。

这个错误会被 yield 关键字抛出。因为错误是在生成器的 try/catch 块中抛出的,所以仍然在生成器内部被捕获。
可是,由于 yield 抛出了那个错误,生成器就不会再产出值 2。

🆗

【深入理解JS核心技术】4. 什么是 JSON 及其常用操作

JSON是一种遵循JavaScript对象语法的基于文本的数据格式,当您想通过网络传输数据时它很有用,它基本只是一个扩展名为.json的文本文件,MIME类型为application/json

JSON语法:

JSON语法支持表示3种类型的值。

  1. 简单值:字符串,数值,布尔值和null可以在JSON中出现,就像在JavaScript中一样。特殊值undefined不可以。
  2. 对象:第一种复杂数据类型,对象表示有序键/值对。每个值可以是简单值,也可以是复杂类型。
  3. 数组:第二种复杂数据类型,数组表示可以通过数值索引访问的值的有序列表。数组的值可以是任意类型,包括简单值,对象,或者其他数组。

解析:将字符串转换为对象

JSON.parse()方法可以接收一个额外的参数,这个函数会针对每个键/值对都调用一次。

JSON.parse(text);

字符串化:将对象转换为字符串,以便可以通过网络传输

JSON.stringify()方法除了要序列化的对象,还可以接收两个参数。这两个参数可以用与指定其他序列化JavaScript对象的方式。第一个参数是过滤器,可以是数组或函数;第二个参数是用于缩进结果JSON字符串的选项。

// 过滤结果
// 如果第二个参数是一个数组,那么JSON.stringify()返回的结果只会包含该数组中列出的对象属性。
// JSON.stringify(book, ['title', 'name']);

// 字符串缩进
// JSON.stringify()方法的第三个参数控制缩进和空格。在这个参数是数值时,表示每一级缩进的空格数。
// JSON.stringify(book, null, 4);

JSON.stringify(object);
let book = {
    title: 'name',
    toJSON: function() {
        return this.title;
    }
}

let jsonText = JSON.stringify(book); // '"name"'

解析与序列化

JSON对象有两个方法:stringify()和parse(),分别将JavaScript序列化为JSON字符串,以及将JSON解析为原生JavaScript值。

JSON是一种轻量级数据格式,可以方便地表示复杂数据结构,这个格式使用JavaScript语法的一个子集表示对象,数组,字符串,数值,布尔值和null。虽然XML也能胜任同样的角色,但JSON更简洁,JavaScript支持也更好。所有浏览器都已经原生支持全局JSON对象。

ES5定义了原生JSON对象,用于将JavaScript对象序列化为JSON字符串,以及将JSON数组解析为JavaScript对象。JSON.stringify()和JSON.parse()方法分别用于实现这两种操作。

未完结!更多内容尽情期待下一节~

【深入理解JS核心技术】欢迎各位观众老爷,求点赞,求关注,求转发~

函数

  1. 函数表达式、函数声明及箭头函数
  2. 默认参数及扩展操作符
  3. 使用函数实现递归
  4. 使用闭包实现私有变量

每个函数都是Function类型的实例 函数名就是指向函数对象的指针

函数通常以函数声明的方式定义

function sum (num1, num2) {
 return num1 + num2;
}

注意函数定义最后没有加分号

函数表达式

let sum = function(num1, num2) {
 return num1 + num2;
};

注意这里的函数末尾是有分号的,与任何变量初始化语句一样

箭头函数

let sum = (num1, num2) => {
 return num1 + num2;
}; 

使用 Function 构造函数。这个构造函数接收任意多个字符串参数,最后一个参数始终会被当成函数体,而之前的参数都是新函数的参数。

let sum = new Function("num1", "num2", "return num1 + num2"); // 不推荐

这段代码会被解释两次:第一次是将它当作常规ECMAScript 代码,第二次是解释传给构造函数的字符串。这显然会影响性能。

箭头函数

let arrowSum = (a, b) => {
 return a + b;
};
let functionExpressionSum = function(a, b) {
 return a + b;
};
console.log(arrowSum(5, 8)); // 13
console.log(functionExpressionSum(5, 8)); // 13

简洁

let ints = [1, 2, 3];
console.log(ints.map(function(i) { return i + 1; })); // [2, 3, 4]
console.log(ints.map((i) => { return i + 1 })); // [2, 3, 4] 

如果只有一个参数,那也可以不用括号。只有没有参数,或者多个参数的情况下,才需要使用括号:

// 以下两种写法都有效
let double = (x) => { return 2 * x; };
let triple = x => { return 3 * x; };
// 没有参数需要括号
let getRandom = () => { return Math.random(); };
// 多个参数需要括号
let sum = (a, b) => { return a + b; };
// 无效的写法:
let multiply = a, b => { return a * b; }; 

箭头函数不能使用 arguments、super 和new.target,也不能用作构造函数。此外,箭头函数也没有 prototype 属性。

函数名

函数名就是指向函数的指针 一个函数可以有多个名称

function sum(num1, num2) {
 return num1 + num2;
}
console.log(sum(10, 10)); // 20
let anotherSum = sum;
console.log(anotherSum(10, 10)); // 20
sum = null;
console.log(anotherSum(10, 10)); // 20

ECMAScript 6 的所有函数对象都会暴露一个只读的 name 属性

这个属性中保存的就是一个函数标识符,或者说是一个字符串化的变量名

即使函数没有名称,也会如实显示成空字符串。如果它是使用 Function 构造函数创建的,则会标识成"anonymous":

function foo() {}
let bar = function() {};
let baz = () => {};
console.log(foo.name); // foo
console.log(bar.name); // bar
console.log(baz.name); // baz
console.log((() => {}).name); //(空字符串)
console.log((new Function()).name); // anonymous

如果函数是一个获取函数、设置函数,或者使用 bind()实例化,那么标识符前面会加上一个前缀

function foo() {}
console.log(foo.bind(null).name); // bound foo
let dog = {
 years: 1,
 get age() {
 return this.years;
 },
 set age(newAge) {
 this.years = newAge;
 }
}
let propertyDescriptor = Object.getOwnPropertyDescriptor(dog, 'age');
console.log(propertyDescriptor.get.name); // get age
console.log(propertyDescriptor.set.name); // set age

理解参数

ECMAScript 函数的参数在内部表现为一个数组 访问 arguments 对象,从中取得传进来的每个参数值

arguments 对象是一个类数组对象(但不是 Array 的实例),因此可以使用中括号语法访问其中的元素(第一个参数是 arguments[0],第二个参数是 arguments[1])。而要确定传进来多少个参数,可以访问 arguments.length 属性

function howManyArgs() {
 console.log(arguments.length);
}
howManyArgs("string", 45); // 2
howManyArgs(); // 0
howManyArgs(12); // 1

箭头函数中的参数

传给函数的参数将不能使用 arguments 关键字访问

function foo() {
 console.log(arguments[0]);
}
foo(5); // 5
let bar = () => {
 console.log(arguments[0]);
};
bar(5); // ReferenceError: arguments is not defined
function foo() {
 let bar = () => {
 console.log(arguments[0]); // 5
 };
 bar();
}
foo(5);

注意 ECMAScript 中的所有参数都按值传递的。不可能按引用传递参数。如果把对象作为参数传递,那么传递的值就是这个对象的引用。

没有重载

没有函数签名,自然也就没有重载

如果在 ECMAScript 中定义了两个同名函数,则后定义的会覆盖先定义的。

可以通过检查参数的类型和数量,然后分别执行不同的逻辑来模拟函数重载。

默认参数值

function makeKing(name) {
 name = (typeof name !== 'undefined') ? name : 'Henry';
 return `King ${name} VIII`;
}
console.log(makeKing()); // 'King Henry VIII'
console.log(makeKing('Louis')); // 'King Louis VIII' 
function makeKing(name = 'Henry') {
 return `King ${name} VIII`;
}
console.log(makeKing('Louis')); // 'King Louis VIII'
console.log(makeKing()); // 'King Henry VIII' 
function makeKing(name = 'Henry', numerals = 'VIII') {
 return `King ${name} ${numerals}`;
}
console.log(makeKing()); // 'King Henry VIII'
console.log(makeKing('Louis')); // 'King Louis VIII'
console.log(makeKing(undefined, 'VI')); // 'King Henry VI' 

在使用默认参数时,arguments 对象的值不反映参数的默认值,只反映传给函数的参数。

function makeKing(name = 'Henry') {
 name = 'Louis';
 return `King ${arguments[0]}`;
}
console.log(makeKing()); // 'King undefined'
console.log(makeKing('Louis')); // 'King Louis'
let romanNumerals = ['I', 'II', 'III', 'IV', 'V', 'VI'];
let ordinality = 0;
function getNumerals() {
 // 每次调用后递增
 return romanNumerals[ordinality++];
}
function makeKing(name = 'Henry', numerals = getNumerals()) {
 return `King ${name} ${numerals}`;
}
console.log(makeKing()); // 'King Henry I'
console.log(makeKing('Louis', 'XVI')); // 'King Louis XVI'
console.log(makeKing()); // 'King Henry II'
console.log(makeKing()); // 'King Henry III' 
let makeKing = (name = 'Henry') => `King ${name}`;
console.log(makeKing()); // King Henry 

默认参数作用域与暂时性死区

function makeKing(name = 'Henry', numerals = 'VIII') {
 return `King ${name} ${numerals}`;
}
console.log(makeKing()); // King Henry VIII
function makeKing() {
 let name = 'Henry';
 let numerals = 'VIII';
 return `King ${name} ${numerals}`;
}
function makeKing(name = 'Henry', numerals = name) {
 return `King ${name} ${numerals}`;
}
console.log(makeKing()); // King Henry Henry

参数初始化顺序遵循“暂时性死区”规则

// 调用时不传第一个参数会报错
function makeKing(name = numerals, numerals = 'VIII') {
 return `King ${name} ${numerals}`;
}
// 调用时不传第二个参数会报错
function makeKing(name = 'Henry', numerals = defaultNumeral) {
 let defaultNumeral = 'VIII';
 return `King ${name} ${numerals}`;
} 

参数扩展与收集

扩展操作符最有用的场景就是函数定义中的参数列表

扩展操作符既可以用于调用函数时传参,也可以用于定义函数参数

扩展参数

arguments 对象只是消费扩展操作符的一种方式

收集参数

在构思函数定义时,可以使用扩展操作符把不同长度的独立参数组合为一个数组

// 不可以
function getProduct(...values, lastValue) {}
// 可以
function ignoreFirst(firstValue, ...values) {
 console.log(values);
}
ignoreFirst(); // []
ignoreFirst(1); // []
ignoreFirst(1,2); // [2]
ignoreFirst(1,2,3); // [2, 3]
let getSum = (...values) => {
 return values.reduce((x, y) => x + y, 0);
}
console.log(getSum(1,2,3)); // 6
function getSum(...values) {
 console.log(arguments.length); // 3
 console.log(arguments); // [1, 2, 3]
 console.log(values); // [1, 2, 3]
}
console.log(getSum(1,2,3));

函数声明与函数表达式

// 没问题
console.log(sum(10, 10));
function sum(num1, num2) {
 return num1 + num2;
}
// 会出错
console.log(sum(10, 10));
let sum = function(num1, num2) {
 return num1 + num2;
};

// 会出错
console.log(sum(10, 10));
var sum = function(num1, num2) {
 return num1 + num2;
};

函数作为值

因为函数名在 ECMAScript 中就是变量,所以函数可以用在任何可以使用变量的地方。这意味着不仅可以把函数作为参数传给另一个函数,而且还可以在一个函数中返回另一个函数。

函数内部

函数内部存在两个特殊的对象:arguments 和 this

ECMAScript 6 新增了 new.target 属性。

arguments

arguments 对象 它是一个类数组对象,包含调用函数时传入的所有参数。

arguments 对象其实还有一个 callee 属性,是一个指向 arguments 对象所在函数的指针。

经典的阶乘函数

function factorial(num) {
 if (num <= 1) {
 return 1;
 } else {
 return num * factorial(num - 1);
 }
}

使用 arguments.callee

function factorial(num) {
 if (num <= 1) {
 return 1;
 } else {
 return num * arguments.callee(num - 1);
 }
}

this

它在标准函数和箭头函数中有不同的行为。

在标准函数中,this 引用的是把函数当成方法调用的上下文对象,这时候通常称其为 this 值(在网页的全局上下文中调用函数时,this 指向 windows)

window.color = 'red';
let o = {
 color: 'blue'
};
function sayColor() {
 console.log(this.color);
}
sayColor(); // 'red'
o.sayColor = sayColor;
o.sayColor(); // 'blue' 

在箭头函数中,this引用的是定义箭头函数的上下文

window.color = 'red';
let o = {
 color: 'blue'
};
let sayColor = () => console.log(this.color);
sayColor(); // 'red'
o.sayColor = sayColor;
o.sayColor(); // 'red' 

箭头函数中的 this 会保留定义该函数时的上下文

function King() {
 this.royaltyName = 'Henry';
 // this 引用 King 的实例
 setTimeout(() => console.log(this.royaltyName), 1000);
}

function Queen() {
 this.royaltyName = 'Elizabeth';
 // this 引用 window 对象
 setTimeout(function() { console.log(this.royaltyName); }, 1000);
}
new King(); // Henry
new Queen(); // undefined

注意 函数名只是保存指针的变量。因此全局定义的 sayColor()函数和 o.sayColor()是同一个函数,只不过执行的上下文不同。

caller

ECMAScript 5 也会给函数对象上添加一个属性:caller。

这个属性引用的是调用当前函数的函数,或者如果是在全局作用域中调用的则为 null。

function outer() {
 inner();
}
function inner() {
 console.log(inner.caller);
}
outer();

VM201:5 ƒ outer() {
 inner();
}

会显示 outer()函数的源代码

如果要降低耦合度,则可以通过 arguments.callee.caller 来引用同样的值

function outer() {
 inner();
}
function inner() {
 console.log(arguments.callee.caller);
}
outer(); 

严格模式下还有一个限制,就是不能给函数的 caller 属性赋值,否则会导致错误。

new.target

ECMAScript 6 新增了检测函数是否使用 new 关键字调用的 new.target 属性

如果函数是正常调用的,则 new.target 的值是 undefined;如果是使用 new 关键字调用的,则 new.target 将引用被调用的构造函数。

function King() {
 if (!new.target) {
 throw 'King must be instantiated using "new"'
 }
 console.log('King instantiated using "new"');
}
new King(); // King instantiated using "new"
King(); // Error: King must be instantiated using "new"

函数属性与方法

每个函数都有两个属性:length 和 prototype

ECMAScript 5中,prototype 属性是不可枚举的,因此使用 for-in 循环不会返回这个属性。

函数还有两个方法:apply()和 call()。这两个方法都会以指定的 this 值来调用函数,即会设置调用函数时函数体内 this 对象的值

apply()方法接收两个参数:函数内 this 的值和一个参数数组。第二个参数可以是 Array 的实例,但也可以是 arguments 对象。

function sum(num1, num2) {
 return num1 + num2;
}
function callSum1(num1, num2) {
 return sum.apply(this, arguments); // 传入 arguments 对象
}

function callSum2(num1, num2) {
 return sum.apply(this, [num1, num2]); // 传入数组
}
console.log(callSum1(10, 10)); // 20
console.log(callSum2(10, 10)); // 20

call()方法

function sum(num1, num2) {
 return num1 + num2;
}
function callSum(num1, num2) {
 return sum.call(this, num1, num2);
}
console.log(callSum(10, 10)); // 20 

apply()和 call()真正强大的地方 控制函数调用上下文即函数体内 this值的能力

window.color = 'red';
let o = {
 color: 'blue'
};
function sayColor() {
 console.log(this.color);
}
sayColor(); // red
sayColor.call(this); // red
sayColor.call(window); // red
sayColor.call(o); // blue

使用 call()或 apply()的好处是可以将任意对象设置为任意函数的作用域,

ECMAScript 5 定义了一个新方法:bind()。bind()方法会创建一个新的函数实例 其 this 值会被绑定到传给 bind()的对象

window.color = 'red';
var o = {
 color: 'blue'
};
function sayColor() {
 console.log(this.color);
}
let objectSayColor = sayColor.bind(o);
objectSayColor(); // blue

函数表达式

定义函数有两种方式:函数声明和函数表达式。

function functionName(arg0, arg1, arg2) {
 // 函数体
}

函数声明的关键特点是函数声明提升,即函数声明会在代码执行之前获得定义。

sayHi();
function sayHi() {
 console.log("Hi!");
}

JavaScript 引擎会先读取函数声明,然后再执行代码

第二种创建函数的方式就是函数表达式。

let functionName = function(arg0, arg1, arg2) {
 // 函数体
};

匿名函数(anonymous funtion)

sayHi(); // Error! function doesn't exist yet
let sayHi = function() {
 console.log("Hi!");
}; 

递归

function factorial(num) {
 if (num <= 1) {
 return 1;
 } else {
 return num * factorial(num - 1);
 }
}

arguments.callee 就是一个指向正在执行的函数的指针

function factorial(num) {
 if (num <= 1) {
 return 1;
 } else {
 return num * arguments.callee(num - 1);
 }
}

arguments.callee 是引用当前函数的首选。

使用命名函数表达式(named function expression)

const factorial = (function f(num) {
 if (num <= 1) {
 return 1;
 } else {
 return num * f(num - 1);
 }
});

命名函数表达式 f()

尾调用优化

ECMAScript 6 规范新增了一项内存管理优化机制,让 JavaScript 引擎在满足条件时可以重用栈帧。

“尾调用”,即外部函数的返回值是一个内部函数的返回值。

function outerFunction() {
 return innerFunction(); // 尾调用
}

(1) 执行到 outerFunction 函数体,第一个栈帧被推到栈上。

(2) 执行 outerFunction 函数体,到 return 语句。计算返回值必须先计算 innerFunction。

(3) 执行到 innerFunction 函数体,第二个栈帧被推到栈上。

(4) 执行 innerFunction 函数体,计算其返回值。

(5) 将返回值传回 outerFunction,然后 outerFunction 再返回值。

(6) 将栈帧弹出栈外。

在 ES6 优化之后

(1) 执行到 outerFunction 函数体,第一个栈帧被推到栈上。

(2) 执行 outerFunction 函数体,到达 return 语句。为求值返回语句,必须先求值 innerFunction。

(3) 引擎发现把第一个栈帧弹出栈外也没问题,因为 innerFunction 的返回值也是 outerFunction
的返回值。

(4) 弹出 outerFunction 的栈帧。

(5) 执行到 innerFunction 函数体,栈帧被推到栈上。

(6) 执行 innerFunction 函数体,计算其返回值。

(7) 将 innerFunction 的栈帧弹出栈外。

第一种情况下每多调用一次嵌套函数,就会多增加一个栈帧。而第二种情况下无论调用多少次嵌套函数,都只有一个栈帧。这就是 ES6 尾调用优化的关键

尾调用优化的条件

尾调用优化的条件就是确定外部栈帧真的没有必要存在了

  1. 代码在严格模式下执行;
  2. 外部函数的返回值是对尾调用函数的调用;
  3. 尾调用函数返回后不需要执行额外的逻辑;
  4. 尾调用函数不是引用外部函数作用域中自由变量的闭包。

不符号尾调用优化

"use strict";
// 无优化:尾调用没有返回
function outerFunction() {
 innerFunction();
}
// 无优化:尾调用没有直接返回
function outerFunction() {
 let innerFunctionResult = innerFunction();
 return innerFunctionResult;
}
// 无优化:尾调用返回后必须转型为字符串
function outerFunction() {
 return innerFunction().toString();
}
// 无优化:尾调用是一个闭包
function outerFunction() {
 let foo = 'bar';
 function innerFunction() { return foo; }
 return innerFunction();
} 

符合尾调用优化条件的

"use strict";
// 有优化:栈帧销毁前执行参数计算
function outerFunction(a, b) {
 return innerFunction(a + b);
}
// 有优化:初始返回值不涉及栈帧
function outerFunction(a, b) {
 if (a < b) {
 return a;
 }
 return innerFunction(a + b);
}
// 有优化:两个内部函数都在尾部
function outerFunction(condition) {
 return condition ? innerFunctionA() : innerFunctionB();
} 

优化在递归场景下的效果是最明显的,因为递归代码最容易在栈内存中迅速产生大量栈帧。

尾调用优化的代码

通过递归计算斐波纳契数列的函数:

function fib(n) {
 if (n < 2) {
 return n;
 }
 return fib(n - 1) + fib(n - 2);
}
console.log(fib(0)); // 0
console.log(fib(1)); // 1
console.log(fib(2)); // 1
console.log(fib(3)); // 2
console.log(fib(4)); // 3
console.log(fib(5)); // 5
console.log(fib(6)); // 8

fib(n)的栈帧数的内存复杂度是 O(2^n)

"use strict";
// 基础框架
function fib(n) {
 return fibImpl(0, 1, n);
}
// 执行递归
function fibImpl(a, b, n) {
 if (n === 0) {
 return a;
 }
 return fibImpl(b, a + b, n - 1);
} 

闭包

闭包指的是那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的。

function createComparisonFunction(propertyName) {
 return function(object1, object2) {
 let value1 = object1[propertyName];
 let value2 = object2[propertyName];
 if (value1 < value2) {
 return -1;
 } else if (value1 > value2) {
 return 1;
 } else {
 return 0;
 }
 };
}

函数执行时,每个执行上下文中都会有一个包含其中变量的对象。全局上下文中的叫变量对象,它会在代码执行期间始终存在。而函数局部上下文中的叫活动对象,只在函数执行期间存在。

function compare(value1, value2) {
 if (value1 < value2) {
 return -1;
 } else if (value1 > value2) {
 return 1;
 } else {
 return 0;
 }
}
let result = compare(5, 10);

this 对象

window.identity = 'The Window';
let object = {
 identity: 'My Object',
 getIdentityFunc() {
 let that = this;
 return function() {
 return that.identity;
 };
 }
};
console.log(object.getIdentityFunc()()); // 'My Object'

内存泄漏

function assignHandler() {
 let element = document.getElementById('someElement');
 element.onclick = () => console.log(element.id);
}

匿名函数引用着 assignHandler()的活动对象 阻止了对element 的引用计数归零 只要这个匿名函数存在,element 的引用计数就至少等于 1。

function assignHandler() {
 let element = document.getElementById('someElement');
 let id = element.id;
 element.onclick = () => console.log(id);
 element = null;
} 

立即调用的函数表达式

立即调用的匿名函数又被称作立即调用的函数表达式(IIFE,Immediately Invoked Function Expression)。

(function() {
 // 块级作用域
})();
// IIFE
(function () {
 for (var i = 0; i < count; i++) {
 console.log(i);
 }
})();
console.log(i); // 抛出错误

在执行到 IIFE 外部的 console.log()时会出错,因为它访问的变量是在 IIFE 内部定义的,在外部访问不到。

// 内嵌块级作用域
{
 let i;
 for (i = 0; i < count; i++) {
 console.log(i);
 }
}
console.log(i); // 抛出错误
// 循环的块级作用域
for (let i = 0; i < count; i++) {
 console.log(i);
}
console.log(i); // 抛出错误
let divs = document.querySelectorAll('div');
// 达不到目的!
for (var i = 0; i < divs.length; ++i) {
 divs[i].addEventListener('click', function() {
 console.log(i);
 });
}
let divs = document.querySelectorAll('div');
for (var i = 0; i < divs.length; ++i) {
 divs[i].addEventListener('click', (function(frozenCounter) {
 return function() {
 console.log(frozenCounter);
 };
 })(i));
} 
let divs = document.querySelectorAll('div');
for (let i = 0; i < divs.length; ++i) {
 divs[i].addEventListener('click', function() {
 console.log(i);
 });
} 

使用 ECMAScript 块级作用域变量

let divs = document.querySelectorAll('div');
for (let i = 0; i < divs.length; ++i) {
 divs[i].addEventListener('click', function() {
console.log(i);
 });
} 

私有变量

私有变量包括函数参数、局部变量,以及函数内部定义的其他函数。

特权方法(privileged method)是能够访问函数私有变量(及私有函数)的公有方法。在对象上有两种方式创建特权方法。第一种是在构造函数中实现

function MyObject() {
 // 私有变量和私有函数
 let privateVariable = 10;
 function privateFunction() {
 return false;
 }
 // 特权方法
 this.publicMethod = function() {
 privateVariable++;
 return privateFunction();
 };
}
function Person(name) {
 this.getName = function() {
 return name;
 };
 this.setName = function (value) {
 name = value;
 };
}
let person = new Person('Nicholas');
console.log(person.getName()); // 'Nicholas'
person.setName('Greg');
console.log(person.getName()); // 'Greg' 

构造函数模式的缺点是每个实例都会重新创建一遍新方法。使用静态私有变量实现特权方法可以避免这个问题。

静态私有变量

特权方法也可以通过使用私有作用域定义私有变量和函数来实现。

(function() {
 // 私有变量和私有函数
 let privateVariable = 10;
 function privateFunction() {
 return false;
 }
 // 构造函数
 MyObject = function() {};
 // 公有和特权方法
 MyObject.prototype.publicMethod = function() {
 privateVariable++;
 return privateFunction();
 };
})(); 
(function() {
 let name = '';
 Person = function(value) {
 name = value;
 };
 Person.prototype.getName = function() {
 return name;
 };
 Person.prototype.setName = function(value) {
 name = value;
 };
})();
let person1 = new Person('Nicholas');
console.log(person1.getName()); // 'Nicholas'
person1.setName('Matt');
console.log(person1.getName()); // 'Matt'
let person2 = new Person('Michael');
console.log(person1.getName()); // 'Michael'
console.log(person2.getName()); // 'Michael'

模块模式

在一个单例对象上实现了相同的隔离和封装。单例对象(singleton)就是只有一个实例的对象。

let singleton = {
 name: value, 
method() {
 // 方法的代码
 }
};

模块模式是在单例对象基础上加以扩展,使其通过作用域链来关联私有变量和特权方法。

let singleton = function() {
 // 私有变量和私有函数
 let privateVariable = 10;
 function privateFunction() {
 return false;
 }
 // 特权/公有方法和属性
 return {
 publicProperty: true,
 publicMethod() {
 privateVariable++;
 return privateFunction();
 }
 };
}();
let application = function() {
 // 私有变量和私有函数
 let components = new Array();
 // 初始化
 components.push(new BaseComponent());
 // 公共接口
 return {
 getComponentCount() {
 return components.length;
 },
 registerComponent(component) {
 if (typeof component == 'object') {
 components.push(component);
 }
 }
 };
}();

前一个方法返回注册组件的数量,后一个方法负责注册新组件。

模块增强模式

另一个利用模块模式的做法是在返回对象之前先对其进行增强。这适合单例对象需要是某个特定类型的实例,但又必须给它添加额外属性或方法的场景。

let singleton = function() {
 // 私有变量和私有函数
 let privateVariable = 10;
 function privateFunction() {
 return false;
 }
 // 创建对象
 let object = new CustomType();
 // 添加特权/公有属性和方法
 object.publicProperty = true;
 object.publicMethod = function() {
 privateVariable++;
 return privateFunction();
 };
 // 返回对象
 return object;
}();
let application = function() {
 // 私有变量和私有函数
 let components = new Array();
 // 初始化
 components.push(new BaseComponent());
 // 创建局部变量保存实例
 let app = new BaseComponent();
 // 公共接口
 app.getComponentCount = function() {
 return components.length;
 }; 
 app.registerComponent = function(component) {
 if (typeof component == "object") {
 components.push(component);
 }
 };
 // 返回实例
 return app;
}(); 
  1. 函数表达式与函数声明是不一样的。函数声明要求写出函数名称,而函数表达式并不需要。没有名称的函数表达式也被称为匿名函数。
  2. ES6 新增了类似于函数表达式的箭头函数语法,但两者也有一些重要区别。
  3. JavaScript 中函数定义与调用时的参数极其灵活。arguments 对象,以及 ES6 新增的扩展操作符,可以实现函数定义和调用的完全动态化。
  4. 函数内部也暴露了很多对象和引用,涵盖了函数被谁调用、使用什么调用,以及调用时传入了什么参数等信息。
  5. JavaScript 引擎可以优化符合尾调用条件的函数,以节省栈空间
  6. 闭包的作用域链中包含自己的一个变量对象,然后是包含函数的变量对象,直到全局上下文的变量对象
  7. 通常,函数作用域及其中的所有变量在函数执行完毕后都会被销毁。
  8. 闭包在被函数返回之后,其作用域会一直保存在内存中,直到闭包被销毁。
  9. 函数可以在创建之后立即调用,执行其中代码之后却不留下对函数的引用。
  10. 可以访问私有变量的公共方法叫作特权方法。

🆗

对象、类与面向对象编程

  1. 理解对象
  2. 理解对象创建过程
  3. 理解继承
  4. 理解类

理解对象

创建对象:

let person = new Object();

person.name = "Nicholas";
person.age = 29;
person.job = "Software Engineer";

person.sayName = function() {
 console.log(this.name);
}; 

使用对象字面量

let person = {

 name: "Nicholas",
 age: 29,
 job: "Software Engineer",

 sayName() {
 console.log(this.name);
 }

}; 

为了将某个特性标识为内部特性,规范会用两个中括号把特性的名称括起来,比如[[Enumerable]]。

属性分两种:数据属性和访问器属性。

  1. 数据属性

数据属性有 4个特性描述它们的行为。

  • [[Configurable]]:表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特性,以及是否可以把它改为访问器属性。默认情况下,所有直接定义在对象上的属性的这个特性都是 true
  • [[Enumerable]]:表示属性是否可以通过 for-in 循环返回。默认情况下,所有直接定义在对象上的属性的这个特性都是 true
  • [[Writable]]:表示属性的值是否可以被修改。默认情况下,所有直接定义在对象上的属性的这个特性都是 true
  • [[Value]]:包含属性实际的值。这就是前面提到的那个读取和写入属性值的位置。这个特性的默认值为 undefined。

要修改属性的默认特性,就必须使用 Object.defineProperty()方法

let person = {};

Object.defineProperty(person, "name", {
 writable: false,
 value: "Nicholas"
});

console.log(person.name); // "Nicholas"
person.name = "Greg";

console.log(person.name); // "Nicholas" 

一个属性被定义为不可配置之后,就不能再变回可配置的了

let person = {};

Object.defineProperty(person, "name", {
 configurable: false,
 value: "Nicholas"
});

console.log(person.name); // "Nicholas"
delete person.name;

console.log(person.name); // "Nicholas"
let person = {};

Object.defineProperty(person, "name", {
 configurable: false,
 value: "Nicholas"
});

// 抛出错误
Object.defineProperty(person, "name", {
 configurable: true,
 value: "Nicholas"
}); 
  1. 访问器属性

它们包含一个获取(getter)函数和一个设置(setter)函数

访问器属性有 4 个特性描述它们的行为。

  • [[Configurable]]:表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特性,以及是否可以把它改为数据属性。默认情况下,所有直接定义在对象上的属性的这个特性都是 true。
  • [[Enumerable]]:表示属性是否可以通过 for-in 循环返回。默认情况下,所有直接定义在对象上的属性的这个特性都是 true。
  • [[Get]]:获取函数,在读取属性时调用。默认值为 undefined。
  • [[Set]]:设置函数,在写入属性时调用。默认值为 undefined。

访问器属性是不能直接定义的,必须使用 Object.defineProperty()。

// 定义一个对象,包含伪私有成员 year_和公共成员 edition
let book = {
 year_: 2017,
 edition: 1
};

Object.defineProperty(book, "year", {
 get() {
 return this.year_;
 },

 set(newValue) {
 if (newValue > 2017) {
 this.year_ = newValue;
 this.edition += newValue - 2017;
 }

 }
});

book.year = 2018;
console.log(book.edition); // 2

在不支持 Object.defineProperty()的浏览器中没有办法修改[[Configurable]]或[[Enumerable]]。

定义多个属性

let book = {};

Object.defineProperties(book, {
 year_: {
 value: 2017
 },
 edition: {
 value: 1
 },
 year: {
 get() {
 return this.year_;
 }, 
set(newValue) {
 if (newValue > 2017) {
 this.year_ = newValue;
 this.edition += newValue - 2017;
 }
 }
 }
});

读取属性的特性

使用 Object.getOwnPropertyDescriptor()方法可以取得指定属性的属性描述符。

这个方法接收两个参数:属性所在的对象和要取得其描述符的属性名。

返回值是一个对象,对于访问器属性包含configurable、enumerable、get 和 set 属性,对于数据属性包含 configurable、enumerable、writable 和 value 属性。

let book = {};

Object.defineProperties(book, {
 year_: {
 value: 2017
 },
 edition: {
 value: 1
 },
 year: {
 get: function() {
 return this.year_;
 },
 set: function(newValue){
 if (newValue > 2017) {
 this.year_ = newValue;
 this.edition += newValue - 2017;
 }
 }
 }
});

let descriptor = Object.getOwnPropertyDescriptor(book, "year_");
console.log(descriptor.value); // 2017
console.log(descriptor.configurable); // false
console.log(typeof descriptor.get); // "undefined"

let descriptor = Object.getOwnPropertyDescriptor(book, "year");
console.log(descriptor.value); // undefined
console.log(descriptor.enumerable); // false
console.log(typeof descriptor.get); // "function" 

ECMAScript 2017 新增了 Object.getOwnPropertyDescriptors()静态方法, 这个方法实际上会在每个自有属性上调用 Object.getOwnPropertyDescriptor()并在一个新对象中返回它们

let book = {};

Object.defineProperties(book, {
 year_: {
 value: 2017
 },
 edition: {
 value: 1
 },
 year: {
 get: function() {
 return this.year_;
 },
 set: function(newValue){
 if (newValue > 2017) {
 this.year_ = newValue;
 this.edition += newValue - 2017;
 }
 }
 }
});

console.log(Object.getOwnPropertyDescriptors(book));
// {
// edition: {
// configurable: false,
// enumerable: false,
// value: 1,
// writable: false
// },
// year: {
// configurable: false,
// enumerable: false,
// get: f(),
// set: f(newValue),
// },
// year_: {
// configurable: false,
// enumerable: false,
// value: 2017,
// writable: false
// }
// }

合并对象

Object.assign()方法 这个方法接收一个目标对象和一个或多个源对象作为参数 然后将每个源对象中可枚举(Object.propertyIsEnumerable()返回 true)和自有(Object.hasOwnProperty()返回 true)属性复制到目标对象

let dest, src, result;

/**
 * 简单复制
 */
dest = {};
src = { id: 'src' };
result = Object.assign(dest, src);

// Object.assign 修改目标对象
// 也会返回修改后的目标对象
console.log(dest === result); // true
console.log(dest !== src); // true
console.log(result); // { id: src }
console.log(dest); // { id: src }

/**
 * 多个源对象
 */
dest = {};
result = Object.assign(dest, { a: 'foo' }, { b: 'bar' });
console.log(result); // { a: foo, b: bar }

/**
 * 获取函数与设置函数
 */
dest = {
 set a(val) {
 console.log(`Invoked dest setter with param ${val}`);
 }
};
src = {
 get a() {
 console.log('Invoked src getter');
 return 'foo';
 }
};

Object.assign(dest, src);
// 调用 src 的获取方法
// 调用 dest 的设置方法并传入参数"foo"
// 因为这里的设置函数不执行赋值操作
// 所以实际上并没有把值转移过来
console.log(dest); // { set a(val) {...} }

Object.assign()实际上对每个源对象执行的是浅复制

let dest, src, result;

/**
 * 覆盖属性
 */
dest = { id: 'dest' };
result = Object.assign(dest, { id: 'src1', a: 'foo' }, { id: 'src2', b: 'bar' });

// Object.assign 会覆盖重复的属性
console.log(result); // { id: src2, a: foo, b: bar }

// 可以通过目标对象上的设置函数观察到覆盖的过程:
dest = {
 set id(x) {
 console.log(x);
 }
};
Object.assign(dest, { id: 'first' }, { id: 'second' }, { id: 'third' });
// first
// second
// third

/**
 * 对象引用
 */
dest = {};
src = { a: {} };
Object.assign(dest, src);

// 浅复制意味着只会复制对象的引用
console.log(dest); // { a :{} }
console.log(dest.a === src.a); // true

对象标识及相等判定

// 这些是===符合预期的情况
console.log(true === 1); // false
console.log({} === {}); // false
console.log("2" === 2); // false

// 这些情况在不同 JavaScript 引擎中表现不同,但仍被认为相等
console.log(+0 === -0); // true
console.log(+0 === 0); // true
console.log(-0 === 0); // true

// 要确定 NaN 的相等性,必须使用极为讨厌的 isNaN()
console.log(NaN === NaN); // false
console.log(isNaN(NaN)); // true

ECMAScript 6 规范新增了 Object.is() 这个方法必须接收两个参数:

console.log(Object.is(true, 1)); // false
console.log(Object.is({}, {})); // false
console.log(Object.is("2", 2)); // false

// 正确的 0、-0、+0 相等/不等判定
console.log(Object.is(+0, -0)); // false
console.log(Object.is(+0, 0)); // true
console.log(Object.is(-0, 0)); // false

// 正确的 NaN 相等判定
console.log(Object.is(NaN, NaN)); // true

要检查超过两个值,递归地利用相等性传递即可:

function recursivelyCheckEqual(x, ...rest) {
 return Object.is(x, rest[0]) &&
 (rest.length < 2 || recursivelyCheckEqual(...rest));
}

增强的对象语法

  1. 属性值简写
  2. 可计算属性
const nameKey = 'name';
const ageKey = 'age';
const jobKey = 'job';
let person = {
 [nameKey]: 'Matt',
 [ageKey]: 27,
 [jobKey]: 'Software engineer'
};
console.log(person); // { name: 'Matt', age: 27, job: 'Software engineer' }

const nameKey = 'name';
const ageKey = 'age';
const jobKey = 'job';
let uniqueToken = 0;
function getUniqueKey(key) {
 return `${key}_${uniqueToken++}`;
}
let person = {
 [getUniqueKey(nameKey)]: 'Matt',
 [getUniqueKey(ageKey)]: 27,
 [getUniqueKey(jobKey)]: 'Software engineer'
};
console.log(person); // { name_0: 'Matt', age_1: 27, job_2: 'Software engineer' }
  1. 简写方法名
let person = {
 sayName(name) {
 console.log(`My name is ${name}`);
 }
};
person.sayName('Matt'); // My name is Matt

const methodKey = 'sayName';
let person = {
 [methodKey](name) {
 console.log(`My name is ${name}`);
 }
}
person.sayName('Matt'); // My name is Matt

对象解构

// 使用对象解构
let person = {
 name: 'Matt',
 age: 27
};

let { name: personName, age: personAge } = person;
console.log(personName); // Matt
console.log(personAge); // 27 

let person = {
 name: 'Matt',
 age: 27
};

let { name, age } = person;
console.log(name); // Matt
console.log(age); // 27 

let person = {
 name: 'Matt',
 age: 27
};

let { name, job } = person;
console.log(name); // Matt
console.log(job); // undefined

let person = {
 name: 'Matt',
 age: 27
};

let { name, job='Software engineer' } = person;
console.log(name); // Matt
console.log(job); // Software engineer

let { length } = 'foobar';
console.log(length); // 6
let { constructor: c } = 4;
console.log(c === Number); // true
let { _ } = null; // TypeError
let { _ } = undefined; // TypeError 

let personName, personAge;
let person = {
 name: 'Matt',
 age: 27
};
({name: personName, age: personAge} = person);
console.log(personName, personAge); // Matt, 27
  1. 嵌套解构
  2. 部分解构
  3. 参数上下文匹配
  1. 嵌套解构

解构对于引用嵌套的属性或赋值目标没有限制。为此,可以通过解构来复制对象属性:

let person = {
 name: 'Matt',
 age: 27,
 job: {
 title: 'Software engineer'
 }
};
let personCopy = {};

({
 name: personCopy.name,
 age: personCopy.age,
 job: personCopy.job
} = person);

// 因为一个对象的引用被赋值给 personCopy,所以修改
// person.job 对象的属性也会影响 personCopy
person.job.title = 'Hacker'
console.log(person);
// { name: 'Matt', age: 27, job: { title: 'Hacker' } }
console.log(personCopy);
// { name: 'Matt', age: 27, job: { title: 'Hacker' } }
let person = {
 name: 'Matt',
 age: 27,
 job: {
 title: 'Software engineer'
 }
};

// 声明 title 变量并将 person.job.title 的值赋给它
let { job: { title } } = person;
console.log(title); // Software engineer

在外层属性没有定义的情况下不能使用嵌套解构。无论源对象还是目标对象都一样:

let person = {
 job: {
 title: 'Software engineer'
 }
};
let personCopy = {};

// foo 在源对象上是 undefined
({
 foo: {
 bar: personCopy.bar
 }
} = person);

// TypeError: Cannot destructure property 'bar' of 'undefined' or 'null'.
// job 在目标对象上是 undefined
({
 job: {
 title: personCopy.job.title
 }
} = person);
// TypeError: Cannot set property 'title' of undefined
  1. 部分解构

需要注意的是,涉及多个属性的解构赋值是一个输出无关的顺序化操作。如果一个解构表达式涉及多个赋值,开始的赋值成功而后面的赋值出错,则整个解构赋值只会完成一部分:

let person = {
 name: 'Matt',
 age: 27
};
let personName, personBar, personAge;

try {
 // person.foo 是 undefined,因此会抛出错误
 ({name: personName, foo: { bar: personBar }, age: personAge} = person);
} catch(e) {}
console.log(personName, personBar, personAge);
// Matt, undefined, undefined 
  1. 参数上下文匹配
let person = {
 name: 'Matt',
 age: 27
};

function printPerson(foo, {name, age}, bar) {
 console.log(arguments);
 console.log(name, age);
}

function printPerson2(foo, {name: personName, age: personAge}, bar) {
 console.log(arguments);
 console.log(personName, personAge);
}

printPerson('1st', person, '2nd');
// ['1st', { name: 'Matt', age: 27 }, '2nd']
// 'Matt', 27
printPerson2('1st', person, '2nd');
// ['1st', { name: 'Matt', age: 27 }, '2nd']
// 'Matt', 27 

创建对象

工厂模式

用于抽象创建特定对象的过程。

function createPerson(name, age, job) {
 let o = new Object();
 o.name = name;
 o.age = age;
 o.job = job;
 o.sayName = function() {
 console.log(this.name);
 };
 return o;
}

let person1 = createPerson("Nicholas", 29, "Software Engineer");
let person2 = createPerson("Greg", 27, "Doctor"); 

这种工厂模式 没有解决对象标识问题(即新创建的对象是什么类型)。

构造函数模式

function Person(name, age, job){
 this.name = name;
 this.age = age;
 this.job = job;
 this.sayName = function() {
 console.log(this.name);
 };
}

let person1 = new Person("Nicholas", 29, "Software Engineer");
let person2 = new Person("Greg", 27, "Doctor");
person1.sayName(); // Nicholas
person2.sayName(); // Greg

Person()内部 与 工厂函数区别:

  1. 没有显式地创建对象
  2. 属性和方法直接赋值给了 this
  3. 没有 return

创建 Person 的实例

  1. 在内存中创建一个新对象
  2. 这个新对象内部的[[Prototype]]特性被赋值为构造函数的 prototype 属性。
  3. 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)。
  4. 执行构造函数内部的代码(给新对象添加属性)。
  5. 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象

确定对象类型 instanceof 操作符

console.log(person1 instanceof Object); // true
console.log(person1 instanceof Person); // true
console.log(person2 instanceof Object); // true
console.log(person2 instanceof Person); // true
let Person = function(name, age, job) {
 this.name = name;
 this.age = age;
 this.job = job;
 this.sayName = function() {
 console.log(this.name);
 };
}

let person1 = new Person("Nicholas", 29, "Software Engineer");
let person2 = new Person("Greg", 27, "Doctor");
person1.sayName(); // Nicholas
person2.sayName(); // Greg

console.log(person1 instanceof Object); // true
console.log(person1 instanceof Person); // true
console.log(person2 instanceof Object); // true
console.log(person2 instanceof Person); // true 
  1. 构造函数也是函数

构造函数与普通函数唯一的区别就是调用方式不同。

任何函数只要使用 new 操作符调用就是构造函数,而不使用 new 操作符调用的函数就是普通函数。

// 作为构造函数
let person = new Person("Nicholas", 29, "Software Engineer");
person.sayName(); // "Nicholas"

// 作为函数调用
Person("Greg", 27, "Doctor"); // 添加到 window 对象
window.sayName(); // "Greg"

// 在另一个对象的作用域中调用
let o = new Object();
Person.call(o, "Kristen", 25, "Nurse");
o.sayName(); // "Kristen"
  1. 构造函数的问题

构造函数的主要问题在于,其定义的方法会在每个实例上都创建一遍。

ECMAScript 中的函数是对象 因此每次定义函数时,都会初始化一个对象。

function Person(name, age, job){
 this.name = name;
 this.age = age;
 this.job = job;
 this.sayName = new Function("console.log(this.name)"); // 逻辑等价
} 
console.log(person1.sayName == person2.sayName); // false 

因为都是做一样的事,所以没必要定义两个不同的 Function 实例

function Person(name, age, job){
 this.name = name;
 this.age = age;
 this.job = job;
 this.sayName = sayName;
}

function sayName() {
 console.log(this.name);
}

let person1 = new Person("Nicholas", 29, "Software Engineer");
let person2 = new Person("Greg", 27, "Doctor");

person1.sayName(); // Nicholas
person2.sayName(); // Greg 

导致自定义类型引用的代码不能很好地聚集一起。这个新问题可以通过原型模式来解决。

原型模式

每个函数都会创建一个 prototype 属性,这个属性是一个对象,包含应该由特定引用类型的实例共享的属性和方法。

function Person() {}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
 console.log(this.name);
};

let person1 = new Person();
person1.sayName(); // "Nicholas"
let person2 = new Person();
person2.sayName(); // "Nicholas"
console.log(person1.sayName == person2.sayName); // true
let Person = function() {};
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
 console.log(this.name);
};

let person1 = new Person();
person1.sayName(); // "Nicholas"
let person2 = new Person();
person2.sayName(); // "Nicholas"
console.log(person1.sayName == person2.sayName); // true
  1. 理解原型

Person.prototype.constructor 指向 Person

在自定义构造函数时,原型对象默认只会获得 constructor 属性,其他的所有方法都继承自Object。

每次调用构造函数创建一个新实例,这个实例的内部[[Prototype]]指针就会被赋值为构造函数的原型对象

Firefox、Safari 和 Chrome会在每个对象上暴露__proto__属性,通过这个属性可以访问对象的原型

理解原型的行为:

/**
 * 构造函数可以是函数表达式
 * 也可以是函数声明,因此以下两种形式都可以:
 * function Person() {}
 * let Person = function() {}
 */
function Person() {}

/**
 * 声明之后,构造函数就有了一个
 * 与之关联的原型对象:
 */
console.log(typeof Person.prototype);
console.log(Person.prototype);
// {
// constructor: f Person(),
// __proto__: Object
// }

/**
 * 如前所述,构造函数有一个 prototype 属性
 * 引用其原型对象,而这个原型对象也有一个
 * constructor 属性,引用这个构造函数
 * 换句话说,两者循环引用:
 */
console.log(Person.prototype.constructor === Person); // true
/**
 * 如前所述,构造函数有一个 prototype 属性
 * 引用其原型对象,而这个原型对象也有一个
 * constructor 属性,引用这个构造函数
 * 换句话说,两者循环引用:
 */
console.log(Person.prototype.constructor === Person); // true

/**
 * 正常的原型链都会终止于 Object 的原型对象
 * Object 原型的原型是 null
 */
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Person.prototype.__proto__.constructor === Object); // true
console.log(Person.prototype.__proto__.__proto__ === null); // true
console.log(Person.prototype.__proto__);

// {
// constructor: f Object(),
// toString: ...
// hasOwnProperty: ...
// isPrototypeOf: ...
// ...
// }
let person1 = new Person(),
 person2 = new Person();

/**
 * 构造函数、原型对象和实例
 * 是 3 个完全不同的对象:
 */
console.log(person1 !== Person); // true
console.log(person1 !== Person.prototype); // true
console.log(Person.prototype !== Person); // true

/**
 * 实例通过__proto__链接到原型对象,
 * 它实际上指向隐藏特性[[Prototype]]
 *
 * 构造函数通过 prototype 属性链接到原型对象
 *
 * 实例与构造函数没有直接联系,与原型对象有直接联系
 */
console.log(person1.__proto__ === Person.prototype); // true
conosle.log(person1.__proto__.constructor === Person); // true

/**
 * 同一个构造函数创建的两个实例
 * 共享同一个原型对象:
 */
console.log(person1.__proto__ === person2.__proto__); // true

/**
 * instanceof 检查实例的原型链中
  * 是否包含指定构造函数的原型:
 */
console.log(person1 instanceof Person); // true
console.log(person1 instanceof Object); // true
console.log(Person.prototype instanceof Object); // true

本质上,isPrototypeOf()会在传入参数的[[Prototype]]指向调用它的对象时返回 true

console.log(Person.prototype.isPrototypeOf(person1)); // true
console.log(Person.prototype.isPrototypeOf(person2)); // true

通过原型对象调用 isPrototypeOf()方法检查了 person1 和 person2。

ECMAScript 的 Object 类型有一个方法叫 Object.getPrototypeOf(),返回参数的内部特性[[Prototype]]的值。

console.log(Object.getPrototypeOf(person1) == Person.prototype); // true
console.log(Object.getPrototypeOf(person1).name); // "Nicholas"

Object 类型还有一个 setPrototypeOf()方法,可以向实例的私有特性[[Prototype]]写入一个新值。

重写一个对象的原型继承关系

let biped = {
 numLegs: 2
};
let person = {
 name: 'Matt'
};

Object.setPrototypeOf(person, biped);
console.log(person.name); // Matt
console.log(person.numLegs); // 2
console.log(Object.getPrototypeOf(person) === biped); // true 

通过 Object.create()来创建一个新对象,同时为其指定原型

let biped = {
 numLegs: 2
};

let person = Object.create(biped);
person.name = 'Matt';
console.log(person.name); // Matt
console.log(person.numLegs); // 2
console.log(Object.getPrototypeOf(person) === biped); // true
  1. 原型层级

hasOwnProperty()方法用于确定某个属性是在实例上还是在原型对象上。这个方法是继承自 Object的,会在属性存在于调用它的对象实例上时返回 true

function Person() {}
Person.prototype.name = "Nicholas";

Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
 console.log(this.name);
};

let person1 = new Person();
let person2 = new Person();

console.log(person1.hasOwnProperty("name")); // false
person1.name = "Greg";
console.log(person1.name); // "Greg",来自实例
console.log(person1.hasOwnProperty("name")); // true
console.log(person2.name); // "Nicholas",来自原型
console.log(person2.hasOwnProperty("name")); // false
delete person1.name;
console.log(person1.name); // "Nicholas",来自原型
console.log(person1.hasOwnProperty("name")); // false 

注意 ECMAScript 的 Object.getOwnPropertyDescriptor()方法只对实例属性有效。要取得原型属性的描述符,就必须直接在原型对象上调用 Object.getOwnPropertyDescriptor()。

  1. 原型和 in 操作符

有两种方式使用 in 操作符:单独使用和在 for-in 循环中使用。在单独使用时,in 操作符会在可以通过对象访问指定属性时返回 true,无论该属性是在实例上还是在原型上。

确定某个属性是否存在于原型上

function hasPrototypeProperty(object, name){
 return !object.hasOwnProperty(name) && (name in object);
} 

在 for-in 循环中使用 in 操作符时,可以通过对象访问且可以被枚举的属性都会返回,包括实例属性和原型属性。

要获得对象上所有可枚举的实例属性,可以使用 Object.keys()方法

function Person() {}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
 console.log(this.name);
};

let keys = Object.keys(Person.prototype);
console.log(keys); // "name,age,job,sayName"
let p1 = new Person();
p1.name = "Rob";
p1.age = 31;
let p1keys = Object.keys(p1);
console.log(p1keys); // "[name,age]"

如果想列出所有实例属性,无论是否可以枚举,都可以使用 Object.getOwnPropertyNames():

let keys = Object.getOwnPropertyNames(Person.prototype);
console.log(keys); // "[constructor,name,age,job,sayName]" 

Object.getOwnPropertySymbols()方法 针对符号

let k1 = Symbol('k1'),
 k2 = Symbol('k2'); 

let o = {
 [k1]: 'k1',
 [k2]: 'k2'
};

console.log(Object.getOwnPropertySymbols(o));
// [Symbol(k1), Symbol(k2)] 
  1. 属性枚举顺序

for-in 循环、Object.keys()、Object.getOwnPropertyNames()、Object.getOwnPropertySymbols()以及 Object.assign()在属性枚举顺序方面有很大区别

for-in 循环和 Object.keys()的枚举顺序是不确定的,取决于 JavaScript 引擎,可能因浏览器而异。

Object.getOwnPropertyNames()、Object.getOwnPropertySymbols()和 Object.assign()的枚举顺序是确定性的。

let k1 = Symbol('k1'),
 k2 = Symbol('k2');

let o = {
 1: 1,
 first: 'first',
 [k1]: 'sym2',
 second: 'second',
 0: 0
};
o[k2] = 'sym2';
o[3] = 3;
o.third = 'third';
o[2] = 2;

console.log(Object.getOwnPropertyNames(o));
// ["0", "1", "2", "3", "first", "second", "third"]
console.log(Object.getOwnPropertySymbols(o));
// [Symbol(k1), Symbol(k2)] 

对象迭代

Object.values()和 Object.entries()接收一个对象 返回它们内容的数组。

Object.values()返回对象值的数组,Object.entries()返回键/值对的数组。

const o = {
 foo: 'bar',
 baz: 1,
 qux: {}
};
console.log(Object.values(o));

// ["bar", 1, {}]
console.log(Object.entries((o)));
// [["foo", "bar"], ["baz", 1], ["qux", {}]]
const o = {
 qux: {}
};
console.log(Object.values(o)[0] === o.qux);
// true
console.log(Object.entries(o)[0][1] === o.qux);
// true

符号属性会被忽略:

const sym = Symbol();
const o = {
 [sym]: 'foo'
};

console.log(Object.values(o));
// []
console.log(Object.entries((o)));
// []
  1. 其他原型语法
function Person() {}
Person.prototype = {
 name: "Nicholas",
 age: 29,
 job: "Software Engineer",
 sayName() {
 console.log(this.name);
 }
};

只有一个问题:这样重写之后,Person.prototype 的 constructor 属性就不指向 Person了。

其 constructor 属性也指向了完全不同的新对象(Object 构造函数)

let friend = new Person();
console.log(friend instanceof Object); // true
console.log(friend instanceof Person); // true

console.log(friend.constructor == Person); // false
console.log(friend.constructor == Object); // true
function Person() {
}
Person.prototype = {
 constructor: Person,
 name: "Nicholas",
 age: 29,
 job: "Software Engineer",
 sayName() {
 console.log(this.name);
 }
}; 

以这种方式恢复 constructor 属性会创建一个[[Enumerable]]为 true 的属性。而原生 constructor 属性默认是不可枚举的

function Person() {}
Person.prototype = {
 name: "Nicholas",
 age: 29,
 job: "Software Engineer",
 sayName() {
 console.log(this.name);
 }
};

// 恢复 constructor 属性
Object.defineProperty(Person.prototype, "constructor", {
 enumerable: false,
 value: Person
}); 
  1. 原型的动态性
function Person() {}
let friend = new Person();
Person.prototype = {
 constructor: Person,
 name: "Nicholas",
 age: 29,
 job: "Software Engineer",
 sayName() {
 console.log(this.name);
 }
};
friend.sayName(); // 错误

因为 firend 指向的原型还是最初的原型,而这个原型上并没有 sayName 属性。

  1. 原生对象原型
console.log(typeof Array.prototype.sort); // "function"
console.log(typeof String.prototype.substring); // "function" 
  1. 原型的问题

它弱化了向构造函数传递初始化参数的能力,会导致所有实例默认都取得相同的属性值

function Person() {}
Person.prototype = {
 constructor: Person,
 name: "Nicholas",
 age: 29,
 job: "Software Engineer",
 friends: ["Shelby", "Court"],
sayName() {
 console.log(this.name);
 }
};

let person1 = new Person();
let person2 = new Person();
person1.friends.push("Van");

console.log(person1.friends); // "Shelby,Court,Van"
console.log(person2.friends); // "Shelby,Court,Van"
console.log(person1.friends === person2.friends); // true

继承

实现继承是 ECMAScript 唯一支持的继承方式,而这主要是通过原型链实现的。

原型链

ECMA-262 把原型链定义为 ECMAScript 的主要继承方式。

每个构造函数都有一个原型对象,原型有一个属性指回构造函数,而实例有一个内部指针指向原型。如果原型是另一个类型的实例呢?那就意味着这个原型本身有一个内部指针指向另一个原型,相应地另一个原型也有一个指针指向另一个构造函数。这样就在实例和原型之间构造了一条原型链。这就是原型链的基本构想。

function SuperType() {
 this.property = true;
}

SuperType.prototype.getSuperValue = function() {
 return this.property;
};

function SubType() {
 this.subproperty = false;
}

// 继承 SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function () { 
return this.subproperty;
};
let instance = new SubType();
console.log(instance.getSuperValue()); // true

在通过原型链实现继承之后,搜索就可以继承向上,搜索原型的原型

  1. 默认原型

默认情况下,所有引用类型都继承自 Object 任何函数的默认原型都是一个 Object 的实例 这意味着这个实例有一个内部指针指向Object.prototype

Object.prototype

  1. constructor
  2. hasOwnProperty
  3. isPrototypeOf
  4. propertyIsEnumerable
  5. toLocaleString
  6. toString
  7. valueOf

这也是为什么自定义类型能够继承包括 toString()、valueOf()在内的所有默认方法的原因。

  1. 原型与继承关系

第一种方式是使用 instanceof 操作符

console.log(instance instanceof Object); // true
console.log(instance instanceof SuperType); // true
console.log(instance instanceof SubType); // true 

第二种方式是使用 isPrototypeOf()方法

console.log(Object.prototype.isPrototypeOf(instance)); // true
console.log(SuperType.prototype.isPrototypeOf(instance)); // true
console.log(SubType.prototype.isPrototypeOf(instance)); // true 
  1. 关于方法
function SuperType() {
 this.property = true;
}
SuperType.prototype.getSuperValue = function() {
 return this.property;
};
function SubType() {
 this.subproperty = false;
}

// 继承 SuperType
SubType.prototype = new SuperType();
// 新方法
SubType.prototype.getSubValue = function () {
 return this.subproperty;
};

// 覆盖已有的方法
SubType.prototype.getSuperValue = function () {
 return false;
};
let instance = new SubType();
console.log(instance.getSuperValue()); // false

以对象字面量方式创建原型方法会破坏之前的原型链

function SuperType() {
 this.property = true;
}
SuperType.prototype.getSuperValue = function() {
 return this.property;
};
function SubType() {
 this.subproperty = false;
}

// 继承 SuperType
SubType.prototype = new SuperType();
// 通过对象字面量添加新方法,这会导致上一行无效
SubType.prototype = {
 getSubValue() {
 return this.subproperty;
 },
 someOtherMethod() {
 return false;
 }
};

let instance = new SubType();
console.log(instance.getSuperValue()); // 出错!
  1. 原型链的问题

主要问题出现在原型中包含引用值的时候。原型中包含的引用值会在所有实例间共享 ,这也是为什么属性通常会
在构造函数中定义而不会定义在原型上的原因。

原先的实例属性摇身一变成为了原型属性。

function SuperType() {
 this.colors = ["red", "blue", "green"];
}
function SubType() {}

// 继承 SuperType
SubType.prototype = new SuperType();
let instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors); // "red,blue,green,black"
let instance2 = new SubType();
console.log(instance2.colors); // "red,blue,green,black"

第二个问题是,子类型在实例化时不能给父类型的构造函数传参。

盗用构造函数

这种技术有时也称作“对象伪装”或“经典继承”

基本思路很简单:在子类构造函数中调用父类构造函数。因为毕竟函数就是在特定上下文中执行代码的简单对象,所以可以使用apply()和 call()方法以新创建的对象为上下文执行构造函数。

function SuperType() {
 this.colors = ["red", "blue", "green"];
}

function SubType() {
 // 继承 SuperType
 SuperType.call(this);
}

let instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors); // "red,blue,green,black"
let instance2 = new SubType();
console.log(instance2.colors); // "red,blue,green"

这相当于新的 SubType 对象上运行了SuperType()函数中的所有初始化代码。

  1. 传递参数

相比于使用原型链,盗用构造函数的一个优点就是可以在子类构造函数中向父类构造函数传参。

function SuperType(name){
 this.name = name;
}

function SubType() {
 // 继承 SuperType 并传参
 SuperType.call(this, "Nicholas");
 // 实例属性
 this.age = 29;
}

let instance = new SubType();
console.log(instance.name); // "Nicholas";
console.log(instance.age); // 29
  1. 盗用构造函数的问题

盗用构造函数的主要缺点,也是使用构造函数模式自定义类型的问题:必须在构造函数中定义方法,因此函数不能重用。此外,子类也不能访问父类原型上定义的方法,因此所有类型只能使用构造函数模式。盗用构造函数基本上也不能单独使用。

组合继承

组合继承综合了原型链和盗用构造函数

基本的思路是使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。

function SuperType(name){
 this.name = name;
 this.colors = ["red", "blue", "green"];
}

SuperType.prototype.sayName = function() {
 console.log(this.name);
};
function SubType(name, age){
 // 继承属性
 SuperType.call(this, name);
 this.age = age;
}

// 继承方法
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function() {
 console.log(this.age);
};

let instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
console.log(instance1.colors); // "red,blue,green,black"
instance1.sayName(); // "Nicholas";
instance1.sayAge(); // 29

let instance2 = new SubType("Greg", 27);
console.log(instance2.colors); // "red,blue,green"
instance2.sayName(); // "Greg";
instance2.sayAge(); // 27

组合继承,是 JavaScript 中使用最多的继承模式。

原型式继承

function object(o) {
 function F() {}
 F.prototype = o;
 return new F();
}

本质上,object()是对传入的对象执行了一次浅复制

let person = {
 name: "Nicholas",
 friends: ["Shelby", "Court", "Van"]
};

let anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");

let yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
console.log(person.friends); // "Shelby,Court,Van,Rob,Barbie"

ECMAScript 5 通过增加 Object.create()方法将原型式继承的概念规范化了。

let person = {
 name: "Nicholas",
 friends: ["Shelby", "Court", "Van"]
};

let anotherPerson = Object.create(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");

let yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
console.log(person.friends); // "Shelby,Court,Van,Rob,Barbie"
let person = {
 name: "Nicholas",
 friends: ["Shelby", "Court", "Van"]
};

let anotherPerson = Object.create(person, {
 name: {
 value: "Greg"
 }
});
console.log(anotherPerson.name); // "Greg"

寄生式继承

创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象

function createAnother(original){
 let clone = object(original); // 通过调用函数创建一个新对象
 clone.sayHi = function() { // 以某种方式增强这个对象
 console.log("hi");
 };
 return clone; // 返回这个对象
}

寄生式组合继承

组合继承其实也存在效率问题。最主要的效率问题就是父类构造函数始终会被调用两次

一次在是创建子类原型时调用,另一次是在子类构造函数中调用。

本质上,子类原型最终是要包含超类对象的所有实例属性,子类构造函数只要在执行时重写自己的原型就行了。

function SuperType(name) {
 this.name = name;
 this.colors = ["red", "blue", "green"];
}

SuperType.prototype.sayName = function() {
 console.log(this.name);
};

function SubType(name, age){
 SuperType.call(this, name); // 第二次调用 SuperType()
 this.age = age;
}

SubType.prototype = new SuperType(); // 第一次调用 SuperType()
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
 console.log(this.age);
};

寄生式组合继承通过盗用构造函数继承属性,但使用混合式原型链继承方法。基本思路是不通过调用父类构造函数给子类原型赋值,而是取得父类原型的一个副本

寄生式组合继承的基本模式

function inheritPrototype(subType, superType) {
 let prototype = object(superType.prototype); // 创建对象
 prototype.constructor = subType; // 增强对象
 subType.prototype = prototype; // 赋值对象
}

这个函数接收两个参数:子类构造函数和父类构造函数。

function SuperType(name) {
 this.name = name;
 this.colors = ["red", "blue", "green"];
}

SuperType.prototype.sayName = function() {
 console.log(this.name);
};

function SubType(name, age) {
 SuperType.call(this, name);
 this.age = age;
}

inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function() {
 console.log(this.age);
}; 

寄生式组合继承可以算是引用类型继承的最佳模式。

两种主要方式:类声明和类表达式。

// 类声明
class Person {}
// 类表达式
const Animal = class {};
console.log(FunctionExpression); // undefined
var FunctionExpression = function() {};
console.log(FunctionExpression); // function() {}

console.log(FunctionDeclaration); // FunctionDeclaration() {}
function FunctionDeclaration() {}
console.log(FunctionDeclaration); // FunctionDeclaration() {}

console.log(ClassExpression); // undefined
var ClassExpression = class {};
console.log(ClassExpression); // class {}

console.log(ClassDeclaration); // ReferenceError: ClassDeclaration is not defined
class ClassDeclaration {}
console.log(ClassDeclaration); // class ClassDeclaration {}

函数受函数作用域限制,而类受块作用域限制

{
 function FunctionDeclaration() {}
 class ClassDeclaration {}
}

console.log(FunctionDeclaration); // FunctionDeclaration() {}
console.log(ClassDeclaration); // ReferenceError: ClassDeclaration is not defined

类的构成

类可以包含构造函数方法、实例方法、获取函数、设置函数和静态类方法,

// 空类定义,有效
class Foo {}

// 有构造函数的类,有效
class Bar {
 constructor() {}
}

// 有获取函数的类,有效
class Baz {
 get myBaz() {}
}

// 有静态方法的类,有效
class Qux {
 static myQux() {}
} 

在把类表达式赋值给变量后,可以通过 name 属性取得类表达式的名称字符串。

let Person = class PersonName {
 identify() {
 console.log(Person.name, PersonName.name);
 }
}

let p = new Person();

p.identify(); // PersonName PersonName
console.log(Person.name); // PersonName
console.log(PersonName); // ReferenceError: PersonName is not defined

类构造函数

constructor 关键字用于在类定义块内部创建类的构造函数

  1. 实例化

(1) 在内存中创建一个新对象。

(2) 这个新对象内部的[[Prototype]]指针被赋值为构造函数的 prototype 属性。

(3) 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)。

(4) 执行构造函数内部的代码(给新对象添加属性)。

(5) 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。

class Animal {}
class Person {
 constructor() {
 console.log('person ctor');
 }
}
class Vegetable {
 constructor() {
 this.color = 'orange';
 }
}
let a = new Animal();
let p = new Person(); // person ctor
let v = new Vegetable();
console.log(v.color); // orange

类实例化时传入的参数会用作构造函数的参数。如果不需要参数,则类名后面的括号也是可选的

class Person {
 constructor(override) {
 this.foo = 'foo';
 if (override) {
 return {
 bar: 'bar'
 };
 }
 }
}
let p1 = new Person(),
 p2 = new Person(true);
console.log(p1); // Person{ foo: 'foo' }
console.log(p1 instanceof Person); // true
console.log(p2); // { bar: 'bar' }
console.log(p2 instanceof Person); // false

调用类构造函数时如果忘了使用 new 则会抛出错误

class Person {}
// 使用类创建一个新实例
let p1 = new Person();
p1.constructor();
// TypeError: Class constructor Person cannot be invoked without 'new'
// 使用对类构造函数的引用创建一个新实例
let p2 = new p1.constructor();
  1. 把类当成特殊函数
class Person {}
console.log(Person); // class Person {}
console.log(typeof Person); // function 

类标识符有 prototype 属性,而这个原型也有一个 constructor 属性指向类自身

class Person{}
console.log(Person.prototype); // { constructor: f() }
console.log(Person === Person.prototype.constructor); // true

可以使用 instanceof 操作符检查构造函数原型是否存在于实例的原型链中

class Person {}
let p = new Person();
console.log(p instanceof Person); // true 

重点在于,类中定义的 constructor 方法不会被当成构造函数,在对它使用instanceof 操作符时会返回 false

class Person {}

let p1 = new Person();

console.log(p1.constructor === Person); // true
console.log(p1 instanceof Person); // true
console.log(p1 instanceof Person.constructor); // false

let p2 = new Person.constructor();

console.log(p2.constructor === Person); // false
console.log(p2 instanceof Person); // false
console.log(p2 instanceof Person.constructor); // true

类是 JavaScript 的一等公民

// 类可以像函数一样在任何地方定义,比如在数组中
let classList = [
 class {
 constructor(id) {
 this.id_ = id;
 console.log(`instance ${this.id_}`);
 }
 }
];
function createInstance(classDefinition, id) {
 return new classDefinition(id);
}
let foo = createInstance(classList[0], 3141); // instance 3141

类也可以立即实例化

// 因为是一个类表达式,所以类名是可选的
let p = new class Foo {
constructor(x) {
 console.log(x);
 }
}('bar'); // bar
console.log(p); // Foo {}

实例、原型和类成员

  1. 实例成员

每次通过new调用类标识符时,都会执行类构造函数。

每个实例都对应一个唯一的成员对象

class Person {
 constructor() {
 // 这个例子先使用对象包装类型定义一个字符串
 // 为的是在下面测试两个对象的相等性
 this.name = new String('Jack');

 this.sayName = () => console.log(this.name);

 this.nicknames = ['Jake', 'J-Dog']
 }
}

let p1 = new Person(),
 p2 = new Person();

p1.sayName(); // Jack
p2.sayName(); // Jack

console.log(p1.name === p2.name); // false
console.log(p1.sayName === p2.sayName); // false
console.log(p1.nicknames === p2.nicknames); // false

p1.name = p1.nicknames[0];
p2.name = p2.nicknames[1];

p1.sayName(); // Jake
p2.sayName(); // J-Dog
  1. 原型方法与访问器

为了在实例间共享方法,类定义语法把在类块中定义的方法作为原型方法。

class Person {
 constructor() {
 // 添加到 this 的所有内容都会存在于不同的实例上
 this.locate = () => console.log('instance');
 } 
 // 在类块中定义的所有内容都会定义在类的原型上
 locate() {
 console.log('prototype');
 }
}
let p = new Person();
p.locate(); // instance
Person.prototype.locate(); // prototype

可以把方法定义在类构造函数中或者类块中,但不能在类块中给原型添加原始值或对象作为成员数据:

class Person {
 name: 'Jake'
}
// Uncaught SyntaxError: Unexpected token
const symbolKey = Symbol('symbolKey');

class Person {
 stringKey() {
 console.log('invoked stringKey');
 }
 [symbolKey]() {
 console.log('invoked symbolKey');
 }
 ['computed' + 'Key']() {
 console.log('invoked computedKey');
 }
}

let p = new Person();
p.stringKey(); // invoked stringKey
p[symbolKey](); // invoked symbolKey
p.computedKey(); // invoked computedKey

类定义也支持获取和设置访问器

class Person {
 set name(newName) {
 this.name_ = newName;
 }
 get name() {
 return this.name_;
 }
}
let p = new Person();
p.name = 'Jake';
console.log(p.name); // Jake
  1. 静态类方法

在静态成员中,this 引用类自身。

class Person {
 constructor() {
 // 添加到 this 的所有内容都会存在于不同的实例上
 this.locate = () => console.log('instance', this);
 }
 // 定义在类的原型对象上
 locate() {
 console.log('prototype', this);
 }
 // 定义在类本身上
 static locate() {
 console.log('class', this);
 }
}
let p = new Person();
p.locate(); // instance, Person {}
Person.prototype.locate(); // prototype, {constructor: ... }
Person.locate(); // class, class Person {}

静态类方法非常适合作为实例工厂:

class Person {
 constructor(age) {
 this.age_ = age;
 }
 sayAge() {
 console.log(this.age_);
 }
 static create() {
 // 使用随机年龄创建并返回一个 Person 实例
 return new Person(Math.floor(Math.random()*100));
 }
}
console.log(Person.create()); // Person { age_: ... } 
  1. 非函数原型和类成员
class Person {
 sayName() {
 console.log(`${Person.greeting} ${this.name}`);
 }
}
// 在类上定义数据成员
Person.greeting = 'My name is';
// 在原型上定义数据成员
Person.prototype.name = 'Jake';
let p = new Person();
p.sayName(); // My name is Jake 
  1. 迭代器与生成器方法
class Person {
 // 在原型上定义生成器方法
 *createNicknameIterator() {
 yield 'Jack';
 yield 'Jake';
 yield 'J-Dog';
 }
 // 在类上定义生成器方法
 static *createJobIterator() {
 yield 'Butcher';
 yield 'Baker';
 yield 'Candlestick maker';
 }
}

let jobIter = Person.createJobIterator();
console.log(jobIter.next().value); // Butcher
console.log(jobIter.next().value); // Baker
console.log(jobIter.next().value); // Candlestick maker
let p = new Person();
let nicknameIter = p.createNicknameIterator();
console.log(nicknameIter.next().value); // Jack
console.log(nicknameIter.next().value); // Jake
console.log(nicknameIter.next().value); // J-Dog

可以通过添加一个默认的迭代器,把类实例变成可迭代对象

class Person {
 constructor() {
 this.nicknames = ['Jack', 'Jake', 'J-Dog'];
 }
 *[Symbol.iterator]() {
 yield *this.nicknames.entries();
 }
}
let p = new Person();
for (let [idx, nickname] of p) {
 console.log(nickname);
} 

也可以只返回迭代器实例:

class Person {
 constructor() {
 this.nicknames = ['Jack', 'Jake', 'J-Dog'];
 }
 [Symbol.iterator]() {
 return this.nicknames.entries();
 }
}
let p = new Person();
for (let [idx, nickname] of p) {
 console.log(nickname);
}
// Jack
// Jake
// J-Dog 

继承

ECMAScript 6 新增特性中最出色的一个就是原生支持了类继承机制。

  1. 继承基础

ES6 类支持单继承。使用 extends 关键字,就可以继承任何拥有[[Construct]]和原型的对象

class Vehicle {}

// 继承类
class Bus extends Vehicle {}
let b = new Bus();
console.log(b instanceof Bus); // true
console.log(b instanceof Vehicle); // true
function Person() {}

// 继承普通构造函数
class Engineer extends Person {}
let e = new Engineer();
console.log(e instanceof Engineer); // true
console.log(e instanceof Person); // true
class Vehicle {
 identifyPrototype(id) {
 console.log(id, this);
 }
static identifyClass(id) {
 console.log(id, this);
 }
}

class Bus extends Vehicle {}
let v = new Vehicle();
let b = new Bus();

b.identifyPrototype('bus'); // bus, Bus {}
v.identifyPrototype('vehicle'); // vehicle, Vehicle {}
Bus.identifyClass('bus'); // bus, class Bus {}
Vehicle.identifyClass('vehicle'); // vehicle, class Vehicle {}

注意 extends 关键字也可以在类表达式中使用,因此 let Bar = class extends Foo {}
是有效的语法。

  1. 构造函数、HomeObject 和 super()

派生类的方法可以通过 super 关键字引用它们的原型。

这个关键字只能在派生类中使用,而且仅限于类构造函数、实例方法和静态方法内部。在类构造函数中使用 super 可以调用父类构造函数。

class Vehicle {
 constructor() {
 this.hasEngine = true;
 }
}

class Bus extends Vehicle {
 constructor() {
 // 不要在调用 super()之前引用 this,否则会抛出 ReferenceError
 super(); // 相当于 super.constructor()
 console.log(this instanceof Vehicle); // true
 console.log(this); // Bus { hasEngine: true }
 }
}
new Bus(); 

在静态方法中可以通过 super 调用继承的类上定义的静态方法:

class Vehicle {
 static identify() {
 console.log('vehicle');
 }
}

class Bus extends Vehicle {
 static identify() {
 super.identify();
 }
}

Bus.identify(); // vehicle

注意 ES6 给类构造函数和静态方法添加了内部特性[[HomeObject]],这个特性是一个指针,指向定义该方法的对象。这个指针是自动赋值的,而且只能在 JavaScript 引擎内部访问。super 始终会定义为[[HomeObject]]的原型。

在使用 super 时要注意几个问题。

  1. super 只能在派生类构造函数和静态方法中使用。
class Vehicle {
 constructor() {
 super();
 // SyntaxError: 'super' keyword unexpected
 }
}
  1. 不能单独引用 super 关键字,要么用它调用构造函数,要么用它引用静态方法。
class Vehicle {}
class Bus extends Vehicle {
 constructor() {
 console.log(super);
 // SyntaxError: 'super' keyword unexpected here
 }
}
  1. 调用 super()会调用父类构造函数,并将返回的实例赋值给 this。
class Vehicle {}
class Bus extends Vehicle {
 constructor() {
 super();
 console.log(this instanceof Vehicle);
 }
}

new Bus(); // true
  1. super()的行为如同调用构造函数,如果需要给父类构造函数传参,则需要手动传入。
class Vehicle {
 constructor(licensePlate) {
 this.licensePlate = licensePlate;
 }
}

class Bus extends Vehicle {
 constructor(licensePlate) {
 super(licensePlate);
 }
}

console.log(new Bus('1337H4X')); // Bus { licensePlate: '1337H4X' }
  1. 如果没有定义类构造函数,在实例化派生类时会调用 super(),而且会传入所有传给派生类的参数。
class Vehicle {
 constructor(licensePlate) {
 this.licensePlate = licensePlate;
 }
}

class Bus extends Vehicle {}
console.log(new Bus('1337H4X')); // Bus { licensePlate: '1337H4X' }
  1. 在类构造函数中,不能在调用 super()之前引用 this。
class Vehicle {}
class Bus extends Vehicle {
 constructor() {
 console.log(this);
 }
}

new Bus();
// ReferenceError: Must call super constructor in derived class
// before accessing 'this' or returning from derived constructor 
  1. 如果在派生类中显式定义了构造函数,则要么必须在其中调用 super(),要么必须在其中返回一个对象。
class Vehicle {}
class Car extends Vehicle {}
class Bus extends Vehicle {
 constructor() {
 super();
 }
}

class Van extends Vehicle {
 constructor() {
 return {};
 }
}

console.log(new Car()); // Car {}
console.log(new Bus()); // Bus {}
console.log(new Van()); // {}
  1. 抽象基类

new.target 保存通过 new 关键字调用的类或函数。通过在实例化时检测 new.target 是不是抽象基类,可以阻止对抽象基类的实例化

// 抽象基类
class Vehicle {
 constructor() {
 console.log(new.target);
 if (new.target === Vehicle) {
 throw new Error('Vehicle cannot be directly instantiated'); 
}
 }
}

// 派生类
class Bus extends Vehicle {}
new Bus(); // class Bus {}
new Vehicle(); // class Vehicle {}
// Error: Vehicle cannot be directly instantiated
// 抽象基类
class Vehicle {
 constructor() {
 if (new.target === Vehicle) {
 throw new Error('Vehicle cannot be directly instantiated');
 }
 if (!this.foo) {
 throw new Error('Inheriting class must define foo()');
 }
 console.log('success!');
 }
}

// 派生类
class Bus extends Vehicle {
 foo() {}
}

// 派生类
class Van extends Vehicle {}
new Bus(); // success!
new Van(); // Error: Inheriting class must define foo()
  1. 继承内置类型
class SuperArray extends Array {
 shuffle() {
 // 洗牌算法
 for (let i = this.length - 1; i > 0; i--) {
 const j = Math.floor(Math.random() * (i + 1));
 [this[i], this[j]] = [this[j], this[i]];
 }
 }
}
let a = new SuperArray(1, 2, 3, 4, 5);
console.log(a instanceof Array); // true
console.log(a instanceof SuperArray); // true

console.log(a); // [1, 2, 3, 4, 5]
a.shuffle();
console.log(a); // [3, 1, 4, 5, 2]
class SuperArray extends Array {}
let a1 = new SuperArray(1, 2, 3, 4, 5);
let a2 = a1.filter(x => !!(x%2))
console.log(a1); // [1, 2, 3, 4, 5]
console.log(a2); // [1, 3, 5]
console.log(a1 instanceof SuperArray); // true
console.log(a2 instanceof SuperArray); // true

如果想覆盖这个默认行为,则可以覆盖 Symbol.species 访问器,这个访问器决定在创建返回的实例时使用的类

class SuperArray extends Array {
 static get [Symbol.species]() {
 return Array;
 }
}

let a1 = new SuperArray(1, 2, 3, 4, 5);
let a2 = a1.filter(x => !!(x%2))
console.log(a1); // [1, 2, 3, 4, 5]
console.log(a2); // [1, 3, 5]
console.log(a1 instanceof SuperArray); // true
console.log(a2 instanceof SuperArray); // false
  1. 类混入

注意 Object.assign()方法是为了混入对象行为而设计的。只有在需要混入类的行为时才有必要自己实现混入表达式。如果只是需要混入多个对象的属性,那么使用Object.assign()就可以了。

class Vehicle {}
function getParentClass() {
 console.log('evaluated expression');
 return Vehicle;
}
class Bus extends getParentClass() {}
// 可求值的表达式
class Vehicle {}
let FooMixin = (Superclass) => class extends Superclass {
 foo() {
 console.log('foo');
 }
};
let BarMixin = (Superclass) => class extends Superclass {
 bar() {
 console.log('bar');
 }
};
let BazMixin = (Superclass) => class extends Superclass {
 baz() {
 console.log('baz');
 }
};
class Bus extends FooMixin(BarMixin(BazMixin(Vehicle))) {}
let b = new Bus();
b.foo(); // foo
b.bar(); // bar
b.baz(); // baz
class Vehicle {}
let FooMixin = (Superclass) => class extends Superclass {
 foo() {
 console.log('foo');
 }
};
let BarMixin = (Superclass) => class extends Superclass {
 bar() {
 console.log('bar');
 }
};
let BazMixin = (Superclass) => class extends Superclass {
 baz() {
 console.log('baz');
 }
};
function mix(BaseClass, ...Mixins) {
 return Mixins.reduce((accumulator, current) => current(accumulator), BaseClass);
}
class Bus extends mix(Vehicle, FooMixin, BarMixin, BazMixin) {}
let b = new Bus();
b.foo(); // foo
b.bar(); // bar
b.baz(); // baz 
  1. 工厂模式就是一个简单的函数,这个函数可以创建对象,为它添加属性和方法,然后返回这个对象。这个模式在构造函数模式出现后就很少用了。
  2. 使用构造函数模式可以自定义引用类型,可以使用 new 关键字像创建内置类型实例一样创建自定义类型的实例。不过,构造函数模式也有不足,主要是其成员无法重用,包括函数。考虑到函数本身是松散的、弱类型的,没有理由让函数不能在多个对象实例间共享。
  3. 原型模式解决了成员共享的问题,只要是添加到构造函数 prototype 上的属性和方法就可以共享。而组合构造函数和原型模式通过构造函数定义实例属性,通过原型定义共享的属性和方法。
  4. 原型式继承可以无须明确定义构造函数而实现继承,本质上是对给定对象执行浅复制。这种操作的结果之后还可以再进一步增强。
  5. 与原型式继承紧密相关的是寄生式继承,即先基于一个对象创建一个新对象,然后再增强这个新对象,最后返回新对象。这个模式也被用在组合继承中,用于避免重复调用父类构造函数导致的浪费。
  6. 寄生组合继承被认为是实现基于类型继承的最有效方式。

🆗

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.