- 前端知识图谱
- 面试的经验总结
- 网络学习总结
- 算法的学习总结
- 设计模式学习总结
相关的内容请查看项目 issues。
如果 github 中的 issues 的图片无法打开,解决方案:#10
总结三篇文章:(侧重点均不一样)
侧重底层《从输入 URL 到页面加载完成的过程中都发生了什么事情?》作者:nwind
http://fex.baidu.com/blog/2014/05/what-happen/
侧重网络《从你输入一个网址,到网页显示,其间发生了什么?》 作者:小林coding
https://mp.weixin.qq.com/s/tZ9-BoP1Oz3K4ZapLlnopQ
侧重前端《在浏览器输入 URL 回车之后发生了什么(超详细版)》作者:4Ark
https://zhuanlan.zhihu.com/p/80551769
分成两个层面来回答:
要说明这两个问题,我们先从计算机数据的基本单位——位,开始说起:
位 bit,是电脑存储的最小单位,一个位就代表二进制中的 0 或者 1。
那么一个字节,一个字符,一个字符串跟位的关系是怎样的呢?
接下来我们来看字符编码,计算机编码要从 ASCII 码说起:
ASCII码
总共有 127 个包含英文字母和符号,但是不支持其他字符。ASCII码的标志是字节的第一位是 0,剩下的7位是ASCII码。为了满足信息交换,必须有新的编码方式来支持世界上的其他字符。
因为 ASCII 码无法表示中文字符,所以需要通过拓展位数来表示中文字符,于是就有了 GB2312 字符集。
GB2312
诞生于1980年,将所有的汉字字符都用2个字节(16位)表示。GB2312 字符集中除常用的简体汉字字符之外还有希腊字母,日文平假名和片假名、俄语西里尔字母等字符,未收录的繁体中文汉字和生僻字。
GBK
首次出现在 Windows 95 简体中文版,因为 GB2312 还是不足以涵盖所有中文字符,所以微软对 GB2312 的进行了拓展,GBK 向下兼容 GB2312 和 ASCII。
GB18030
2000年,取代了 GBK 成为正式国家标准,该标准收录了27484个汉字,同时还收录了一些少数民族文字。它和前面两者的差别是,他是变长的字符集,意味着一个 GB18030 的字可能由 1个、2个或4个字节组成。兼容 GBK,GB2312,ASCII。
前面讲到的 GB 系列的编码虽然很好的解决了汉字编码问题,但是却无法形成世界统一的字符集,因此,世界需要一个统一的字符集,所以 Unicode 诞生了。
Unicode
是一个统一的字符集,涵盖了世界上所有的字符,统一用两个字节来表示(后面诞生了4个字节的 unicode),包括 ASCII。虽然统一了字符集,但是 ASCII 字符也变成了2个字节的,这样数据在进行网络传输的时候就会造成流量浪费。所以诞生了 utf-8, utf-16, utf-32 等编码格式,横杠后面的数字代表尝试将单个 unicode 字符压缩成几 bit。
UTF-8
是用一个字节来尝试表示一个 unicode 字符的编码格式。如果一个字节能容纳得了,那么Unicode 转成 UTF-8 就可以省下一个字节,当一个字节容纳不下的时候,会增加一个字节来尝试表示。所以可能会出现 unicode 只要两个字节,但是转换成 utf-8 会变成 3 或 4 个字节。因此出现了 utf-16。
UTF-16
是用两个字节来尝试表示一个 unicode 字符的编码格式。当一个 unicode 字符转换UTF-8 需要 3 或 4 个字节的时候,用 UTF-16 的基本都能转换成 2 个字节,所以现在在 windows 下的 unicode 实现大多使用 utf-16 编码。
《CDN 详解》https://segmentfault.com/a/1190000010631404 作者 Pines_Cheng
tips: 组合搭配 2 和 3 可以最大化节省内存。
自备梯子的前提下, url 输入 chrome://extensions
即可进行拓展程序管理
没有的话,找国内的插件网站对应下载即可
之前笔者只接触了 vue-cli@3,长期作为业务仔,自然没有使用最新的脚手架建立项目的机会。
不过没关系,最近有机会了。😁
基于某个公司的招聘项目,使用 vue-cli@4 的完成了一个小 demo,因为还没有走通前后台。
废话不多,开始了解。
官网的东西不用细讲,安装链接
npm install -g @vue/cli
安装下来后在用 vue --version
检查即可
在 terminal 输入 vue create test-app
后,会出来几个选项。
你可以通过配置好的,或者选择自定义的选项来初始化项目,这部分根据项目的需求、特性、技术栈、性能、生态、用户场景等多个角度去考量。当然折腾能力强的,就直接选个最简单的组合就行了。比如 babel, sass/less, vue, vuex, vue-router, eslint 就行。pwa, ts, ssr, jest 和 cypress 就暂时先不考虑。
初始之后,会在 package.json 中帮你把对应的脚本,比如 eslint 的校验修复都处理好了,一行命令运行就行。
我们来看看官网上随口提了一下但没怎么细讲的 vue ui.
你可以通过 vue ui 命令使用 GUI 运行更多的特性脚本
通过在当前项目 vue ui 你就可以启动当前项目的 cli 服务,基于一个页面的可视化操作。
在仪表盘下,默认只有一个杀掉端口,场景在于杀掉一个node进程后,这个端口还是被占用的。这时候再跑起前端代码会提示端口占用。这时候就要杀掉这个端口的进程占用。
通过右上角的自定义,可以把界面定制成这个样子:
可以注意到,除了可视化操作一些构建命令之外,还有依赖更新和插件更新正在 TODO 中,最后一个 Vue News 的面板,我试着点了一下设置,发现是可以自定义订阅内容的,只是我也没有合适的 rss 源来测试下替换后的效果。
整体感觉像是个玩具,事实上我并不会用这种方式去检查依赖版本,像 npm pnpm cnpm yarn 可能都已经内置了依赖版本检查,有需求的时候再去更新就好,何况一更新还经常会引起 bug。
根据一些通用的场景,我做了一个优化版的项目初始配置的,后续有需求的话也可以整合 ts,欢迎小伙伴留言。
涉及到的特性:
工程特性
.editorconfig 增加编辑器配置
.npmrc npm 设置 npm 和 node-sass 代理
.prettierrc 优化 prettier 选项
babel.config.js 实现 element-ui 按需加载
jsconfig.json 增加 vscode 文件跳转
jest.config.js 增加 identity-obj-proxy 用于含有 elementui 的组件测试
.eslintrc.js 增加全局变量配置
vue.config.js 增加 definePlugin 和 webpackBundleAnalyzer 的配置,自定义 loader(只是举例)
业务特性
dayjs 用于日期处理
normalize.css 用于 css reset
elementui 作为前端组件库
vchart 封装的 echart 作为数据可视化
《The process: Making Vue 3》 EVAN YOU
Mac 下安装 nginx 可以通过 brew 进行。
$ brew search nginx
$ brew install nginx
出现如下的信息表示安装成功:
Docroot is: /usr/local/var/www
The default port has been set in /usr/local/etc/nginx/nginx.conf to 8080 so that nginx can run without sudo.
nginx will load all files in /usr/local/etc/nginx/servers/.
To have launchd start nginx now and restart at login: brew services start nginx
Or, if you don't want/need a background service you can just run: nginx
输入如下的命令来启动 Nginx:
$ nginx
在浏览器中输入:http://localhost:8080 ,查看 Nginx 首页。
nginx -s reload
:重新加载配置nginx -s reopen
:重启nginx -s stop
:停止nginx -s quit
:退出其中 Nginx 配置文件路径在 /usr/local/etc/nginx/nginx.conf
vim /usr/local/etc/nginx/nginx.conf
最近在参与一个项目,我需要配置两个本地域名,目的如下:
yesplaymusic.com
的请求转发到前端开发 devServer
服务 localhost:8081
api.yesplaymusic.com
的请求转发到本地运行接口服务 localhost:3000
那么我们开始改动配置(带 # 为注释语句)
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root html;
index index.html index.htm;
}
# ...
}
yesplaymusic.com
的服务器规则server {
listen 80;
server_name yesplaymusic.com;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
location / {
proxy_pass http://localhost:8081;
proxy_connect_timeout 600;
proxy_read_timeout 600;
}
}
api.yesplaymusic.com
的服务器规则server {
listen 80;
server_name api.yesplaymusic.com;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
location / {
proxy_pass http://localhost:3000;
proxy_connect_timeout 600;
proxy_read_timeout 600;
}
}
# user hawtim;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 80;
server_name yesplaymusic.com;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
location / {
proxy_pass http://localhost:8081;
proxy_connect_timeout 600;
proxy_read_timeout 600;
}
}
server {
listen 80;
server_name api.yesplaymusic.com;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
location / {
proxy_pass http://localhost:3000;
proxy_connect_timeout 600;
proxy_read_timeout 600;
}
}
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root html;
index index.html index.htm;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;
# location / {
# root html;
# index index.html index.htm;
# }
#}
# HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost;
# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
# location / {
# root html;
# index index.html index.htm;
# }
#}
include servers/*;
}
nginx -t # 检查有无错误
nginx -s reload # 重新加载新的配置
这里使用 switchhosts 在 hosts 中添加上面设定好的两个域名
# develop
127.0.0.1 yesplaymusic.com
127.0.0.1 api.yesplaymusic.com
http://yesplaymusic.com
,查看页面是否正常http://yesplaymusic.com
命令行中输入: 查看当前占用 80 端口的服务
lsof:i 80
如下图,并没有 nginx 进程在使用 80 端口,就是有问题的。
彻底关闭 Google Chrome 和 Wechat 后,重新启动 nginx nginx -s stop && nginx
,现在就能看到只有 nginx 在使用 80 端口了
如果遇到其他问题,建议查看这篇文章
如果遇到前端 ERR_CONTENT_LENGTH_MISMATCH
报错,在前端的规则下 proxy_pass http://localhost:8081;
这行下加一行 proxy_buffering off;
server {
listen 80;
server_name yesplaymusic.com;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
location / {
proxy_pass http://localhost:8081;
proxy_buffering off;
proxy_connect_timeout 600;
proxy_read_timeout 600;
}
}
如果帮助到你了,欢迎给我点个赞喔👍
之前的分享 《WebAssembly从入门到实践》
首先是 JavaScript 继承机制的设计**
来源:http://www.ruanyifeng.com/blog/2011/06/designing_ideas_of_inheritance_mechanism_in_javascript.html
充当着网络流中“交通指挥官”的角色,“站在”服务器前处理所有服务器端和客户端之间的请求,从而最大程度地提高响应速率和容量利用率,同时确保任何服务器都没有超负荷工作
客户端非常明确要访问的服务器地址,它代理客户端,替客户端发出请求,比如 vpn
反向代理能够隐藏后端服务器
反向代理能够更快速地移除故障结点
反向代理服务器支持手动设定每台后端服务器的权重
location / {
root /home/www/html;
index index.php index.html index.htm;
}
# 匹配到 /api 开头的路由时候,将请求转发到 http://192.168.0.1,
# 但是通常不是直接填写地址,而是设置一个 `upstream`配置,后面会提到
location /api {
# 请求转向地址192.168.0.1
proxy_pass http://192.168.0.1;
# 不修改被代理服务器返回的响应头中的 location 头
proxy_redirect off;
# 使用 nginx 反向代理后,如果要使服务获取真实的用户信息,用以下的设置
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
upstream backend {
server backend1.example.com weight=5;
server backend2.example.com:8080;
server unix:/tmp/backend3;
}
server {
location / {
proxy_pass http://backend;
}
}
- http {
#开启gzip压缩
gzip on;
#IE6的某些版本对gzip的压缩支持很不好,故关闭
gzip_disable "MSIE [1-6]\.(?!.*SV1)";
#HTTP1.0以上的版本都启动gzip
gzip_http_version 1.0;
#指定哪些类型的相应才启用gzip压缩,多个用空格分隔
gzip_types
application/javascript
application/json
text/css
text/plain;
# 压缩等级,可选1-9,值越大压缩时间越长压缩率越高,
# 通常选5,能压缩到原来的1/4
gzip_comp_level 5;
gzip_min_length 1024;
}
最近学了 JavaScript 相关的设计模式,书名《JavaScript设计模式与开发实践》,为加深对 OOP 和设计模式的理解,分成三个部分来理解 JavaScript 的面向对象设计。
面向对象三大特性:
「封装」 把客观事物封装成抽象的类,隐藏属性和方法,仅对外公开接口。
只能在构造函数内访问不能被外部所访问(在构造函数内使用 var 声明的属性)
对象外可以访问到对象内的属性和方法(在构造函数内使用 this 设置,或者设置在构造函数原型对象上比如 Person.prototype.xxx)
定义在构造函数上的方法(比如 Person.xxx),不需要实例就可以调用;
首先来看零散的语句
var oDiv = document.getElementsByTagName("div")[0];
var p = document.createElement("p");
body.style.backgroundColor = "green";
p.innerText ="我是新增的标签内容";
oDiv.appendChild(p);
缺点很明显:
接下来将零散的的语句写进函数的花括号内,成为函数体。
function createTag() {
var oDiv = document.getElementsByTagName("div")[0];
var p = document.createElement("p");
body.style.backgroundColor = "green";
p.innerText = "我是新增的标签内容";
oDiv.appendChild(p);
}
优点也很明显
提高了代码的复用性;
按需执行——解析器读到此处,函数并不会立即执行,只有当被调用的时候才会执行;
避免全局变量——因为存在函数作用的问题。
var p1 = {};
p1.name = 'a';
p2.sex = 'male';
var p2 = {};
p2.name = 'b';
p2.sex = 'female'
优点:一把梭
缺点:生成几个实例,相似的对象,代码重复;而且实例与原型之间没有什么联系;
function Person(name, sex) {
// this.name = name
// this.sex = sex
return { name, sex }
}
var p1 = new Person('a', 'male')
var p2 = new Person('b', 'female')
优点:解决代码重复的问题
缺点:p1 和 p2 没有内在联系,不能反映出是同一个原型的实例
构造函数其实就是一个普通函数,但是内部有 this 指向,对构造函数使用 new 运算符就可以生成构造函数的实例。并且 this 会指向新生成的实例。
比如工厂模式中的 Person 改造成构造函数
function Person(name, sex) {
this.name = name;
this.sex = sex;
this.getName = function() {
console.log(this.name)
}
}
// 通过 new 运算符
var p1 = new Person('a','male');
var p2 = new Person('b','female');
p1.getName()
console.log(p1) //{name: "a", sex: "male"}
console.log(p2) //{name: "b", sex: "female"}
优点:解决代码重复的问题,反映出 p1 和 p2 是同一个原型的实例。
缺点:多个实例有重复的属性和方法,占用内存。(所有的实例都会有 getName 方法)
每一个构造函数都有一个 prototype 属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。这意味着那些不变的属性和方法,可以直接定义在 prototype 对象上。
function Person(name, sex) {
this.name = name
this.sex = sex
}
Person.prototype.getName = function() {
console.log(this.name)
}
var p1 = new Person('a','male');
var p2 = new Person('b','female');
p1.getName()
console.log(p1) //{name: "a", sex: "male"}
console.log(p2) //{name: "b", sex: "female"}
优点:所有实例的 getName 方法指向都是同一个内存地址,指向 prototype 对象,减少内存占用,提高效率。
function Person(name, sex) {
// !!! 私有属性和方法
var id = +new Date()
var getID = function() {
return id
}
// !!! 公有属性和方法
this.name = name
this.sex = sex
this.description = '我是公有属性'
this.getInfo = function() {
var id = getID()
return { id, name, sex }
}
}
// !!! 静态属性和方法
Person.description = '我是静态属性'
Person.work = function() {
return 'freelancer'
}
// !!! 原型上的属性和方法
Person.prototype.description = '我是原型上的属性'
Person.prototype.hobby = ['游泳', '跑步'];
Person.prototype.getHobby = function() {
console.log(this.hobby)
}
var p1 = new Person('小明','male');
// 试着输出私有属性 id
console.log(p1.id) // undefined
// 输出公有属性或方法
console.log(p1.name, p1.sex, p1.description, p1.getInfo())
// 输出静态属性或方法
console.log(Person.description, Person.work())
// 输出原型上的属性或方法
console.log(p1.hobby, p1.getHobby()
注意 p1.description 输出的是 ”我是公有属性“
在 ES6 之后,新增了 class
这个关键字,代表传统面向对象语言的类的概念。但是并不是真的在 JavaScript 中实现了类的概念,还是一个构造函数的语法糖。存在只是为了让对象的原型功能更加清晰,更加符合面向对象语言的特点。
class Person {
constructor(name, sex) {
this.name = name
this.sex = sex
}
getInfo() {
console.log(this.name, this.sex)
}
}
注意 class 内部的属性和方法都是不可枚举的
类的数据类型就是函数,类本身指向构造函数即 Person.prototype.constructor === Person 成立
class Person {
constructor(name, sex) {
// 公有属性和方法
this.name = name
this.sex = sex
this.getInfo = function() {
return { name, sex }
}
}
}
class Person {
constructor(name, sex) {
this.name = name
this.sex = sex
this.getInfo = function() {
return { id, name, sex }
}
// 私有属性和方法
var id = +new Date()
var getID = function() {
return id
}
}
}
let p = new Person('小明','male')
console.log(p.getInfo())
class Person {
constructor(name, sex) {
this.name = name
this.sex = sex
// 原型上的属性和方法
a = 1
getInfo() {
console.log(this.name, this.sex)
}
}
}
let p = new Person('小明','male')
console.log(p.a)
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
static description = '我是一个静态属性'
static getDescription() {
console.log("我是一个静态方法")
}
}
// 或者
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
}
Person.description = '我是一个静态属性'
Person.getDescription = function() {
console.log("我是一个静态方法")
}
// 我是一个静态属性
console.log(Person.description)
// 我是一个静态方法
Person.getDescription()
class Person {
constructor(name, age) {
this.name = name
this.age = age
this.getName = function() {
console.log(this.name)
}
}
// !!! 实例的属性和方法
myProp = '我是实例属性'
getMyProp = function() {
console.log(this.myProp + '=')
}
// !!! 类的原型上的方法
getMyProp() {
console.log(this.myProp)
}
getInfo() {
console.log('获取信息')
}
}
let p = new Person('小明','male')
// 我是实例属性
p.getMyProp() // 我是实例属性=,实例属性优先于类的原型的上的属性和方法
console.log(p.hasOwnProperty('getName')) // true
console.log(p.hasOwnProperty('getMyProp')) // true
console.log(p.hasOwnProperty('getInfo')) // false
new Foo();class Foo{}
会报错;继承是面向对象语言中最有意思的概念。
许多面向对象语言都支持两种继承方式,继承通常包括"实现继承"和"接口继承"。
由于 JS 中没有签名,所以无法实现接口继承,只支持实现继承,依赖原型链实现。
function Father(name){
this.fatherName = name
}
Father.prototype.getFatherValue = function(){
return this.fatherName
}
function Son(name){
this.sonName = name
}
// 继承 Father
Son.prototype = new Father('Dad') // Son.prototype 被重写, 导致 Son.prototype.constructor 也一同被重写
Son.prototype.getSonValue = function() {
return this.sonName
}
// 实例化 son
var son = new Son('Son')
console.log(son.getFatherValue()) // Dad
实践中很少会单独使用原型链,有几种办法尝试弥补原型链的不足
在子类构造函数的内部调用超类构造函数
// 构造函数
function Father() {
this.colors = ['red', 'blue', 'green']
// 添加超类方法
this.test = function() {
console.log('测试添加超类方法')
}
}
function Son(name, age) {
this.name = name
this.age = age
// 相当于调用 Father 函数将父类的属性和方法添加到子类上,原型没有被修改
return Father.call(this) // 继承了 Father,且向父类型传递参数
}
var son1 = new Son('son1', 12)
son1.colors.push('black')
console.log(son1.colors) // "red,blue,green,black"
var son2 = new Son('son2', 13)
console.log(son2.colors) // "red,blue,green" 可见引用类型值是独立的
使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承
function Father(name) {
this.name = name
this.colors = ['red', 'blue', 'green']
}
Father.prototype.sayName = function() {
console.log(this.name)
}
function Son(name, age) {
Father.call(this, name) // 第二次调用 Father() 使用构造函数来继承实例属性
this.age = age
}
// 第一次调用 Father() 使用原型链实现对原型属性和方法的继承
Son.prototype = new Father()
Son.prototype.sayAge = function() {
console.log(this.age)
}
var son1 = new Son('son1', 10)
son1.colors.push('black')
son1.sayName(), son1.sayAge()
console.log('son1.colors', son1.colors)
var son2 = new Son('son2', 12)
son2.sayName(), son2.sayAge()
console.log('son2.colors', son2.colors)
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为 JavaScript 中最常用的继承模式。而且, instanceof
和 isPrototypeOf()
也能用于识别基于组合继承创建的对象。同时我们还注意到组合继承其实调用了两次父类构造函数,造成了不必要的消耗。
在 es5 中,通过 Object.create()
方法规范化了上面的原型式继承,接收了两个参数,一个用作新对象原型的对象、一个为新对象定义额外属性的对象(可选的)。
提醒:因为对传入的对象使用的是浅拷贝,所以包含引用类型值的属性始终都会共享相应的值,就像使用原型模式一样。
var person = {
friends: ["Van","Louis","Nick"]
}
var anotherPerson = Object.create(person, {
name: {
value: "Louis"
}
})
anotherPerson.friends.push("Rob")
// 在创建一个人,修改朋友列表
var yetAnotherPerson = Object.create(person)
yetAnotherPerson.friends.push("Style")
console.log(person.friends) // "Van,Louis,Nick,Rob,Style” 不同实例对象始终共享引用类型值
构造函数+工厂模式:创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象。
function createAnother(origin) {
var clone = Object.create(origin)
clone.sayHi = function() {
console.log('hi')
}
return clone
}
var a = {
test: 1
}
var a1 = createAnother(a)
console.log(a1)
输出
a1: {
sayHi: f(),
__proto__: {
test: 1,
__proto__: {
constructor: f Object()
}
}
}
使用寄生式继承来为对象添加函数,无法做到函数复用而降低效率。
组合继承是最常用的,但是会调用两次父类构造函数。
一是在创建子类型原型的时候,另一次是在子类型的构造函数内部。
寄生组合式继承就是为了降低父类构造函数的开销而出现的。
集寄生式继承和组合式继承的优点于一身,是最有效的实现类型继承的方法。
// 寄生继承
function extend(subClass, superClass) {
//基于超类(构造函数)的原型对象创建新的原型对象
var prototype = Object.create(superClass.prototype)
// 子类的原型对象指向新创建的原型对象
subClass.prototype = prototype // 指定对象
// 原型对象的构造函数指向子类
prototype.constructor = subClass // 增强对象
}
function Father(name){
console.log('调用了 father')
this.name = name;
this.colors = ["red","blue","green"];
}
Father.prototype.sayName = function(){
console.log(this.name);
};
// 组合继承
function Son(name, age){
Father.call(this, name); //继承实例属性,第一次调用Father()
this.age = age;
}
extend(Son, Father) // 继承父类方法,此处并不会第二次调用Father()
Son.prototype.sayAge = function(){
console.log(this.age);
}
看完了上面的封装和继承,到了多态这一步就没那么多复杂的代码了。
同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。
通常采用抽象类
或者接口,进行更高一层的抽象,从而可以直接使用该抽象,即”向上转型“。本质上是为了弱化具体类型带来的限制。
在 JavaScript 中,万物皆对象,对象的多态性是与生俱来的。多态可以把过程化的条件分支语句转化为对象的多态性,从而消除条件分支语句。
function Person(name, sex) {
this.name = name
this.sex = sex
}
Person.prototype.getInfo = function() {
return "I am " + this.name + " and sex is " + this.sex
}
function Employee(name, sex, age) {
this.name = name
this.sex = sex
this.age = age
}
Employee.prototype = new Person()
Employee.prototype.getInfo = function() {
return "I am " + this.name +
" and sex is " + this.sex +
" and age is " + this.age
}
var person = new Person('xiaoming', 'male')
var employee = new Employee('xiaoming', 'male', 12)
console.log(person.getInfo())
console.log(employee.getInfo())
一个 JavaScript 对象,既可以表示 Duck 类型的对象,又可以表示 Chicken 类型的对象,这意味着JavaScript 对象的多态性是与生俱来的。
var makeSound = function(animal) {
animal.sound()
}
var Duck = function() {}
Duck.prototype.sound = function() {
console.log('gagaga')
}
var Chicken = function() {}
Chicken.prototype.sound = function() {
console.log('gegege')
}
makeSound(new Duck())
makeSound(new Chicken())
在 JavaScript 中,会很难看到多态性的影响。因为 JavaScript 具有动态类型系统,因此在编译时没有函数重载或自动类型强制。由于语言的动态特性,我们甚至都不需要 JavaScript 中的参数多态性。
但是 JavaScript 仍具有两种多态的形式:
总而言之,多态的设计是为了面向对象编程时共享对象的行为。
function myNew(func, ...args) {
// 创建一个空对象
let obj = {}
// 将对象与构造函数原型链接起来
Object.setPrototypeOf(obj, func.prototype)
// 将构造函数的 this 指向新生成的空对象
let result = func.apply(obj, args)
// 最后返回新对象的实例
return result
}
for...in...
能获取到实例对象自身的属性和原型链上的属性;Object.keys()
和 Object.getOwnPropertyNames()
只能获取实例对象自身的属性;hasOwnProperty()
方法传入属性名来判断一个属性是不是实例自身的属性;instance.hasOwnProperty('property')
检查属性是否在指定实例对象上Father.prototype.isPrototypeOf(instance)
判断调用该方法的对象是不是参数实例对象的原型对象先创建一个临时的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个构造函数的一个新实例。
function objectCreate(o) {
var F = function() {}
F.prototype = o
return new F()
}
subClass.prototype = objectCreate(superClass.prototype)
Remote-SSH扩展使您可以将带有SSH服务器的任何远程计算机用作开发环境。这可以在各种情况下极大地简化开发和故障排除。您可以:
单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。
有一些对象我们往往只需要一个,比如 线程池,全局缓存,浏览器中的 window 对象。
单例模式更加适合传统的面向对象语言中的类。在 javascript 中生搬单例模式的概念并无意义。
js 中两种单例模式的体现,本质都是减少全局变量,因为全局变量也可以理解为一种单例
class Singleton {
// 静态私有成员变量
// 提供全局访问的方法
static getInstance(name) {
// 确保一个类只有一个实例
if (!this.instance) {
// 自行实例化并提供这个实例
this.instance = new Singleton(name)
}
return this.instance
}
// 私有构造函数
constructor(name) {
this.name = name
this.instance = null
}
// 公有的静态工厂方法
getName() {
return this.name
}
}
// 简单的测试
let sven1 = Singleton.getInstance('sven1')
let sven2 = Singleton.getInstance('sven2')
console.log(sven1 === sven2) // true
上述的例子中,不透明的地方体现在我们不知道这是一个单例类,并且跟以往通过 new XXX
的方式来获取对象不同,这里偏要使用 Singleton.getInstance
来获取对象。
要实现一个透明的单例模式就需要允许我们使用 new 操作符来获取对象。
我们来看一个在页面中创建唯一的 div 节点的例子。
let CreateDiv = (function() {
let instance
let CreateDiv = function(html) {
if (instance) {
return instance
}
this.html = html
this.init()
return instance = this
}
CreateDiv.prototype.init = function() {
let div = document.createElement('div')
div.innerHTML = this.html
document.body.appendChild(div)
}
// 使用闭包返回真正的 Singleton 构造方法
return CreateDiv
})()
let a = new CreateDiv('sven1')
let b = new CreateDiv('sven2')
console.log(a === b)
上述的代码使用自执行的匿名函数和闭包,并且让这个匿名函数返回真正 Singleton 构造方法,增加了一些程序的复杂度,阅读起来也不是很舒服。
把负责管理单例的逻辑移到了代理类 proxySingletonCreateDiv
中
// 改造成创建 div 的类,将控制唯一实例的逻辑在代理中实现
class CreateDiv {
constructor(html) {
this.html = html
this.init()
}
init() {
let div = document.createElement('div')
div.innerHTML = this.html
document.body.appendChild(div)
}
}
// 使用代理类实现 proxySingletonCreateDiv
let ProxySingletonCreateDiv = (function(){
let instance
return function(html) {
if (!instance) {
instance = new CreateDiv(html)
}
return instance
}
})()
let a = new ProxySingletonCreateDiv('sven1')
let b = new ProxySingletonCreateDiv('sven2')
console.log(a === b)
class CreateDiv {
constructor(html) {
this.html = html
this.init()
}
init() {
let div = document.createElement('div')
div.innerHTML = this.html
document.body.appendChild(div)
}
}
// 通用的单例代理函数
function commonProxySingleton(funClass) {
let instance
let funClass = funClass
return function() {
if (!instance) {
instance = new funClass(arguments)
}
return instance
}
}
// 因为只是一个普通函数,所以不使用 new
let proxySingletonCreateDiv = commonProxySingleton(CreateDiv)
let a = proxySingletonCreateDiv('sven1')
let b = proxySingletonCreateDiv('sven2')
// 使用 new 的话 this 指向为 commonProxySingleton 的实例
// let ProxySingletonCreateDiv = new commonProxySingleton(CreateDiv)
// let c = ProxySingletonCreateDiv('sven3')
console.log(a === b)
在需要实例化对象的时候才创建实例,将创建实例对象的职责和管理单例的职责分别开来,独立不互相影响。
利用闭包来实现。
// 惰性单例
let createLoginLayer = (function() {
let div
return function() {
if (!div) {
div = document.createElement('div')
div.innerHTML = '我是登录浮窗'
div.style.display = 'none'
document.body.appendChild(div)
}
return div
}
})()
document.getElementById('loginBtn').onclick = function() {
let loginLayer = createLoginLayer()
loginLayer.style.display = 'block'
}
let getSingle = function(fn) {
let result
return function() {
return result || (result = fn.apply(this, arguments))
}
}
// 创建登录浮窗
let createLoginLayer = function() {
let div = document.createElement('div')
div.innerHTML = '我是登录浮窗'
div.style.display = 'none'
document.body.appendChild(div)
return div
}
// 惰性函数包裹
let createSingleLoginLayer = getSingle(createLoginLayer)
document.getElementById('loginBtn').onclick = function() {
let loginLayer = createSingleLoginLayer()
loginLayer.style.display = 'block'
}
// 直接传入回调函数创建 iframe
let createSingleIFrame = getSingle(function() {
let iframe = document.createElement('iframe')
document.body.appendChild(iframe)
return iframe
})
document.getElementById('loginBtn').onclick = function() {
let loginLayer = createSingleIFrame()
loginLayer.src = 'http://baidu.com';
}
笔者最近发现 github 的用户头像和自己文章中的图片显示不出来了。然后今天发现安装 homeBrew 和 nvm 出现了标题的报错信息。
以上是安装 pnpm 的报错信息,可以发现,脚本需要到 raw.githubusercontent.com 上拉取代码。
网上搜索了一下,发现是 github 的一些域名的 DNS 解析被污染,导致DNS 解析过程无法通过域名取得正确的IP地址。
DNS 污染,感兴趣的朋友可以去了解一下。
打开 https://www.ipaddress.com/ 输入访问不了的域名
查询之后可以获得正确的 IP 地址
在本机的 host 文件中添加,建议使用 switchhosts 方便 host 管理
199.232.68.133 raw.githubusercontent.com
199.232.68.133 user-images.githubusercontent.com
199.232.68.133 avatars2.githubusercontent.com
199.232.68.133 avatars1.githubusercontent.com
添加以上几条 host 配置,页面的图片展示就正常了,homebrew 也能装了,nvm 也行动灵活了。
Created: Nov 25, 2020 12:32 PM
JSON.stringify 有三个参数
第一个参数是我们最常见的用法也最受关注的,比如在说简单的深拷贝的时候,可以用这种办法。
常见的问题有以下几个:
我们主要后面看两个参数
如果该参数是一个函数,则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理。作为函数,它有两个参数,键(key)和值(value),它们都会被序列化。
function replacer(key, value) {
if (typeof value === "string") {
return undefined;
}
return value;
}
var foo = {
foundation: "Mozilla",
model: "box",
week: 45,
transport: "car",
month: 7
};
var jsonString = JSON.stringify(foo, replacer);
// {"week":45,"month":7}
var foo = {
foundation: "Mozilla",
model: "box",
week: 45,
transport: "car",
month: 7
};
JSON.stringify(foo, ['week', 'month']);
// '{"week":45,"month":7}', 只保留 “week” 和 “month” 属性值。
指定缩进用的空白字符串,用于美化输出(pretty-print)
var foo = {
foundation: "Mozilla",
model: "box",
week: 45,
transport: "car",
month: 7
};
// 亲测最好用的
JSON.stringify(foo, null, '\t')
// or
JSON.stringify(foo, null, 4)
/**
输出结果
"{
"foundation": "Mozilla",
"model": "box",
"week": 45,
"transport": "car",
"month": 7
}"
*/
注意事项:
先看现象和问题,它会决定我们做规划的完整性
问题变得更加的完整或者全面
追问法,一般要问三层
关键词联想
分维度思考
问题不重复,不遗漏
现象和问题明确清楚
做定位,做定位的时候我们就要去收敛了
梳理定位的模板
找定位的过程
我们要做到什么程度,或者说在什么阶段做到什么程度,这个就是定目标的过程
目标拆解
首先需要有一个聚焦的东西,让我们能一起往目标去走,之后才是走多远
步骤
问题
瀑布流 water fall
敏捷迭代 scrum
常见的项目管理工具
程序未能释放已经不再使用的内存,即存在不需要的引用。
JavaScript 是垃圾回收语言,在代码中创建对象和变量会占用内存,但是 JavaScript 是有自己的内存回收机制,可以确定那些变量不再需要,并将其清除。
垃圾回收算法
引用计数
标记清除
delete window.a
defer 和 async 以及 type="module" 的差异
最近在写算法题,很多题解下说,数组的 sort 方法用的是快排,时间复杂度为 O(nlogn)
面试的时候被问到 sort 的时间复杂度,跟面试官说是内部是用快排实现的,面试官表示这个观点有问题,所以现在我自己来看这个问题了。
图片来自文章 https://segmentfault.com/a/1190000010648740
看图非常直观,感谢大佬的耐心整理,等我有空实际验证下结论。
《认知红利》是笔者最近花了一些时间精读的一本书,书中有很多概念,但是概念并不十分抽象,书中会从现象分析入手,引导你思考更深,更高层次的问题,读起来不会枯燥,反而经常让我产生“噢,原来是这样的!”的感叹。有些看过类似的书的读者可能会说是把一些思维方式的书籍的观点整理起来讲。但我想讲这本书把这些概念和观点串联得真的很好,值得一看。
读书笔记我做了一个脑图,有点混乱,摘要我认为非常重要的 NLP 的概念。前面部分的内容就用h3标题带过了。
没有两个人是一样的
一个人不能控制另外一个人
有效果比有道理更重要
只有由感官经验塑造出来的世界,没有绝对的真实世界
沟通的意义在于对方的回应
重复旧的做法,只会得到旧的结果
凡事必有至少三个解决方法
每一个人都选择给自己最佳利益的行为
每个人都已经具备使自己成功快乐的资源
在任何一个系统里,最灵活的部分便是最能影响大局的部分
没有失败,只有反馈
动机和情绪总不会错,只是行为没有效果而已
我的人生使命是什么?世界因为我会有什么不同
为了实现这个使命,五年后我会成为一个怎样的人,描述得越具体越好
一套怎样的价值观能帮助我达到这个身份,什么最重要,我应该坚持什么,放弃什么?我该相信一些什么原则和规律
为了实现这个身份,以及这套BVR,我应该学一些什么知识和技能,掌握什么方法和套路。什么可以做,什么不可以做。
具体怎么做,第一步是什么,今年的具体计划怎么实现
哪些人和资源可以帮助我实现目标,我如何去使用身边的资源。
另外跟大家分享书中我认为很重要的一个专注的观点。这部分看起来会很抽象,建议可以去看看原书。
选择能专注的事情
对恐惧的逃避
对愉悦的追求
设置一个威胁对象或者时间期限
阻断外部干扰:创造纯净的环境
视频地址:go tutorial
package main
// 导入所需要的依赖包
import (
"fmt"
"errors"
"math"
)
// 必须有的 main 函数
func main() {
// 变量定义
// x := 5
// y := 7
// sum := x + y
// fmt.Println("hello, world!", sum)
// if else 条件语句
// if sum >= 12 {
// fmt.Println("More than to 12")
// } else if x <= 5 {
// fmt.Println("x less than to 5")
// }
// 可拓展数组
// a := [] int {1, 2, 3, 4, 5}
// a = append(a, 13)
// fmt.Println("hello, world!", a)
// 可拓展 map
// vertices := make(map[string]int)
// vertices["triangle"] = 2
// vertices["square"] = 3
// vertices["dodecagon"] = 12
// delete(vertices, "triangle")
// fmt.Println(vertices)
// for 循环
// for i:= 0; i < 5; i++ {
// fmt.Println(i)
// }
// 遍历数组
// arr := []string{"a", "b", "c"}
// for index, value := range arr {
// fmt.Println(index, value)
// }
// 遍历 map
// m := make(map[string]string)
// m["a"] = "alpha"
// m["b"] = "beta"
// for key, value := range m {
// fmt.Println(key, value)
// }
// 函数调用
// result := sumfunc(1, 2)
// fmt.Println(result)
// 回调函数
// result, err := sqrt(-16)
// if err != nil {
// fmt.Println(err)
// } else {
// fmt.Println(result)
// }
// 构造函数/构造类
// p := person{name: "hawtim", age: 27}
// fmt.Println(p.age)
// 内存地址操作
// i := 7
// inc(&i)
// fmt.Println(i, &i)
}
// 一个普通函数
func sumfunc(x int, y int) int {
return x + y
}
// 定义一个函数和返回类型
func sqrt(x float64) (float64, error) {
if x < 0 {
return 0, errors.New("Undefined for negative numbers")
}
return math.Sqrt(x), nil
}
// 定义一个数据类型
type person struct {
name string
age int
}
func inc(x *int) {
*x++
}
最近面试遇到一个代码题
题目大概是这样,问代码执行顺序,主要考察事件循环代码
setTimeout(() => {console.log('a')}, 0)
console.log('b')
new Promise(resolve => {
resolve(console.log('c'))
console.log('d')
}).then(() => console.log('e'))
(async () => console.log('f'))()
平时 async 用的比较少,当时就说这个地方不是很理解,跳过了对 async 的分析
之后复盘的时候找了这个题,执行了上面的代码竟然报错了。如下:
看报错是这一行代码 (async () => console.log('f'))()
,那我单独拿出来执行一遍看有没有问题。
结果执行结果又是正常的。
来 Google 一遍,Uncaught TypeError: (intermediate value)(…) is not a function
,结果如同标题所说,就是分号的问题。
https://stackoverflow.com/questions/42036349/uncaught-typeerror-intermediate-value-is-not-a-function
按照高赞的回答,虽然 JS 有自动插入分号的机制,但是因为括号的下一行是括号,所以可以解释为函数参数的调用执行。
那么为了理解这句话,我们先精简一下以上代码,对现象进行复现:
(function test() {
console.log('c')
})
(async () => console.log('f'))()
如果不加分号,等同于执行函数(所以执行上面的代码会报错)
(function test() {
console.log('c')
})(async () => console.log('f'))()
这个问题给我这个不加分号党当头一棒,emmm 如果平时开发没有 babel 啥的,估计自己会踩不少坑吧。
OK 那我们老老实实加上分号,有兴趣的读者也可以试着回答一下:
setTimeout(() => {console.log('a')}, 0);
console.log('b');
new Promise(resolve => {
resolve(console.log('c'));
console.log('d');
}).then(() => console.log('e'));
(async () => console.log('f'))();
最终的执行顺序是
b c d f e a
push/pop 为一组
shift/unshift 为一组
some/every 为一组
map/filter 为一组
includes/find/findIndex为一组
slice/splice为一组
concat/join为一组
from/isArray 为一组
indexOf/lastIndexOf 为一组
reduce 单独一组,对数组中每个元素升序执行 reducer 函数,将结果汇总到累加器中最终返回
命令模式是一种行为型模式。把对象之间的请求封装在命令对象中,将命令的调用者和命令的接收者完全解耦。
当调用命令的 execute
方法时,不同的命令会做不同的事情,从而阐释不同的执行结果,此外还有 undo 和 redo 等操作。
我们直接从代码入手,依次注释 命令的接收者、命令对象、命令的调用者
document.write(`<body>
<button id="execute">execute</button>
<button id="undo">undo</button>
</body>`)
// 命令的接收者
let Tv = {
open() {
console.log('open the tv')
},
close() {
console.log('close the tv')
}
}
// 命令对象
class OpenTvCommand {
constructor(receiver) {
this.receiver = receiver
}
// 行为 => 请求
execute() {
this.receiver.open()
}
undo() {
this.receiver.undo()
}
}
// 命令的调用者
let setCommand = (command) => {
document.getElementById('execute').onclick = () => {
command.execute()
}
document.getElementById('undo').onclick = () => {
command.undo()
}
}
// 实例化命令并解耦
setCommand(new OpenTvCommand(Tv))
上述的简单的例子可以直观的理解命令模式的原理。
我们接下看另外一个例子,通过控制小球的移动,本质的模式是相同的,只是增加了更多的逻辑,更接近我们的业务代码。
// 补充 Animate 类
let tween = {
linear(t, b, c, d) {
return c * t / d + b
},
easeIn(t, b, c, d) {
return c * (t /= d) * t + b
}
// ...
}
class Animate {
constructor(dom) {
this.dom = dom // 进行运动的dom节点
this.startTime = 0 // 动画开始时间
this.endTime = 0
this.startPos = 0 // 动画开始时,dom节点的位置,即dom的初始位置
this.endPos = 0 // 动画开始时,dom节点的位置,即dom的目标位置
this.propertyName = null // dom 节点需要被改变的 CSS 的属性名
this.easing = null // 缓动算法
this.duration = null // 动画持续时间
}
start(propertyName, endPos, duration, easing) {
this.startTime = +new Date
this.startPos = this.dom.getBoundingClientRect()[propertyName]
this.propertyName = propertyName
this.endPos = endPos
this.duration = duration
this.easing = tween[easing]
let timeId = setInterval(() => {
if (this.step() === false) {
clearInterval(timeId)
}
}, 19)
}
step() {
let t = +new Date
if (t >= this.startTime + this.duration) {
this.update(this.endPos)
return false
}
let pos = this.easing(t - this.startTime, this.startPos, this.endPos - this.startPos, this.duration)
this.update(pos)
}
update(pos) {
this.dom.style[this.propertyName] = `${pos}px`
}
}
document.write(`<body>
<div id="ball" style="position: absolute;top: 50px; left: 0; background: #000; width: 50px; height: 50px;"></div>
输入小球移动后的位置: <input id="pos"/>
<button id="moveBtn">开始移动</button>
<button id="cancelBtn">撤销移动</button>
</body>`)
let ball = document.getElementById('ball')
let pos = document.getElementById('pos')
let moveBtn = document.getElementById('moveBtn')
let cancelBtn = document.getElementById('cancelBtn')
// 控制小球的命令对象
class MoveCommand {
constructor(receiver, pos) {
this.receiver = receiver
this.pos = pos
this.oldPos = null
}
execute() {
this.receiver.start('left', this.pos, 1000, 'linear')
this.oldPos = this.receiver.dom.getBoundingClientRect()[this.receiver.propertyName]
}
undo() {
this.receiver.start('left', this.oldPos, 1000, 'linear')
}
}
var moveCommand
moveBtn.onclick = () => {
var animate = new Animate(ball)
moveCommand = new MoveCommand(animate, pos.value)
moveCommand.execute()
}
cancelBtn.onclick = () => {
moveCommand.undo()
}
比如 编辑器的实现、命令行的实现等等。
永久记录这本书可以理解为作者本人的自传,书中前面一半讲述了他是如何利用校园及考试的规则来实现一些学业上的逃避及年级的跨越,后续是如何通过长达一年的安全测试,并在这期间遇见了他的女朋友。中间过程讲述了他在安全部门中的所见所闻及职业生涯的变化。在他的职业生涯过程中,通过不断的观察和一些无意的查看,逐渐在脑海中拼凑了出了国家层面的全民信息监听系统的模样。
后面一半大概是在他的癫痫症状出现之后,他的职业变动让他又接触到了一些信息,他渐渐地清楚了政府的行为,并对政府的行为感到失望,并最后决定揭发政府的行为。这个过程是十分漫长的, 而他也清楚这个行为的代价会有多大,但是他依然决定要去做这个事情。
通过长期的规划之后,充分获取了秘密数据之后并做好加密工作后,他最终选择在香港揭发美国政府的行为,他漫长的等待等到了记者的到来,后来随着对应的棱镜计划曝光,美国的民众也开始抵制政府的这些行为。
经过漫长的流亡,作者最后被留在了俄罗斯,而他最终也在非盈利机构工作,致力于数据安全领域。后来他的女朋友去俄罗斯找他,并在那里定居,最终结婚。并且在国际社会上获得极大的声望,happy ending。
不知道写这个书评会不会被封账号,能精简就精简,有资源的推荐大家去看看,非常精彩的一本书。
原本我的理解里,财富、权利、健康可能就是一切,但是这本书带给我第四个维度,正如他是“第四公民”一样,这个维度是自由。从小到大的环境导致了我们完全忽略了还有自由这个层面的存在,因为我们实际上处于非常严格的监管之下,只是我们完全没有去注意这个细节。外国人群体的意识里和我们不一样,他们注重这个维度,讲究人权。而不是选择忽略这个维度,因为一个人选择了忽略,其他的人可能就被迫被忽略,最终所有人都失去了这个维度的东西。而作者这本书,我觉得也就是希望大家去意识到这个事情,去重视和追求属于这个维度的东西。
书本是速读完的,概要中如果有错漏的地方,感谢指正。
http0.9
http1.0
http1.1
http2.0
http3.0(QUIC)
笔者最近的工作中,前端资源很紧缺,日常招人,领导希望我整理一些前端面试的知识点,方便有些时候可以直接由他去面试。
然后我开始用思维导图来整理,基本上是从 HTML CSS JavaScript 的顺序来写的,想到什么就补充点什么,以工作中使用到的知识点来看的话,一个下午就搞定写完了。
思前想后,应该可以更加系统全面的整合前端的知识体系,第一点是先整理出哪些主要的模块,比如一开始我定的就是前端三板斧 HTML CSS JS,第二点是是根据前面的主要模块,在每一块里面找到经典的面试题目,由点及面,由浅到深的学习相关的知识和内容。帮助我做到以下三点:
单例模式、原型模式、工厂模式、观察者模式、策略模式、代理模式、发布订阅模式
以上内容肯定不是前端的全部,大概才20%,主要是认为这些问题比较大,水平的区分度比较强。
脑图还在持续优化中,后续开源。
// 作为对象的方法调用
var obj = {
a: 1,
getA: function() {
console.log(this === obj)
console.log(this.a)
}
}
obj.getA()
// 作为普通函数调用
window.name = 'global'
var getName = function() {
return this.name
}
console.log(getName()) // global
var myObject = {
name: 'sven',
getName: function() {
return this.name
}
}
var getName = myObject.getName
console.log(getName()) // global
// 函数构造器调用
var MyClass = function() {
this.name = 'sven'
}
var obj = new MyClass()
console.log(obj.name)
// 显式返回一个 object 类型的对象,则会指向返回的对象
// 但如果不返回,或者返回基本类型值,则 this 指向正常
var MyClass = function() {
this.name = 'sven'
return {
name: 'anne'
}
}
var obj = new MyClass()
console.log(obj.name)
// Function.prototype.call & Function.prototype.apply 调用
var obj1 = {
name: 'sven',
getName: function() {
return this.name
}
}
var obj2 = {
name: 'anne'
}
console.log('call & apply ' + obj1.getName()) // sven
console.log('call & apply ' + obj1.getName.call(obj2)) // anne
// 修正 document 方法的 this 指向
document.getElementById = (function(func) {
return function() {
return func.apply(document, arguments)
}
})(document.getElementById)
var getId = document.getElementById
var div = getId('div1')
console.log(div.id)
this 的指向大致可以分为
防抖,指事件多次触发的时候,只有在一定时间内没有再次触发的时候,事件才会执行一次。
防抖的核心在于重复触发进行定时器的清除,即 clearTimeout(timer)
Tips: 再次触发同一事件之后,上一次触发的定时器会被清除,新的定时器会被赋值给 timer,必须等到间隔大于 wait 的值时,才能触发 setTimeout 回调。
const debounce = (func, wait) => {
let timer = null
return (...args) => {
clearTimeout(timer)
timer = setTimeout(()=> {
fn.apply(this, args)
}, wait)
}
}
节流,指一定时间内,多次触发同一事件只执行一次。
Tips:
节流的核心在于 timer 变量起到的作用。即 timer = setTimeout 及 timer = null。
Tips: 当 timer 为 setTimeout 返回的 number 时,内部的回调不会触发,直到回调执行后,timer 被重新设置回 null 的时候才会起作用。
const throttle = (func, wait = 500) => {
let timer = null
return (...args) => {
if (timer) return
timer = setTimeout(() => {
fn.apply(this, args)
timer = null
}, wait)
}
}
我们在开发过程中可能经常看到 200 (memory cache)
200 (disk cache)
304 Not Modified
的状态码,他们分别是什么意思?
这里先有个大体的概念,200 状态码是浏览器的强制缓存机制,304 则是协商缓存机制,这两个缓存机制是通过 HTTP 报文的缓存标识来生效的。
这里我们分析一个普通的 HTTP 请求的请求报文,省略了不影响缓存的消息头
Request URL: https://hawtim.github.io/blog/
Request Method: GET
Status Code: 200
Remote Address: 185.199.111.153:443
Referrer Policy: no-referrer-when-downgrade
:authority: hawtim.github.io
:method: GET
accept: text/html
# 注意 cache-control 和 pragma
cache-control: no-cache
pragma: no-cache
#============================
content-encoding: gzip
content-type: text/html; charset=utf-8
date: Thu, 21 May 2020 03:49:51 GMT
# 注意 cache-control etag expires last-modified
cache-control: max-age=600
etag: W/"5ec1ee1c-8ac"
expires: Thu, 21 May 2020 03:51:50 GMT
last-modified: Mon, 18 May 2020 02:08:28 GMT
#=============================================
server: GitHub.com
status: 200 # 现在的状态是 200
Request URL: https://hawtim.github.io/blog/
Request Method: GET
Status Code: 304
Remote Address: 185.199.111.153:443
Referrer Policy: no-referrer-when-downgrade
:authority: hawtim.github.io
:method: GET
accept: text/html
# 注意 cache-control if-modified-since if-none-match
cache-control: max-age=0
if-modified-since: Mon, 18 May 2020 02:08:28 GMT
if-none-match: W/"5ec1ee1c-8ac"
# =================================================
content-encoding: gzip
content-length: 924
content-type: text/html; charset=utf-8
date: Thu, 21 May 2020 04:13:25 GMT
# 注意 cache-control etag expires last-modified
cache-control: max-age=600
etag: W/"5ec1ee1c-8ac"
expires: Thu, 21 May 2020 04:23:25 GMT
last-modified: Mon, 18 May 2020 02:08:28 GMT
#=============================================
server: GitHub.com
status: 304 # 状态变成 304
看完上面的例子,你可能还对上面注意的消息头,比如
cache-control
expires
last-modified
if-modified-since
etag
if-none-match
有点懵,不知道它们是怎么在浏览器的缓存机制中生效的,没关系,我们下面来分析缓存过程
从图片可以看出
以上两点就是浏览器缓存机制的关键,确保了每个请求缓存存入与读取。根据是否需要向服务器重新发起 HTTP 请求,我们将缓存过程分为两部分,第一部分是强制缓存,第二部分是协商缓存。
强制缓存就是在浏览器的缓存中查找请求结果,并根据该结果的缓存标识决定是否使用该结果。有以下三种情况:
协商缓存
Pragma
是 HTTP/1.0 中规定的通用首部,它用来向后兼容只支持 HTTP/1.0 协议的缓存服务器,因为 那时候 HTTP/1.1 协议中的 Cache-Control 还没出来
Pragma: no-cache
与 Cache-Control: no-cache
效果一致
Expires
是 HTTP/1.0 中规定的通用首部,它用来指指定资源缓存到期的时间。时间是绝对时间,受客户端时间影响。
Cache-Control
是 HTTP/1.1中通用首部,同样用来指指定资源缓存到期的时间,max-age 设置的时间的单位为秒,是相对时间,不受客户端时间影响,优先级比 Expires 高,主要取值为:
public
:所有内容都将被缓存(客户端和代理服务器都可缓存)private
:所有内容只有客户端可以缓存,Cache-Control 的默认取值must-revalidate
:如果超过了 max-age 的时间,浏览器必须向服务器发送请求,验证资源是否还有效。no-cache
:客户端缓存内容,但是是否使用缓存则需要经过协商缓存来验证决定no-store
:所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存max-age=600
:缓存内容将在 600 秒 (任意正整型数字)后失效Cache-Control: no-store
Cache-Control: public, max-age=31536000
Cache-Control: no-store
或 max-age=0
细心的朋友可能发现了,一开始的例子里,第二次请求的时候 github 就是用了 max-age=0来向服务端校验我的文件是否有更改。
以 github 的博客请求为例,状态码是灰色 200 则代表使用了强制缓存,请求对应的 size 值则代表缓存存放的位置,分别为 memory cache
和 disk cache
,那这两者有什么差别,什么情况用 memory 什么情况用 disk?
内存缓存会将编译解析后的文件,直接存入该进程的内存,占据一定的内存资源,方便下次使用时快速读取,但是一旦该进程关闭,则该进程的内存会被清空。
硬盘缓存是直接将缓存写入硬盘文件中,读取缓存需要进行 I/O 操作,读取该缓存并解析,速度比内存缓存慢。
还是以博客的页面为例,没有禁用缓存的情况下,多次刷新页面。就会如出现如 memory cache 和 disk cache 都有的情况。
然后关闭这个标签页,然后重新打开这个页面,则会出现全部都是从 disk cache 的情况。
这里有一点要注意的是,虽然 rem.js 是在 memory cache 中获取的,但实际上它也存在 disk cache,只是 memory cache 的优先级更高。
在浏览器中,浏览器会将 js 和 base64 的图片等文件解析执行后直接存入内存缓存中,那么当刷新页面时只需直接从内存缓存中读取(from memory cache);而 css 文件,图片后缀的图片文件则会存入硬盘文件中,所以每次渲染页面都需要从硬盘读取缓存(from disk cache)
下面我们来讲讲强制缓存的第二种情况,强制缓存标识失效,走协商缓存的过程。
在强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:
协商缓存的标识在响应报文的 HTTP 头中和请求结果一起返回给浏览器。
控制协商缓存的字段分别有:
Last-Modified / If-Modified-Since
下次请求时把上次返回的 Last-Modified
作为 If-Modified-Since
的值,服务端发现请求头中有 If-Modified-Since
字段,服务端将该值与资源的最后修改时间比对,大于则重新返回资源,否则返回 304 代表 资源无更新,可继续使用缓存。
Etag / If-None-Match
下次请求时把上次返回的 Etag
作为 If-None-Match
的值,服务端将 If-None-Match
的值与资源的 Etag
比对,一致则返回 304,代表资源无更新,继续使用缓存文件,不一致则重新返回资源文件,状态码 200
注 Etag / If-None-Match 优先级高于 Last-Modified / If-Modified-Since,同时存在则只有前者生效。
综合上面的分析,现在看文章前面的第一次请求和第二次请求的变化,应该就能很好理解了。
在浏览器的缓存机制中,强制缓存优先于协商缓存。强制缓存的标识(Expires 和 Cache-Control),如果生效则使用缓存,如果不生效则发起请求进行协商缓存的标识验证。(Last-Modified / If-Modified-Since和 Etag / If-None-Match),若协商缓存失效,则重新获取请求结果,再传入浏览器缓存中;生效的话,则继续使用缓存。
带着上面两个问题,我们开始调试吧。
<script src="../../dist/vue.min.js"></script>
为 <script src="../../dist/vue.js"></script>
yarn install
yarn dev
在 patchVnode
和 updateChildren
下打断点。
点击展示一个弹窗,点击 OK 关闭。
首先我们点击 show modal 按钮,会进入 function patch (oldVnode, vnode, hydrating, removeOnly)
的断点。我们可以看到 vnode 和 oldVnode 的状态。
后者的 children 多了一个 vue-component-1-modal
的子节点。
在 patchVnode 的逻辑中,对应了节点比对的逻辑:
oldVnode === vnode
节点引用的位置一致,直接 returnif (oldCh && ch && oldCh !== ch)
,新旧子节点都存在并且两者不同,则检查新旧节点的子节点,调用 updateChildren
函数递归比较子节点if (ch)
,只有新子节点存在,调用 addVnodes
内部对应调用 createEle
来讲新的子节点添加到 vnode.el 上else if (oldCh)
,新节点没有子节点,老节点有子节点,调用 removeVnodes
移除子节点在这个例子中,进入 patchVnode 之后,由于 oldCh 和 ch 都存在且不同,所以会进入上面的第二种情况,updateChildren 函数中。
updateChildren
的逻辑很长,但花点时间看尤大还是写得很亲民了:
新旧子节点列表各自维护一对指针,即 oldStartIdx, oldEndIdx
和 newStartIdx, newEndIdx
,不停的对新旧子节点列表的头部和尾部进行对比,将指针不断向内收缩。
如果是 sameVnode
,递归进入 patchVnode
的过程,针对单个 vnode
进行 diff,如果 vnode
又有 children,则继续往进行 diff 算法,内部递归 updateChildren
。
在这个示例中,开始比对新旧节点的子节点列表。
新旧节点的子节点第一个都是 show modal 的 button,进入上面的第三种情况,sameVnode(oldStartVnode, newStartVnode)
旧首部和新首部,在这个条件下,会再次进入上面的 patchVnode 检查子节点是否相同,即检查 button 里的 #text 节点,由于两个 button 最终是相同的,所以检查 button 完毕之后会重新回到节点比对的过程。
紧接着检查新旧节点的子节点列表的第二个元素,是一个空格文本节点,同样的,新旧节点的子节点中,这个元素也是相同的,经过检查后来到第三个元素。
由于旧节点的子节点中,第三个元素是一个注释节点,用于 v-if 组件的切换显示,而新节点的子节点中,第三个元素则是 modal 组件,这时候满足了两个节点肯定不满足任意一种 sameVnode,所以会进入在旧子树中找复用的逻辑,这时候,复用逻辑也是不生效的,最终会满足 isUndef(idxInOld)
的条件,进入 createElm 的逻辑。(生成节点这部分就不细讲了,有兴趣的读者可以去看看逻辑。检查当前的 vnode ,有子节点调用 createChildren 内部循环调用 createElm 再挂载到 parentElm 上,最终挂到 vnode.elm 下)
最后完成了节点的更新,此时页面出现了弹框内容。
点击了 OK,弹框将开始隐藏。
这时候的 diff 过程其实就很相似了,旧子节点是 [button, ' ', modal],新子节点是 [button, ' ', 'comment']。跳过前两者的比较,我们直接到第三个子节点的比较。第三个子节点的比较,同样在旧节点中无法找到复用的节点,所以进入了创建一个 comment 节点的逻辑。
这时候我们发现 v-if 的组件在切换的时候,实际上是使用一个 comment 节点去切换挂载新的组件的。并且在这个 comment 节点之前,还会有一个 text 内容是一个空格的 node。这个地方的分析会在后边的题外话分析。
现在开头的问题应该比较清楚了,vue diff 算法的过程我们已经通过一个简单的例子分析了,那么 vue 的 diff 是如何从 O(n^3) 到 O(n) 的呢?同层比较是怎样一个过程?
这个问题要引用一个issue。Advanced-Frontend/Daily-Interview-Question#151 (comment) 。首先 O(n^3) 是怎么计算出来的。我的观点是 递归新树和旧树就是 O(n^2),修改树中的节点是 O(n),所以 O(n^3)。
那么 react 和 vue 是怎么优化的,在引用里 React 和 Vue 做优化的前提是“放弃了最优解“,本质上是一种权衡,有利有弊。这个前提是:
这次的探索中发现了一个很妙的地方,但是我没想明白,就是上面那行 紧接着检查新旧节点的子节点列表的第二个元素,是一个空格文本节点
。设置了一个空格的文本节点会导致 diff 的时候检查比对这个节点,这样肯定是一种开销。如果有读者了解的话,欢迎评论区解答一下。
iterator
接口,就可以完成遍历操作iterator
接口默认部署在数据结构的 Symbol.iterator
属性Symbol.iterator
属性,就可以认为是“可遍历的”(iterable)for...of
消费Array
Map
Set
String
TypedArray
arguments
对象NodeList
对象读取 js 文件中的字符流
分词 token
标识符(Identifier)
数字 (Numeric)
运算符(operator)
字符串 (string)
注释(comment)
空格 (space)
其他(paren)
语法分析 Parser
生成 AST
生成机器码执行
Babylon.parse
babel-traverse
babel-generator
const ast = parse(template.trim(), options)
optimize(ast, options)
const code = generate(ast, options)
定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化
目的是将算法的使用和算法的实现分离开。
策略模式至少包含两个部分:
我们首先来看基于传统面向对象语言的模仿,各个类的职责体现得更加鲜明。
class performanceS {
calculate(salary) {
return salary * 4
}
}
class performanceA {
calculate(salary) {
return salary * 3
}
}
class performanceB {
calculate(salary) {
return salary * 2
}
}
class Bonus {
constructor() {
this.salary = null
this.strategy = null
}
setSalary(salary) {
this.salary = salary
}
setStrategy(strategy) {
this.strategy = strategy
}
getBonus() {
return this.strategy.calculate(this.salary)
}
}
let bonus = new Bonus()
bonus.setSalary(10000)
bonus.setStrategy(new performanceS())
console.log(bonus.getBonus()) // 输出 40000
bonus.setStrategy(new performanceA())
console.log(bonus.getBonus()) // 输出 30000
在JavaScript的策略模式中,策略类往往基于函数实现,因此策略模式在 JS 中是”隐形“的,我们通过高阶函数封装不同的行为,并把它传递到另一个函数(策略类)中,当我们调用这些高阶函数时,不同的函数调用返回不同的结果。
let strategies = {
"S": function(salary) {
return salary * 4
},
"A": function(salary) {
return salary * 3
},
"B": function(salary) {
return salary * 2
}
}
let calculateBonus = function(level, salary) {
return strategies[level](salary)
}
console.log(calculateBonus('S', 20000))
console.log(calculateBonus('A', 10000))
笔者最近在学习 《JavaScript设计模式与开发实践》。
在 5.6 节的代码中,是一段表单验证的代码,如下,读者可以新开一个 tab在控制台运行一下的代码。
document.write(`<html>
<body>
<form action="http://example.com/register" id="registerForm" method="post">
<label htmlFor="">请输入用户名<input type="text" name="userName"/></label>
<label htmlFor="">请输入密码<input type="text" name="password"/></label>
<label htmlFor="">请输入手机号码<input type="text" name="phoneNumber"/></label>
<button>提交</button>
</form>
</body>
</html>`)
// 在一个Web项目中,注册、登录、修改用户信息等功能的实现都离不开提交表单
// 表单验证可以避免不合法的数据带来不必要的网络开销
var strategies = {
isNonEmpty: function(value, errorMsg) {
if (value === '') {
return errorMsg
}
},
minLength: function(value, length, errorMsg) {
if (value.length < length) {
return errorMsg
}
},
isMobile: function(value, errorMsg) {
if (!/(^1[3|5|7|8|9][0-9]{9}$)/.test(value)) {
return errorMsg
}
}
}
// 接下来实现 validator 类
var validateFunc = function() {
var validator = new Validator()
// registerForm 是 dom,做为表单的项,可以通过 registerForm.userName 取得 userName 输入框的 dom 引用
validator.add(registerForm.userName, 'isNonEmpty', '用户名不能为空')
validator.add(registerForm.password, 'minLength:6', '用户名不能为空')
validator.add(registerForm.phoneNumber, 'isMobile', '用户名不能为空')
var errorMsg = validator.start()
return errorMsg
}
var registerForm = document.getElementById('registerForm')
registerForm.onsubmit = function() {
var errorMsg = validateFunc()
if (errorMsg) {
console.log(errorMsg)
return false
}
return false
}
var Validator = function() {
this.cache = []
}
Validator.prototype.add = function(dom, rule, errorMsg) {
var ary = rule.split(':')
// 添加到缓存规则中
this.cache.push(function() {
var strategy = ary.shift()
ary.unshift(dom.value)
ary.push(errorMsg)
return strategies[strategy].apply(dom, ary)
})
}
Validator.prototype.start = function() {
for (var i = 0, validatorFunc; validatorFunc = this.cache[i++];) {
var msg = validatorFunc()
if (msg) { // 如果有确切的返回值,说明校验没有通过
return msg
}
}
}
我们注意到代码中 Validator 的 add 方法下
this.cache.push(function() {
var strategy = ary.shift()
ary.unshift(dom.value)
ary.push(errorMsg)
return strategies[strategy].apply(dom, ary)
})
最后一行 apply 传入了 dom,我之前一直不解,这里传一个 dom 的引用和传 null 有什么差别?
运行效果不都是一样吗?读者可以试一下,控制台输出的都是”用户名不能为空“
那它们有什么差别呢?什么时候有差别呢?带着这两个疑问,我们在 strategies 下的 isNonEmpty 下添加一行打印。
var strategies = {
isNonEmpty: function(value, errorMsg) {
console.log(this) // 打印下 this 的输出
if (value === '') {
return errorMsg
}
},
// ...
}
我们重新运行,能发现,apply null 的话,在浏览器打印出来的值是 Window。
而如果是 apply dom 的话,this 指向的就是对应表单的输入框。
那么我们可能还有疑问,为什么传入的是 null,返回回来 this 的指向却是 window 对象呢?这里我们引用一下规范
可以看到规范里对 this 这个参数说明了,在非严格模式下,null 或者 undefined 都会变成指向全局对象(浏览器 Window,nodejs 下就是 global),基本类型值则会被包装成对象的形式。
所以我们试试 return strategies[strategy].apply('', ary)
传入字符串,会有什么效果。此时 this 指向的是
看了上面 apply 的 this 指向了 null,dom,'' 代码都是一样的输出,什么时候会不一样呢?
查看下面的代码改动
var strategies = {
isNonEmpty: function(value, errorMsg) {
console.log(this.value) // 注意这里,打印 dom 的 value 属性值,如果指定错了 this,那么这里无疑不能拿到正确的值。
if (value === '') {
return errorMsg
}
},
// ...
}
当我们要在 strategies 下的验证规则中获取 dom 的引用并访问数据时,指定正确的 this 就很关键了。这里我隐约感觉到,为什么 null 和 undefined 会变成指向全局对象,因为 this 变成了 window 了 this.value 也只是 undefined 而不会直接报错。至于是不是这样,等我翻了源码来补充一下哈哈。
当你正在调用 .apply 方法,并且需要访问实例数据,则必须传递适合的对象作为第一个参数,以便该方法有权使用此指针来按预期方式执行工作。
define(function(require, exports, module) {
var a = require('./a'); //在需要时申明
a.doSomething();
if (false) {
var b = require('./b');
b.doSomething();
}
});
define(function(require, exports, module) {
var $ = require('jquery.js');
var add = function(a,b){
return a + b;
}
exports.add = add;
});
// 加载模块
seajs.use(['math.js'], function(math){
var sum = math.add(1 + 2);
});
/** 网页中引入require.js及main.js **/
<script src="js/require.js" data-main="js/main"></script>
/** main.js 入口文件/主模块 **/
// 首先用config()指定各模块路径和引用名
require.config({
baseUrl: "js/lib",
paths: {
"jquery": "jquery.min", //实际路径为js/lib/jquery.min.js
"underscore": "underscore.min",
}
});
// 执行基本操作
require(["jquery","underscore"],function($,_){
// some code here
});
应用层
表示层
会话层
传输层
网络层
数据链路层
物理层
应用层
运输层
网络层
链路层
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.