0%

朝花夕拾

笨猫不如烂笔头

更友好的创建对象方式

3d5024b55687373af54fcb9ef4e0eb4.png

上面的方式,对JVM来说是更友好的,因为堆内存的调用无法避免,所以从栈内存这边入手解决内存问题是一个不错的解决的方式


下面代码是否线程安全

1
2
3
4
5
6
7
8
9
10
11
public class Singleton {
static Singleton instance;
static Singleton getInstance(){
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null) instance = new Singleton();
}
}
return instance;
}
}

乍一看类似饿汉式的单例,线程安全,其实是有问题的

虽然只有一个线程能够获得锁,并且这个锁还是类锁,所有对象共享的

关键在于 jvm 对 new 的优化,这个变量没有声明 volatile,new 不是一个线程安全的操作,

对于 new 这个指令,一般的顺序是申请内存空间,初始化内存空间,然后把内存地址赋给 instance 对象,但是 jvm 会对这段指令进行优化,优化之后变成 申请内存空间,内存地址赋给 instance 对象,初始化内存空间,这就导致 第二层检查可能会出错,标准写法只需要在变量前声明 volatile 即可。

677701574e4f69f35e226ed6bc9a380.png


volatile利用了什么协议来实现可见性

volatile 是通过内存屏障实现的,MESI协议,缓存一致性协议

JVM推荐书《The Java Language Specification》
volatile 修饰的变量如果值发生变化 发现线程的高速缓存与主存数据不一致时候 由于缓存一致性协议 则总线将高速缓存中的值清空 其他线程只能通过访问主存来获取最新的值 并缓存到告诉缓存上。


Java Trainsient 关键字

1.一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。

2.transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。

3.一个静态变量不管是否被transient修饰,均不能被序列化。

使用总结和场景:某个类的有些属性需要序列化,其他属性不需要被序列化,比如:敏感信息(如密码,银行卡号等),java 的transient关键字为我们提供了便利,你只需要实现Serilizable接口,将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。

多线程中Random的使用

1.不要在多个线程间共享一个java.util.Random实例,而该把它放入ThreadLocal之中。

2.Java7以上我们更推荐使用java.util.concurrent.ThreadLocalRandom。

下面两条建议是 IDEA给的:

1.不要将将随机数放大10的若干倍然后取整,直接使用Random对象的nextInt或者nextLong方法

2.Math.random()应避免在多线程环境下使用

为什么阿里禁止使用Executor创建线程池

阿里规约之所以强制要求手动创建线程池,也是和这些参数有关。具体为什么不允许,规约是这么说的:

线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

Executor提供的四个静态方法创建线程池,但是阿里规约却并不建议使用它。

Executors各个方法的弊端:
1)newFixedThreadPool和newSingleThreadExecutor:
  主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
2)newCachedThreadPool和newScheduledThreadPool:
  主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。

看一下这两种弊端怎么导致的。

第一种,newFixedThreadPool和newSingleThreadExecutor分别获得 FixedThreadPool 类型的线程池 和 SingleThreadExecutor 类型的线程池。 

1
2
3
4
5
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
1
2
3
4
5
6
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}

因为,创建了一个无界队列LinkedBlockingQueuesize,是一个最大值为Integer.MAX_VALUE的线程阻塞队列,当添加任务的速度大于线程池处理任务的速度,可能会在队列堆积大量的请求,消耗很大的内存,甚至导致OOM。

阿里开发手册上不推荐(禁止)使用Double的根本原因

精度丢失就不谈了,稍微深入一下为什么精度会丢失,分为一些不同情况

典型现象(一):条件判断超预期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
System.out.println( 1f == 0.9999999f );   // 打印:false
System.out.println( 1f == 0.99999999f ); // 打印:true 纳尼?

1.0(十进制)

00111111 10000000 00000000 00000000(二进制)

0x3F800000(十六进制)

0.99999999(十进制)

00111111 10000000 00000000 00000000(二进制)

0x3F800000(十六进制)

果不其然,这两个十进制浮点数的底层二进制表示是一毛一样的,怪不得==的判断结果返回true

浮点数的精度问题。

浮点数在计算机中的存储方式遵循IEEE 754 浮点数计数标准,可以用科学计数法表示为:
1 + 2 + 3

1、符号部分(S)

0-正 1-负

2、阶码部分(E)(指数部分):

对于float型浮点数,指数部分8位,考虑可正可负,因此可以表示的指数范围为-127 ~ 128
对于double型浮点数,指数部分11位,考虑可正可负,因此可以表示的指数范围为-1023 ~ 1024


3、尾数部分(M):

浮点数的精度是由尾数的位数来决定的:

对于float型浮点数,尾数部分23位,换算成十进制就是 2^23=8388608,所以十进制精度只有6 ~ 7位;
对于double型浮点数,尾数部分52位,换算成十进制就是 2^52 = 4503599627370496,所以十进制精度只有15 ~ 16

所以对于上面的数值0.99999999f,很明显已经超过了float型浮点数据的精度范围,出问题也是在所难免的。

典型现象(二):数据转换超预期

1
2
3
4
float f = 1.1f;
double d = (double) f;
System.out.println(f); // 打印:1.1
System.out.println(d); // 打印:1.100000023841858 纳尼?

典型现象(三):基本运算超预期

1
2
3
System.out.println( 0.2 + 0.7 );  

// 打印:0.8999999999999999 纳尼?

典型现象(四):数据自增超预期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
float f1 = 8455263f;
for (int i = 0; i < 10; i++) {
System.out.println(f1);
f1++;
}
// 打印:8455263.0
// 打印:8455264.0
// 打印:8455265.0
// 打印:8455266.0
// 打印:8455267.0
// 打印:8455268.0
// 打印:8455269.0
// 打印:8455270.0
// 打印:8455271.0
// 打印:8455272.0

float f2 = 84552631f;
for (int i = 0; i < 10; i++) {
System.out.println(f2);
f2++;
}
// 打印:8.4552632E7 纳尼?不是 +1了吗?
// 打印:8.4552632E7 纳尼?不是 +1了吗?
// 打印:8.4552632E7 纳尼?不是 +1了吗?
// 打印:8.4552632E7 纳尼?不是 +1了吗?
// 打印:8.4552632E7 纳尼?不是 +1了吗?
// 打印:8.4552632E7 纳尼?不是 +1了吗?
// 打印:8.4552632E7 纳尼?不是 +1了吗?
// 打印:8.4552632E7 纳尼?不是 +1了吗?
// 打印:8.4552632E7 纳尼?不是 +1了吗?
// 打印:8.4552632E7 纳尼?不是 +1了吗?

解决办法:

1.我们我们可以用字符串或者数组来表示这种大数,然后按照四则运算的规则来手动模拟出具体计算过程,中间还需要考虑各种诸如:进位、借位、符号等等问题的处理,有点复杂。

  1. JDK早已为我们考虑到了浮点数的计算精度问题,因此提供了专用于高精度数值计算的大数类来方便我们使用。

mac 清理maven仓库的脚本

1
2
3
4
5
6
7
# 这里写你的仓库路径
REPOSITORY_PATH=~/Documents/tools/apache-maven-3.0.3/repository
echo 正在搜索...
find $REPOSITORY_PATH -name "*lastUpdated*" | xargs rm -fr
echo 删除完毕

mac(linux)系统-创建.sh文件脚本执行(mac用.command终端也可以)

idea目录较多,文件名较长产生的错误

1
2
3
Error running 'ServiceStarter': Command line is too long. Shorten command line for ServiceStarter or also for Application default configuration.

修改项目下 .idea\workspace.xml,找到标签 <component name="PropertiesComponent"> , 在标签里加一行 <property name="dynamic.classpath" value="true" />

Log4J 指定屏蔽某些特定报警信息

Logger.getLogger(“org.apache.library”).setLevel(Level.OFF)

对于需要设置EventTime的流来说,我们的TimestampAssigner应该在Source之后立即调用,原因是时间戳分配器看到的元素的顺序应该和source操作符产生数据的顺序是一样的,否则就乱了,也就是说,任何分区操作都会将元素的顺序打乱,例如:改变并行度 keyBy操作等等。,所以最佳实践是:

在尽量接近数据源source操作符的地方分配时间戳和产生水位线,甚至最好在SourceFunction中分配时间戳和产生水位线。当然在分配时间戳和产生水位线之前可以对流进行map和filter操作是没问题的,也就是说必须是窄依赖。

JB套件的一个实用功能

1
之前没注意,更改变量名字的时候直接使用refactor就可以了,真的实用

zk使用的分布式协议并不是paxos

1
而是zab协议

为什么说NULL是计算机科学中最大的错误,至少值十亿美金

1
2
3
4
5
6
7
1.覆类型
2.是凌乱的
3.是一个特例
4.使 API 变得糟糕
5.使错误的语言决策更加恶化
6.难以调试
7.是不可组合的

1. NULL 颠覆类型

静态类型语言不需要实际去执行程序,就可以检查程序中类型的使用,并且提供一定的程序行为保证。

例如,在 Java 中,如果我编写 x.toUppercase(),编译器会检查 x 的类型。如果 x 是一个 String,那么类型检查成功;如果 x 是一个 Socket,那么类型检查失败。

在编写庞大的、复杂的软件时,静态类型检查是一个强大的工具。但是对于 Java,这些很棒的编译时检查存在一个致命缺陷:任何引用都可以是 null,而调用一个 null 对象的方法会产生一个 NullPointerException。所以,

  • toUppercase() 可以被任意 String 对象调用。除非 String 是 null。
  • read() 可以被任意 InputStream 对象调用。除非 InputStream 是 null。
  • toString() 可以被任意 Object 对象调用。除非 Object 是 null。

Java 不是唯一引起这个问题的语言;很多其它的类型系统也有同样的缺点,当然包括 AGOL W 语言。

在这些语言中,NULL 超出了类型检查的范围。它悄悄地越过类型检查,等待运行时,最后一下子释放出一大批错误。NULL 什么也不是,同时又什么都是。

2. NULL 是凌乱的

在很多情况下 null 是没有意义的。不幸的是,如果一种语言允许任何东西为 null,好吧,那么任何东西都可以是 null。

Java 程序员冒着患腕管综合症的风险写下

Java

if (str == null 丨丨 str.equals("")) {}

而在 C# 中添加 String.IsNullOrEmpty 是一个常见的语法

C#

if (string.IsNullOrEmpty(str)) {}

真可恶!

每次你写代码,将 null 字符串和空字符串混为一谈时,Guava 团队都要哭了。– Google Guava

说得好。但是当你的类型系统(例如,Java 或者 C#)到处都允许 NULL 时,你就不能可靠地排除 NULL 的可能性,并且不可避免的会在某个地方混淆。

null 无处不在的可能性造成了这样一个问题,Java 8 添加了 @NonNull 标注,尝试着在它的类型系统中以追溯方式解决这个缺陷。

3. NULL 是一个特例

考虑到 NULL 不是一个值却又起到一个值的作用,NULL 自然地成为各种特别处理方法的课题。

1
2
3
char c = 'A';
char *myChar = &c;
std::cout << *myChar << std::endl;

单个 NUL 字符的例外已经导致无数的错误:API 的怪异行为、安全漏洞和缓冲区溢出。

NULL 是 C 字符串中最糟糕的错误;更确切地说,以 NUL 结尾的字符串是最昂贵的一字节错误

4.NULL 使 API 变得糟糕

我们可以想象在很多语言中类似的类(Python、JavaScript、Java、C# 等)。

现在假设我们的程序有一个慢的或者占用大量资源的方法,来找到某个人的电话号码——可能通过连通一个网络服务。

为了提高性能,我们将会使用本地存储作为缓存,将一个人名映射到他的电话号码上。

然而,一些人没有电话号码(即他们的电话号码是 nil)。我们仍然会缓存那些信息,所以我们不需要在后面重新填充那些信息。

但是现在意味着我们的结果模棱两可!它可能表示:

  1. 这个人不存在于缓存中(Alice)
  2. 这个人存在于缓存中,但是没有电话号码(Tom)

一种情形要求昂贵的重新计算,另一种需要即时的答复。但是我们的代码不够精密来区分这两种情况。

在实际的代码中,像这样的情况经常会以复杂且不易察觉的方式出现。因此,简单通用的 API 可以马上变成特例,迷惑了 null 凌乱行为的来源。

用一个 contains() 方法来修补 Store 类可能会有帮助。但是这引入重复的查找,导致降低性能和竞争条件。

5.NULL 使错误的语言决策更加恶化

6.NULL 难以调试

来解释 NULL 是多么的麻烦,C++ 是一个很好的例子。调用成员函数指向一个 NULL 指针不一定会导致程序崩溃。更糟糕的是:它可能会导致程序崩溃。

7.NULL不可组合

IDEA maven修改pom文件,导致jdk版本重置问题

1
2
3
4
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>

MAVEN的scope

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
compile
默认就是compile,什么都不配置也就是意味着compile。compile表示被依赖项目需要参与当前项目的编译,当然后续的测试,运行周期也参与其中,是一个比较强的依赖。打包的时候通常需要包含进去。

test
scope为test表示依赖项目仅仅参与测试相关的工作,包括测试代码的编译,执行。比较典型的如junit。

runntime
runntime表示被依赖项目无需参与项目的编译,不过后期的测试和运行周期需要其参与。与compile相比,跳过编译而已,说实话在终端的项目(非开源,企业内部系统)中,和compile区别不是很大。比较常见的如JSR×××的实现,对应的API jar是compile的,具体实现是runtime的,compile只需要知道接口就足够了。oracle jdbc驱动架包就是一个很好的例子,一般scope为runntime。另外runntime的依赖通常和optional搭配使用,optional为true。我可以用A实现,也可以用B实现。

provided
provided意味着打包的时候可以不用包进去,别的设施(Web Container)会提供。事实上该依赖理论上可以参与编译,测试,运行等周期。相当于compile,但是在打包阶段做了exclude的动作。

system
从参与度来说,也provided相同,不过被依赖项不会从maven仓库抓,而是从本地文件系统拿,一定需要配合systemPath属性使用。

scope的依赖传递
A–>B–>C。当前项目为A,A依赖于B,B依赖于C。知道B在A项目中的scope,那么怎么知道C在A中的scope呢?
答案是:
当C是test或者provided时,C直接被丢弃,A不依赖C;
否则A依赖C,C的scope继承于B的scope。

EXCEL一点小技巧

正好最近用来有点小用处

1
2
3
4
5
6
7
8
9
1.从固定的单元格里随机取一个值
=INDEX($G$2:$M$2, RANDBETWEEN(1,7))
$G$2:下拉的时候不会自动延伸

2.从固定列取值用在本单元格里
="INSERT INTO `event_mapping` VALUES ('"&B2&"',"&C2&","&D2&");"

3.下拉到某行
在有第一行的情况下,直接双击右下角小箭头即可
1
2
3
4
5
6
/opt/flink-1.10/flink-1.10.0/lib

目前看来应该是放在flink包里面的,会稳定上传,已经确定

在interpreter 依赖里面设置了路劲
/opt/flink-1.10/flink-1.10.0/lib/jimipojo-1.0.jar

Flink系列深度好文,等待细读

1
2
https://www.jianshu.com/c/b6089c70072f
flink的apply和process方法有什么区别呢

FastJson直接解析

1
2
3
4
5
6
7
8
.map(
a -> JSON
.parseObject(
a,
Pojo.class)
).returns(
Pojo.class
)

具体的还要试一下,我故意写的很难看来督促自己。。。

FastJson很多坑 准备放弃

配置框架无法访问的问题

1
2
3
有点脑残了,今天在mac上配置了zeppelin win无法访问,其原因是配置文件中的网络地址写死了 172.0.0.1, 如果想要别尔德位置能够访问的话,必须改变配置为其局域网id

更好的选择是更改为0.0.0.0

解决GitHub提交历史头像不显示问题,以及首页没有绿色方块的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
最近把本地的一个项目提交推送到GitHub的时候发现有两个问题, 
1.在commit提交历史里面

提交内容的旁边,显示的不是原本github主页的头像,而是默认的灰色章鱼头像

2.我的contributions里面提交的历史(绿色方块)也没有了

怎么解决呢?

1.首先在终端里切到项目所在目录

2.输入git show命令,你会发现 有一行写着Author: Apple <邮箱>,这个邮箱肯定不是你绑定到github的邮箱

3.输入git config user.email "你的邮箱地址",修改邮箱

4.修改完以后输入git config user.email 检查是否修改成了你的邮箱

5.到目前为止现在只是修改这个项目的邮箱,重新推送一个新的改动,在查看该项目的提交历史和contributions里面提交的历史(绿色方块),问题已经解决了(之前的依旧不显示)

6.如果你想其他项目提交时,也避免此类情况,把上面的两条命令改成 (1) git config --global user.email "your_email@example.com"

(2)git config --global user.email 就可以了

解决anaconda无法连接的问题

1
win10下更换清华镜像后无法连接 是因为win10里面无法解析https协议,修改‪~\.condarc文件,把https换成http

排查挖矿程序中会用到的一些追踪某个进程的命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#查看PID启动文件的路径
ls -l /proc/$PID/exe
#查看PID执行目录的路径
ls -l /proc/$PID/cwd
#在定时器配置目录查看是否存在异常定时器配置
/var/spool/cron/root 和/etc/crontab 和/etc/rc.lcoal
#查看定时器启动日志,跟踪自启动程序
tail -f /var/log/cron
#查看各个进程的cpu使用情况,默认按cpu使用率排序
top
#显示所有运行中的进程,q退出
ps aux | less
#查看test.jar进程号
ps -aux|grep test.jar | grep -v grep
#查看test.jar进程号
ps -ef|grep test.jar | grep -v grep
#查看该进程下各个线程的cpu使用情况
top -Hp pid
#将线程pid转换为十六进制 8f7
printf "%x\n" pid
#查看pid进程里面的线程信息,线程Id为十六进制
jstack pid | grep 8f7
#查看该进程打开的文件
lsof -p pid
#查看pid线程内存分配
cat /proc/pid/maps
#查看PID启动文件的路径
ls -l /proc/$PID/exe
#查看PID执行目录的路径
ls -l /proc/$PID/cwd
#查看PID详细的内存占比
cat /proc/$PID/status

Kerberos缺点

1
2
3
4
5
6
7
8
9
10
11
12
13
1、KDC 有单点风险,除非设置HA系统(Aictive Directory 可以做到这一点,目前apache directoryserver 也可以做到这一点);

2、访问压力可能使KDC过载;分布式服务使用Kerberos 必须做到这一点,KDC无法承受高负载请求;为什么Hadoop 要使用代理tokens的原因也是如此;

3、服务之间的通信通道也需要安全认证,kerberos不保证数据加密;如果通信通道不安全,tickets 可能会被拦截或者通信伪造;

4、机器之前需要保证时间的精确一致性,不然具备时限的tockens不会正常工作;这个在分布式领域是一个典型的问题,Paxos &Raft协议也必须保证时间的一致性;

5、如果机器间的时间没有被安全管理,理论上可能延长被盗token的使用时间;

6、被盗用的token可以拿来直接访问服务,在KDC是没有访问日志的。每一个application需要拥有自己的以用户为单位的审计日志,这样才能保证被盗的ticket可被追踪,比如在Hadoop里面HDFS审计日志;

7、这是一个仅仅认证服务:验证caller的合法性并准许给caller传递认证信息,他不处理任何授权信息;

mac无法运行.sh文件的解决办法

1
2
3
4
5
6
7
8
今天解决了一下内网穿透的问题,
轻量级的选择有frp,
重量级的有goproxy

几个问题记录一下,第一点:
zsh无法运行.sh文件,要进行切换
chsh -s /bin/bash
chsh -s /bin/zsh

解决git下载速度慢的终极方法

因为本地的网络始终有一些问题,再忍受了很久很久的龟速下载之后,终于找了个一个非常顶的方法

前提是现有一个vpn,但是vpn不会自动代理git的流量,不管是在windows下面还是在mac下面都不会自动代理git,这点一直让我十分苦恼,现在终于找到了一劳永逸的办法

1
2
git config --global http.proxy 'socks5://127.0.0.1:1080' 
git config --global https.proxy 'socks5://127.0.0.1:1080'

简单说明:vpn一般都是走的1080端口,通过这个端口转发git的流量,跳过本地运营商。

取消代理

1
2
3
4
# 取消代理
git config --global --unset http.proxy

git config --global --unset https.proxy

Maven代理配置

不需要配置什么https或者http模式,在有代理的前提下,只要配置一个代理即可

1
2
3
4
5
6
7
8
9
10
<proxy>
<id>ss</id>
<active>true</active>
<protocol>socks5</protocol>
<username></username>
<password></password>
<host>127.0.0.1</host>
<port>1080</port>
<nonProxyHosts>127.0.0.1</nonProxyHosts>
</proxy>

要注意的是监控一下端口,如果代理没开的话那肯定是无法连接上的,mirror就不用设置了,直接从中央仓库拉去数据。

npm更换源

1
2
3
4
5
6
7
8
//设置淘宝源
npm config set registry https://registry.npm.taobao.org

//设置公司的源
npm config set registry http://127.0.0.1:4873

//查看源,可以看到设置过的所有的源
npm config get registry

其实感觉应该把Mac管理node的brew n弄一下

HDFS的某个错误

HBase和Flink在运行的时候报错

hbase启动后region自动挂了,Flink任务失败,文件丢失,然后查看hdfs日志

错误原因 dfs.datanode.max.transfer.threads 的参数4096,已经不足以支持现在的Thread,修改为2倍或者4倍或者更多

IDEA MAVEN停止加载

经常遇到大型项目idea 停止加载mvn,然后就没办法了。。

1
2
3
4
maven -> maven goal idea的maven第六个按钮
点击 然后
mvn -U idea:idea
即可

窗口触发的一些问题

窗口是按 watermark 触发的,watermark 如果没有前进到 window end , window 是不会触发的。

Flink的窗口触发具体机制需要去源码里面探寻

Lateral View() 在Flink SQL中是unnest

1
SELECT users, tag FROM Orders CROSS JOIN UNNEST(tags) AS t (tag)
1
Flink UI 在 Yarn下很多选线卡看不到详细信息,很正常,因为Yarn这个运行模式有的消息只能在Yarn上管理控制

Flink的时区问题

1
Flink的时间戳差了8个小时,可以用时间减去八个小时时差,生成一个减去8小时的列,作为watermark的时间戳。

Flink的心跳需求问题

1
2
3
4
5
使用flink sql在实时计算当天凌晨截止到现在的累计数据的时候,计算步长是10分钟,如果这10分钟内没有新数据达到的话,现在的情况是这10分钟没有写记录,这就会造成业务查这个数据的时候需要找last value这种情形,设计一条方案让没有数据到达的时候也生成一条记录,这条记录的值就是last value。

方案:
往数据源发心跳数据。
发送的数据格式和普通数据一样,只是这些数据不影响你的 agg 计算,比如 null 值。发送频率就根据需求去确定。

Flink避免重复劳动的一些方法

1
2
1.写DDL+DML分别声明数据源和进行数据处理。
2.groovy+ 规则引擎

oppo:

微信截图_20200624155221.png

FF36AEE6-1E6D-44da-80BB-9BD2FE8142D6.png

Flink配置参数

1
2
3
4
5
6
7
8
9
10
# 开启 distinct agg 切分 
table.optimizer.distinct-agg.split.enabled=true
# 开启两阶段 即local-global 􏰝优化
table.optimizer.agg-phase-strategy=TWO_PHASE
# mini-batch 开启微批操作
table.exec.mini-batch.enabled=true
# mini-batch的时间间隔,即作业需要额外忍受的延迟
table.exec.mini-batch.allow-latency=5s
# 一个节点中允许最多缓存的数据
table.exec.mini-batch.size=5000
1
2
3
4
5
6
7
8
TableEnvironment tEnv = ...

// access flink configuration
Configuration configuration = tEnv.getConfig().getConfiguration();
// set low-level key-value options
configuration.setString("table.exec.mini-batch.enabled", "true");
configuration.setString("table.exec.mini-batch.allow-latency", "5 s");
configuration.setString("table.exec.mini-batch.size", "5000");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
def testJoin(): Unit ={
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(3)
val person = env.fromElements(("1","小张"),("2","小刘"),("3","小力"),("4","小心"))
val money = env.fromElements(("1",100),("2",200),("3",300))
person.join(money)
.where(_._1)
.equalTo(_._1)
.window(GlobalWindows.create())
.apply((x,y) =>{
println( x+"==="+y)
"xxx"
}).print()
env.execute()
println("end")
}

无法运行,加个triggle(xxx)解决,默认是NeverTrigger

1
2
3
4
....
.window....
.trigger(CountTrigger.of(1))
.apply...

如何排查Kafka消息的异常

1
记录住报错时的kafka offset,然后分阶段打印到控制台,再对比一下,把输出的格式分别调为Row.class 以前pojo类,或注释下一阶段代码,回放kafka 故障的offset数据,各个stream排查

MySQL的迁移

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
--涉及到MySQL的迁移,我这边推荐少量数据的话使用MySQLDump.
--还涉及到mysql安装在docker里的情况
--mysqldump的用法
--备份所有数据库:

mysqldump -uroot -p --all-databases > /backup/mysqldump/all.db
--备份指定数据库:

mysqldump -uroot -p test > /backup/mysqldump/test.db
--备份指定数据库指定表(多个表以空格间隔)

mysqldump -uroot -p mysql db event > /backup/mysqldump/2table.db
--备份指定数据库排除某些表

mysqldump -uroot -p test --ignore-table=test.t1 --ignore-table=test.t2 > /backup/mysqldump/test2.db
--Docker进入mysql容器
docker exec -it mysql1 bash //mysql1是我启动的mysql服务的name
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
--带docker的命令
1.查看当前启动的mysql运行容器

docker ps

2.使用以下命令备份导出数据库中的所有表结构和数据

docker exec -it  mysql mysqldump -uroot -p123456 paas_portal > /cloud/sql/paas_portal.sql
3.只导数据不导结构

    mysqldump -t 数据库名 -uroot -p > xxx.sql 

docker exec -it mysql mysqldump -t -uroot -p123456 paas_portal >/cloud/sql/paas_portal_dml.sql
4.只导结构不导数据

mysqldump --opt -d 数据库名 -u root -p > xxx.sql 

docker exec -it mysql mysqldump --opt -d -uroot -p123456 paas_portal >/cloud/sql/paas_portal_ddl.sql

5.导出特定表的结构

mysqldump -uroot -p -B 数据库名 --table 表名 > xxx.sql

docker exec -it mysql mysqldump -uroot -p -B paas_portal --table user > user.sql

TIM截图20200706160357.png

https://stackoverflow.com/questions/34816847/debugging-on-the-remote-cluster

Log4J 和 slf4j联合使用

slf4j是什么?slf4j只是定义了一组日志接口,但并未提供任何实现,既然这样,为什么要用slf4j呢?log4j不是已经满足要求了吗?

是的,log4j满足了要求,但是,日志框架并不只有log4j一个,你喜欢用log4j,有的人可能更喜欢logback,有的人甚至用jdk自带的日志框架,这种情况下,如果你要依赖别人的jar,整个系统就用了两个日志框架,如果你依赖10个jar,每个jar用的日志框架都不同,岂不是一个工程用了10个日志框架,那就乱了!

如果你的代码使用slf4j的接口,具体日志实现框架你喜欢用log4j,其他人的代码也用slf4j的接口,具体实现未知,那你依赖其他人jar包时,整个工程就只会用到log4j日志框架,这是一种典型的门面模式应用,与jvm思想相同,我们面向slf4j写日志代码,slf4j处理具体日志实现框架之间的差异,正如我们面向jvm写java代码,jvm处理操作系统之间的差异,结果就是,一处编写,到处运行。况且,现在越来越多的开源工具都在用slf4j了

1
2
3
4
5
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>

然后,弄到slf4j与log4j的关联jar包,通过这个东西,将对slf4j接口的调用转换为对log4j的调用,不同的日志实现框架,这个转换工具不同

1
2
3
4
5
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>

以及原来的

1
2
3
4
5
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

Log4J2的使用

Log4J2和Log4J1的比较

配置文件

log4j是通过一个**.properties的文件作为主配置文件的,而现在的log4j 2则已经弃用了这种方式,采用的是.xml,.json或者.jsn**这种方式。

依赖

log4j只需要引入一个依赖

1
2
3
4
5
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

log4j 2则是需要2个核心

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.5</version>
</dependency>
配置文件

log4J.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#此句为定义名为stdout的输出端是哪种类型,可以是
#org.apache.log4j.ConsoleAppender(控制台),
#org.apache.log4j.FileAppender(文件),
#org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件),
#org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件)
#org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)
log4j.appender.stdout=org.apache.log4j.ConsoleAppender

#此句为定义名为stdout的输出端的layout是哪种类型,可以是
#org.apache.log4j.HTMLLayout(以HTML表格形式布局),
#org.apache.log4j.PatternLayout(可以灵活地指定布局模式),
#org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串),
#org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息)
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

#如果使用pattern布局就要指定的打印信息的具体格式ConversionPattern,打印参数如下:
#%m 输出代码中指定的消息
#%p 输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL
#%r 输出自应用启动到输出该log信息耗费的毫秒数
#%c 输出所属的类目,通常就是所在类的全名
#%t 输出产生该日志事件的线程名
#%n 输出一个回车换行符,Windows平台为“rn”,Unix平台为“n”
#%d 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式
#比如:%d{yyyy MMM dd HH:mm:ss,SSS} 输出类似:2002年10月18日 22:10:28,921
#%l 输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数。
#[Log4JDemo]是log信息的开头,可以为任意字符,一般为项目简称。
#log4j.appender.stdout.layout.ConversionPattern=[Log4JDemo] %p [%t] %C.%M(%L) | %m%n
log4j.appender.stdout.layout.ConversionPattern=[Log4JDemo] %p [%t] %C.%M(%L) | %m%n

#设置日志文件
log4j.appender.LogFile=org.apache.log4j.FileAppender
log4j.appender.LogFile.File=log4j.log
log4j.appender.LogFile.layout=org.apache.log4j.PatternLayout
log4j.appender.LogFile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %l %m%n

#此句为将等级为ALL的日志信息输出到stdout和LogFile这两个目的地
#stdout和R的定义在下面的代码,可以任意起名
#等级可分为OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL
#如果配置OFF则不打出任何信息
#如果配置为INFO这样只显示INFO, WARN, ERROR的log信息,而DEBUG信息不会被显示,
#log4j.rootCategory=ERROR,stdout,LogFile
#log4j.rootCategory=ERROR,LogFile
log4j.rootCategory=ERROR,stdout

log4j2.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration status="WARN" monitorInterval="30">
<!--先定义所有的appender-->
<appenders>
<!--这个输出控制台的配置-->
<console name="Console" target="SYSTEM_OUT">
<!--输出日志的格式-->
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
</console>
<!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用-->
<File name="log" fileName="log/test.log" append="false">
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
</File>
<!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileInfo" fileName="${sys:user.home}/logs/info.log"
filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
</RollingFile>
<RollingFile name="RollingFileWarn" fileName="${sys:user.home}/logs/warn.log"
filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log">
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了20 -->
<DefaultRolloverStrategy max="20"/>
</RollingFile>
<RollingFile name="RollingFileError" fileName="${sys:user.home}/logs/error.log"
filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log">
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
</RollingFile>
</appenders>
<!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
<loggers>
<!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
<logger name="org.springframework" level="INFO"></logger>
<logger name="org.mybatis" level="INFO"></logger>
<root level="all">
<appender-ref ref="Console"/>
<appender-ref ref="RollingFileInfo"/>
<appender-ref ref="RollingFileWarn"/>
<appender-ref ref="RollingFileError"/>
</root>
</loggers>
</configuration>

调用

log4j

1
2
import org.apache.log4j.Logger;
private final Logger LOGGER = Logger.getLogger(Test.class.getName());

log4j2

1
2
3
4
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
private static Logger logger = LogManager.getLogger(Test.class.getName());

Spark On Yarn 结束任务的方式

1
2
3
4
1、yarn app -kill  appid 丢数据或者多数据
2、kill -15 pid 丢数据或者多数据
3、监听http或hdfs目录方式 ok
建议大家用第三种方式
1
我们用的第一种 针对丢数据或者多数据 我们代码里把实时过来的数据checkpoint 下一次再跑的时候会去mysql里修改offset Kafka再读进来的数据和上次checkpoint的数据对比一下 去重

CharSequence

第一次见到这个CharSequence的时候感觉挺疑惑的,不知道为什么要有这个东西。这个CharSequence是String和Stringbuilder共同实现的接口类,在下面这种应用场景中,只有CharSequence是适用的。

1
2
3
4
String str = "abc";
StringBuilder strbu = new StringBuilder("def");
boolean boo = true;
CharSequence cs = boo?str:strbu;

并发编程,创建多少个线程合适

分为两种情况讨论,CPU密集型和 I/O密集型

在CPU密集型的程序中,理论上 线程数量 = CPU 核数(逻辑)就可以了,但是实际上,数量一般会设置为 CPU 核数(逻辑)+ 1,

计算(CPU)密集型的线程恰好在某时因为发生一个页错误或者因其他原因而暂停,刚好有一个“额外”的线程,可以确保在这种情况下CPU周期不会中断工作。

在I/O密集型程序的程序中,单核心线程数一般来说是这么设置的:

最佳线程数 = (1/CPU利用率) = 1 + (I/O耗时/CPU耗时)

多核心的线程数为:

最佳线程数 = CPU核心数 * (1/CPU利用率) = CPU核心数 * (1 + (I/O耗时/CPU耗时))

如果都是IO耗时的话,可以从纯理论上直接回答是2N或者2N+1

还有很多APM(Application Performance Manager)工具可以帮我们得到具体的数据比如 SkyWalking、CAT、zipkin

假设要求一个系统的 TPS(Transaction Per Second 或者 Task Per Second)至少为20,然后假设每个Transaction由一个线程完成,继续假设平均每个线程处理一个Transaction的时间为4s

如何设计线程个数,使得可以在1s内处理完20个Transaction?

但是,但是,这是因为没有考虑到CPU数目。家里又没矿,一般服务器的CPU核数为16或者32,如果有80个线程,那么肯定会带来太多不必要的线程上下文切换开销(希望这句话你可以主动说出来),这就需要调优了,来做到最佳 balance

计算操作需要5ms,DB操作需要 100ms,对于一台 8个CPU的服务器,怎么设置线程数呢?

线程数 = 8 * (1 + 100/5) = 168 (个)

Google AutoValue

Google的 AutoValue 用起来说实话不是特别方便,对于一些需要用到映射的支持也不是十分友好,总之一句话,在国内的生态下是不太适合使用的,虽然EffectiveJava的作者嗯吹这个组件。

https://www.jianshu.com/p/e778e96fb751

这篇博客和AutoValue在Github上面自己的文档算是讲的比较好一点的文档。

Idea 注释模板设置

https://blog.csdn.net/shadow_zed/article/details/80551460#commentBox

Python中使用.join()替代+处理字符串

https://towardsdatascience.com/do-not-use-to-join-strings-in-python-f89908307273

存储图片

1.使用CDN 等技术替代数据库存储。

2.存储选择特殊文件系统,比如S3、淘宝的TFS等等

数据库里面尽量只要写路径

java的一些包的解释

PO(persistant object) 持久对象
在o/r映射的时候出现的概念,如果没有o/r映射,没有这个概念存在了。通常对应数据模型(数据库),本身还有部分业务逻辑的处理。可以看成是与数据库 中的表相映射的java对象。最简单的PO就是对应数据库中某个表中的一条记录,多个记录可以用PO的集合。PO中应该不包含任何对数据库的操作。

VO(value object) 值对象
通常用于业务层之间的数据传递,和PO一样也是仅仅包含数据而已。但应是抽象出的业务对象,可以和表对应,也可以不,这根据业务的需要.个人觉得同DTO(数据传输对象),在web上传递。

TO(Transfer Object),数据传输对象
在应用程序不同tie(关系)之间传输的对象

BO(business object) 业务对象
从业务模型的角度看,见UML元件领域模型中的领域对象。封装业务逻辑的java对象,通过调用DAO方法,结合PO,VO进行业务操作。

POJO(plain ordinary java object)

简单无规则java对象
纯的传统意义的java对象。就是说在一些Object/Relation Mapping工具中,能够做到维护数据库表记录的persisent object完全是一个符合Java Bean规范的纯Java对象,没有增加别的属性和方法。我的理解就是最基本的Java Bean,只有属性字段及setter和getter方法。

DAO(data access object) 数据访问对象
是一个sun的一个标准j2ee设计模式,这个模式中有个接口就是DAO,它负持久层的操作。为业务层提供接口。此对象用于访问数据库。通常和PO结合使 用,DAO中包含了各种数据库的操作方法。通过它的方法,结合PO对数据库进行相关的操作。夹在业务逻辑与数据库资源中间。配合VO, 提供数据库的CRUD操作…

IDEA2020 显示内存大小

双击shift 填入

1
show memory indicator

在里面打开ON

Java程序结束前运行的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ShutdownTest {

public static void main(String[] args) {
System.out.println("开始");

for (int i = 0; i < 1000000000; i++) {
System.out.println("repeat");
}

// 当使用 kill pid 或者 kill -15 pid的时候这个部分是会执行后才关闭程序的
Runtime.getRuntime().addShutdownHook(new Thread(){
@Override
public void run(){
System.out.println("关闭");
}
});
}
}

Java8 try()…catch()

1
2
3
4
5
6
// 这个FileWriter会自己关掉
try(FileWriter fw = new FileWriter("test.txt")) {
fw.write("test");
} catch(Exception ex) {
ex.printStackTrace();
}

通常我们使用try…catch()捕获异常的,如果遇到类似IO流的处理,要在finally部分关闭IO流,当然这个是JDK1.7之前的写法了;在JDK7优化后的try-with-resource语句,该语句确保了每个资源,在语句结束时关闭。所谓的资源是指在程序完成后,必须关闭的流对象。写在()里面的流对象对应的类都实现了自动关闭接口AutoCloseable。

idea中设置maven的jvm参数

1
2
file->setting->Build,Execution,Deployment->Maven->Runner
VM option栏设置jvm参数,-Xmx1g -XX:MaxMetaspaceSize=128m

命令行中设置maven的jvm参数

1
2
1. 可以在mvn.cmd(linux中是mvn.sh或mvn)添加set MAVEN_OPTS=-Xmx1g -XX:MaxMetaspaceSize=128m
2. 也可以添加MAVEN_OPTS环境变量

String SubString

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String args[]) {
String Str = new String("www.runoob.com");

System.out.print("返回值 :" );
System.out.println(Str.substring(4) );

System.out.print("返回值 :" );
System.out.println(Str.substring(4, 10) );
}

返回值 :runoob.com
返回值 :runoob

通用的空间/地理空间ASL许可的开源Java库

Spatial4j

IntegerCache

QzpcVXNlcnNcZmx5aG9cQXBwRGF0YVxSb2FtaW5nXERpbmdUYWxrXDExMzI4MDM2MF92MlxJbWFnZUZpbGVzXDIxODg5NzUyOFwxNTk5MTQ4OTI2NTQ3XzAwNjc2REI3LTEwMUYtNDFmNi04RDg1LTQxRDUxNEJENTZGMS5wbmc=.png

1
2
3
4
5
6
public static void main(String[] args) {
Integer a = 1000, b = 1000;
System.out.println(a == b);//1
Integer c = 100, d = 100;
System.out.println(c == d);//2
}

false
true

因为存在这个IntegerCache,-128-127范围内是有Cache对象的,不会新生成。

书单推荐

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
《Effective Java中文版》
《实战Java虚拟机:JVM故障诊断与性能优化》
《HotSpot实战》
《实战Java高并发程序设计》
《深入分析Java Web技术内幕》
《大型网站技术架构 核心原理与案例分析》
《大型网站系统与Java中间件实践》
《从Paxos到ZooKeeper 分布式一致性原理与实践》
《代码大全(第2版) 》
《算法导论》
《计算机程序设计艺术》
《重构》
《设计模式》
《人月神话》
《程序员修炼之道》

反射API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.hugh.draft.reflection;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;

/**
* @program: draft
* @description: 反射
* @author: Fly.Hugh
* @create: 2020-09-04 14:29
**/
public class RefleactionAPI {
public static void main(String[] args) throws Exception{
URL url = new URL("https://www.baidu.com");
String urlString = url.toExternalForm();
System.out.println(urlString);

System.out.println();
System.out.println("==============等价==============>");
System.out.println();

Class<?> type = Class.forName("java.net.URL");
Constructor<?> constructor = type.getConstructor(String.class);
Object instance = constructor.newInstance("https://www.baidu.com");
Method method = type.getMethod("toExternalForm");
Object methodCallResult = method.invoke(instance);

System.out.println(methodCallResult);
}
}

简单的Java 反射API,还需要更深入的了解

最详细的Anaconda安装步骤和注意点

1
https://zhuanlan.zhihu.com/p/32925500

CUDA Driver版本选型

1
https://docs.nvidia.com/cuda/cuda-toolkit-release-notes/index.html#cuda-major-component-versions

{291168FC-C79D-4F29-AE0B-DA3E3184D006}_20200907110920.jpg

CuDNN版本

CuDNN的版本是和CUDA版本对应,下载页面就能看到,安装十分简单,三个文件夹的文件和外面的一个文件,单独复制到已经安装好的CUDA文件夹中即可。

Maven 阿里云插件仓库配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<repositories>
<repository>
<id>aliyun</id>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>aliyun-plugin</id>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>

IDEA配置仓库最好用HTTPS

Win上有可能出现如下错误,mac上却没问题,佛了

QzpcVXNlcnNcQWRtaW5pc3RyYXRvclxBcHBEYXRhXFJvYW1pbmdcRGluZ1RhbGtcMzU1NTg3MDEyX3YyXEltYWdlRmlsZXNcMjE4ODk3NTI4XDE1OTk3MDA4MzA4MTBfNkQxRUYzMkItMURFMy00MjE1LThEODMtQ0M5ODMzOTdFMDY2LnBuZw==.png

Spring

五个常用框架

undefined

1.spring framework
也就是我们经常说的spring框架,包括了ioc依赖注入,Context上下文、bean管理、springmvc等众多功能模块,其它spring项目比如spring boot也会依赖spring框架。

2.spring boot
它的目标是简化Spring应用和服务的创建、开发与部署,简化了配置文件,使用嵌入式web服务器,含有诸多开箱即用的微服务功能,可以和spring cloud联合部署。

Spring Boot的核心思想是约定大于配置,应用只需要很少的配置即可,简化了应用开发模式。

3.Spring Data
是一个数据访问及操作的工具集,封装了多种数据源的操作能力,包括:jdbc、Redis、MongoDB等。

4.Spring Cloud
是一套完整的微服务解决方案,是一系列不同功能的微服务框架的集合。Spring Cloud基于Spring Boot,简化了分布式系统的开发,集成了服务发现、配置管理、消息总线、负载均衡、断路器、数据监控等各种服务治理能力。比如sleuth提供了全链路追踪能力,Netflix套件提供了hystrix熔断器、zuul网关等众多的治理组件。config组件提供了动态配置能力,bus组件支持使用RabbitMQ、kafka、Activemq等消息队列,实现分布式服务之间的事件通信。

5.Spring Security
主要用于快速构建安全的应用程序和服务,在Spring Boot和Spring Security OAuth2的基础上,可以快速实现常见安全模型,如单点登录,令牌中继和令牌交换。你可以了解一下oauth2授权机制和jwt认证方式。oauth2是一种授权机制,规定了完备的授权、认证流程。JWT全称是JSON Web Token,是一种把认证信息包含在token中的认证实现,oauth2授权机制中就可以应用jwt来作为认证的具体实现方法。

undefined

本文涉及的流程与实现默认都是基于最新的5.x版本。

spring中的几个重要概念如下:

1.IOC

IOC,就是控制反转,如最左边,拿公司招聘岗位来举例:

假设一个公司有产品、研发、测试等岗位。如果是公司根据岗位要求,逐个安排人选,如图中向下的箭头,这是正向流程。如果反过来,不用公司来安排候选人,而是由第三方猎头来匹配岗位和候选人,然后进行推荐,如图中向上的箭头,这就是控制反转。

在spring中,对象的属性是由对象自己创建的,就是正向流程;如果属性不是对象创建,而是由spring来自动进行装配,就是控制反转。这里的DI也就是依赖注入,就是实现控制反转的方式。正向流程导致了对象于对象之间的高耦合,IOC可以解决对象耦合的问题,有利于功能的复用,能够使程序的结构变得非常灵活。

2.context上下文和bean

spring进行IOC实现时使用的有两个概念:context上下文和bean。

如中间图所示,所有被spring管理的、由spring创建的、用于依赖注入的对象,就叫做一个bean。Spring创建并完成依赖注入后,所有bean统一放在一个叫做context的上下文中进行管理。

3.AOP

AOP就是面向切面编程。如右面的图,一般程序执行流程是从controller层调用service层、然后service层调用DAO层访问数据,最后在逐层返回结果。

这个是图中向下箭头所示的按程序执行顺序的纵向处理。但是,一个系统中会有多个不同的服务,例如用户服务、商品信息服务等等,每个服务的controller层都需要验证参数,都需要处理异常,如果按照图中红色的部分,对不同服务的纵向处理流程进行横切,在每个切面上完成通用的功能,例如身份认证、验证参数、处理异常等等、这样就不用在每个服务中都写相同的逻辑了,这就是AOP思想解决的问题。

AOP以功能进行划分,对服务顺序执行流程中的不同位置进行横切,完成各服务共同需要实现的功能。

Java加载properties的六种方式

Java加载properties文件的方式主要分为两大类:

一种是通过import java.util.Properties类中的load(InputStream in)方法加载;

另一种是通过import java.util.ResourceBundle类的getBundle(String baseName)方法加载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package com.util;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;

public class PropertiesUtil {
private static String basePath = "src/prop.properties";
private static String name = "";
private static String nickname = "";
private static String password = "";

/**
* 一、 使用java.util.Properties类的load(InputStream in)方法加载properties文件
*
*/
public static String getName1() {
try {
Properties prop = new Properties();
InputStream is = new FileInputStream(basePath);
prop.load(is);
name = prop.getProperty("username");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return name;
}

/**
* 二、 使用class变量的getResourceAsStream()方法
* 注意:getResourceAsStream()读取路径是与本类的同一包下
*
*/
public static String getName2() {
Properties prop = new Properties();
InputStream is = PropertiesUtil.class
.getResourceAsStream("/com/util/prop.properties");
try {
prop.load(is);
name = prop.getProperty("username");
} catch (IOException e) {
e.printStackTrace();
}
return name;
}

/**
* 三、
* 使用class.getClassLoader()所得到的java.lang.ClassLoader的getResourceAsStream()方法
* getResourceAsStream(name)方法的参数必须是包路径+文件名+.后缀 否则会报空指针异常
*
*/
public static String getName3() {
Properties prop = new Properties();
InputStream is = PropertiesUtil.class.getClassLoader()
.getResourceAsStream("com/util/prop.properties");
try {
prop.load(is);

} catch (IOException e) {
e.printStackTrace();
}
return name;
}

/**
* 四、 使用java.lang.ClassLoader类的getSystemResourceAsStream()静态方法
* getSystemResourceAsStream()方法的参数格式也是有固定要求的
*
*/
public static String getName4() {
Properties prop = new Properties();
InputStream is = ClassLoader
.getSystemResourceAsStream("com/util/prop.properties");
try {
prop.load(is);
name = prop.getProperty("username");
} catch (IOException e) {
e.printStackTrace();
}
return name;
}

/**
* 五、 使用java.util.ResourceBundle类的getBundle()方法
* 注意:这个getBundle()方法的参数只能写成包路径+properties文件名,否则将抛异常
*
*/
public static String getName5() {
ResourceBundle rb = ResourceBundle.getBundle("com/util/prop");
password = rb.getString("password");
return password;
}

/**
* 六、 使用java.util.PropertyResourceBundle类的构造函数
*
*/
public static String getName6() {
try {
InputStream is = new FileInputStream(basePath);
ResourceBundle rb = new PropertyResourceBundle(is);
nickname = rb.getString("nickname");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

return nickname;
}

/**
* 测试
*
*/
public static void main(String[] args) {
System.out.println("name1:" + PropertiesUtil.getName1());
System.out.println("name2:" + PropertiesUtil.getName2());
System.out.println("name3:" + PropertiesUtil.getName3());
System.out.println("name4:" + PropertiesUtil.getName4());
System.out.println("password:" + PropertiesUtil.getName5());
System.out.println("nickname:" + PropertiesUtil.getName6());
}
}

GPS第三方工具

GIS基本概念

  1. WKT(Well-known text)是开放地理空间联盟OGC(Open GIS Consortium )制定的一种文本标记语言,用于表示矢量几何对象、空间参照系统及空间参照系统之间的转换。
  2. WKB(well-known binary) 是WKT的二进制表示形式,解决了WKT表达方式冗余的问题,便于传输和在数据库中存储相同的信息
  3. GeoJSON 一种JSON格式的Feature信息输出格式,它便于被JavaScript等脚本语言处理,OpenLayers等地理库便是采用GeoJSON格式。此外,TopoJSON等更精简的扩展格式
几何对象

WKT可以表示的对象包括以下几种:

  • Point, MultiPoint
  • LineString, MultiLineString
  • Polygon, MultiPolygon
  • GeometryCollection
    • 可以由多种Geometry组成,如:GEOMETRYCOLLECTION(POINT(4 6),LINESTRING(4 6,7 10)
Type Shape WKT GeoJSON
Point undefined POINT (30 10) { “type”: “Point”, “coordinates”: [30, 10] }
LineString undefined LINESTRING (30 10, 10 30, 40 40) { “type”: “LineString”, “coordinates”: [ [30, 10], [10, 30], [40, 40] ] }
Polygon undefined POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10)) { “type”: “Polygon”, “coordinates”: [ [[30, 10], [40, 40], [20, 40], [10, 20], [30, 10]] ] }
Polygon undefined POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10),(20 30, 35 35, 30 20, 20 30)) { “type”: “Polygon”, “coordinates”: [ [[35, 10], [45, 45], [15, 40], [10, 20], [35, 10]], [[20, 30], [35, 35], [30, 20], [20, 30]] ] }
MultiPoint undefined MULTIPOINT ((10 40), (40 30), (20 20), (30 10)) { “type”: “MultiPoint”, “coordinates”: [ [10, 40], [40, 30], [20, 20], [30, 10] ] }
MultiPoint undefined MULTIPOINT (10 40, 40 30, 20 20, 30 10) { “type”: “MultiPoint”, “coordinates”: [ [10, 40], [40, 30], [20, 20], [30, 10] ] }
MultiLineString undefined MULTILINESTRING ((10 10, 20 20, 10 40),(40 40, 30 30, 40 20, 30 10)) { “type”: “MultiLineString”, “coordinates”: [ [[10, 10], [20, 20], [10, 40]], [[40, 40], [30, 30], [40, 20], [30, 10]] ] }
MultiPolygon undefined MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)),((15 5, 40 10, 10 20, 5 10, 15 5))) { “type”: “MultiPolygon”, “coordinates”: [ [ [[30, 20], [45, 40], [10, 40], [30, 20]] ], [ [[15, 5], [40, 10], [10, 20], [5, 10], [15, 5]] ] ] }
MultiPolygon undefined MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)),((20 35, 10 30, 10 10, 30 5, 45 20, 20 35),(30 20, 20 15, 20 25, 30 20))) { “type”: “MultiPolygon”, “coordinates”: [ [ [[40, 40], [20, 45], [45, 30], [40, 40]] ], [ [[20, 35], [10, 30], [10, 10], [30, 5], [45, 20], [20, 35]], [[30, 20], [20, 15], [20, 25], [30, 20]] ] ] }

WKB格式

WKB采用二进制进行存储,更方便于计算机处理,因此广泛运用于数据的传输与存储,以二位点Point(1 1)为例,

其WKB表达如下:

01 0100 0020 E6100000 000000000000F03F 000000000000F03F

undefined

  1. byteOrder
  • 表示编码方式,00为使用big-endian编码(XDR),01为使用little-endian编码(NDR)。他们的不同仅限于在内存中放置字节的顺序,比如我们将0x1234abcd写入到以0×0000开始的内存中,则结果如下表:

  • Address big-endian little-endian
    0×0000 0x12 0xcd
    0×0001 0x34 0xab
    0×0002 0xab 0x34
    0×0003 0xcd 0x12
  1. webTypd

第二到第九字节对矢量数据基本信息进行了定义

  • 第二与第三个字节规定了矢量数据的类型,如例子中的0100代表Point;
  • 第三与第四个字节规定了矢量数据的维数,如例子中的0020代表该点是二位的;
  • 第五到第九个字节规定了矢量数据的空间参考SRID,如例子中的E6100000是4326的整数十六位进制表达
  1. srid
  • 第五到第九个字节规定了矢量数据的空间参考SRID,如例子中的E6100000是4326的整数十六位进制表达
  1. structPoint
  • 第十个字节开始,每16个字节就代表一个坐标对,如例子中的000000000000F03F是浮点型1的十六进制表达

JTS

简介
  1. JTS是加拿大的 Vivid Solutions公司做的一套开放源码的 Java API。它提供了一套空间数据操作的核心算法。为在兼容OGC标准的空间对象模型中进行基础的几何操作提供2D空间谓词API。
操作

表示Geometry对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
package com.alibaba.autonavi;


import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKTReader;


public class GeometryDemo {

private GeometryFactory geometryFactory = new GeometryFactory();

/**
* create a point
* @return
*/
public Point createPoint(){
Coordinate coord = new Coordinate(109.013388, 32.715519);
Point point = geometryFactory.createPoint( coord );
return point;
}

/**
* create a point by WKT
* @return
* @throws ParseException
*/
public Point createPointByWKT() throws ParseException{
WKTReader reader = new WKTReader( geometryFactory );
Point point = (Point) reader.read("POINT (109.013388 32.715519)");
return point;
}

/**
* create multiPoint by wkt
* @return
*/
public MultiPoint createMulPointByWKT()throws ParseException{
WKTReader reader = new WKTReader( geometryFactory );
MultiPoint mpoint = (MultiPoint) reader.read("MULTIPOINT(109.013388 32.715519,119.32488 31.435678)");
return mpoint;
}
/**
*
* create a line
* @return
*/
public LineString createLine(){
Coordinate[] coords = new Coordinate[] {new Coordinate(2, 2), new Coordinate(2, 2)};
LineString line = geometryFactory.createLineString(coords);
return line;
}

/**
* create a line by WKT
* @return
* @throws ParseException
*/
public LineString createLineByWKT() throws ParseException{
WKTReader reader = new WKTReader( geometryFactory );
LineString line = (LineString) reader.read("LINESTRING(0 0, 2 0)");
return line;
}

/**
* create multiLine
* @return
*/
public MultiLineString createMLine(){
Coordinate[] coords1 = new Coordinate[] {new Coordinate(2, 2), new Coordinate(2, 2)};
LineString line1 = geometryFactory.createLineString(coords1);
Coordinate[] coords2 = new Coordinate[] {new Coordinate(2, 2), new Coordinate(2, 2)};
LineString line2 = geometryFactory.createLineString(coords2);
LineString[] lineStrings = new LineString[2];
lineStrings[0]= line1;
lineStrings[1] = line2;
MultiLineString ms = geometryFactory.createMultiLineString(lineStrings);
return ms;
}

/**
* create multiLine by WKT
* @return
* @throws ParseException
*/
public MultiLineString createMLineByWKT()throws ParseException{
WKTReader reader = new WKTReader( geometryFactory );
MultiLineString line = (MultiLineString) reader.read("MULTILINESTRING((0 0, 2 0),(1 1,2 2))");
return line;
}

/**
* create a polygon(多边形) by WKT
* @return
* @throws ParseException
*/
public Polygon createPolygonByWKT() throws ParseException{
WKTReader reader = new WKTReader( geometryFactory );
Polygon polygon = (Polygon) reader.read("POLYGON((20 10, 30 0, 40 10, 30 20, 20 10))");
return polygon;
}

/**
* create multi polygon by wkt
* @return
* @throws ParseException
*/
public MultiPolygon createMulPolygonByWKT() throws ParseException{
WKTReader reader = new WKTReader( geometryFactory );
MultiPolygon mpolygon = (MultiPolygon) reader.read("MULTIPOLYGON(((40 10, 30 0, 40 10, 30 20, 40 10),(30 10, 30 0, 40 10, 30 20, 30 10)))");
return mpolygon;
}

/**
* create GeometryCollection contain point or multiPoint or line or multiLine or polygon or multiPolygon
* @return
* @throws ParseException
*/
public GeometryCollection createGeoCollect() throws ParseException{
LineString line = createLine();
Polygon poly = createPolygonByWKT();
Geometry g1 = geometryFactory.createGeometry(line);
Geometry g2 = geometryFactory.createGeometry(poly);
Geometry[] garray = new Geometry[]{g1,g2};
GeometryCollection gc = geometryFactory.createGeometryCollection(garray);
return gc;
}

/**
* create a Circle 创建一个圆,圆心(x,y) 半径RADIUS
* @param x
* @param y
* @param RADIUS
* @return
*/
public Polygon createCircle(double x, double y, final double RADIUS){
final int SIDES = 32;//圆上面的点个数
Coordinate coords[] = new Coordinate[SIDES+1];
for( int i = 0; i < SIDES; i++){
double angle = ((double) i / (double) SIDES) * Math.PI * 2.0;
double dx = Math.cos( angle ) * RADIUS;
double dy = Math.sin( angle ) * RADIUS;
coords[i] = new Coordinate( (double) x + dx, (double) y + dy );
}
coords[SIDES] = coords[0];
LinearRing ring = geometryFactory.createLinearRing( coords );
Polygon polygon = geometryFactory.createPolygon( ring, null );
return polygon;
}

/**
* @param args
* @throws ParseException
*/
public static void main(String[] args) throws ParseException {
GeometryDemo gt = new GeometryDemo();
Polygon p = gt.createCircle(0, 1, 2);
//圆上所有的坐标(32个)
Coordinate coords[] = p.getCoordinates();
for(Coordinate coord:coords){
System.out.println(coord.x+","+coord.y);
}
}
}

火星坐标系

在谷歌还没有发布谷歌地图时,在GIS领域常见的坐标系主要有WGS84经纬度坐标、北京54坐标或西安80坐标等;但自从谷歌地图发布之后,其海量的高清卫星免费影像是让整个GIS领域为之震惊的,但同时也为安全问题带来了一定的隐患。为了对实际坐标进行加密,于是国测局研究了一套算法,凡是公开发布的商业互联网地图,一定要在此加密算法的基础上进行发布,这样一来地图的坐标就与实地的坐标不相符了,于是大家把这种坐标戏称为“火星坐标”,这里我们就针对这一坐标作一些更为详细的说明。

所有的电子地图、导航设备,都需要加入该保密插件。第一步,地图公司测绘地图,测绘完成后,送到国家测绘局,将真实坐标的电子地图,加密成“火星坐标”,这样的地图才是可以出版和发布的,然后才可以让GPS公司处理。第二步,所有的GPS公司,只要需要汽车导航的,需要用到导航电子地图的,都需要在软件中加入该保密算法,将COM口读出来的真实的坐标信号,加密转换成ZF要求的保密的坐标。这样,GPS导航仪和导航电子地图就可以完全匹配,GPS也就可以正常工作了。

GCJ02

GCJ-02是由中国国家测绘局(G表示Guojia国家,C表示Cehui测绘,J表示Ju局)制订的地理信息系统的坐标系统。

中文名:国家测量局02号标准

外文名:GCJ-02

它是一种对经纬度数据的加密算法,即加入随机的偏差。

国内出版的各种地图系统(包括电子形式),必须至少采用GCJ-02对地理位置进行首次加密。

综上所述,其实火星坐标系和GCJ-02是同一种事物,它是国家测量(绘)局制定的02号标准,是一种对经纬度坐标进行非线性的随机加偏算法。

为了响应国家制定的标准,国内所有在线地图服务商(如百度地图、高德地图、搜狗地图和SOSO地图等)和国外部分在线地图服务商(如谷歌地图、必应地图和雅虎地图等)都必须进行GCJ-02加密才对公众进行开放,这就是为什么大家在用地图时总是发现有偏移的原因。

GCJ-02只是一种坐标偏移标准(算法),对投影没有任何限制,如果再以投影为基础作细分

PrototBuf

undefined

IDEA莫名其妙每次自动生成变量都有final修饰符

undefined

FlinkKafka兼容性

Maven Dependency Supported since Consumer and Producer Class name Kafka version Notes
flink-connector-kafka-0.8_2.11 1.0.0 FlinkKafkaConsumer08 FlinkKafkaProducer08 0.8.x Uses the SimpleConsumer API of Kafka internally. Offsets are committed to ZK by Flink.
flink-connector-kafka-0.9_2.11 1.0.0 FlinkKafkaConsumer09 FlinkKafkaProducer09 0.9.x Uses the new Consumer API Kafka.
flink-connector-kafka-0.10_2.11 1.2.0 FlinkKafkaConsumer010 FlinkKafkaProducer010 0.10.x This connector supports Kafka messages with timestamps both for producing and consuming.
flink-connector-kafka-0.11_2.11 1.4.0 FlinkKafkaConsumer011 FlinkKafkaProducer011 0.11.x Since 0.11.x Kafka does not support scala 2.10. This connectorsupports Kafka transactional messaging to provide exactly once semantic for the producer.
flink-connector-kafka_2.11 1.7.0 FlinkKafkaConsumer FlinkKafkaProducer >= 1.0.0 This universal Kafka connector attempts to track the latest version of the Kafka client.The version of the client it uses may change between Flink releases.Modern Kafka clients are backwards compatible with broker versions 0.10.0 or later.However for Kafka 0.11.x and 0.10.x versions, we recommend using dedicatedflink-connector-kafka-0.11_2.11 and flink-connector-kafka-0.10_2.11 respectively.Attention: as of Flink 1.7 the universal Kafka connector is considered to be in a BETA status and might not be as stable as the 0.11 connector. In case of problems withthe universal connector, you can try to use flink-connector-kafka-0.11_2.11 which should be compatible with all of the Kafka versions starting from 0.11.

关于数据仓库数据中台数据湖的一部分图片

2B5AE031-28F3-4273-96BA-AAA9D3A1A25E.png
68C1B34D-B7F7-4026-ACDE-9C0C53A57F71.png
365E67AF-D9E0-49f7-A847-7657EF3FFCAA.png
788AA5A9-709B-471e-AB31-2AB8D79EEC14.png
5234F018-146E-4c51-97FA-6206F82EC8B6.png
1CAFEC39-30A9-4b51-9B16-1B9D11A3DD09.png

元数据管理还有个普元元数据

Mac版本的终端要走Proxy除了梯子还需要一个设置

1
➜ ~ vim ~/.zshrc
1
2
3
# proxy list
alias proxy='export all_proxy=socks5://127.0.0.1:1080'
alias unproxy='unset all_proxy'
1
➜ ~ source ~/.zshrc
1
2
3
切换:
~ proxy
~ unproxy

无状态 和 有状态 的区别

在整个程序生态圈中,无状态的意思总是 不需要自己手动关闭,有状态的意识是 总是需要自己手动关闭

Linux Command

most + more + less

more, less and most are three pagers, we can compare them this way:

less is more than more,
most is more than more, aproximatly,
less and most are different, none is better.

linux通过进程号查看运行文件目录

1
ll /proc/{进程号}/cwd

CentOS查看版本

1
2
3
4
5
6
7
8
9
10
11
hostnamectl 

Static hostname: datanode127
Icon name: computer-server
Chassis: server
Machine ID: abe6faef122d47c1a66436e7936b4301
Boot ID: 8010542f5e21440797d9e3fc669fb752
Operating System: CentOS Linux 7 (Core)
CPE OS Name: cpe:/o:centos:centos:7
Kernel: Linux 3.10.0-693.el7.x86_64
Architecture: x86-64

文件目录大小常见的命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 flyhugh@MacBook-Pro-2  ~/Documents/Env  pwd
/Users/flyhugh/Documents/Env
flyhugh@MacBook-Pro-2  ~/Documents/Env  ls
apache-maven-3.6.3 brew_install.rb
flyhugh@MacBook-Pro-2  ~/Documents/Env  du -sh
7.2G .
flyhugh@MacBook-Pro-2  ~/Documents/Env  df -h
Filesystem Size Used Avail Capacity iused ifree %iused Mounted on
/dev/disk1s5 466Gi 10Gi 180Gi 6% 488290 4882988630 0% /
devfs 187Ki 187Ki 0Bi 100% 646 0 100% /dev
/dev/disk1s1 466Gi 271Gi 180Gi 61% 2520713 4880956207 0% /System/Volumes/Data
/dev/disk1s4 466Gi 3.0Gi 180Gi 2% 4 4883476916 0% /private/var/vm
map auto_home 0Bi 0Bi 0Bi 100% 0 0 100% /System/Volumes/Data/home
/dev/disk1s3 466Gi 504Mi 180Gi 1% 54 4883476866 0% /Volumes/Recovery

Linux生僻命令

在两个文件夹之间来回切换

1
cd -

递归创建目录

1
mkdir -p

Win10网络问题汇总

用无线网有个问题就是电脑开机时间久了 加上我使用科学上网频繁开关,导致网络会有错乱问题,在系统开了很多东西的情况下,直接重启,导致了网络错误,打开IDEA的时候出现了错误提示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Internal error. Please refer to http://jb.gg/ide/critical-startup-errors

java.net.BindException: Address already in use: bind
at java.base/sun.nio.ch.Net.bind0(Native Method)
at java.base/sun.nio.ch.Net.bind(Net.java:461)
at java.base/sun.nio.ch.Net.bind(Net.java:453)
at java.base/sun.nio.ch.ServerSocketChannelImpl.bind(ServerSocketChannelImpl.java:227)
at io.netty.channel.socket.nio.NioServerSocketChannel.doBind(NioServerSocketChannel.java:132)
at io.netty.channel.AbstractChannel$AbstractUnsafe.bind(AbstractChannel.java:551)
at io.netty.channel.DefaultChannelPipeline$HeadContext.bind(DefaultChannelPipeline.java:1345)
at io.netty.channel.AbstractChannelHandlerContext.invokeBind(AbstractChannelHandlerContext.java:503)
at io.netty.channel.AbstractChannelHandlerContext.bind(AbstractChannelHandlerContext.java:488)
at io.netty.channel.DefaultChannelPipeline.bind(DefaultChannelPipeline.java:984)
at io.netty.channel.AbstractChannel.bind(AbstractChannel.java:247)
at io.netty.bootstrap.AbstractBootstrap$2.run(AbstractBootstrap.java:355)
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:163)
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:416)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:515)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:918)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at java.base/java.lang.Thread.run(Thread.java:834)

这个错误造成的原因就是hypervisior(Windows 10的Hyper-V虚拟机),把端口保留了

解决方法也很简单,hypervisior重新开关一下

1
2
3
4
5
6
7
8
Disable hyper-v (which will required a couple of restarts)
dism.exe /Online /Disable-Feature:Microsoft-Hyper-V

When you finish all the required restarts, reserve the port you want so hyper-v doesn't reserve it back
netsh int ipv4 add excludedportrange protocol=tcp startport=<端口号> numberofports=1

Re-Enable hyper-V (which will require a couple of restart)
dism.exe /Online /Enable-Feature:Microsoft-Hyper-V /All

实际使用中第一个命令执行完直接重启就行了。

win10图形化查看端口占用工具

cports

重置win10网络命令

netsh winsock reset

win10远程窗口大小不可调的问题

我的显示器分辨率比较高,两台显示器都是4K分辨率的,但是远程的时候默认是锁定1600 * 1200的

并且默认是窗口化的,无法调整大小,这个时候需要使用命令行配置一下远程窗口的分辨率

1
mstsc /w:2560 /h:1440 /f

这个命令分别是长宽和全屏设置。

给win10商店设置代理

最近发现。。。只要有一个好的代理服务器,win10的商店原来也是能随便打开的,这里介绍一下win10的商店的流量怎么走代理

1
首先通过 Win + R 快捷键打开「运行」窗口,输入「Regedit」打开注册表编辑器,然后定位到 HKEY_CURRENT_USER\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\AppContainer\Mappings,接着在左边的注册表项中找到你想解除网络隔离的应用,右边的 DisplayName 就是应用名称,而左边那一大串字符就是应用的 SID 值了。

找到这个值之后,然后在cmd命令行中输入:

CheckNetIsolation.exe loopbackexempt -a -p=SID

这SID就是上面搜索到的,这样就行

关于SSTap转发UDP的问题

查看SSTap是否成功转发UDP需要用到一个工具,netcat,简称nc,使用这个工具,在命令行中

1
.\nc64.exe -u ip 10000

就可以像服务器发送UDP请求,然后在服务器上使用root用户

1
tcpdump udp port 10000

就可以查看转发日志

Tips:我在ubuntu上设置的ss-server 默认是不打开udp的端口的,需要在开启的时候手动打开。(加上 -u)

1
nohup ss-server -c /etc/shadowsocks.json -u

验证:

1
netstat -lnp | grep 10000
1
2
3
root@ubuntu:/home/ubuntu# netstat -lnp | grep 10000
tcp 0 0 0.0.0.0:10000 0.0.0.0:* LISTEN 25056/ss-server
udp 0 0 0.0.0.0:10000 0.0.0.0:* 25056/ss-server

如图可以看到端口已经开放了udp和tcp即可,udp是无状态的,不会显示LISTEN,这个是正确的。

最终结论:是服务器上的SSR没有开启UDP转发,默认是不转发UDP的,这个是有问题的。

win10 访问墙外世界的最终办法:

使用 SSTap这类软件,SSTap是比较简单的,复杂的软件会有流量分流等等功能。

SSTap新建了一个虚拟网卡,所有的流量从新的虚拟网卡中转发出去,不用再考虑软件的流量进出口有没有被代理等等问题。

Win下Chrome无法访问新浪外链地址

拓展解决:

https://chrome.google.com/webstore/detail/wbimgfix/bdhgcfmghkbbdmejdaadfdhhdjphogkp/related

云服务器通过转发windows的3389端口,实现远程对PC的访问

https://www.jianshu.com/p/939dd2f78399

使用了文中的方法,需要注意的方法不是很多,一个是和 ssh 配置有关的问题,最后采用的配置文件,我在最后贴出来(这种配置文件下可以使用root帐号登录)。配置完成后使用

1
systemctl restart sshd

重启服务。

登录用到的keygen需要在cygwin64的文件系统中单独保存,使用的并不是windows下面的路径。

我最终使用的命令是

1
ssh -o ServerAliveInterval=180 -i /home/key/id_rsa_oracle_2 root@#服务器IP# -p 22 -R #以后远程登录要使用的端口,不建议使用3389,为了安全#:127.0.0.1:3389 -fN

ssh配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#       $OpenBSD: sshd_config,v 1.100 2016/08/15 12:32:04 naddy Exp $

# This is the sshd server system-wide configuration file. See
# sshd_config(5) for more information.

# This sshd was compiled with PATH=/usr/local/bin:/usr/bin

# The strategy used for options in the default sshd_config shipped with
# OpenSSH is to specify options with their default value where
# possible, but leave them commented. Uncommented options override the
# default value.

# If you want to change the port on a SELinux system, you have to tell
# SELinux about this change.
# semanage port -a -t ssh_port_t -p tcp #PORTNUMBER
#
#Port 22
#AddressFamily any
#ListenAddress 0.0.0.0
#ListenAddress ::

HostKey /etc/ssh/ssh_host_rsa_key
#HostKey /etc/ssh/ssh_host_dsa_key
HostKey /etc/ssh/ssh_host_ecdsa_key
HostKey /etc/ssh/ssh_host_ed25519_key

# Ciphers and keying
#RekeyLimit default none

# Logging
#SyslogFacility AUTH
SyslogFacility AUTHPRIV
#LogLevel INFO

# Authentication:

#LoginGraceTime 2m
PermitRootLogin yes
#StrictModes yes
#MaxAuthTries 6
#MaxSessions 10

#PubkeyAuthentication yes

# The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2
# but this is overridden so installations will only check .ssh/authorized_keys
AuthorizedKeysFile .ssh/authorized_keys

#AuthorizedPrincipalsFile none

#AuthorizedKeysCommand none
#AuthorizedKeysCommandUser nobody

# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts
#HostbasedAuthentication no
# Change to yes if you don't trust ~/.ssh/known_hosts for
# HostbasedAuthentication
#IgnoreUserKnownHosts no
# Don't read the user's ~/.rhosts and ~/.shosts files
#IgnoreRhosts yes

# To disable tunneled clear text passwords, change to no here!
#PasswordAuthentication yes
#PermitEmptyPasswords no
PasswordAuthentication yes

# Change to no to disable s/key passwords
#ChallengeResponseAuthentication yes
ChallengeResponseAuthentication no

# Kerberos options
#KerberosAuthentication no
#KerberosOrLocalPasswd yes
#KerberosTicketCleanup yes
#KerberosGetAFSToken no
#KerberosUseKuserok yes

# GSSAPI options
GSSAPIAuthentication yes
GSSAPICleanupCredentials no
#GSSAPIStrictAcceptorCheck yes
#GSSAPIKeyExchange no
#GSSAPIEnablek5users no

# Set this to 'yes' to enable PAM authentication, account processing,
# and session processing. If this is enabled, PAM authentication will
# be allowed through the ChallengeResponseAuthentication and
# PasswordAuthentication. Depending on your PAM configuration,
# PAM authentication via ChallengeResponseAuthentication may bypass
# the setting of "PermitRootLogin without-password".
# If you just want the PAM account and session checks to run without
# PAM authentication, then enable this but set PasswordAuthentication
# and ChallengeResponseAuthentication to 'no'.
# WARNING: 'UsePAM no' is not supported in Red Hat Enterprise Linux and may cause several
# problems.
UsePAM yes

#AllowAgentForwarding yes
#AllowTcpForwarding yes
GatewayPorts yes
X11Forwarding yes
#X11DisplayOffset 10
#X11UseLocalhost yes
#PermitTTY yes
#PrintMotd yes
#PrintLastLog yes
#TCPKeepAlive yes
#UseLogin no
#UsePrivilegeSeparation sandbox
#PermitUserEnvironment no
#Compression delayed
#ClientAliveInterval 0
#ClientAliveCountMax 3
#ShowPatchLevel no
#UseDNS yes
#PidFile /var/run/sshd.pid
#MaxStartups 10:30:100
#PermitTunnel no
#ChrootDirectory none
#VersionAddendum none

# no default banner path
#Banner none

# Accept locale-related environment variables
AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES
AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT
AcceptEnv LC_IDENTIFICATION LC_ALL LANGUAGE
AcceptEnv XMODIFIERS

# override default of no subsystems
Subsystem sftp /usr/libexec/openssh/sftp-server

# Example of overriding settings on a per-user basis
#Match User anoncvs
# X11Forwarding no
# AllowTcpForwarding no
# PermitTTY no
# ForceCommand cvs server

这种访问方式有点过于笨重,后来采用了frp工具来实现了远程代理

https://zhuanlan.zhihu.com/p/138092534

BT下载 扔掉迅雷

  1. Aria2 下载解压

  2. 配置文件 下载解压

  3. 按照顺序使用管理员打开

  4. 默认的下载目录为downloads,可通过修改aria2.conf配置文件改变下载目录。

  5. Aria2的没有自带下载面板的,因此需要自行下载第三方的面板。

常用的Aria2_WebYAAWwebui-aria2AriaNg

1
2
3
YAAW:https://github.com/binux/yaaw/archive/master.zip
AriaNg:https://github.com/mayswind/AriaNg/releases
webui-aria2:https://github.com/ziahamza/webui-aria2/archive/master.zip

我这里尝试使用AriaNg,下载下来直接点击那个网页就能使用,修改一下tracker

https://trackerslist.com/#/zh?id=xiu2trackerslistcollection

Tracker列表

网络知识

TCP/IP

建立TCP需要三次握手才能建立(客户端发起SYN,服务端SYN+ACK,客户端ACK),

断开连接则需要四次握手(客户端和服务端都可以发起,FIN-ACK-FIN-ACK)。

为什么连接的时候是三次握手,关闭的时候却是四次握手?

答:
因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,”你发的FIN报文我收到了”。只有等到Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。

SYN攻击:发送大量的SYN,导致服务端无法识别哪些是有效的

RPC

RPC是指远程调用,两服务器A,B,A要调用B上的一个方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据

  1. 通讯问题:在客户端和服务端建立TCP连接,远程调用的所有交换数据都在这个连接里传输。

  2. 解决寻址问题:IP及端口寻址,方法名

  3. 序列化(Serialize):发生远程调用时,方法的参数需要通过底层的网络协议如TCP传送到服务器,由于网络协议是基于二进制的,内存中的参数值需要序列化成二进制的形式,通过寻址和传输序列化的二进制发送给服务器。

  4. 服务器反序列化:服务器收到请求后需要反序列化,恢复内存中的表达方式,然后找到对应的方法(寻址的一部分),进行本地调用。

  5. 返回值发送给客户端,这个部分也需要序列化和反序列化。

SOA

采用一组服务的方式来构建一个应用,服务(hedwig、jsf、RESTful)独立部署在不同的进程中,不同服务通过一些轻量级交互机制来通信,例如RPC、HTTP等。服务可独立扩展伸缩,每个服务定义了明确的边界,不同的服务甚至可以采用不同的编程语言来实现,由独立的团队来维护。

RPC 的实现是基于SOA这样的一个架构 C/S模式 远程调用的通讯使用TCP 然后hedwig restful jsf这些就是不同的服务形式

**http协议和tcp/ip 协议的关系
**(1) http是应用层协议,tcp协议是传输层协议,ip协议是网络协议。
(2) IP协议主要解决网络路由和寻址问题
(3) tcp协议主要解决在IP层协议之上,如何可靠的传输数据,即接收端收到的数据包的大小和顺序,和发送端保持一致。tcp协议是可靠的面相连接的。

1. http协议是无状态的,指的是http协议对于事务处理没有记忆功能,客户端向服务端请求完数据之后,服务端不知道客户端是什么状态。

2. http的长连接和短连接,本质上是tcp层的长连接和短连接:

http 1.0 默认使用短连接,http 1.1 默认使用长连接,在使用的http协议,在响应头会加上 Connection:keep-alive

3. RPC比http请求快的原因:http使用http协议,rpc使用tcp协议,比http少了应用层,表示层,会话层,这3层,rpc使用长连接,而长连接比短连接更节省资源,效率更高(每个连接的建立和释放都是需要资源和时间的)。

TCP/IP

是个协议组,可分为三个层次:网络层、传输层和应用层。

在网络层有IP协议、ICMP协议、ARP协议、RARP协议和BOOTP协议。

传输层中有TCP协议与UDP协议。

在应用层有:TCP包括FTP、HTTP、TELNET、SMTP等协议

UDP包括DNS、TFTP等协议

当网络通信时采用TCP协议时,在真正的读写操作之前,server与client之间必须建立一个连接,当读写操作完成后,双方不再需要这个连接时它们可以释放这个连接,连接的建立是需要三次握手的,而释放则需要4次握手,所以说每个连接的建立都是需要资源消耗和时间消耗的。

通信过程:

主机 A 的应用程序要能和主机 B 的应用程序通信,必须通过 Socket 建立连接,而建立 Socket 连接必须需要底层 TCP/IP 协议来建立 TCP 连接。建立 TCP 连接需要底层 IP 协议来寻址网络中的主机。我们知道网络层使用的 IP 协议可以帮助我们根据 IP 地址来找到目标主机,但是一台主机上可能运行着多个应用程序,如何才能与指定的应用程序通信就要通过 TCP 或 UPD 的地址也就是端口号来指定。这样就可以通过一个 Socket 实例唯一代表一个主机上的一个应用程序的通信链路了。

建立通信链路

客户端要与服务端通信,客户端首先要创建一个 Socket 实例,操作系统将为这个 Socket 实例分配一个没有被使用的本地端口号,并创建一个包含本地和远程地址和端口号的套接字数据结构,这个数据结构将一直保存在系统中直到这个连接关闭。在创建 Socket 实例的构造函数正确返回之前,将要进行 TCP 的三次握手协议,TCP 握手协议完成后,Socket 实例对象将创建完成,否则将抛出 IOException 错误。

与之对应的服务端将创建一个 ServerSocket 实例,ServerSocket 创建比较简单只要指定的端口号没有被占用,一般实例创建都会成功,同时操作系统也会为 ServerSocket 实例创建一个底层数据结构,这个数据结构中包含指定监听的端口号和包含监听地址的通配符,通常情况下都是“*”即监听所有地址。之后当调用 accept() 方法时,将进入阻塞状态,等待客户端的请求。当一个新的请求到来时,将为这个连接创建一个新的套接字数据结构,该套接字数据的信息包含的地址和端口信息正是请求源地址和端口。这个新创建的数据结构将会关联到 ServerSocket 实例的一个未完成的连接数据结构列表中,注意这时服务端与之对应的 Socket 实例并没有完成创建,而要等到与客户端的三次握手完成后,这个服务端的Socket 实例才会返回,并将这个 Socket 实例对应的数据结构从未完成列表中移到已完成列表中。所以 ServerSocket 所关联的列表中每个数据结构,都代表与一个客户端的建立的TCP 连接。

TCP短连接

TCP短连接,client向server发起连接请求,server接到请求,然后双方建立连接。client向server 发送消息,server回应client,然后一次读写就完成了,这时候双方任何一个都可以发起close操作,不过一般都是client先发起 close操作。因为一般的server不会回复完client后立即关闭连接的,当然不排除有特殊的情况。从上面的描述看,短连接一般只会在client/server间传递一次读写操作。短连接的优点是:管理起来比较简单,存在的连接都是有用的连接,不需要额外的控制手段。

TCP长连接

长连接,client向server发起连接,server接受client连接,双方建立连接。Client与server完成一次读写之后,它们之间的连接并不会主动关闭,后续的读写操作会继续使用这个连接。

TCP保活功能,保活功能主要为服务器应用提供,服务器应用希望知道客户主机是否崩溃,从而可以代表客户使用资 源。如果客户已经消失,使得服务器上保留一个半开放的连接,而服务器又在等待来自客户端的数据,则服务器将应远等待客户端的数据,保活功能就是试图在服务器端检测到这种半开放的连接。如果一个给定的连接在两小时内没有任何的动作,则服务器就向客户发一个探测报文段。客户主机必须处于以下4个状态之一:

1.客户主机依然正常运行,并从服务器可达。客户的TCP响应正常,而服务器也知道对方是正常的,服务器在两小时后将保活定时器复位。

2.客户主机已经崩溃,并且关闭或者正在重新启动。在任何一种情况下,客户的TCP都没有响应。服务端将不能收到对探测的响应,并在75秒后超时。服务器总共发送10个这样的探测 ,每个间隔75秒。如果服务器没有收到一个响应,它就认为客户主机已经关闭并终止连接。

3.客户主机崩溃并已经重新启动。服务器将收到一个对其保活探测的响应,这个响应是一个复位,使得服务器终止这个连接。

4.客户机正常运行,但是服务器不可达,这种情况与2类似,TCP能发现的就是没有收到探查的响应。

从上面可以看出,TCP保活功能主要为探测长连接的存活状况,不过这里存在一个问题,存活功能的探测周期太长,还有就是它只是探测TCP连接的存活,属于比较斯文的做法,遇到恶意的连接时,保活功能就不够使了。

在长连接的应用场景下,client端一般不会主动关闭它们之间的连接,Client与server之间的连接如果一直不关闭的话,会存在一个问 题,随着客户端连接越来越多,server早晚有扛不住的时候,这时候server端需要采取一些策略,如关闭一些长时间没有读写事件发生的连接,这样可以避免一些恶意连接导致server端服务受损;如果条件再允许就可以以客户端机器为颗粒度,限制每个客户端的最大长连接数,这样可以完全避免某个蛋疼的客户端连累后端服务。

长连接和短连接的产生在于client和server采取的关闭策略,具体的应用场景采用具体的策略,没有十全十美的选择,只有合适的选择。

HTTP长连接与短连接

长连接:client方与server方先建立连接,连接建立后不断开,然后再进行报文发送和接收。

这种方式下由于通讯连接一直存在。此种方式常用于P2P通信。

短连接:Client方与server每进行一次报文收发交易时才进行通讯连接,交易完毕后立即断开连接。

此方式常用于一点对多点通讯。C/S通信。

长连接和短连接异同

长连接:长连接多用于操作频繁,点对点的通讯,而且连接数不能太多的情况。

每个TCP连接的建立都需要三次握手,每个TCP连接的断开要四次握手。

如果每次操作都要建立连接然后再操作的话处理速度会降低,所以每次操作后,下次操作时直接发送数据就可以了,不用再建立TCP连接。例如:数据库的连接用长连接,如果用短连接频繁的通信会造成socket错误,频繁的socket创建也是对资源的浪费。

\短连接\:**web网站的http服务一般都用短连接。因为长连接对于服务器来说要耗费一定的资源。像web网站这么频繁的成千上万甚至上亿客户端的连接用短连接更省一些资源。试想如果都用长连接,而且同时用成千上万的用户,每个用户都占有一个连接的话,可想而知服务器的压力有多大。所以并发量大,但是每个用户又不需频繁操作的情况下需要短连接。

发送接收方式

1、异步:报文发送和接收是分开的,相互独立,互不影响的。这种方式又分两种情况:

异步双工:接收和发送在同一个程序中,有两个不同的子进程分别负责发送和接送。

异步单工:接送和发送使用两个不同的程序来完成。

2、同步:报文发送和接收是同步进行,即报文发送后等待接送返回报文。同步方式一般需要考虑超时问题,试想我们发送报文以后也不能无限等待啊,所以我们要设定一个等待时候。超过等待时间发送方不再等待读返回报文。直接通知超时返回。

阻塞与非阻塞方式

1、非阻塞方式:读函数不停的进行读动作,如果没有报文接收到,等待一段时间后超时返回,这种情况一般需要指定超时时间。

2、阻塞方式:如果没有接收到报文,则读函数一直处于等待状态,知道报文到达。

及时通信与游戏的长短连接

实际场合究竟需要使用短连接还是长连接,主要看实时性要求、数据流向和并发量这三个问题。

长连接优点:节约TCP握手时间,可以保证高实时性,数据流向可以采用服务器端的主动推模式。
长连接缺点:并发量不宜太高,持续占用服务端口(相对消耗资源)。

长连接、长轮询一般应用与WebIM、ChatRoom和一些需要及时交互的网站应用中。其真实案例有:WebQQ、Hi网页版、Facebook IM等。

1.现在游戏中的玩家与玩家之间的聊天无法实现实时性,而且系统有邮件或信息时也不能及时的通知玩家
—— 如果涉及到聊天的话,一般来说还是用长连接会更合适,否则大量时间浪费到握手上了;
—— 但是手机的网络长连接网络质量可能会比较撮,你需要严重考虑容错和重链机制。

2.客户端每隔几秒就会发送一个请求,这样服务器的压力岂不是很大?
—— 压力会比较大,关键是聊天往往对时间的要求很高,如果是团战的话,1秒内没看到信息,可能就会觉得完全受不了了;当然也看你聊天的场景如何,是群聊还是单聊,以后会不会发展为语音啥的;

NIO没有任何问题,大规模长连接处理的主流都是用NIO;而且也不是Java发明的,本身就是借助了操作系统的网络管理能力。

http keep-alive与tcp keep-alive,不是同一回事,意图不一样。http keep-alive是为了让tcp活得更久一点,以便在同一个连接上传送多个http,提高socket的效率。而tcp keep-alive是TCP的一种检测TCP连接状况的保鲜机制

Maven问题汇总

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<phase>generate-resources</phase>
<configuration>
<!-- Execute the shell script to generate the spark build information. -->
<target>
<!-- windows 目录下修改成CMD Linux下面用的是Bash-->
<exec executable="cmd">
<arg value="${project.basedir}/../build/spark-build-info"/>
<arg value="${project.build.directory}/extra-resources"/>
<arg value="${project.version}"/>
</exec>
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>

上面的这个插件 antrun里面的命令在linux环境下使用bash没有问题,在linux环境下应该配置bash

Maven手动添加Jar包之后没有加载

File->Setting->Maven->Repositories->Update(选择本地仓库的路径)

Active 使用遇到的坑

线上activemq内存超过限制,导致生产者无法推送告警信息。Activemq重启,导致部分堆积数据丢失

考虑一下ActiveMQ的监控

Redis持久化

RDB模式全栈快照,当内存比较大,访问比较频繁时(我们的设备实时信息是典形的写多读少,异于一般的写少读多的情况),刷新频率高,磁盘IO要求非常大。

AOF模式,文件体大,Rewrite时会有RDB类似的情况

AOF重写或是RDB进行Dump时都会fork子进程,当Redis使用物理内存过半时,会失败

LinkedList等队列size()

JDK自带队列size()需要遍历集合,如果集合数据比较大,且该函数比较频繁访问情况下,可能会有性能问题。并发接近万数量级慎用

(可以用Stopwatch监控程序每个步骤运行时间)

开源协议

undefined

Flink问题汇总

Flink使用滑动窗口出现的一个问题

使用滑动窗口保存checkpoint失败,日志里面没有错误日志,个别情况下在使用不同的时间语义会出现成功的情况。

Flink数据写入了HDFS,Hive没有识别

1.11版本,需要设置sink.partition.commit参数,分区表

flink-runtime-web工程有几个特点,

=> 在网络OK,别的问题都没有的情况下,第一次编译成功,后面编译的话会报错(npm或者node.js被占用的错误),也就是说,只有第一次能编译成功。

时空数据结构简介

后面如果有时间的话 会把这部分独立出去重新整理一章

所谓时空数据,顾名思义,包含了两个维度的信息:空间信息与时间信息。空间信息,以地理位置点最为基础,还包括线、多边形以及更为复杂的多维结构。最典型的时空数据,莫过于移动对象的轨迹点数据,如每隔5秒钟记录的车辆实时位置信息。这类数据,在物联网领域司空见惯,在可预见的未来,这类数据将会出现爆炸性的增长。

关于时空索引,这里,了解到的主流技术有R-Trees、Quad-Trees、K-D Trees以及Space Filling Curve这四种技术。

RTree

R-Trees源自论文《R-Trees: A Dynamic Index Structure For Spatial Searching》,下面的图也源自此论文:

undefined

R-Trees基于这样的思想设计:

每一个空间对象,如一个多边形区域,都存在一个最小的矩形,能够恰好包含此时空对象,如上图中的矩形R6所包含的弯月形区域。这个最小包围矩形被称之为MBR(Minimum Bounding Rectangle)。

多个相邻的矩形,可以被包含在一个更大的最小包围矩形中。如R6、R9以及R10三个矩形,可以被包含在R3中,而R11与R12则被包含在R4中。

继续迭代,总能找到若干个最大的区域,以一种树状的形式,将所有的时空对象给容纳进去,如上图中的R1, R2。这样,整个树状结构,呈现如下:

undefined

从最小的矩形区域,到最大的矩形区域,就好比地图中的行政区域划分:村 -> 县 -> 市 -> 省份。查询时,先从锁定的最大区域开始,逐级缩小比例尺后,就可找到最终的对象。如若将上图中的R1与R2理解成两个平级的”行政区域”,却又存在本质的区别:不同的行政区域,并不存在相互重叠,而R1与R2却可能是重叠的。

R-Trees的定义:

  1. 每一个Leaf Node包含m到M个索引记录(Root节点除外)。

  2. 每一个索引记录是一个关于(I, tuple-identifier)的二元组信息,I是空间对象的最小包围矩形,而tuple-identifier用来描述空间对象本身。

  3. 每一个Non-leaf节点,包含m到M个子节点(Root节点除外)。

  4. Non-leaf节点上的每一个数据元素,是一个(I, child-pointer)的二元组信息,child-pointer指向其中一个子节点,而I则是这个子节点上所有矩形的最小包围矩形。

上图中,R3、R4以及R5,共同构成一个non-leaf节如点,R3指向包含元素R8,R9以及R10的子节点,这个指针就是child-pointer,而与R8,R9以及R10相关的最小包围矩形,就是I。

  1. Root节点至少包含两个子节点,除非它本身也是一个Leaf Node。
  2. 所有的Leaf Nodes都出现在树的同一层上。

从定义上来看,R-Trees与B-Trees存在诸多类似:Root节点与Non-Leaf节点,均包含限定数量的子节点;所有的Leaf Nodes都出现在树的同一层上。这两种树也都是自平衡的。

前面也已经提到了,B-Trees主要用来存放一维排序的数据元素,而R-Tree存放的则是多维空间数据元素。从查询方式上来看,两者也存在显著的差异:B-Trees更擅长于数据点查,它的设计并不利于数据的范围查询。基于空间元素的查询,却以范围查询为主,而且往往需要对多个子树进行并行查询,例如,在地图上划定某一个区域,查询这个区域内有哪些公园,可能有多个子树都与划定的这个区域存在交集。从这一点看来,R-Tree的搜索性能其实并没有很好的保障。

R-Trees有多种变种,如R+-Trees,R*-Trees,X-Trees, M-Trees,BR-Trees等等,不再展开过多的介绍。

Quad-Trees

HBase中的数据按照RowKey单维度排序组织,而我们现在却面临的是一个多维数据问题。因此,HBase如果想很好的支持时空数据的存储,需要引入时空索引技术。

undefined

上图中的A,B,C,E,F,G均为Point,以每一个Point作为中心点,可以将一个区间分成4个象限。

假设,先写入Point A,以A为中心,将整个区间分成了4个象限。

写入Point B,Point B位于A的东北象限中,同样,以B为中心,依然可以将A东北象限进一步细分为4个子象限。

写入Point C,Point C位于A的东南象限中,以C为中心,可以将A的东南象限细分成4个子象限。

…..

任何新写入的一个Point,总能找到一个某一个已存在的Point的子象限,来存放这个新的Point。

整个树状结构呈现如下:

undefined

可见,Quad-Trees有几个鲜明的特点:1. 对于非叶子节点,均包含4个子节点,对应4个面积不一的象限。2.不平衡,树的结构与数据的写入顺序直接相关。3.有空的Leaf Nodes,且所有的Leaf Nodes则是”参差不齐”的(并不一定都出现在树的同一层中)。4.数据既可能出现在分枝节点中,也可能出现在叶子节点中。

因为Quad-Trees存在诸多变种,为了有所区分,上面提到的最简单的这种Quad-Tree,被称之为Point Quad-Trees。还有一种典型的Quad-Trees,被称之为Point Region QuadTrees(简称为PR QuadTrees):

undefined

PR Quad-Trees中,每一次迭代出来的4个象限,面积相同,且不依赖于用户数据Point作为分割点,或者说,数据分区与用户数据无关。每一个划分出来的子象限中,只允许存在一个Point。

与Point Quad-Trees相比,PR Quad-Trees允许两份不同的数据集,拥有相同的分区信息。但PR Quad-Trees存在的问题也明显:1. 两个相邻的Points,可能在树的Level高度上相隔甚远。2.两份数据集如果追求相同的分区信息,可能需要进行足够粒度的分割,这可能导致空间浪费。

K-D Trees

K-D Trees是一种针对高维点向量数据的索引结构,一棵简单的K-D Tree如下图所示(原图源自James Fogarty的”K-D Trees and Quad Trees”,但为了更直观,关于分区分割线的线条做了改动):

undefined

与Quad Trees思想类似,K-D Trees也是将整个区间进行不断分割,不同之处在于,Quad Trees每一次迭代,将一个区间分割成四个象限,而K-D Trees则是分成左右或上下两个区间。如上图所示:S1把整个空间分成了左右两个区间,左侧区间中,又被S2横向分割成了上下两个区间,而S3又在S2的分割基础上,将下部分分割成了左右两个区间,….

Space Filling Curve

如果将一个完整的二维空间,分割成一个个大小相同的矩形,可以将Space Filling Curve简单理解为它是一种将所有的矩形区域用一条线”串”起来的方法,因”串”的方式不同,也就引申出了不同的Space Filling Curve算法。

比较典型的如Z-Order Curve:

undefined

再如Hilbert Curve:

undefined

GeoMesa使用了基于Z-order填充曲线的GeoHash空间索引技术, 并针对时间维度进行了扩展,具体分为: • Z2:空间,点索引 • Z3:时间+空间,点索引 • XZ2:空间,线\面索引 • XZ3:时间+空间,线\面索引。

为什么选择了RTree =>

  • 原先的项目中选择RTree
  • 别的项目没有RTree这么成熟的工业库

image-20201120152806907

  • RTree的优化方向比较好找

  • RTree可以理解为BTree的多维版本,相对来说比较熟悉,此外R树还可以退化成一维,但是分割的线段存在重叠问题,效果不如Btree

R树的操作
搜索

R树的搜索操作很简单,跟B树上的搜索十分相似。它返回的结果是所有符合查找信息的记录条目。而输入是什么?输入不仅仅是一个范围了,它更可以看成是一个空间中的矩形。也就是说,我们输入的是一个搜索矩形。

插入

R树的插入操作也同B树的插入操作类似。当新的数据记录需要被添加入叶子结点时,若叶子结点溢出,那么我们需要对叶子结点进行分裂操作。显然,叶子结点的插入操作会比搜索操作要复杂。插入操作需要一些辅助方法才能够完成。
来看一下伪代码:

删除

R树的删除操作与B树的删除操作会有所不同,不过同B树一样,会涉及到压缩等操作。相信读者看完以下的伪代码之后会有所体会。R树的删除同样是比较复杂的,需要用到一些辅助函数来完成整个操作。

优化
  • 为了最小化内存使用可以针对数据类型优化–>
1
2
3
Rectangle r = Geometries.rectangle(1.0, 2.0, 3.0, 4.0);

Rectangle r = Geometries.rectangle(1.0f, 2.0f, 3.0f, 4.0f);

理论上这样使用RTree消耗的内存只有Double的一半。

  • 使用R-Tree的变种 R*-Tree,对数据结构进行了优化,并且同样存在成熟的常用项目 RTee2 可以使用。

Base4BigData(Version.1)

MapReduce

MapReduce产生的灵感来源于2004年Google发表的《MapReduce》论文中的大数据计算模型,用于大规模数据集(TB甚至PB级)的并行计算,利用分治策略,将计算过程分两个阶段,Map阶段和Reduce阶段,可谓是第一代大数据分布式计算引擎,为后来各类优秀的大数据计算引擎的出现提供了基础和可行性。

架构

MapReduce 1.x架构如下:

图片

  1. 客户端向JobTracker提交任务
  2. JobTraker将任务拆解为多个子任务,分配给TaskTracker执行
  3. TaskTracker定时与JobTracker保持心跳,汇报任务执行情况

存在的问题:

  • 单点故障:一旦JobTracker出现故障,会导致任务无法提交和正常执行
  • JobTracker负载高:所有的任务的提交和分配以及资源管理都是由JobTracker控制,压力过于集中
  • 场景有限制:只能运行MapReduce作业,可兼容性差

为了解决MR v1的问题,MapReduce v2引入了资源管理器YARN (Yet Another Resource Negotiator),即新一代的MapReduce 2.0。

YARN中的重要角色及功能:

  1. ResourceManager:资源管理节点(RM),对应MR v1的JobTracker

    1. 负责处理来自客户端的提交的Job
    2. 启动Application Master管理任务
    3. 监控Application Master状态
    4. 为NodeManager分配资源(CPU、内存、磁盘、网络)
  2. NodeManager:工作节点(NM),对应MR v1的TaskTracker

    1. 管理节点container任务资源和运行情况
    2. 与ResourceManager保持通信,汇报自身的状态
  3. Application Master:任务管理服务(AM),其实就是ResourceManager的小弟

    1. 负责检查集群资源,申请mr程序所需资源
    2. 分配任务到相应的container容器执行
    3. 监控任务执行状态并向ResourceManager汇报任务执行情况
  4. Container:YARN资源抽象,封装了节点上的资源,如内存、CPU、磁盘等

YARN的优势:

  1. ResourceManager支持HA,解决了JobTracker单点故障的问题,提高集群可用性
  2. 实现资源管理和job管理分离,解决了JobTracker负载高的难题
  3. 提供Application Master负责监控所有的任务,解决了JobTracker集中管理监控的压力
  4. 高扩展性,不仅可以跑mr任务,还支持spark作业以及其他计算引擎任务
  5. 提高了资源的利用率
MapReduce核心计算阶段

Mapper阶段:负责数据 的载入、解析、转换和过滤,map任务的输出被称为中间键和中间值

Reducer阶段:负责处理map任务输出结果,对map任务处理完的结果集进行排序、局部聚合计算再汇总结果

MapReduce为什么慢?

  • MapReduce是基于磁盘数据进行计算的
  • Shuffle过程进行一系列分区、排序,耗费大量时间
  • map计算结果频繁落盘
  • reduce任务通过磁盘获取数据,占用IO
  • spill会会产生大量的小文件,极大占用集群的资源
  • 容错性差,错了就重头来过
  • MapReduce抽象层次低,只有map和reduce两种,处理数据效率较低

因此MapReduce只适用于处理大规模离线数据,延时高。MapReduce的局限性推动了计算引擎技术的革新,Spark的出现是为了解决MapReduce计算慢的问题,随着数据量的指数增长,对数据处理效率有了更高的要求,我们需要在更短的时间内得到正确的结果。

Spark

Apache Spark是用于大规模数据处理的统一分析计算引擎,最初诞生于加州大学伯克利分校的AMP Lab实验室。基于内存进行并行计算,通过使用最先进的DAG调度器、查询优化器和物理执行引擎,实现了批处理和流处理的高性能,与Hadoop MapReduce相比,运行在内存中的计算速度要快上100倍(实际上或许没有快这么多),但是可见spark是要比Hadoop MapReduce计算能力更强的计算引擎。

组件角色

Spark由Spark Core、Spark SQL、Spark Streaming、MLib和GraphX四大组件构成 .

角色说明:

  • Spark Core:spark的核心,将数据抽象为弹性分布式数据集(RDD),提供了分布式任务调度,RPC通信、序列化和压缩等特性,是内存计算的框架,用于离线计算
  • Spark SQL:基于Spark Core之上用于结构化数据建模和数据处理组件,实现交互式查询
  • Spark Streaming:利用Spark Core快速调度能力进行流式计算
  • MLib:是Spark上分布式机器学习框架,提供了大量的算法
  • GraphX:是Spark上的分布式图形处理框架,能进行高效的图计算
架构

Spark是一个典型的master/slave主从架构,基于内存计算引擎,提供了多种缓存机制,将RDD 缓存到内存或者磁盘中,这种机制使得Spark可以进行迭代计算和数据共享,从而减少数据读取的IO开销,架构如下:

  • Driver:初始化Spark运行环境,创建SparkContext上下文环境,是用户程序的入口,即main() 方法
  • Cluster Manager:资源管理器,目前支持Standalone、YARN、Mesos和Kubernetes这几种模式,在Standalone模式中,Cluster Manager即为Master节点,控制整个集群
  • Worker:spark计算节点,负责计算任务的管理,为task分配并启动Executor,定时向Cluster Manager汇报任务执行情况
  • Executor:Spark Task工作的容器,是用户程序在worker节点上的一个进程,运行计算任务,负责数据的读取和写入,缓存中间数据
工作原理

  1. Driver驱动程序会初始化SparkContext
  2. 初始化过程中会启动DAGSchedulerTaskScheduler
  3. TaskScheduler通过后台进程向Master注册用户程序
  4. Master收到注册请求之后会通知Worker为用户程序启动多个Executor容器
  5. Executor反向SparkContext注册
  6. SparkContext将应用程序发给Executor
  7. SparkContext完成初始化,构建DAG,创建Job并且根据action操作划分Stage,形成TaskSet发送给TaskScheduler,最后发给Executor执行
  8. 运行完释放资源
Spark 为什么比MapReduce快
  • Spark相对于MapReduce减少了磁盘IO,没有太多中间结果落盘
  • Spark采用了多线程模型,基于线程池复用降低task线程的开销
  • spark提供了多种缓存策略,避免了重复计算
  • 灵活的内存管理策略
  • Spark的DAG(有向无环图)算法
  • 提供丰富的抽象方法,MapReduce只有map和reduce两种抽象
  • 缓存和checkpoint,通过lineage实现高度容错性

以上列举了spark比MapReduce快的部分特性,Spark的出现逐步取代了MapReduce成为新一代离线计算引擎的最佳选择,不仅如此,spark还提供了Spark Streaming组件作为流式计算引擎。

Spark Streaming

Spark Streaming是Spark Core API的扩展,支持实时数据流的可伸缩、高吞吐量、容错流处理。支持多种数据源,数据可以从Kafka、Flume、HDFS、S3、Kinesis或TCP套接字等许多来源获取,并可以使用复杂的算法处理,这些算法由map、reduce、join和window等高级函数表达。最后,可以将处理过的数据推送到文件系统、数据库和实时仪表板中。

工作模型

image-20201230103445976

Spark Streaming基于micro batch方式的计算和处理流数据,提供了称为离散流或DStream的高级抽象,它表示连续的数据流。将接收到的数据流切分为多个独立的DStream,本质上也是一系列RDD,通过spark计算引擎进行计算。

DStreams(Discretized Streams)

离散数据流是Spark Streaming提供的基本抽象,将待处理数据转变为连续不断的数据流,可以是外部数据源转换而来,也可以通过内部流之间的转换生成,从DStream的内部流模型可以看到Dstream就是由一系列的RDD组成。

image-20201230103618209

实际上,Dstream执行的操作都会转换为对底层RDD的操作。

v

Spark Streaming是怎么实现数据实时计算的呢?

image-20201230103754154

当spark Streaming接收到数据后,会将数据流切分成多个批次,形成有界的数据集,设置时间间隔,当不同批次的数据进入窗口后会触发计算机制,通过Spark Core进行一系列tranformation和action操作,因为划分批次之后的数据比较小,实时计算得出结果。

图片

为什么要用Spark Streaming呢?

从数据的边界来说,我们可以把数据分为有界数据和无界数据。顾名思义,有界数据是有范围的,一般来说与时间是强关联的,以历史数据最为典型。而无界数据就是难以限定范围的数据,会持续不断发生变化,最常见的场景就是实时数据流,看不到数据的尽头,一直在发生。

MapReduce和Spark SQL等框架只能进行离线计算,无法满足实时性要求高的业务场景,如购买商品后进行实时推荐、实时交易业务等等。而spark Streaming巧妙地将数据细分为多个微小的批次,依赖于spark计算引擎能做到准实时计算,不是真正意义上的实时计算,尽管如此,spark Streaming还是得到了业界的认可和广泛应用。

Spark Streaming优势:

  • 实时性:Spark Streaming 是一个实时计算框架,微批处理数据,延迟可以控制到秒级
  • 高容错性:Spark Streaming底层依赖RDD lineage特性、缓存机制、checkpoint机制以及WAL预写日志,可以实现高度容错
  • 高吞吐:相对于实时计算框架Storm吞吐量更高
  • 一体化:依托spark生态,不仅能进行实时计算,还能应用于机器学习和Spark SQL场景

对于大部分企业来说,秒级的延时是可以接受的,而且一个大数据项目通常会包含离线计算、交互式查询、数据分析、实时计算等模块,Spark Streaming毫无疑问是很好的选择。

Storm

storm是一个真正的实时流计算引擎,相对于Spark Streaming的微批处理,storm则是来一条数据计算一条数据,延时可以控制到毫秒级。

Storm架构

storm的架构与Hadoop相似,都是master/slave主从架构。

成员 Storm Hadoop
主节点 Nimbus JobTracker
从节点 Supervisor TaskTracker
计算模型 Spout / Bolt Map / Reduce
应用程序 Topology Job
工作进程 Worker Child

图片

Nimbus:master节点,负责提交任务,分配到supervisor的worker上,运行Topology上的Spout/Bolt任务

Zookeeper:协调节点,负责管理storm集群的元数据信息,比如heartbeat信息、集群状态和配置信息以及Nimbus分配的任务信息等

Supervisor:slave节点,负责管理运行在supervisor节点上的worker进程

Storm工作流程

图片

  1. 客户端提交topology任务到Nimbus节点
  2. Nimbus主节点将任务提交到zookeeper集群管理
  3. Supervisor节点从Zookeeper集群获取任务信息
  4. 启动worker进程开始执行任务
Storm Vs Spark Streaming

用一个生动形象的生活场景来比喻Storm和Spark Streaming,Storm好比是手扶电梯,一直在运行,来一个人都会将他带上/下楼,而Spark Streaming更像是升降电梯,要装满一批人才开始启动。

  • storm可以实现毫米级计算响应 VS Spark Streaming只能做到秒级响应
  • Storm吞吐量低 VS Spark Streaming吞吐量高
Item Storm Spark Streaming
Streaming Model Native Micro-Batch
Guarantees At-Least-Once Exactly-Once
Back Pressure No Yes
Latency Very Low Low
Throughput Low High
Fault Tolerance Record ACKs RDD Based CheckPoint
Stateful No Yes(DStream)

Apache Flink是一个分布式处理引擎,用于在无边界和有边界数据流上进行有状态的计算。Flink能在所有常见集群环境中运行,并能以内存速度和任意规模进行计算。现在Flink也是主流的实时流计算框架并且同时支持批处理,支持基于有状态的事件时间进行计算,成为大数据计算引擎的领头羊,大有盖过Spark风头之势。

图片

擅长处理有界(bounded)数据和无界(unbounded)数据,有界数据通常指的是离线历史数据,用于批处理,而无界数据指数据流有定义数据产生的开始,却无法定义数据何时结束,因此无界数据流通常说的就是实时流,用于实时计算。

图片

架构

Flink和Spark一样,也是主从架构,由JobManager和TaskManager组成

图片

JobManager:主节点,负责处理客户端提交的Job,管理Job状态信息,调度分配集群任务,对完成的 Task 或执行失败做出反应、协调 checkpoint、并且协调从失败中恢复

TaskManager:从节点,负责管理节点上的资源,向JobManager汇报集群状态,执行计算JobManager分配的Task

Flink工作调度

img

  1. 用户提交Flink程序时,client会对程序进行预处理,构建Dataflow graph,封装成Job提交到JobManager
  2. JobManager接收到client提交的Job,获取并管理Job的基本信息,构建DAG执行计划,通过Scheduler调度任务并分配到对应的TaskManager节点
  3. TaskManager向JobManager注册,JobManager将Job分配到TaskManager执行,每个Task Slot代表着用来执行Task的资源,包括了内存、cpu等
  4. TaskManager与JobManager保持心跳,定时汇报节点资源情况以及任务执行情况
  5. JobManager将将任务执行的状态和结果反馈给客户端
Flink的优势
  • Flink支持实时计算,且基于内存管理,性能优越
  • 具有高吞吐、低延迟、高性能的流处理特性
  • Flink与Hadoop生态高度融合
  • 高度灵活的时间窗口语义
  • 流批一体化,同时支持批处理和流计算
  • 高容错,基于分布式快照(snapshot)和checkpoint检查点机制
  • 具有反压(Backpressure)功能
  • 支持有状态计算的Exactly-once语义
  • 可以进行机器学习处理(FlinkML)、图分析(Gelly)、关系数据处理(FLink SQL)以及复杂事件处理(CEP)
Item Flink Storm Spark Streaming
Streaming Model Native Native Micro-Batch
Guarantees Exactly-Once At-Least-Once Exactly-Once
Back Pressure Yes No Yes
Latency Medium Very Low Low
Throughput High Low High
Fault Tolerance Checkouting Record ACKs RDD Based CheckPoint
Stateful Yes(Operators) No Yes(DStream)

Tool

JS & Tool & Other

VSCODE

插件搜索 @installed 可以查到已经安装的插件

自动格式化代码:Shift + Alt + F

特别有用的正则:

1.删除A之后的所有字符用:A.*$

2.删除A之前的所有字符用:^([^s]*)A

####如果是其他字符就把A替换为其他字符

注释:如何是特殊字符注意转义

Anaconda Navigator & Pyhton & 脚本语言 & Others

Anaconda Navigator 介绍 / 背景

conda anaconda anaconda-navigator pip virtualenv区别

  • Anaconda是一个包含180+的科学包及其依赖项的发行版本。其包含的科学包包括:conda, numpy, scipy, ipython notebook等。

  • conda是包及其依赖项和环境的管理工具。

  • pip是用于安装和管理软件包的包管理器。

  • virtualenv:用于创建一个独立的Python环境的工具。

  • “Anaconda-Navigator”中已经包含“Jupyter Notebook”、“Jupyterlab”、“Qtconsole”和“Spyder”。

conda基础操作

1
2
3
4
5
6
7
8
9
创建环境 conda create -n envname python=xxx

删除环境 conda remove -n envname --all

进入环境 conda activate envname (old version: source activate xxx)

离开环境 conda deactivate (old version: source deactivate)

列出所有环境 conda info -e #这里列出环境的同时会列出该环境的绝对路径

conda参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
conda install -y 覆盖提示,默认yes

The following NEW packages will be INSTALLED:

libgfortran: 1.0-0
numpy: 1.10.2-py27_0
openblas: 0.2.14-3
pandas: 0.17.1-np110py27_0
python-dateutil: 2.4.2-py27_0
pytz: 2015.7-py27_0
six: 1.10.0-py27_0

Proceed ([y]/n)? y
等于提前输入了这个y

-------------------

conda install -c 这个c的意思是channel,也就是指定了下载通道,在出现某些网络问题的情况下可以换一个下载源就这个意思

-------------------

conda install 和 pip install 的区别

1
2
3
pip只能安装python包,而conda可以安装由任何语言编写的包
pip不能创建虚拟环境,需要借助另外的包,例如virtualenv,而conda可以创建虚拟环境。
pip是按照python时自带的,而conda需要安装anaconda才能用。

具体区别可以在找个官网上找到 https://www.anaconda.com/blog/understanding-conda-and-pip

conda更新

很多时候打不开可能也是因为要更新了

管理员运行 conda prompt
然后 conda update anaconda-navigator

第三步 anaconda-navigator --reset

第四步 conda update anaconda-client

第五步 conda update -f anaconda-client

Jupyter Book

使用技巧

端口

1
默认8888 值得一提的是如果同时开启jupyter book和jupyterlab需要小心端口冲突

快捷键

1
快捷键这里不过多介绍,因为我本身就是远程到windows机器上 `等待补充`

显示多行输出

1
Jupyter Book默认只显示一行输出,要想显示多行输出,需要添加如下代码
1
2
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = 'all'

简易命令

1
2
不必离开Jupyter笔记本来执行Shell命令,而是可以在命令开头使用感叹号(!)。例如,您可以安装软件包。
!pip install matplotlib

大量魔术命令

1
2
魔术命令(magic commands)是有助于提高生产率的特殊命令。您可能最熟悉下面的魔术命令,该命令让Notebook渲染Python Matplotlib创建的图表。
%matplotlib inline
1
2
3
4
5
6
7
%pwd #打印当前工作目录
%cd #更改工作目录
%ls #显示当前目录中的内容
%load [在此处插入Python文件名] #将代码加载到Jupyter Notebook
%store [在此处插入变量] #这使您可以传递Jupyter Notebooks之间的变量
%who #使用它列出所有变量
%lsmagic # 查看完整的魔术命令列表

记录单元格运行时间

1
%%time

显示函数文档

shift + tab

1
2
3
# 在Jupyter Book里面查看这个Book后面的Python版本的命令
import sys
sys.version

Python

python基础

1
2
# 照例来个 Hello World 先
print("Hello, Jupyter Book")
1
2
3
# 打印 Python 中的所有关键字
import keyword
print(keyword.kwlist)

[‘False’, ‘None’, ‘True’, ‘and’, ‘as’, ‘assert’, ‘async’, ‘await’, ‘break’, ‘class’, ‘continue’, ‘def’, ‘del’, ‘elif’, ‘else’, ‘except’, ‘finally’, ‘for’, ‘from’, ‘global’, ‘if’, ‘import’, ‘in’, ‘is’, ‘lambda’, ‘nonlocal’, ‘not’, ‘or’, ‘pass’, ‘raise’, ‘return’, ‘try’, ‘while’, ‘with’, ‘yield’]

1
2
3
4
5
6
7
8
# 幂运算
1.01 ** 365
# 乘法运算符和字符串的混合使用
print('-------' * 10)
# 除法运算
10 / 20
# 取整运算
9 // 2 # 取整

37.78343433288728

----------------------------------------------------------------------

0.5

1
2
3
4
"""
多行注释
"""
print("多行注释")

'\n多行注释\n'

多行注释

1
2
3
4
5
# 按照是否是数字型类可以把python里面的类型分为两类,数字型和非数字型。
# 数字类型分为 int float bool complex(复数型),非数字型分为 字符串、列表、元组、字典。(这四种非数字型类型其实都可以看为容器或者序列)
# 判定类型 python 3 取消了python 2里面的长整形的概念
account = 1101
print(type(account))

<class 'int'>

1
2
3
# Bool 类型 参与计算 True 是 1, false 是 0
# input() 输入的总是String类型
inputStr = input("记录输入")

记录输入 (内容 )

1
2
# Python类型强制转换 强制把123字符串转换成了int
type(int("123"))

int

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 格式化字符串
# %s 字符串
name = "demo"
print("我的名字叫 %s" % name)
# %d 有符号十进制整数 %09d 的意思是如果没有get到9位的话,就用0补全,如果get到了 不进行别的多余操作
num = 1
print("学号是 %09d" % num)
# %f 取舍同上 %.2f 的意思是取小数点后两位,没有的话0补全
price = 8.5
weight = 7.5
money = price * weight
print("苹果单价 %.2f元/斤 ,购买了 %.3f斤,需要支付%.4f元" % (price,weight,money))
scale = 0.8
print("数据比例是 %.2f%%" % (scale * 100)) # 此处内部的括号如果省略的话,是另一种效果 --> 字符串 * 100

我的名字叫 demo
学号是 000000001
苹果单价 8.50元/斤 ,购买了 7.500斤,需要支付63.7500元
数据比例是 80.00%

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 判断条件 没有括号 使用 and 和 or 作为逻辑判断连接符号 而不像Java里面的 & 和 | 。

# if not

# elif => else if in Java

# 多行if条件:
# if ((player == 1 and b == 2))
# or (...)
# or (...)

# break 和 continue
# break: 某一天剑满足时,退出循环,不再执行后续重复的代码
# continue: 某一条件满足时,不执行后续重复的代码
1
2
3
# 随机
import random
random.randint(10,20) # 10 <= n <= 20

16

1
2
# 赋值运算符
# = += -= *= /= //= %= **=
1
2
3
4
5
6
7
8
9
10
11
# 在控制台输出
# 1. 定义一个计数器变量,从数字1开始,循环会比较方便
row = 1

# 2.开始循环
while row <= 5:
print("*" * row)
row += 1

# print 详解
print() # <=> print("内容",end="换行符")
1
2
3
4
5
*
**
***
****
*****
1
2
3
4
5
6
7
8
9
# 经典 99 乘法表
row = 1
while row <= 9:
col = 1
while col <= row:
print("%d * %d = %d" % (col, row, col * row ), end = "\t")
col += 1
row += 1
print("")

1 * 1 = 1
1 * 2 = 2 2 * 2 = 4
1 * 3 = 3 2 * 3 = 6 3 * 3 = 9
1 * 4 = 4 2 * 4 = 8 3 * 4 = 12 4 * 4 = 16
1 * 5 = 5 2 * 5 = 10 3 * 5 = 15 4 * 5 = 20 5 * 5 = 25
1 * 6 = 6 2 * 6 = 12 3 * 6 = 18 4 * 6 = 24 5 * 6 = 30 6 * 6 = 36
1 * 7 = 7 2 * 7 = 14 3 * 7 = 21 4 * 7 = 28 5 * 7 = 35 6 * 7 = 42 7 * 7 = 49
1 * 8 = 8 2 * 8 = 16 3 * 8 = 24 4 * 8 = 32 5 * 8 = 40 6 * 8 = 48 7 * 8 = 56 8 * 8 = 64
1 * 9 = 9 2 * 9 = 18 3 * 9 = 27 4 * 9 = 36 5 * 9 = 45 6 * 9 = 54 7 * 9 = 63 8 * 9 = 72 9 * 9 = 81

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 函数
def multiple_table():
row = 1
while row <= 9:
col = 1
while col <= row:
print("%d * %d = %d" % (col, row, col * row ), end = "\t")
col += 1
row += 1
print("")

multiple_table()

# 函数注释的说明
# 1. 注释写在方法名下面 2. 方法的第一排要和上面最后一行有两行空白的距离 3.pycharm中,鼠标放在方法名上,Ctrl+Q就可以调出注释。

1 * 1 = 1
1 * 2 = 2 2 * 2 = 4
1 * 3 = 3 2 * 3 = 6 3 * 3 = 9
1 * 4 = 4 2 * 4 = 8 3 * 4 = 12 4 * 4 = 16
1 * 5 = 5 2 * 5 = 10 3 * 5 = 15 4 * 5 = 20 5 * 5 = 25
1 * 6 = 6 2 * 6 = 12 3 * 6 = 18 4 * 6 = 24 5 * 6 = 30 6 * 6 = 36
1 * 7 = 7 2 * 7 = 14 3 * 7 = 21 4 * 7 = 28 5 * 7 = 35 6 * 7 = 42 7 * 7 = 49
1 * 8 = 8 2 * 8 = 16 3 * 8 = 24 4 * 8 = 32 5 * 8 = 40 6 * 8 = 48 7 * 8 = 56 8 * 8 = 64
1 * 9 = 9 2 * 9 = 18 3 * 9 = 27 4 * 9 = 36 5 * 9 = 45 6 * 9 = 54 7 * 9 = 63 8 * 9 = 72 9 * 9 = 81

1
2
3
4
5
6
# 函数参数
def sum_2_num(num1,num2):
result = num1 + num2
print("%d + %d = %d" % (num1,num2,result))

sum_2_num(50,20)

50 + 20 = 70

1
# pyc文件:python的编译后文件,浏览程序目录里面会有一个 __pycache__ 的目录,里面会有pyc为后缀的文件,就是编译后文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# List 是使用最频繁的数据类型,对应别的语言中的数组。
# 专门用于存储一串信息
# 列表用 [] 定义,数据之间使用 , 分隔
# 列表的索引从 0 开始 --> index

lname_list = []
# 增加
# 列表自带了很多的方法,
# 列表.insert(索引) 在指定位置插入数据
# 列表.append(数据) 在末尾追加数据
# 列表.extend(列表2) 将列表2 的数据追加到列岛

# 修改
# 列表[索引] = 数据 修改指定索引的数据

# 删除
# del 列表[索引] 删除指定索引的数据
# 列表.remove[数据] 删除第一个出现的指定数据
# 列表.pop 删除末尾数据
# 列表.pop(索引) 删除指定索引数据
# 列表.clear 清空列表

# 统计
# len(列表) 列表长度
# 列表.count(数据) 数据在列表中出现的次数

# 排序
# 列表.sort() 生序排序
# 列表.sort(reverse=True) 降序排序
# 列表.reverse() 逆序、反转
1
2
3
4
5
# List之遍历
# 类比 Java 里面的 foreach
# 语法形式:
# for 别名 in List:
# 理论上List是可以存储不同类型的数据的, 习惯上 使用元组保存不同类型的数据,使用List保存相同类型的数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 元组 Tuple
# 元组的元素不能修改
# 使用()定义,索引从0开始
info_tuple = ("zhangsan",18)

# 混淆点 定义只有一个变量的元组 需要加上一个逗号
single_tuple = (5)
type(single_tuple) #int
single_tuple = (5,)
type(single_tuple) # tuple

# 但是如果是定义一个空的元祖的话,我们只需要定义一个括号即可

# 格式化字符串 % 后面的本质上就是一个元组, 使用元组来拼接一个新的字符串效果相同

# List 和 Tuple 的转换 List(元组) 可以把元组转换成List,使用tuple(列表)可以把列表转换成元组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 字典 dictionary 使用 {} 定义,类似于 Map
# 字典无序 List有序 字典的key必须是不可变类型
# 值可以是任何数据类型,键只能使用字符串、数字或者元组
xiaoming = {
"name": "小明",
"age": 18,
"gender": True,
"height": 1.75,
"weight": 75.6,
}

print(xiaoming)
print(xiaoming["name"])

# 增加 / 修改
xiaoming["age"] = 10 # 如果这个值得没有的话,就会新建

# 删除 pop
xiaoming.pop("age")

# 合并字典 update
zidian = {"key": "value"}
zidian.update(xiaoming)
print(zidian)

# 清空 clear
zidian.clear()
print(zidian)

for k in xiaoming:
print("%s - %s" % (k, xiaoming[k]))

{‘name’: ‘小明’, ‘age’: 18, ‘gender’: True, ‘height’: 1.75, ‘weight’: 75.6}
小明

10

{‘key’: ‘value’, ‘name’: ‘小明’, ‘gender’: True, ‘height’: 1.75, ‘weight’: 75.6}
{}
name - 小明
gender - True
height - 1.75
weight - 75.6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 字符串
# 单引号和双引号都能定义字符串 但是单引号不推荐
str1 = "hello python"
print(str1[0])

for k in str1:
print(k)

# 统计长度
print(len(str1))

# 统计出现次数
print(str1.count("o"))

# 某个字符串出现的位置 (第一个字母出现的index)
print(str1.index("o"))

h
h
e
l
l
o

p
y
t
h
o
n
12
2
4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 字符串切片 List和tuple也能切片 字典不能切片
num_str = "0123456789"

num_str[2:6] # index 2 - 5 含头不含尾 '2345'

num_str[2:] # index 2 到末尾 '23456789'

num_str[:6] # 开始 -> index 6 之前的所有char, 前6位 '012345'

num_str[:] # 全部 '0123456789'

num_str[::2] # 从头开始 步长为1取值 '02468'

num_str[1::2] # 从index = 1 开始 步长为1取值 '13579'

num_str[2:-1] # 负数代表从后面往前 '2345678'

# 字符串逆序 *
num_str[-1::-1] # '9876543210'
num_str[::-1] # '9876543210'
1
2
3
4
5
6
# 公共方法 
# len() # 容器内元素个数
# del() # del 有两种方式 还有一种是作为关键字
# max() # 返回容器中元素的最大值
# min() # 返回容器中元素的最小值
# cmp() # python 3 中取消了这个函数
1
2
3
4
5
6
7
# 运算符在高级类型中的作用

# + 合并 : 字符串 列表 元组
# * 重复 : 字符串 列表 元组
# in 判断是否存在: 字符串 列表 元组 字典
# not in 和上面对应
# > >= == < <= 元素比较 :字符串 列表 元组
1
2
3
4
# 完整 for 循环
# 也就是 for 下面 跟了 一个 else , 如果for循环没有被break打断就会执行else里面的内容,如果被break打断了 ,就不会执行

# 使用场景: 迭代遍历 所需要的数据类型,查找某个元素,如果存在,整体退出循环,如果不存在,在整体循环结束后,进行统一的操作。
1
2
# python 查看内存地址
# id()
1
2
3
4
5
# 可变和不可变类型
# 区分标准是 内存中的数据能否修改
# 可变类型只有两个就是 列表和字典 ,可变类型的数据变化是通过方法来实现的

# 可变类型 被函数里面修改的时候(并不是创建新的内存使用,而是修改内存里面的数据),虽然是通过参数传入的,但是原值还是会被修改,不可变类型并不会
1
2
3
4
5
6
# 因为Python是解释型语言 所以在Python中原则上不允许直接修改全局变量的值,如果在函数中对全局变量进行复制操作的话,Python会自己创造一个局部变量
# 所以在这个地方对全局变量进行的修改操作时无效操作。

# 但是,凡事无绝对,如果一定想要在函数里面修改全局变量的话,我们会在变量前面加上一个Global,这样就不会在函数中重新创建这个局部变量了。

# 为了避免全局变量和局部变量在明明的时候出现混淆,一般来说习惯上会在全局变量命名的时候前缀加上一个g_或者gl_
1
2
3
4
# 如果函数返回的类型是元组,我们的小括号可以省略

# return (temp, wetness)
# return temp, wetness # 小括号可以省略
1
2
3
4
5
6
7
8
# Python中特有的交换两个值的方式
a = 6
b = 100

a,b = (b,a)

print(a)
print(b)
1
2
3
4
5
6
7
8
9
10
11
12
# 缺省参数 具有默认值的参数就叫做缺省参数

# 在函数里面定义形参的时候直接写 gender = True ,默认就是True的意思
num_list = [7, 5, 4, 9]
# 默认就是升序排序,因为这种需求更多
num_list.sort()
print(num_list) # 4,5,7,9
# 只有当降序排序时,才需要传递reverse参数
num_list.sort(reverse=True)
print(num_list) # 9,7,5,4

# 缺省参数后面不能再跟参数

case:

image-20220728223807310

1
2
3
4
5
6
7
# 多值参数
def demo(num, *nums, **numss):
print(num)
print(nums)
print(numss)

# 习惯上 多值参数里面,我们使用*args表示元组,使用*kwargs表示键值对参数
1
demo(1,2,3,4, age=18)

1
(2, 3, 4)
{‘age’: 18}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 拆包 字典和元组作为参数传进参数的时候会变成一个整体传入,我们为了拆解这个参数,在传入参数的时候,根据他们的类型在前面 添加一个 * 或者两个 *

run(1,2,3)

def run(a,*args):
#第一个参数传给了a
print(a) # 1
# args是一个元组,里面是2和3两个参数
print(args) # (2,3)
# *args是将这个元组中的元素依次取出来
print("对args拆包")
print(*args) # *args 相当于 a,b = args 结果是 2 3
print("将未拆包的数据传给run1")
run1(args) # 输出元组 ((2,3),) 对元组进行拆包 (2,3)
print("将拆包后的数据传给run1")
run1(*args) # 输出元组 (2,3) 对元组进行拆包 2 3

def run1(*args):
print("输出元组")
print(args)
print("对元组进行拆包")
print(*args)

# 我对装包,拆包的理解是 参数中的*/**是装包,在函数内部使用这个*的意思就是拆包
1
2
3
4
5
6
7
8
9
10
11
12
def run(**kwargs):#传来的 key = value 类型的实参会映射成kwargs里面的键和值
# kwargs是一个字典,将未命名参数以键值对的形式
print(kwargs)
print("对kwargs拆包")
# 此处可以把**kwargs理解成对字典进行了拆包,{"a":1,"b":2}的kwargs字典又
# 被拆成了a=1,b=2传递给run1,但是**kwargs是不能像之前*args那样被打印出来看的
run1(**kwargs)

def run1(a,b): #此处的参数名一定要和字典的键的名称一致
print(a,b)

run(a=1,b=2)

{‘a’: 1, ‘b’: 2}
对kwargs拆包
1 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# OOP 面向对象
# 通用方法:dir(对象) 能够把对象里面所有能调用的方法都调用出来
# 添加属性 Python特有的方法,先把对象创建出来,然后直接使用 .属性名 就可以为这个对象添加新的属性,这种方式虽然简单,但是是不推荐的。

# 对象初始化
# 当使用类名创建对象时,会自动执行以下操作
# 1. 为对象在内存中分配空间 -- 创建对象
# 2.为对象的属性设置初始值 -- 初始化方法 int
# 这个初始化方法就是 __intit__方法 ,是对象的内置方法
# __init__方法是专门用来定义一个类有哪些属性的方法


# 初始化
"""
__init__方法是专门用来定义一个类具有哪些属性的
"""


class Cat:

# 设置默认值
def __init__(self, new_name):
print("初始化")

# 属性
self.name = new_name

def eat(self):
print("%s 爱吃鱼" % self.name)

# __del__
# 对象被销毁前执行的方法
def __del__(self):
print("%s 我去了" % self.name)

# 必须返回字符串 类似于Java的toString方法
def __str__(self):
# print("我是小猫 %s" % self.name) 错误示例
return "我是小猫 %s" % self.name


# 使用类名()创建对象的时候,会自动调用初始化方法__init__
tom = Cat("TomDemo")

# 可以用__init__方法内部使用self.属性名 = 属性的初始化值 就可以定义属性
# 定义属性之后,再使用Cat类创建的对象,都会拥有该属性
print(tom.name)
tom.eat()

# del 销毁是在一个python文件即将运行到结尾的时候
print(tom)

# Python 能够自动的将一堆括号内部的代码链接在一起

# 运行结果里面的del方法的位置我完全没有看懂,这个坑以后再填,花了点时间没有整明白

"""
身份运算符
is is是判断两个标示符是不是引用同一个对象
is not is not 是判断两个表示服是不是引用不用对象

is 与 == 的区别
is用于判断两个变量引用对象是否为同一个
==用于判断引用变量的值是否相等
"""

初始化
TomDemo 我去了
TomDemo
TomDemo 爱吃鱼
我是小猫 TomDemo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# 封装 继承 多态

# 继承
class Animal:
def eat(self):
print("吃")

def drink(self):
print("喝")

def run(self):
print("跑")

def sleep(self):
print("睡")


wangcai = Animal()

wangcai.eat() #吃
wangcai.drink() #喝
wangcai.run() #跑
wangcai.sleep() #睡

# 子类拥有父类的所有方法
class Dog(Animal):

def bark(self):
print("汪汪叫")


wangwang = Dog()

wangwang.eat() #吃
wangwang.drink() #喝
wangwang.run() #跑
wangwang.sleep() #睡
wangwang.bark() #汪汪叫


class XiaoTianQuan(Dog):

def fly(self):
print("我会飞")

def bark(self):

print("我是啸天犬")

# 调用父类的bark方法
super().bark()
# Python2.0 如果要实现上面的功能,有点复杂 代码如下
"""
Dog.bark(self)
"""

print("aafsdwaerwgybds")

# 有没有括号 对Python来说 说法很大
xtq = XiaoTianQuan()
xtq.bark() #我是哮天犬 汪汪叫
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class A:
# 属性在init方法中定义 使用__(两个下划线)定义的属性是私有属性,无法被子类访问
def __init__(self):
self.num1 = 100
self.__num2 = 200

def __test(self):
print("私有方法 %d %d" % (self.num1, self.__num2))


class B(A):

def demo(self):

# 在子类的对象方法中不能访问父类的私有属性和方法
# 但是可以通过调用父类的共有方法间接调用父类的公有属性和方法
print("访问父类的私有属性 %d" % self.__num2)


b = B() # <__main__.B object at 0x0000020EA7593BE0>
print(b)
"""
在外界不能直接访问对象的私有属性,调用方法
print(b.__num2)
b.__test()
"""
print(b.num1) #100
b.demo() # 'B' object has no attribute '_B__num2'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 多继承
class C:

def test(self):
print("test 方法")


class D:

def demo(self):
print("demo 方法")


# 多继承
class E(C, D):

pass


# 创建子类对象
e = E()
e.test()
e.demo()

# 应该避免两个父类汇总存在同名的属性和方法
"""
python中多继承两个父类出现相同的方法 属性名的时候
使用MRO选择执行顺序的
"""
print(E.__mro__)
# (<class '__main__.E'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>)


"""
新式类 以Object为基类的对象
旧式类 不以Object为基类的对象
两者在多继承时 会影响到方法的搜索顺序
"""

test 方法
demo 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 私有属性 / 方法

class Woman:

def __init__(self, name):
self.name = name
self.__age = 18

def secret(self):
print("%s 的年龄是 %d" %
(self.name,
self.__age))


xiaofang = Woman("小芳")
# 私有属性不能直接调用 但是Python中没有真正的私有属性和方法 可以使用下面第二行的代码调用
print(xiaofang.__age) # 报错,因为不能直接调用
print(xiaofang._Woman__age) # 18
xiaofang.secret() # 小芳 的年龄是 18
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 多态

# 类的结构
"""
__init__ 为对象初始化
实例方法(self)


类对象 模版 只有一个
实例对象 可以有很多个

"""


# 类方法
class Tool(object):

count = 0

def __init__(self, name):
self.name = name

Tool.count += 1

@classmethod
def show_tool_count(cls):

print("工具对象的数量 %d" % cls.count)


# 创建工具对象
tool1 = Tool("斧头")
tool2 = Tool("榔头")

# 调用类方法
Tool.show_tool_count() # 工具对象的数量 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# 单例

"""
python 中的self和cls一句话描述:self是类(Class)实例化对象,cls就是类(或子类)本身,取决于调用的是那个类。

__new__
为对象分配空间
返回对象的引用

python获得了对象的引用后,将引用作为第一个参数,传递给__init__方法

重写__new__方法的代码非常固定 如下

"""


class MusicPlayer(object):

def __new__(cls, *args, **kwargs):

# 创建对象时,new方法会被自动调用
print("创建对象,分配空间")

# 为对象分配空间
instance = super().__new__(cls)

# 返回对象的引用
return instance

def __init__(self):
print("播放器初始化")


player = MusicPlayer()
print(player) # 每次都指向内存中不同的位置

"""
单例:让类创建的对象,在系统中只哦与唯一的一个实例
"""
print("--------完整单例-------")


class SingleMusicPlayer(object):
instance = None

init_flag = False

def __new__(cls, *args, **kwargs):

# 1 判断类属性是否是空对象
if cls.instance is None:
# 2 调用父类方法,为第一个对象分配空间
cls.instance = super().__new__(cls)

# 返回类属性保存的对象引用
return cls.instance

def __init__(self):

# 1 判断是否执行过初始化动作
if SingleMusicPlayer.init_flag:
return

# 如果没有初始化过
print("初始化播放器")

SingleMusicPlayer.init_flag = True


player1 = SingleMusicPlayer()
print(player1)

player2 = SingleMusicPlayer()
print(player2) # 和上面相同的地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 异常
"""
try:
尝试执行的代码
except:
出现错误的处理
"""

try:
num = int(input("请输入一个整数:"))

result = 8 / num

print(result)

except ZeroDivisionError:
# 错误的处理代码
print("请输入正确的整数")
except ValueError:
print("请输入正确的整数")
# 捕获未知错误
except Exception as result:
print("未知错误 %s" % result)
else:
# 没有异常的时候才会执行的,这个else出现的位置和Java并不是一脉相承的,他这个else针对的是except
print("尝试成功")
finally:
# 总是会执行的代码
print("总是会执行")

print("-" * 50)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
"""
异常的传递
python 会把异常一直向上传递
"""


def demo1():
return int(input("输入整数:"))


def demo2():
return demo1()


print(demo2())

'''
ValueError Traceback (most recent call last)
Input In [2], in <cell line: 47>()
43 def demo2():
44 return demo1()
---> 47 print(demo2())

Input In [2], in demo2()
43 def demo2():
---> 44 return demo1()

Input In [2], in demo1()
39 def demo1():
---> 40 return int(input("输入整数:"))

ValueError: invalid literal for int() with base 10: 'dsf'
'''
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 导入

"""
from package import sth
from package import *
"""


# 所有倒入的模块 没有缩进的部分都会被执行一次

"""

__name__的用法
如果需要测试模块 就增加一个条件判断
这样增加了判断之后,在作为被包倒入的时候 下面的代码不会执行
"""

"""
模版
# 导入模块
# 定义全局变量
# 定义类
# 定义函数

def main():
# ...
pass

# 根据__name__判断是否执行下方代码
if __name__ == "__main__"
main()
"""
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Package


"""
包是一个包含多个模块的特殊目录
目录下有一个特殊的文件 __init__.py
包名的命名方式和变量名一直,小写字母+_

好处
使用import 包名 可以一次性倒入包中所有的模块

在__init__.py文件中用
from . import 模块名
制定外部文件需要倒入的模块
"""
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# 发布
"""
发布模块
三步
1. 创建setup.py

"""


"""
发布
固定模版:

from distutils.core import setup

# 多值的字典参数,setup是一个函数
setup(name="Hello", # 包名
version="1.0", # 版本
description="a simple example", # 描述信息
long_description="简单的模块发布例子", # 完整描述信息
author="FlyHugh", # 作者
author_email="flyhobo@live.com", # 作者邮箱
url="flymetothemars.github.io", # 主页
py_modules=["hello.request",
"hello.response"]) # 记录包中包中包含的所有模块
"""

"""
第二步
构建
python3 setup.py build
"""

"""
第三步
python3 setup.py sdist
"""

"""
安装
tar zxvf 解压
sudo python3 setup.py install

然后打开python3运行即可

"""
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File

# Python 文件指针
# 文件指针标记从哪个位置开始读取数据,第一次打开文件时,通常文件指针会指向文件的开始位置
# 当执行了 read 方法后,文件指针对移动到 读取内容的末尾

# 也就是说
# 第一次读取了之后,文件指针移动到了文件末尾,再次调用不会读取到任何内容

# f = open("文件名","访问方式")
# r w a r+ w+ a+ 具体见百度


"""
python操作文件

open 打开文件 返回文件对象
read 文件内容读取到内存
write 将内容写入文件
close 关闭文件

文件指针
open()方法有两个参数 第二个参数制定的是打开的方式
存在r w a r+ w+ a+这些方式 用str格式传入

readline 按行读取
"""


"""
小文件复制
file_read = open("README")
file_write = open("README[复件]","w")

text = file_read.read()
file_write.write(text)

file_read.close()
file_write.close()


复制大文件 用readline
"""

"""
倒入 import os
rename 重命名
remove 删除

listdir 目录列表
mkdir 创建目录
rmdir 删除目录
getcwd 获取当前目录
chdir 修改工作目录
path.isdir 判断是否是文件
"""

"""
python2.x 默认ASCII编码
python2 使用
# *-* coding:utf-8 *-*
来指定编码格式
python3.x 默认使用UTF-8

"""
1
# eval 函数 -- 将字符串当成有效的表达式 来求值 并 返回计算结果

python包

众所周知,pyhton之所以能够流行火爆起来,他的各种开箱即用的工具包是一个很大的原因,我这里会把我能接触到的各种工具包陆陆续续的填上来。

pdf2pptx

用来把pdf转换成pptx的一个工具,会把每次做成一个单独的pptx,我唯一注意到的一个问题是,低一点的python版本可能不支持(大概3.6)3.8是随便能够安装使用的。

使用的时候命令也很简单pdf2pptx demo.pdf即可,文件最后会存放在同目录中。

Python随机随到的一些问题,那你妈逼随机就这么容易随到的啊

anaconda代理问题

设置代理的方法:找到位置在path-for-install目录下.condarc文件,添加Http和Https的代理,这里默认本地cfw

1
2
3
proxy_servers:
http: http://127.0.0.1:7890
https: http://127.0.0.1:7890

如果有密码的话

1
2
3
proxy_servers:
http: http://user:password@xxxx:8080
https: https://user:password@xxxx:8080
1
anaconda 并不能被Clash4Windows在Tun模式下代理,虽然不知道原因(后续知道了会在这补上),anaconda只能在配置了proxy之后才能被代理流量

python和Clash4windows的冲突问题

现象: 和pip有关的几乎一切指令都会报错

1
2
3
4
WARNING: Retrying (Retry(total=4, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ProxyError('Cannot connect to proxy.', OSError(0, 'Error'))': /simple
/gitpython/
WARNING: Retrying (Retry(total=3, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ProxyError('Cannot connect to proxy.', OSError(0, 'Error'))': /simple
/gitpython/

ts:

遇到这种问题那肯定是直接debug走起,首先用文本搜索到抛这个错的函数,然后在嫌疑语句上都打上断,我们就能找到罪魁祸首:

image-20220727225126154

同时发现windwos中原生的python环境并没有这个问题,于是和venv外没问题的老版pip一比较,很容易就能发现不对劲:选中的那两行在老版是不存在的

image-20220727225518321

有了这个额外信息,我们很容易就能找到这个 Support for web proxies is broken in pip 20.3 · Issue #9190 · pypa/pip (github.com) 来说明问题,2016年底,curl加入了这个把https协议前缀另加解释与定义的联盟:HTTPS proxy with curl | daniel.haxx.se,而urllib3显然也跟上了这个脚步:

因为前缀设置了 tls_in_tls_required 之后,urllib3会企图把这个代理服务器看作一个套了tls的http CONNECT代理。
但是问题是,我并没有设置 https:// 前缀的代理服务器,这个行为是什么神奇的情况呢?
继续向下追,找到如何获取代理的:

image-20220727225732878

确实,env里面没有proxy,那按照windows的习俗找找注册表也情有可原对吧?
这个函数的内部实现是这样:

image-20220727225820001

看红框的行为是不是好像很眼熟?IE的代理设置似乎就是这样的?

不,并不是一样的,因为IE的代理设置把HTTPS(在zh-MS方言里叫安全)代理定义为支持 CONNECT 动词的HTTP代理,尽管很久以来人们都是这样用的,但是当它前面出现一个协议前缀的时候就不一样了。

因为clash for windows打开系统代理的代理配置看起来并没有写明了protocol:

image-20220727230459589

所以首先,我们的py会根据IE时代的约定俗成把这样一个没有指明protocol的proxy url自动补全三种协议然后再按照约定俗成的行为为https请求使用https_proxy最后在一个http代理上试图开tls

这个配置在IE时代行为会是正常的,在现代的库中行为也是正常的,但是对于这样一个混杂了两种行为的库,模糊不清就成了问题。
上面的截图并不是urllib3的,而是 属于py自己的标准库urllib的,也算是urllib3开发人员的思想和urllib的历史遗留实现冲突了吧。

似乎此问题在新版Python中的报错信息会变成 There was a problem confirming the ssl certificate: HTTPSConnection
Pool(host='pypi.org', port=443): Max retries exceeded with url: /simple/plotly/ (Caused by SSLError(SSLEOFError(8, 'EOF 
occurred in violation of protocol (_ssl.c:1123)'))) 或者类似的错误。

在后续的版本更新中,CFW解决了Specify Protocol解决了部分此问题,但是解决的并不是很好,启用Specify Protocol会导致只设置http代理,https无代理。

image-20220727231553719

可以看到如果只是单纯地把注册表项的内容由 127.0.0.1:7890改为http://127.0.0.1:7890的话,urllib只会返回一个只有一个key也就是http的代理dict。
这时候从pip的请求调用链往上找,可以看到负责决定使用这个dict中那个代理的代码是 requests/utils.pyselect_proxy函数:

因为红框中的部分的限制,当你请求https://pypi.org的时候,只有key为https/https://pypi.org/all/all://pypi.org的代理会被使用,上面那个http的代理自然也就不会被使用。
BTW,因为这个代码是requests库的,这也就意味着在Windows平台上Clash for Windows的系统代理不会影响到大部分py应用的http请求。
而正确的做法是什么呢?让我们在IE中设置上代理,然后看一看它给出的行为:

image-20220727231825488

image-20220727231841031

同时,因为urllib中还存在代理的类型推测代码,所以正确的设置应该是:
http=http://127.0.0.1:7890;https=http://127.0.0.1:7891

python和解释性语言

https://blog.csdn.net/balabalamerobert/article/details/1649490

JavaScript

基础

诞生时间:javascript 诞生于 1995年

诞生目的:处理服务器端语言负责的一些输入验证操作

javascript组成部分:ECMAScript(核心) DOM (文档对象模型) BOM(浏览器对象模型)

简介 & 基本概念

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script>元素
属性(参数):
async:可选。表示应该立即下载脚本,但不妨碍页面中的其他操作。只对外部文件有效
charset:可选。通过src属性指定代码的字符集。大部分浏览器都会忽略他的值,很少有人用
defer:可选。表示脚本可以延迟到文档完全被解析和显示之后再执行。只对外部脚本有效
language:已废弃。原来用于表示编写代码的脚本语言
src:可选。表示包含要执行的外部文件
type:可选。可以看成是language的替代属性 虽然text/javascript 和 text/ecmascript都不
推荐使用,但大部分开发者依然使用。默认值为text/javascript

注意事项
在使用<script>嵌入javascript代码时,不要在代码中任何地方出现</script>字符串
如果要使用要通过转义字符"/" 可以解决

<noscript>元素
触发条件
浏览器不支持脚本
浏览器支持脚本,但脚本被禁用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
语法
ECMAScript的语法大量借鉴了C及其他类C语言的语法

严格区分大小写
标识符
第一个字符必须是一个字母、下划线或一个美元符号
其他字符可以是字母、下划线、美元符号或数字

数据类型
undefined 只有一个值就是undefined。在使用变量但未对其加以初始化时,
这个变量的值就是undefined

null 从逻辑角度来看,null值表示一个空对象指针,这也是typeof操作符
检测null时会返回Object的原因,如果定义的变量将用于保存对象,那么最好
将该变量初始化为null而不是其他值

boolean
number
string
object
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// typeof 操作符
var arr = ['1', '2', '3']
var obj = {
a: 'bbb'
}
var func = function () {
}
console.log(typeof undefined) // undefined
console.log(typeof null) // object
console.log(typeof true) // boolean
console.log(typeof 0) // number
console.log(typeof 'aaa') // string
console.log(typeof arr) // object
console.log(typeof obj) // object
console.log(typeof func) // function
console.log('____________________________')

/*
* 很明显typeof不能判断 null array object
* 常用判断方法 使用下面的判断方法可以详细的判断种类
* */
function type(any) {
return Object.prototype.toString.call(any).slice(8, -1).toLocaleLowerCase()
}
console.log(type(undefined)) // undefined
console.log(type(null)) // null
console.log(type(true)) // boolean
console.log(type(0)) // number
console.log(type('aaa')) // string
console.log(type(arr)) // array
console.log(type(obj)) // object
console.log(type(func)) // function
1
2
3
4
5
6
7
8
9
10
11
12
13
/*
* object的通用属性和方法
* construct:保存着用于创建当前对象的函数
* hasOwnProperty(propertyName):用于检查给定属性在当前对象实例中(而不是
* 在实例的原型中)是否存在。其中作为参数的属性名(propertyName)必须以字
* 符串形式指定
* isPrototypeOf(object):用于检查传入的对象是否是传入对象的原型
* propertyIsEnumerable(propertyName):用于检查给定的属性是否能够使用for-in
* 语句来枚举
* toLocaleString(): 返回对象字符串表示,改字符串与执行环境的地区对应
* toString() 返回对象字符串表示
* valueOf():返回对象字符串、数值或布尔表示。通常与toString()方法的返回值相同
* */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// 基本类型和引用类型的值
/*
* 基本类型值:简单的数据段(undefined null Boolean number String)
* 引用类型值:由多个值构成的对象(object array function)
* 引用类型的值是保存在内存中的对象。与其他语言不同,javascript不容许
* 直接访问内存中的位置,也就是说不能直接操作对象的内存空间。在操作对象
* 时,实际上是在操作对象的引用而不是实际的对象。为此,引用类型的值是
* 按引用访问的。
* */

// 复制变量的值
/*
* 复制基本类型的值是相互独立的互不影响
* 复制引用类型的值实际上是复制一个指针,复制结束后,实际上是引用的
* 同一个对象。因此改变其中一个变量就会影响另一个变量
* */

// 传递参数
/*
* 所有函数的参数都是按值传递的。也就是说,把函数外部的值复制给函数
* 内部的参数,就和把值从一个变量复制到另一个变量一样。基本类型值的
* 传递如同基本类型变量的复制一样,而引用类型也是一样
* */

// 基本类型的值
function addTen(num) {
num += 10;
return num
}

var count = 20;
var result = addTen(count);
// 没有改变外部的count

console.log(count, result)//20 30

// 引用类型的值
function setName(obj) {
obj.name = 'testName'
}

var person = {}
setName(person)
console.log(person.name) // testName
// 修改了外部的person

// 证明为按值传递 整个过程有点子复杂,和我以前使用的java完全不同,person2传入函数setName2之后,obj和person2同时指向同一个堆内存位置-->person2的位置,首先修改了name属性,重新指定obj之后,obj和person2指向的位置不再相同,这个时候修改obj并不会修改person2的属性,所以最终属性定格在了testName,这里面有个思维误区就是obj等于person2,并不相等。
function setName2(obj) {
obj.name = 'testName'
obj = {}
obj.name = 'changeName'
}

var person2 = {}
setName2(person2)
console.log(person2.name) // testName
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 检测类型
/*
*基本类型 typeof (undefined string boolean)
*引用类型 可以根据原型链或调用对象的toString()方法来判断
* */

var s = "Nicholas";
var b = true;
var i = 22;
var u;
var a = ['a']
var n = null;
var o = {};

// typeof
console.log(typeof s); //string
console.log(typeof i); //number
console.log(typeof b); //boolean
console.log(typeof u); //undefined
console.log(typeof a); //object
console.log(typeof n); //object
console.log(typeof o); //object

// instanceof
console.log(a instanceof Array) // true
console.log(a instanceof Object) // true

变量 & 作用 & 内存问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 执行环境及作用域
/*
* 执行环境(execution context,简称环境)
* 执行环境定义了变量或函数有权访问的其他数据,决定了他们
* 各自的行为。每个执行环境都有一个与之关联的变量对象,环
* 境中定义的所有变量和函数都保存在这个对象中。但我们不能
* 访问它,解析器会在后台调用它
* */

// 延长作用域链

/*
* 虽然执行环境的类型只有两种-全局和局部,但还是有其他方法来延长作用域链。
* 这么说是因为有些语句可以在作用域链的前端临时增加一个变量对象,该变量对
* 象会在代码执行后移除。有两种情况下会发生这种现象。
*
* try-catch 语句的catch块
* width语句
*
* 这两个语句都会在作用域链的前端添加一个变量对象。对width语句来说,会将指定
* 的对象添加到作用域链中。对catch语句来说,会创建一个新的变量对象,其中包含
* 的是被抛出的错误对象的声明
* */

// 没有块级作用域(es6后不是这样了)
/*
* javascript 没有块级作用域经常会导致理解上的困惑。在其他类C的语言中,由花括
* 号封闭的代码块都有自己的作用域,因而支持根据条件来定义变量
* */

if (true) {
var color = 'blue';
}
console.log(color) // blue

// 很明显花括号没有自己的作用域
// es6
if (true) {
let color2 = 'blue'
}
//console.log(color2) // Error color2 is not define

for (var i = 0; i < 10; i++) {
console.log('for var')
}
console.log(i) //for 也没有自己的作用域

// es6
for (let j = 0; i < 10; j++) {
console.log('for let')
}
//console.log(j) // Error j is not define
// 用let定义的变量在花括号内有自己的作用域,而var的没有
1
2
3
4
5
6
7
8
9
10
// 声明变量
/*
* 用var声明的变量会自动被添加到最近的环境中。在函数内部,最近的
* 环境就是函数的局部环境,如果初始化变量没有使用var声明,该变量
* 会自动被添加到全局环境
*
* 在编写javaScript代码的过程中,不申明而直接初始化变量是一个常见
* 的错误做法,因为这样可能会导致意外。在严格模式下未经声明的变量
* 会导致错误
* */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 查询标识符
/*
* 在某个环境中为了读取或写入而引用一个标识符时,必须通过搜索来确定该
* 标识符实际代表什么。搜索过程从作用域链的前端开始,向上逐级查询与给
* 定名字匹配的标识符。如果在局部环境中找到了该标识符,搜索过程停止,
* 变量就绪。如果在局部标识符中没有找到该变量名,则继续沿作用域链向上
* 搜索。搜索过程将一直追溯到全局环境的变量对象。如果在全局变量环境中
* 也没有找到这个标识符,则意味着该变量尚未声明。
* */

var color3 = 'blue'

function getColor() {
return color3
}

console.log(getColor()) //blue
// 局部环境中没有color3 的标识符,则在他的上级环境中寻找(也就是全局环境)
// 返回了全局环境中的color3

var color4 = 'blue'

function getColor2() {
var color4 = 'red'
return color4
}

console.log(getColor2()) // red
// 直接返回了局部环境的color4

/*
* 注意 变量查询也不是没有代价的。很明显,访问局部变量要比访问全局更快,因为
* 不用向上搜索作用域链。
* */
1
2
3
4
5
6
7
8
9
10
// 垃圾收集

/*
* 标记清除(最常用) (标记变量的不同状态,去除无用变量)
* 引用计数(计算变量引用次数,引用次数为0,清除该变量)
* */

// 管理内存
// 由于安全考虑浏览器可分配内存比桌面应用内存要少,为了性能,执行环境只保存
// 必要数据。一旦数据不在用就将值设为null(一般用于全局环境)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// object 类型
// 第一种创建方式
var person = new Object()
person.name = 'tom'
person.age = 29

// 第二种
var person2 = {
name: 'tom',
age: 29
}

// 读取
console.log(person2['name'])
console.log(person2.name)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// Array
// 创建
var color = new Array(3)
var color2 = ['red', 'blue', 'green']

// 检测数组 instanceof isArray()
console.log(color instanceof Array) // true
console.log(Array.isArray(color)) // true

// 转换方法
console.log(color2.toString()) // red,blue,green
console.log(color2.toLocaleString()) // red,blue,green
console.log(color2.valueOf()) //['red','blue','green']
console.log(color2) //['red','blue','green']

// toString 和 toLocaleString
var person3 = {
toLocaleString: function () {
return 'toLocaleString3'
},
toString: function () {
return 'toString3'
}
}

var person4 = {
toLocaleString: function () {
return 'toLocaleString4'
},
toString: function () {
return 'toString4'
}
}

var person5 = [person3, person4]
console.log(person5)
console.log(person5.toString()) //toString3,toString4
console.log(person5.toLocaleString()) //toLocaleString3,toLocaleString4
// 分别调用了数组元素的toString 和toLocaleString

toString和toLocalString还有另外一个valueOf的区别有点细,详细参考这个网页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 栈方法(push() 和 pop())
var colors = []
var count = colors.push('red', 'green')
console.log(count) // 2
count = colors.push('black')

var item = colors.pop()
console.log(item) // black
console.log(colors.length) // 2

// 队列方法(push() shift()) shift() 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值。 注意: 此方法改变数组的长度! 提示: 移除数组末尾的元素可以使用pop() 方法。
var colors2 = []
var count2 = colors2.push('red', 'green')
console.log(count2) //2

count2 = colors2.push('black')
console.log(count2) //3

var item2 = colors.shift()
console.log(item2) //red

// 重排序方法
var values = [1, 2, 3, 4, 5]
console.log(values.reverse()) // [ 5, 4, 3, 2, 1 ]

var values2 = [2, 1, 6, 4, 3, 5]
console.log(values2.sort(function (x, y) {
return x - y
})) // [ 1, 2, 3, 4, 5, 6 ]

// splice (删除 插入 替换)

// 位置方法(indexOf()和lastIndexOf())

//迭代方法
/*
* every(): 对数组中的每一项运行给定函数,如果该函数每一项都返回true,则返回true
* filter(): 对数组中的每一项运行给定函数,返回该函数会返回true的项组成的数组
* forEach():对数组中的每一项运行给定函数,这个方法没有返回值
* map():对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组
* some():对数组中的每一项运行给定函数,如果该函数对任一项返回true,则返回true
* */

// 归并方法
/*
* reduce()
* reduceRight()
* */
var values3 = [1, 2, 3, 4]
var sum = values3.reduce(function (prev, cur) {
return prev + cur
})
console.log(sum) //10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// Date 类型

// Date 比较大小
/*
* Data 的 toString() 和 valueOf() 不同浏览器返回不同
* Data类型的valueOf()方法不返回字符串,而是返回日期的毫秒表示,
* 因此,可以方便比较日期值
* */

var date1 = new Date(2018, 6, 9)
var date2 = new Date(2018, 6, 10)

console.log(date1 > date2) // false
console.log(date1 < date2) // true
// 这为比较日期提供了极大的方便

// 日期格式化方法
/*
* toDateString() 以特定于实现的格式显示星期几、月、日和年
* toTimeString() ...时、分、秒和时区
* toLocaleDateString() 以特定于地区的格式显示星期几、月、日和年
* toLocaleTimeString() 以特定于实现的格式显示时、分、秒
* toUTSString() 以特定实现的格式显示时、分、秒
* toLocaleString()和toString()方法一样不同浏览器显示不同不能使用
* */

// 日期、时间组件方法
// {Year Month(0-11) Date(1-31) Day(星期) Hours(0-23) Minutes(0-59) Seconds Milliseconds}
/*
* getTime() 返回日期的毫秒数;与valueOf()方法返回的值相同
* setTime(毫秒) 以毫秒设置日期,会改变整个日期
* getFullYear() 取得4位数的年份(2018而非仅07)
* getUTCFullYear
* ... 其他类似
* */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// RegExp

// 表达式 var expression = / pattern /flags(pattern 模式 正则表达式 flag 标志)
// g:global 全局模式 应用于所有字符串,而非匹配的第一项就停止
// i:表示不区分大小写
// m:表示多行模式

// RegExp 实例属性
/*
* global: 布尔值,表示是否设置了g标志
* ignoreCase: 布尔值,表示是否设置了i标志
* lastIndex:整数,表示开始搜索下一个匹配项的字符位置,从0算起
* multiline:布尔值,表示是否设置了m标志
* source:正则表达式的字符串表示
* */

//RegExp 实例方法
/*
* exec() 返回第一个匹配项的数组
* text() 返回布尔值
* */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// Function 类型
// 创建
/*
* function sum (num1,num2){ return num1 + num2 } (函数声明式)
* var sum = function (num1,num2){return num1 + num2} (函数表达式)
* var sum = new Function("num1","num2","return num1 + num2")(不推荐)
* 函数是对象,函数名是指针
* */

function sum2(num1, num2) {
return num1 + num2
}

console.log(sum2(10, 10)) //20

var autoHerSum = sum2
console.log(autoHerSum(10, 10)) //20

sum2 = null
console.log(autoHerSum(10, 10)) // 20
// 函数名是指针

/*
* 也可以同时使用函数声明和函数表达式 var sum = function sum() {}
* */

// 作为值的函数
function callSomeFunction(someFunction, someArgument) {
return someFunction(someArgument)
}

function add10(num) {
return num + 10
}

var result = callSomeFunction(add10, 10)
console.log(result) // 20

function getGreeting(name) {
return "Hello," + name
}

var result2 = callSomeFunction(getGreeting, 'Nicholas')
console.log(result2) // Hello,Nicholas

// 从一个函数内返回另一个函数
function createComparisonFunction(propertyName) {
return function (object1, object2) {
var value1 = object1[propertyName]
var value2 = object2[propertyName]

if (value1 < value2) {
return -1
} else if (value1 > value2) {
return 1
} else {
return 0
}
}
}

function CreateObject(name, age) {
this.name = name
this.age = age
}

// 这里相当于重写了toString方法
CreateObject.prototype.toString = function () {
return `{name:${this.name},age:${this.age}}`
}
var object1 = new CreateObject('Zachary', 28)
var object2 = new CreateObject('Nicholas', 29)
var data = [object1, object2]
// 这里引用到了上面的createComparisonFunction,其实很像是把函数式编程分开写了,仅此而已
data.sort(createComparisonFunction('name'))
console.log(data.toString())
data.sort(createComparisonFunction('age'))
console.log(data.toString())

// 函数内部属性
/*
* 函数内部,有两个特殊的对象:arguments 和 this。
* arguments(类数组对象):保存函数参数
* 该对象还有一个名叫callee的属性,改属性是一个指针,指向拥有这个arguments
* 对象的函数
* this:函数执行的环境对象
* */
// arguments.callee
// 经典的阶乘函数
function factorial(num) {
if (num <= 1) {
return 1
} else {
return num * factorial(num - 1)
}
}

console.log(factorial(10)) // 3628800

// 这样写有点不好 就是函数名和函数体耦合在了一起
function factorial2(num) {
if (num <= 1) {
return 1
} else {
return num * arguments.callee(num - 1)
}
}

console.log(factorial2(10))
// 将函数名解耦

// this
var color5 = 'red'
var o = {
color5: 'blue'
}

function sayColor() {
console.log(this.color5)
}

sayColor() // red
o.sayColor = sayColor
o.sayColor() // blue

/*
* 注意:函数名仅仅是一个包含指针的变量而已。因此,即使是在不同的
* 环境中执行,全局的sayColor与o.sayColor指向的仍然是同一个函数
* */

// 函数属性和方法
/*
* length:表示函数希望接收的命名参数的个数
* prototype
* prototype上的属性是不可被枚举的(for-in-无法发现)
* */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

// call 与 apply

function sum4(num1, num2) {
return num1 + num2
}

// apply
function callSum1(num1, num2) {
return sum4.apply(this, arguments) // arguments(类数组)
}

function callSum2(num1, num2) {
return sum4.apply(this, [num1, num2]) // 数组
}

console.log(callSum1(10, 10)) // 20
console.log(callSum2(10, 10)) // 20

// call
function callSum3(num1, num2) {
return sum4.call(this, num1, num2)
}

console.log(callSum3(10, 10)) // 20

// call 和 apply作用一样,只是参数的形式不一样
js的连续赋值
1
2
3
4
5
var a = {n: 1}
var b = a;
a.x = a = {n: 2}
console.log(a.x); //undefined
console.log(b.x) //Object {n: 2}

首先是

1
2
var a = {n:1}; 
var b = a;

在这里a指向了一个对象{n:1}(我们姑且称它为对象A),

image-20220816053812942

b指向了a所指向的对象,也就是说,在这时候a和b都是指向对象A的,

image-20220816053832601

接着继续看下一行非常重要的代码:a.x = a = {n: 2},这句话也是关键所在,根据js引擎语法解析,会先去从左到右寻找有没有未声明的变量,如果有就把该变量提升至作用域顶部并声明该变量。那么恭喜js引擎他找到a.x这个属性没有声明,那么他会在{n: 1}这个内存区声明一个x属性等待赋值!

image-20220816053909000

接着按照从左到右的寻找变量顺序运行,a = {n: 2};,运行以后a的指向变成了一个新地址。

image-20220816053942097

ok,接下来是最关键的一步,解析变量/变量名是从左往右的,但是运行的时候实际顺序是从右往左的,这个时候原先的a.x已经改变了表达方式,如图上的红圈,这个部分是a.x,而不是这个栈内存中的a指针所指向的地方,js里面很多东西讲的都是值本身,而非指针,所以最后的结果也并不难以理解了。

引用类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 基本的包装类型,特殊的引用类型(Boolean、Number、String)
var s1 = 'some text';
var s2 = s1.substring(2);
console.log(s1, s2) // some text me text

// 想象成
var s3 = new String('some text')
var s4 = s1.substring(2)
s3 = null
console.log(s3, s4) // null 'me text'
// 引用类型和基本包装类型的主要区别就是对象的生存期。使用new操作符创建
// 的引用类型的实例,在执行流离开当前作用域之前一直都保存在内存中。而自
// 动创建的基本包装类型的对象,则只存在于一行代码的执行瞬间,然后立即销
// 毁。这意味着我们不能在运行时为基本类型值添加属性和方法 eg
var s5 = 'some text';
s5.color = 'red'
console.log(s5.color) //undefined

// Object 构造函数也会像工厂方法一样,根据传入值的类型放回相应基本包装实例
var obj = new Object('some text')
console.log(obj instanceof String) // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Boolean 类型
/*
* Boolean 类型与布尔值对应的引用类型。要创建Boolean对象,就可以像下面这样
* 调用Boolean构造函数并传入true或false
* */

var BooleanObject = new Boolean(true)
var falseObject = new Boolean(false)
console.log(falseObject)//boolean对象 Boolean {false} 打印出来是[Boolean: false]
var result = falseObject && true // 对象会转换为true
console.log(result) // true

var falseValue = false
result = falseValue && true
console.log(result) // false
// Boolean对象容易造成误解
// 理解基本类型的布尔值和Boolean对象的区别非常重要,建议永远不要使用Boolean对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Number类型
/*
* Number是与数字值对应的引用类型。要创建Number对象,可以在调用Number构造函数时向
* 中传递对应的数值
* 与Boolean类型一样,Number类型也重写了valueOf()、toLocaleString()和toString()
* 重写后的valueOf()方法返回对象表示基本类型的数值,另外两个方法返回字符串形式的
* 数值
* */
// Number 的方法
/*
* toFixed() 指定的小数返回数值的字符串表示
* toExponent() 返回指数表示法(e表示法)
* toPrecision() 参数表示数值的所有数字的位数(不包括指数部分)
* */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// String类型
/*
* String类型是字符串对象的包装类型
* */
// 字符串方法
/*
* charAt() 以单字符字符串的形式放回给定位置的那个字符 可以用str[1]
* 代替
* charCodeAt() 以单字符字符串的形式放回给定位置的那个字符的字符编码
* */

// 字符串操作方法
/*
* concat 拼接字符串可用+代替
* slice()
* substr()
* substring()
* 效果类似参数不同
* slice substring 第一第二各参数分别表示起始和结束位置
* substr 第二各参数表示返回的字符个数
* slice substr substring 都不会改变原字符串
* */
// 字符串位置方法
/*
* indexOf(从开头向结尾搜索放回-1或字符位置)
* lastIndexOf(从结尾向开头搜索返回-1或字符位置)
* 第二各参数可以表示从哪个位置搜索
* */

// trim() 创建字符串副本,删除前后所有空格

// 大小写转换
/*
* toLowerCase() 转小写
* toLocaleLowerCase() 根据本地语言转小写
* toUpperCase() 转大写
* toLocaleUpperCase() 根据本地语言转大写
* */

// 字符串的模式匹配方法
/*
* match() 返回数组 与调用RegExp对象的exec()方法并传递本例中的字符串
* 作为参数的到的结果一样
* search() 返回字符串中的第一个匹配项的索引;如果没有找到匹配项则返回
* -1否则返回位置,search方法始终是从字符串的开头向后查找
* replace() 替换 根据正则表达式替换字符串,如果要全局替换要在正则表达式
* 中加入g标志
*
* . 在[.]中匹配.字符即\.在此之外匹配所有元素
* */
// 特殊字符序列
/*
* $$ $
* $& 匹配整个模式的子字符串。与RegExp.lastMatch的值相同
* $' 匹配的子字符串之前的子字符串。与RegExp.leftContext的值相同
* $` 匹配的子字符串之后的子字符串。RegExp.rightContext的值相同
* $n 匹配第n个捕获的子字符串,其中n等于0-9。如果正则表达式中没有
* 定义捕获组,则使用空字符串
* $nn 匹配第nn个捕获的子字符串,其中n等于01-99。
* */

var text = 'cat,bat,sat,fat'
result = text.replace(/(.at)/g, 'word($`)')
console.log(result)

// replace 可以通过第二各参数进行更精细的操作
function htmlEscape(text) {
return text.replace(/[<>"&]/g, function (match, pos, originalText) {
switch (match) {
case "<":
return "&lt"
case ">":
return "&gt"
case "&":
return "&amp"
case "\"":
return "&quot"
}
})
}

console.log(htmlEscape('<p class="greeting">hello world!</p>'))
// &ltp class=&quotgreeting&quot&gthello world!&lt/p&gt

// split() 基于指定分隔符将一个字符串分割成多个子字符串,并将结果
// 放到一个数组中 ,可接受第二个参数指定数组的大小

var colorText = "red,blue,green,yellow"
console.log(colorText.split(',')) // ["red","blue","green","yellow"]
console.log(colorText.split(',', 2)) // ["red","blue"]
console.log(colorText.split(/[^\,]+/)) // ["", ",", ",", ",", ""]

// localeCompare() 方法 比较两个字符串返回下列值中的一个
/*
* 如果子字符串在字母表中应该排在子字符串之前,则返回一个负数(大多数情况
* 是-1,具体值要视情况而定)
* 如果子字符串等于子字符串,则返回0
* 如果字符创在字母表中应该排在子字符串之后,则返回一个正数(大多数情况是
* 1,具体视情况而定)
* */
var stringValue = 'yellow'
console.log(stringValue.localeCompare('brick')) // 1
console.log(stringValue.localeCompare('yellow')) // 0
console.log(stringValue.localeCompare('zoo')) //-1

// fromCharCode() //接收一或多个字符编码,然后将他们换成一个字符串。从本质
// 上看这个方法与charCodeAt()执行的是相反的操作

// Global对象
// URL编码方法
/*
* encodeURL() 主要用于整个url
* encodeURLComponent() 对url中的一部分进行转码
* 将url转换为浏览器可识别的字符串
*
* 一般来说,我们使用encodeURLComponent()方法要比使用encodeURL要多,因为常见
* 是处理url的一部分
*
* 相对的
* decodeURL() 和decodeURLComponent() 是对上面的两种方法进行解码
* */
// eval()
/*
* 能够解释代码字符串的能力非常强大,但也非常危险。因此使用eval()时必须极为谨慎,
* 特别是在用他执行用户输入数据的情况下。否则可能会有恶意用户输入威胁你的站点或
* 应用程序安全的代码(即所谓的代码注入)
* */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// Math 对象
/*
* Math.E 自然对数的底数
* Math.LN10 10的自然对数
* Math.LN2 2的自然对数
* Math.LOG2E 以2为底e的对数
* Math.LOG10E 以10为底e的对数
* Math.PI PI的值
* Math.SQRT1_2 1/2的平方根
* Math.SQRT2 2的平方根
* */
// 最大值与最小值
// min() 和 max() 方法

// 舍入方法
/*
* Math.ceil() 向上舍入 25.9 -> 26 25.5-> 26 25.1 ->26
* Math.floor() 向下舍入 25.9 -> 25 25.5 ->25 25.1 ->25
* Math,round() 四舍五入
* */

// random() 方法 返回[0,1)之间的值

// 取[min,max]之间的值
function selectFrom(min, max) {
var choices = max - min + 1;
return Math.floor(Math.random() * choices + min)
}

console.log(selectFrom(2, 10))// [2,10]

// 其他方法
/*
* Math.abs(num) 绝对值
* Math.exp(num) Math.E的num次幂
* Math.log(num) num的自然对数
* Math.log(num,power) num 的power次幂
* math.sqrt(num) num的平方根
* Math.acos(x) x的反余弦值
* Math.asin(x) x的反正弦
* Math.atan(x) x的反正切
* Math.atan2(y,x) y/x的反正切
* Math.cos(x) x的余弦
* Math.sin(x) x的正弦
* Math.tan(x) x的正切
* */

面向对象

虽然说在JavaScript编程语言中,函数是第一公民,但是JavaScript不仅支持函数式编程,也支持面向对象编程。JavaScript对象设计成了一组属性的无序集合,由key和value组成,key为一个标识符名称,而value可以是任意类型的值,当函数作为对象的属性值时,这个函数就可以称之为对象的方法。

1.JavaScript创建对象的方式

一般地,常用于创建对象的方式有两种,早期经常使用Object类,通过new关键字来创建一个对象,有点类似于Java中创建对象,后来为了方便就直接使用对象字面量的方式来创建对象了,用法更为简洁。

  • 使用Object类创建对象;

    1
    2
    3
    4
    const obj = new Object() // 创建一个空对象
    // 往对象中添加属性
    obj.name = 'curry'
    obj.age = 30
  • 使用对象字面量创建对象;

    1
    2
    3
    4
    5
    // 直接往{}添加键值对
    const obj = {
    name: 'curry',
    age: 30
    }
2.对象属性操作的控制

对象创建出来后,如何对该对象进行操作控制呢?这里涉及到一个很重要的方法:Object.defineProperty()。

2.1.Object.defineProperty()

该方法可以在对象上定义一个新的属性,也可修 改对象现有属性,并将该对象返回。

1
Object.defineProperty(obj, prop, descriptor)

接收三个参数:

  • obj:指定操作的对象;
  • prop:指定需要定义或修改的属性名称;
  • description:定义或修改的属性描述符;
2.2.属性描述符的分类

什么是属性描述符?顾名思义就是对对象中的属性进行描述,简单来说就是给对象某个属性指定一些规则。属性描述符主要分为数据属性描述符存取属性描述符两种类型。

对于属性描述符中的属性是否两者都可以设置呢?其实数据和存取属性描述符两者是有区别,下面的表格统计了两者可用和不可用的属性:

属性 configurable enumerable value writable get set
数据属性描述符 可以 可以 可以 可以 不可以 不可以
存取属性描述符 可以 可以 不可以 不可以 可以 可以

那么为什么有些属性可以用,有些属性又不能用呢?因为数据属性描述符和存取属性描述符所担任的角色不一样,下面就来详细介绍一下,它们两者的区别。

2.3.数据属性描述符

从上面的表格可以知道,数据属性描述符可以使用configurable、enumerable、value、writable。而这就是数据属性描述符的四个特性。

  • Configurable:表示是否可以通过delete删除对象属性,是否可以修改它的特性,或者是否可以将它修改为存取属性描述符。当通过new Object()或者字面量的方式创建对象时,其中的属性的configurable默认为true,当通过属性描述符定义一个属性时,其属性的configurable默认为false
  • Enumerable:表示是否可以通过for-in或者Object.keys()返回该属性。当通过new Object()或者字面量的方式创建对象时,其中的属性的enumerable默认为true,当通过属性描述符定义一个属性时,其属性的enumerable默认为false
  • Writable:表示是否可以修改属性的值。当通过new Object()或者字面量的方式创建对象时,其中的属性的writable性描述符定义一个属性时,其属性的writable默认为false
  • Value:属性的value值,读取属性时会返回该值,修改属性时会对其进行修改。(默认:undefined)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const obj = {
name: 'curry'
}

Object.defineProperty(obj, 'age', {
configurable: false, // age属性是否可以删除,默认false
enumerable: false, // age属性是否可以枚举,默认false
writable: false, // age属性是否可以写入(修改),默认false
value: 30 // age属性的值,默认undefined
})

// 当configurable为false,age属性是不可被删除的
delete obj.age
console.log(obj) // { name: 'curry', age: 30 }

// 当writable为false,age属性的值是不可被修改的
obj.age = 18
console.log(obj) // { name: 'curry', age: 30 }
1
2
3
4
// 如果将enumerable修改为false,age属性是不可以被遍历出来的
for (const key in obj) {
console.log(key) // name
}
2.4.存取属性描述符

存取属性描述符可以使用configurable、enumerable、get、set。在获取对象某个属性值时,可以通过get来拦截,在设置对象某个属性值时,可以通过set来拦截。configurable和enumerable的用法和特性跟数据属性描述符一样。

  • Get:获取属性时会执行的函数。(默认undefined)
  • Set:设置属性时会执行的函数。(默认undefined)

get和set的使用场景:

  • 隐藏某一个私有属性,不希望直接被外界使用和赋值。如下代码_age表示不想直接被外界使用,外界就可以通过使用age的set和get来访问设置_age了。

  • 如果希望截获某一个属性它访问和设置值的过程。(Vue2的响应式原理就在这)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    const obj = {
    name: 'curry',
    _age: 30
    }

    // 注意:这里的this是指向obj对象的
    Object.defineProperty(obj, 'age', {
    configurable: true,
    enumerable: true,
    get: function() {
    console.log('age属性被访问了')
    return this._age
    },
    set: function(newValue) {
    console.log('age属性被设置了')
    this._age = newValue
    }
    })

    obj.age // age属性被访问了
    obj.age = 18 // age属性被设置了
2.5.同时给多个属性定义属性描述符

上面使用Object.defineProperty()方法都是给单个属性进行定义描述符,想要一次性定义多个属性,那么就可以使用Object.defineProperties()方法了。写法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Object.defineProperties(obj, {
name: {
configurable: true,
enumerable: true,
writable: true,
value: 'curry'
},
age: {
configurable: false,
enumerable: false,
get: function() {
return this._age
},
set: function(newValue) {
this._age = newValue
}
}
})
3.Object中常用的方法

上面介绍了Object中definePropertydefineProperties两个方法。其实Object中还有很多方法,下面介绍一些常用的。

  • 获取对象的属性描述符:

    • 获取单个属性:Object.getOwnPropertyDescriptor
    • 获取所有属性:Object.getOwnPropertyDescriptors
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    const obj = {
    name: 'curry',
    age: 30
    }

    console.log(Object.getOwnPropertyDescriptor(obj, 'age')) // { value: 30, writable: true, enumerable: true, configurable: true }
    console.log(Object.getOwnPropertyDescriptors(obj))
    /*
    {
    name: {
    value: 'curry',
    writable: true,
    enumerable: true,
    configurable: true
    },
    age: { value: 30, writable: true, enumerable: true, configurable: true }
    }
    */
  • Object.preventExtensions():禁止对象扩展新属性,给一个对象添加新的属性会失败(在严格模式下会报错)。

  • Object.seal():将对象密封起来,不允许配置和删除属性。(实际还是调用preventExtensions,并且将现有属性的configurable设置为false

  • Object.freeze():将对象冻结起来,不允许修改对象现有属性。(实际上是调用seal,并且将现有属性的writable设置为false

4.JavaScript创建多个对象

上面提到的创建对象的方式仅适用于创建单个对象适用,如果有多个对象比较类似,那么一个个创建必然是很麻烦的,如何批量创建对象呢?JavaScript也给我们提供了一些方案。

4.1.方案一:工厂函数

如果我们不想在创建对象时做重复的工作,那么就可以定义一个函数为我们去做这些重复性的工作,我们只需要将属性对应的值传入函数即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function createObj(name, age) {
// 创建一个空对象
const obj = {}

// 设置对应属性值
obj.name = name
obj.age = age
// 公共方法共用
obj.sayHello = function() {
console.log(`My name is ${this.namename}, I'm ${this.age} years old.`)
}

// 将对象返回
return obj
}

const obj1 = createObj('curry', 30)
const obj2 = createObj('kobe', 24)
console.log(obj1) // { name: 'curry', age: 30, sayHello: [Function (anonymous)] }
console.log(obj2) // { name: 'kobe', age: 24, sayHello: [Function (anonymous)] }
obj1.sayHello() // My name is undefined, I'm 30 years old.
obj2.sayHello() // My name is undefined, I'm 24 years old.

缺点:创建出来的对象全是通过字面量创建的,获取不到对象真实的类型。

4.2.方案二:构造函数

(1)什么是构造函数?

  • 构造函数也称之为构造器(constructor),通常是我们在创建对象时会调用的函数;
  • 在其他面向对象的编程语言里面,构造函数是存在于类中的一个方法,称之为构造方法;
  • 如果一个普通的函数被使用new操作符来调用了,那么这个函数就称之为是一个构造函数;
  • 一般规定构造函数的函数名首字母大写;

(2)new操作符调用函数的作用

当一个函数被new操作符调用了,默认会进行如下几部操作:

  • 在内存中创建一个新的对象(空对象);
  • 这个对象内部的**[[prototype]]属性会被赋值为该构造函数的prototype属性**;
  • 构造函数内部的this,会指向创建出来的新对象
  • 执行函数的内部代码(函数体代码);
  • 如果构造函数没有返回对象,则默认返回创建出来的新对象。

(3)构造函数创建对象的过程

  • 通过构造函数创建的对象就真实的类型了,如下所示的Person类型;
1
2
3
4
5
6
7
8
9
10
11
12
13
function Person(name, age) {
this.name = name
this.age = age

this.sayHello = function() {
console.log(`My name is ${this.name}, I'm ${this.age} years old.`)
}
}

const p1 = new Person('curry', 30)
const p2 = new Person('kobe', 24)
console.log(p1) // Person { name: 'curry', age: 30, sayHello: [Function (anonymous)] }
console.log(p2) // Person { name: 'kobe', age: 24, sayHello: [Function (anonymous)] }

缺点:在每次使用new创建新对象时,会重新给每个对象创建新的属性,包括对象中方法,实际上,对象中的方法是可以共用的,消耗了不必要的内存。

1
console.log(p1.sayHello === p2.sayHello) // false
4.3.方案三:原型+构造函数

在了解该方案之前,需要先简单的认识一下何为原型。

(1)对象的原型

JavaScript中每个对象都有一个特殊的内置属性[[prototype]](我们称之为隐式原型),这个特殊的属性指向另外一个对象。那么这个属性有什么用呢?

  • 前面介绍了,当我们通过对象的key来获取对应的value时,会触发对象的get操作;
  • 首先,get操作会先查看该对象自身是否有对应的属性,如果有就找到并返回其值;
  • 如果在对象自身没有找到该属性就会去对象的[[prototype]]这个内置属性中查找;

那么对象的[[prototype]]属性怎么获取呢?主要有两种方法:

  • 通过对象的__proto__属性访问;
  • 通过Object.getPrototypeOf()方法获取;
1
2
3
4
5
6
7
const obj = {
name: 'curry',
age: 30
}

console.log(obj.__proto__)
console.log(Object.getPrototypeOf(obj))

img

(2)函数的原型

所有的函数都有一个prototype属性,并且只有函数才有这个属性。前面提到了new操作符是如何在内存中创建一个对象,并给我们返回创建出来的对象,其中第二步这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性。将代码与图结合,来看一下具体的过程。

示例代码:

1
2
3
4
5
6
7
8
9
10
function Person(name, age) {
this.name = name
this.age = age
}

const p1 = new Person('curry', 30)
const p2 = new Person('kobe', 24)
// 验证:对象(p1\p2)内部的[[prototype]]属性(__proto__)会被赋值为该构造函数(Person)的prototype属性;
console.log(p1.__proto__ === Person.prototype) // true
console.log(p2.__proto__ === Person.prototype) // true

内存表现:

  • p1和p2的原型都指向Person函数的prototype原型;
  • 其中还有一个constructor属性,默认原型上都会有这个属性,并且指向当前的函数对象;

img

(3)结合对象和函数的原型,创建对象

先简单的总结一下:

  • 前面使用构造函数创建对象的缺点是对象中的方法不能共用;
  • 对象的属性可以通过[[prototype]]隐式原型进行查找;
  • 构造函数创建出来的对象[[prototype]]与构造函数prototype指向同一个对象(同一个地址空间);
  • 那么我们可以将普通的属性放在构造函数的内部,将方法放在构造函数的原型上,当查找方法时,就都会去到构造函数的原型上,从而实现方法共用;
1
2
3
4
5
6
7
8
9
10
11
12
13
function Person(name, age) {
this.name = name
this.age = age
}

Person.prototype.sayHello = function() {
console.log(`My name is ${this.name}, I'm ${this.age} years old.`)
}

const p1 = new Person('curry', 30)
const p2 = new Person('kobe', 24)

console.log(p1.sayHello === p2.sayHello) // true
4.4.其他

创建对象的模式还有许许多多,包括不限于动态原型模式寄生构造函数模式稳妥构造函数模式、``

原型模式

原型模式是一种设计模式,那么为什么要在JavaScript这章里面单独拿出来讲呢,因为js可以归纳为是一门基于原型的面向对象语言。基于原型这个概念是深入进js的基因的,所以在这里单独开一章。

简单地说,JavaScript 是基于原型的语言。当我们调用一个对象的属性时,如果对象没有该属性,JavaScript 解释器就会从对象的原型对象上去找该属性,如果原型上也没有该属性,那就去找原型的原型,直到最后返回null为止,null没有原型。这种属性查找的方式被称为原型链(prototype chain)。

JavaScript原型 & 原型链

preview

首先来一个总纲,总纲里面包括了原型/原型链的所有内容,然后我们把图拆解。

  • 想要弄清楚原型和原型链,这几个属性必须要搞清楚,__proto__prototypeconstructor
  • 其次你要知道js中对象和函数的关系,函数其实是对象的一种。
  • 最后你要知道函数、构造函数的区别,任何函数都可以作为构造函数,但是并不能将任意函数叫做构造函数,只有当一个函数通过new关键字调用的时候才可以成为构造函数。如:
1
2
3
4
5
6
7
var Parent = function(){

}
//定义一个函数,那它只是一个普通的函数,下面我们让这个函数变得不普通
var p1 = new Parent();
//这时这个Parent就不是普通的函数了,它现在是一个构造函数。因为通过new关键字调用了它
//创建了一个Parent构造函数的实例 p1

我们再引出一个概念,开始说过了要想清楚原型就要先搞清楚这三个属性,__proto__prototypeconstructor

  • 我们记住两点

1.__proto__constructor属性是对象所独有的;
2.prototype属性是函数独有的;
3.上面说过js中函数也是对象的一种,那么函数同样也有属性__proto__constructor

下面开始进入正题,我将上面的一张图拆分成3张图,分别讲解对应的3个属性。

1.prototype属性

为了方便举例,我们在这模拟一个场景,父类比作师父,子类比作徒弟。师父收徒弟,
徒弟还可以收徒弟。徒弟可以得到师父传授的武功,然后徒弟再传给自己的徒弟。
师父想要传授给徒弟们的武功就放到“prototype”这个琅琊福地中。徒弟徒孙们就去这里学习武功。

prototype属性可以看成是一块特殊的存储空间,存储了供“徒弟”、“徒孙”们使用的方法和属性。

preview

它是函数独有的属性,从图中可以看到它从一个函数指向另一个对象,代表这个对象是这个函数的原型对象,这个对象也是当前函数所创建的实例的原型对象。
prototype设计之初就是为了实现继承,让由特定函数创建的所有实例共享属性和方法,也可以说是让某一个构造函数实例化的所有对象可以找到公共的方法和属性。有了prototype我们不需要为每一个实例创建重复的属性方法,而是将属性方法创建在构造函数的原型对象上(prototype)。那些不需要共享的才创建在构造函数中。
继续引用上面的代码,当我们想为通过Parent实例化的所有实例添加一个共享的属性时,

1
Parent.prototype.name = "我是原型属性,所有实例都可以读取到我";

这就是原型属性,当然你也可以添加原型方法。那问题来了,p1怎么知道他的原型对象上有这个方法呢,往下看↓↓↓

2.proto属性

__proto__属性相当于通往prototype(“琅琊福地”)唯一的路(指针)
让“徒弟”、“徒孙” 们找到自己“师父”、“师父的师父” 提供给自己的方法和属性

preview

__proto__属性是对象(包括函数)独有的。从图中可以看到__proto__属性是从一个对象指向另一个对象,即从一个对象指向该对象的原型对象(也可以理解为父对象)。显然它的含义就是告诉我们一个对象的原型对象是谁。
prototype篇章我们说到,Parent.prototype上添加的属性和方法叫做原型属性和原型方法,该构造函数的实例都可以访问调用。那这个构造函数的原型对象上的属性和方法,怎么能和构造函数的实例联系在一起呢,就是通过__proto__属性。每个对象都有__proto__属性,该属性指向的就是该对象的原型对象。

1
p1.__proto__ === Parent.prototype; // true

__proto__通常称为隐式原型,prototype通常称为显式原型,那我们可以说一个对象的隐式原型指向了该对象的构造函数的显式原型。那么我们在显式原型上定义的属性方法,通过隐式原型传递给了构造函数的实例。这样一来实例就能很容易的访问到构造函数原型上的方法和属性了。
我们之前也说过__proto__属性是对象(包括函数)独有的,那么Parent.prototype也是对象,那它有隐式原型么?又指向谁?

1
Parent.prototype.__proto__ === Object.prototype; //true

可以看到,构造函数的原型对象上的隐式原型对象指向了Object的原型对象。那么Parent的原型对象就继承了Object的原型对象。由此我们可以验证一个结论,万物继承自Object.prototype。这也就是为什么我们可以实例化一个对象,并且可以调用该对象上没有的属性和方法了。如:

1
2
//我们并没有在Parent中定义任何方法属性,但是我们可以调用
p1.toString();//hasOwnProperty 等等的一些方法

我们可以调用很多我们没有定义的方法,这些方法是哪来的呢?现在引出原型链的概念,当我们调用p1.toString()的时候,先在p1对象本身寻找,没有找到则通过p1.__proto__找到了原型对象Parent.prototype,也没有找到,又通过Parent.prototype.__proto__找到了上一层原型对象Object.prototype。在这一层找到了toString方法。返回该方法供p1使用。
当然如果找到Object.prototype上也没找到,就在Object.prototype.__proto__中寻找,但是Object.prototype.__proto__ === null所以就返回undefined。这就是为什么当访问对象中一个不存在的属性时,返回undefined了。

3.constructor属性

constructor属性是让“徒弟”、“徒孙” 们知道是谁创造了自己,这里可不是“师父”啊
而是自己的父母,父母创造了自己,父母又是由上一辈人创造的,……追溯到头就是Function() 【女娲】。


补丁:上面的说法严格来讲并不正确,明显在逻辑上有一些不通顺的地方,那是因为:constructor属性本质上只有prototype对象才有,也就是说构造函数.prototype.contructor === 该构造函数,实例对象之所以会有是只是通过_proto继承来自其构造函数的原型对象。

所以p1 => Parent() => Function()这里的contructor属性应该用虚线表示,注明来自继承得来。

可以通过

1
2
p1.hasOwnProperty('constructor') // false
Parent.prototype.hasOwnProperty('constructor') // true

得到验证。

preview

constructor是对象才有的属性,从图中看到它是从一个对象指向一个函数的。指向的函数就是该对象的构造函数。每个对象都有构造函数,好比我们上面的代码p1就是一个对象,那p1的构造函数是谁呢?我们打印一下。

1
console.log(p1.constructor); // ƒ Parent(){}

通过输出结果看到,很显然是Parent函数。我们有说过函数也是对象,那Parent函数是不是也有构造函数呢?显然是有的。再次打印下。

1
console.log(Parent.constructor); // ƒ Function() { [native code] }

通过输出看到Parent函数的构造函数是Function(),这点也不奇怪,因为我们每次定义函数其实都是调用了new Function(),下面两种效果是一样的。

1
2
3
4
var fn1 = new Function('msg','alert(msg)');
function fn1(msg){
alert(msg);
}

那么我们再回来看下,再次打印Function.constructor

1
console.log(Function.constructor); // ƒ Function() { [native code] }

可以看到Function函数的构造函数就是本身了,那我们也就可以说Function是所有函数的根构造函数。
到这里我们已经对constructor属性有了一个初步的认识,它的作用是从一个对象指向一个函数,这个函数就是该对象的构造函数。通过栗子我们可以看到,p1constructor属性指向了Parent,那么Parent就是p1的构造函数。同样Parentconstructor属性指向了Function,那么Function就是Parent的构造函数,然后又验证了Function就是根构造函数。

脑筋急转弯之 存在没有原型的对象吗?使用Objetct.create(null)可以创建出没有原型的对象。

4.总结

ok,上面的比喻是为了初学者能够方便的理解原型和原型链,下面梳理一下流程。

img

在这个过程里面,关于prototype我觉得有两个理解含义,在箭头上面的prototype有点类似于.prototype指针的意思,而Person.prototype毫无疑问就是常规意义上的原型对象。

5.使用习惯

关于构造函数,咱们引入一些使用习惯:

1
2
3
4
5
6
7
8
9
10
funcion A(name) {
this.name = name; // 实例基本属性 (该属性,强调私有,不共享)
this.arr = [1]; // 实例引用属性 (该属性,强调私用,不共享)
this.say = function() { // 实例引用属性 (该属性,强调复用,需要共享)
console.log('hello')
}
}
//注意:数组和方法都属于‘实例引用属性’,但是数组强调私有、不共享的。方法需要复用、共享。

//注意:在构造函数中,一般很少有数组形式的引用属性,大部分情况都是:基本属性 + 方法。

原型对象的使用习惯

原型对象的用途是为每个实例对象存储共享的方法和属性,它仅仅是一个普通对象而已。并且所有的实例是共享同一个原型对象,因此有别于实例方法或属性,原型对象仅有一份。而实例有很多份,且实例属性和方法是独立的。

在构造函数中:为了属性(实例基本属性)的私有性、以及方法(实例引用属性)的复用、共享。我们提倡:

  • 将属性封装在构造函数中
  • 将方法定义在原型对象上
1
2
3
4
5
6
funcion A(name) {
this.name = name; // (该属性,强调私有,不共享)
}
A.prototype.say = function() { // 定义在原型对象上的方法 (强调复用,需要共享)
console.log('hello')
}
1
2
3
4
5
6
// 不推荐的写法
A.prototype = {
say: function() {
console.log('hello')
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 不推荐的原因:(原因和上面连续赋值的例子很像)
function Person () {
this.name = 'John';
}
var person = new Person();
Person.prototype.say = function() {
console.log('Hello,' + this.name);
};
// 此时可以正常调用
person.say();//Hello,John

// 如果换成另一种表达方式
function Person () {
this.name = 'John';
}
var person = new Person();
Person.prototype = {
say: function() {
console.log('Hello,' + this.name);
}
};
// 此时无法正常调用 原因很简单,在给Person.prototype重新赋值的时候,person的_proto_并不会跟着Person.prototype一起指向新值,所以在下面调用say,是访问不到的,同样属于是原型里面的知识点。
person.say();//person.say is not a function
JS继承

js中的继承有多种方式,大体上就是两种,一种是基于原型链覆盖的形式、一种是对象冒充也就是通过改变this指向。支持多继承,通过原型链可以一直往上找。

1.原型链继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Parent(){
this.x = 100;
}
Parent.prototype.getX = function getX(){
return this.x;
}
function Child(){
this.y = 200;
}
//原型继承 Child通过原型链可以找到Parent中的私有属性和Parent.prototype中的公有属性
Child.prototype = new Parent;
Child.prototype.getY = function getY(){
return this.y;
}
var child1 = new Child;

优点:父类方法可以复用

缺点:

1.父类所有的引用类型数据(对象,数组)会被子类共享,更改一个子类的数据,其他数据会受到影响,一直变化。

2.子类实例不能给父类构造函数传参

2.构造函数继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Person(){
this.name = '小明'
this.eats = ['苹果']
this.getName = () => {
console.log(this.name)
};
};
Person.prototype.get = () => {
console.log("Person.prototype上的方法")
};

function Student() {
Person.call(this)
}

const stu1 = new Student;
console.log(stu1.name);
console.log(stu1.eats);
stu1.getName();
stu1.get(); // 报错 提示没有这种方法

console.log(stu1)

这种情况下,在Child的构造函数中,使用Parent.call(this)直接调用了Parent的“构造函数”,这里是要突出这个引号,原因是其实只调用了这个函数,严格意义上并不能称之为构造函数,在JS中只有创建了实例的函数才能叫构造函数,但是在此例中并没有构造实例,也就造成了一个问题——因为并没有构造实例,并不会触发Parent.prototype,所以是没法调用到上面的getX的。

这种方式的优点是 父类的引用类型不会被子类共享,不会互相影响。

缺点是不能访问父类的原型属性(因为父类的原型属性根本没被创造出来)

3.组合继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Person(){
this.name = '小明'
this.eats = ['苹果']
this.getName = () => {
console.log(this.name)
};
};
Person.prototype.get = () => {
console.log("Person.prototype上的方法")
};

function Student() {
Person.call(this)
}
Student.prototype = new Person();

const stu1 = new Student;
console.log(stu1.name);
console.log(stu1.eats);
stu1.getName();
stu1.get();

console.log(stu1)

这种方式相当于是上面两种的合体,这种方式在第二种的前提上,真正的把父类的对象创建了,所以自然也就会调用到父类的原型对象。也能用到里面的方法。

但是这个里面也会有一个缺点。

image-20220818101558357

可以看到,只需要一份的数据,在这种继承模式下,会分别在父类和子类里面都有一份,虽然能满足需求但是性能会比较差一点。

4.寄生组合继承

细推上面那种方式,想知道父类里面的重复数据是哪里来的,其实就是父类在创建对象的过程中,Student.prototype = new Person();这里把Person对象创建出来重新加载了一次。如果这个Person构造函数中没有属性的话(只有父类的原型对象有),其实是满足我们要求的。

考虑到这,那么有没有办法能够忽略到父类构造函数中属性的方法呢?

有的,使用解几何题中常见的手段——辅助线(中间函数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function Person(){
this.name = '小明'
this.eats = ['苹果']
this.getName = () => {
console.log(this.name)
};
};
Person.prototype.get = () => {
console.log("Person.prototype上的方法")
};

function Student() {
Person.call(this)
}
// Student.prototype = new Person();
const Fn = function(){}
Fn.prototype = Person.prototype

Student.prototype = new Fn()

const stu1 = new Student;
console.log(stu1.name);
console.log(stu1.eats);
stu1.getName();
stu1.get();

console.log(stu1)

使用Fn指向Person的原型对象,然后新建一个Fn,这样就相当于构建了一个没有属性的父类,等效的。

理论上是比较优秀的方案。

5.ES6自带继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Parent{
constructor(){
this.x=100;
}
getX(){
retrun this.x;
}
}
class Child extends Parent{
constructor(){
super();//类似于call继承super(100,200) 相当于把Parent中的constructor执行,传递了100和200
this.y=200;
}
getY(){
retrun this.y;
}
}

是更新之后,语言自己给出的解决方案。添加了一些class extend等新概念。

函数表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
function functionName(){
// 函数体
}
var functionName2 = function(){
// 函数体
}

// 函数表达式: JavaScript 函数可以通过一个表达式定义,函数表达式可以存储在变量中:
var x = function (a, b) {return a * b};
// 在函数表达式存储在变量后,变量也可作为一个函数使用:
var x = function (a, b) {return a * b};
var z = x(4, 3);

// 在函数表达式存储在变量后,变量也可作为一个函数使用:
var x = function (a, b) {return a * b};
var z = x(4, 3);

// 函数同样可以通过内置的 JavaScript 函数构造器(Function())定义。
var myFunction = new Function("a", "b", "return a * b");
var x = myFunction(4, 3);

// 函数提升(Hoisting) 函数可以在声明之前调用 使用表达式定义函数时无法提升。
myFunction(5);

function myFunction(y) {
return y * y;
}

/* 自调用函数
函数表达式可以 "自调用"。
自调用表达式会自动调用。
如果表达式后面紧跟 () ,则会自动调用。
不能自调用声明的函数。
通过添加括号,来说明它是一个函数表达式:*/
(function () {
var x = "Hello!!"; // 我将调用自己
})();

// 箭头函数
/*(参数1, 参数2, …, 参数N) => { 函数声明 }

(参数1, 参数2, …, 参数N) => 表达式(单一)
相当于:(参数1, 参数2, …, 参数N) =>{ return 表达式; } */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 递归
// arguments.callee 指向正在执行的函数的指针
// 求阶乘
function factorial(num) {
if (num <= 1) {
return 1
} else {
return num * arguments.callee(num - 1)
}
}

// 严格模式下不能访问arguments.callee 从而出错
var factorial2 = (function f(num) {
if (num <= 1) {
return 1
} else {
return num * f(num - 1)
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// 闭包
/*
* 有权访问另一个函数作用域中的变量函数
* 由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。
* 过度使用闭包会导致内存占用过多
* */

// 函数可以访问由函数内部定义的变量,如:
function myFunction() {
var a = 4;
return a * a;
}

// 函数也可以访问函数外部定义的变量,如:
var a = 4;
function myFunction() {
return a * a;
}

/* 后面一个实例中, a 是一个 全局 变量。
在web页面中全局变量属于 window 对象
全局变量可应用于页面上的所有脚本。
在第一个实例中, a 是一个 局部 变量。
局部变量只能用于定义它函数内部。对于其他的函数或脚本代码是不可用的。
全局和局部变量即便名称相同,它们也是两个不同的变量。修改其中一个,不会影响另一个的值。 */

// 计数器困境
var counter = 0;

function add() {
return counter += 1;
}

add();
add();
add();

// 计数器现在为 3

/*计数器数值在执行 add() 函数时发生变化。
但问题来了,页面上的任何脚本都能改变计数器,即便没有调用 add() 函数。
如果我在函数内声明计数器,如果没有调用函数将无法修改计数器的值:*/
function add() {
var counter = 0;
return counter += 1;
}

add();
add();
add();

// 本意是想输出 3, 但事与愿违,输出的都是 1 !

//JavaScript 内嵌函数可以解决该问题。
/*所有函数都能访问全局变量。
实际上,在 JavaScript 中,所有函数都能访问它们上一层的作用域。
JavaScript 支持嵌套函数。嵌套函数可以访问上一层的函数变量。
该实例中,内嵌函数 plus() 可以访问父函数的 counter 变量:*/
function add() {
var counter = 0;
function plus() {counter += 1;}
plus();
return counter;
}

/*如果我们能在外部访问 plus() 函数,这样就能解决计数器的困境。
我们同样需要确保 counter = 0 只执行一次。
我们需要闭包。(closures)*/

var add = (function () {
var counter = 0;
return function () {return counter += 1;}
})();

add();
add();
add();

// 计数器为 3, 变量 add 指定了函数自我调用的返回字值。自我调用函数只执行一次。设置计数器为 0。并返回函数表达式。add变量可以作为一个函数使用。非常棒的部分是它可以访问函数上一层作用域的计数器。这个叫作 JavaScript 闭包。它使得函数拥有私有变量变成可能。计数器受匿名函数的作用域保护,只能通过 add 方法修改。

// 关于this对象
// 在闭包中使用this对象也可能会导致一些问题。this对象是在运行时基于函数的执行
// 环境绑定的:在全局函数中,this等于window,而当函数被作为某个对象的方法调用
// 时,this等于那个对象。不过匿名函数的执行环境具有全局性,因此其this对象通常
// 指向window

BOM

Browser Object Model

BOM的解释是:

1
2
3
4
5
BOM即浏览器对象模型。
BOM提供了独立于内容 而与浏览器窗口进行交互的对象;
由于BOM主要用于管理窗口与窗口之间的通讯,因此其核心对象是window;
BOM由一系列相关的对象构成,并且每个对象都提供了很多方法与属性;
BOM缺乏标准,JavaScript语法的标准化组织是ECMA,DOM的标准化组织是W3C,BOM最初是Netscape浏览器标准的一部分。

使用BOM,开发者可以操控浏览器显示页面之外的部分。而它最独特的地方,就是问题最多的地方,激素它唯一一个没有相关标注的javascript实现。总体来说,BOM主要针对的是浏览器窗口和子窗口,但是通常会把任何特定于浏览器的扩展都归于在BOM的范畴内。下面是一些拓展:

1
2
3
4
5
6
7
弹出新浏览器窗口的能力。
移动、缩放和关闭浏览器窗口的详近信息。
navigator对象,提供关于浏览器的详尽信息。
location对象,提供浏览器加载页面的详尽信息。
screen对象,提供关于用户屏幕分辨率的详尽信息。
performance对象,提供浏览器内存占用、导航行为和时间统计的详尽信息。
对cookie的支持。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/*
* 如果页面中包含框架(frame),则每个框架都拥有自己的window对象,并且保存在frames集合中
* 在frames集合中,可以通过数值索引(从0开开始,从左到右,从上到下)或者框架名来访问相应
* 的window对象。每个window对象都有一个name属性,其中包含框架名称。
* */

// 窗口位置(screenLeft,screenTop)
/*
* moveTo()接收新位置
* moveBy()接收的是水平和垂直方向上移动的像素数
* */

/*
* 窗口大小
* */
/*
* 不同浏览器之间的差异性
* IE9+,Safari和Firefox中,outerWidth,outHeight,返回浏览器窗口本身的尺寸。Opera中,这
* 两个属性的值表示视图容器(Opera中单个标签页对应的浏览器窗口)的大小。innerWidth和
* innerHeight则表示该容器中页面视图区的大小(减去边框宽度)。在Chrome中,outerWidth、
* outerHeight与innerWidth、innerHeight返回相同的值,即视口大小(viewport)大小而非浏览
* 器窗口大小
*
* IE、Firefox,Safari,Opera,和Chrome中document.documentElement.clientWidth 和 document.
* documentElement.clientHeight中保存了页面视口信息。在ie6中,这些属性必须在标准模式下才
* 有效。如果是混杂模式,就必须通过document.body.clientWidth和document.body.clientHeight
* 取得相同信息。而对于混杂模式下的Chrome,则无论通过document.documentElement还是document。
* body中的clientWidth和clientHeight属性都可以获取视口的大小
* */
// 获取页面视口大小
var pageWidth = window.innerWidth,
pageHeight = window.innerHeight;
if (typeof pageWidth !== "number") {
if (document.compatMode === "CSS1Compat") {//是否处于标准模式
pageWidth = document.documentElement.clientWidth;
pageHeight = document.documentElement.clientHeight;
} else {
pageWidth = document.body.clientWidth;
pageHeight = document.body.clientHeight;
}
}
/*
* 在移动端:视口信息保存在document.body.clientWidth和document.body.clientHeight中
* */
/*
* 调整浏览器窗口大小
* resizeTo() 接收浏览器窗口的新宽度和新高度
* resizeBy() 接收新窗口与原窗口的宽高差值
* moveTo() 移动位置
* */

// 导航和打开窗口
// 弹出窗口 window.open()(大多数浏览器会屏蔽窗口弹框)
// 安全限制
// 弹出窗口屏蔽程序

// 间歇调用和超时调用
/*
* 间歇调用:setInterval(function(){},time)
* 超时调用:setTimeout(function(){},time)
* 清除:clearInterval(intervalId) clearTimeout(timer)
* */
/*
* 间歇和超时调用的代码都是在全局作用域中执行的,因此函数中的this的值在非严格
* 模式下指向window对象,在严格模式下是undefined
* */

// 系统对话框
// alert()(提示文本、确认) confirm()(提示文本、取消、确定) prompt(文本、输
// 入框、取消、确认)

// location 对象
/*
* location对象的所有属性
* hash '#contents' 返回URL中的hash(#号后跟零或多个字符),如果URL中不包含散列,
* 则返回空字符串
* host 'www.wrox.com:80' 返回服务器名称和端口号(如果有)
* hostname 'www.wrox.com' 返回不带端口号的服务名称
* href 'http:/www.wrox.com' 返回当前加载页面的完整URL。而location对象的toString()
* 方法也返回这个值
* pathname '/WileyCDA/' 返回URL中的目录和(或)文件名
* port '8080' 返回URL中指定的端口号。如果URL中不包含端口号,则这个属性返回空字符串
* protocol 'http' 返回页面使用的协议。通常是http:或https:
* search '?q=javascript' 返回URL的查询字符串。这个字符串以问号开头
* */

// 查询字符串参数
function getQueryStringArgs() {
//查询字符串并去掉开头的问号
var qs = (location.search.length > 0 ? location.search.substring(1) : ""),
// 保存数据的对象
args = {},
//取得每一项
items = qs.length ? qs.split("&") : [],
item = null,
name = null,
value = null,
//在for循环中使用
i = 0,
len = items.length;
//逐个将每一项添加到args对象中
for (; i < len; i++) {
item = items[i].split("=");
name = decodeURIComponent(item[0]);
value = decodeURIComponent(item[1]);
if (name.length) {
args[name] = value;
}
}
return args;
}

// 位置操作
// location.assign()
// location.href
// window.location
// location.replace() 浏览器位置改变,但不会在历史记录中生成新记录。所以跳转后不能后退

// navigator 用于检测浏览器类型

// 检测插件

// screen 对象表明客户端的能力

// history 对象
/*
*history.go()可以在历史记录中任意跳转
*history.back() 后退
* history.forward() 前进
* history.length 历史记录的数量
* */

客户端检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// 能力检测
/*
* 在实际开发中,应该讲能力检测作为确定下一步解决方案的依据,而不是用它来判断浏览器
* */

// 怪癖检测

// 用户代理检测

// 用户代理字符串检测技术(navigator.userAgent)

var browser={
versions:function(){
var u = navigator.userAgent, app = navigator.appVersion;
return {
trident: u.indexOf('Trident') > -1, //IE内核
presto: u.indexOf('Presto') > -1, //opera内核
webKit: u.indexOf('AppleWebKit') > -1, //苹果、谷歌内核
gecko: u.indexOf('Gecko') > -1 && u.indexOf('KHTML') == -1,//火狐内核
mobile: !!u.match(/AppleWebKit.*Mobile.*/), //是否为移动终端
ios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/), //ios终端
android: u.indexOf('Android') > -1 || u.indexOf('Linux') > -1, //android终端或者uc浏览器
iPhone: u.indexOf('iPhone') > -1 , //是否为iPhone或者QQHD浏览器
iPad: u.indexOf('iPad') > -1, //是否iPad
webApp: u.indexOf('Safari') == -1, //是否web应该程序,没有头部与底部
};
}(),
language:(navigator.browserLanguage || navigator.language).toLowerCase()
}
alert(JSON.stringify(browser));
if (browser.versions.mobile) {//判断是否是移动设备打开。browser代码在下面
var ua = navigator.userAgent.toLowerCase();//获取判断用的对象
if (ua.match(/MicroMessenger/i) === "micromessenger") {
//在微信中打开
alert("微信");
}else if(ua.match(/QQ/i) === "qq"){
alert("qq");
}
if (ua.match(/WeiBo/i) === "weibo") {
//在新浪微博客户端打开
alert("新浪微博客户端");
}
if (browser.versions.ios) {
//是否在IOS浏览器打开
alert("ios");
}
if(browser.versions.android){
//是否在安卓浏览器打开
alert("安卓浏览器");
}
}else {
//否则就是PC浏览器打开
alert("pc");
}

DOM

当网页被加载时,浏览器会创建页面的文档对象模型(Document Object Model)。

HTML DOM 模型被构造为对象的树:

image-20220819111052132

通过可编程的对象模型,JavaScript 获得了足够的能力来创建动态的 HTML。

  • JavaScript 能够改变页面中的所有 HTML 元素
  • JavaScript 能够改变页面中的所有 HTML 属性
  • JavaScript 能够改变页面中的所有 CSS 样式
  • JavaScript 能够对页面中的所有事件做出反应
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
// DOM(文档对象模型)
/*
* 是针对HTML和XML文档的一个API(应用程序编程接口)。dom描绘了一个层次化的节点树,允许开发人员
* 添加、移除和修改页面的某一部分。dom脱胎于DHTML(动态HTML)。
* */

// node类型
// nodeType
/*
Node.ELEMENT_NODE (1)
Node.ATTRIBUTE_NODE (2)
Node.TEXT_NODE (3)
Node.CDATA_SECTION_NODE (4)
Node.ENTITY_REFERENCE_NODE (5)
Node.ENTITY_NODE (6)
Node.PROCESSING_INSTRUCTION_NODE (7)
Node.COMMENT_NODE (8)
Node.DOCUMENT_NODE (9)
Node.DOCUMENT_TYPE_NODE (10)
Node.DOCUMENT_FRAGMENT_NODE (11)
Node.NOTATION_NODE (12)
*/
// nodeName(标签名) nodeValue(null)

// node 节点之间的关系
/*
* parentNode 父节点
* childNodes 子节点
* firstChild 第一个子节点
* lastChild 最后一个子节点
* nextSibling 下一个子节点
* previousSibling 上一个子节点
* */
/*
* hasChildNodes()
* */

// 操作节点
/*
* appendChild() 向childNodes列表的末尾添加一个节点
* insertBefore() 要插入的节点和作为参照的节点如果参照节点为null则和appendChild()一样
* replaceChild() 替换节点
* removeChild() 移除节点
* cloneNode() 复制节点
* normalize() 处理文档树中的文本节点
* */

// Document
/*
* nodeType 9
* nodeName #document
* nodeValue null
* parentNode null
* ownerDocument null
* */
// 文档信息
// 查找元素
/*
* document.getElementById() 通过ID
* document.getElementsByTagName() 通过标签名
* document.getElementByName() 通过name属性值
* */

// DOM一致性检测
// 文档写入
/*
* write()
* writeln()
* open()
* close()
* */

// Element
/*
* nodeType:1
* nodeName的值为元素的标签名
* nodeValue null
* parentNode Document 或 Element
* */
// html 元素
/*
* id,元素文档中的唯一标识符
* title,有关元素的附加说明信息
* lang,元素内容的语言代码,很少使用
* dir,语言的方向,很少使用
* className,与元素的class特性对应,即为元素指定Css类
* */
// 取得特性
/*
*getAttribute()
*setAttribute()
*removeAttribute()
* */

// 创建元素
/*
* document.createElement()
* */

// 元素的子节点(childNodes)

// text类型
/*
* nodeType 3
* nodeName #text
* nodeValue 节点所包含的文本
* parentNode Element
* appendData(text) 将text添加到节点末尾
* deleteData(offset,count)从offset指定位置删除count个字符
* insertData(offset,text)在offset指定的位置插入text
* replaceData(offset,count,text)用text替换从offset指定位置到offset+count为止处的文本
* splitText(offset) 从offset指定位置将当前文本节点分成两个文本节点
* substringData(offset,count) 提取从offset指定位置开始到offset+count为止处的字符串
* */
// 创建文本节点
/*
* document.createTextNode()
* */

// 规范化文本节点(normalize())
/*
* 将所有文本节点合并成一个节点,结果节点的nodeValue等于合并前每个文本节点的nodeValue值拼
* 起来的值
* */

// 分割文本节点 (splitText()) 与 normalize()相反

// Comment 类型
/*
* nodeType 8
* nodeName #comment
* nodeValue 注释的内容
* parentNode Document或Element
* */
// 创建 document.createComment()

// CDATASection 类型
/*
* nodeType 4
* nodeName #cdata-section
* nodeValue CDATA 区域的内容
* parentNode 可能是Document 或 Element
* */

// DocumentType类型
/*
* nodeType 10
* nodeName doctype的名称
* nodeValue null
* parentNode Document
* */

// DocumentFragment 类型
/*
* nodeType 11
* nodeName #document-fragment
* nodeValue null
* parentNode null
* */

// 创建 document.createDocumentFragment()

// Attr类型
/*
* nodeType 2
* nodeName 特性的名称
* nodeValue 特性的值
* parentNode null
* */
// 创建 document.createAttribute()
/*
* 不建议直接访问特性节点。实际上,使用getAttribute(),setAttribute()和removeAttribute()
* 方法远比操作特性节点方便
* */

// DOM操作技术
// 动态添加脚本
function loadScript(url){
var script = document.createElement("script");
script.type = "text/javascript";
script.src = url;
document.body.appendChild(script);
}
// 动态添加代码
function loadScriptString(code){
var script = document.createElement("script");
script.type = "text/javascript";
try {
script.appendChild(document.createTextNode(code));
} catch (ex){
script.text = code;
}
document.body.appendChild(script);
}
// 动态添加样式表
function loadStyles(url){
var link = document.createElement("link");
link.rel = "stylesheet";
link.type = "text/css";
link.href = url;
var head = document.getElementsByTagName("head")[0];
head.appendChild(link);
}
// 动态添加css
function loadStyleString(css){
var style = document.createElement("style");
style.type = "text/css";
try{
style.appendChild(document.createTextNode(css));
} catch (ex){
style.styleSheet.cssText = css;
}
var head = document.getElementsByTagName("head")[0];
head.appendChild(style);
}
/*
* 如果专门针对IE编写代码,务必小心使用styleSheet.cssText属性。在重用同一个<style>元素
* 并再次设置这个属性时,有可能会导致浏览器奔溃。同样,将cssText属性设置为空字符串也可
* 能导致浏览器奔溃
* */

// 操作表格
// 创建表格
/*
html 创建
<table border="1" width="100%">
<tbody>
<tr>
<td>Cell 1,1</td>
<td>Cell 2,1</td>
</tr>
<tr>
<td>Cell 1,2</td>
<td>Cell 2,2</td>
</tr>
</tbody>
</table>
* */
// 核心dom创建
//创建 table
var table = document.createElement("table");
table.border = '1';
table.width = "100%";
//创建 tbody
var tbody = document.createElement("tbody");
table.appendChild(tbody);

//创建 第一行
var row1 = document.createElement("tr");
tbody.appendChild(row1);
var cell1_1 = document.createElement("td");
cell1_1.appendChild(document.createTextNode("Cell 1,1"));
row1.appendChild(cell1_1);
var cell2_1 = document.createElement("td");
cell2_1.appendChild(document.createTextNode("Cell 2,1"));
row1.appendChild(cell2_1);

//创建 第二行
var row2 = document.createElement("tr");
tbody.appendChild(row2);
var cell1_2 = document.createElement("td");
cell1_2.appendChild(document.createTextNode("Cell 1,2"));
row2.appendChild(cell1_2);
var cell2_2= document.createElement("td");
cell2_2.appendChild(document.createTextNode("Cell 2,2"));
row2.appendChild(cell2_2);

//将表格添加到文档主体中
document.body.appendChild(table);

// HTMLDOM
/*
* 为table元素添加的属性和方法
* caption <caption>元素的指针
* tBodies 是一个<tbody>元素的HTMLCollection
* tFoot tfoot元素指针
* tHead thead元素指针
* rows 表格中所有行的HTMLCollection
* createThead() 创建thead
* createTFoot() 创建tfoot
* createCaption() 创建caption
* deleteThead() 删除thead
* deleteTFoot() 删除tfood
* deleteRow(pos) 删除指定位置行
* insertRow(pos) 向rows集合中的指定位置插入一行
* 为tbody元素添加的属性和方法
* rows 保存着tbody元素中行的HTMLCollection
* deleteRow(pos) 删除指定行
* insertRow(pos) 向rows集合中的指定位置插入一行,返回对新插入的引用
* 为tr元素添加的属性和方法
* cells 保存这tr元素中单元格的HTMLCollection
* deleteCell(pos) ...
* insertCell(pos) ...
* */
// HTMLDOM
//创建 table
var table = document.createElement("table");
table.border = 1;
table.width = "100%";
//创建 tbody
var tbody = document.createElement("tbody");
table.appendChild(tbody);
// 创建第一行

tbody.insertRow(0);
tbody.rows[0].insertCell(0);
tbody.rows[0].cells[0].appendChild(document.createTextNode("Cell 1,1"));
tbody.rows[0].insertCell(1);
tbody.rows[0].cells[1].appendChild(document.createTextNode("Cell 2,1"));

// 创建第二行
tbody.insertRow(1);
tbody.rows[1].insertCell(0);
tbody.rows[1].cells[0].appendChild(document.createTextNode("Cell 1,2"));
tbody.rows[1].insertCell(1);
tbody.rows[1].cells[1].appendChild(document.createTextNode("Cell 2,2"));

//将表格添加到文档主体中
document.body.appendChild(table);

// 使用NodeList
/*
* NodeList、NameNodeMap、HTMLCollection这3个都是动态的;换句话来说,每当文档结构发生变化
* 时,他们都会得到跟新。因此,他们始终会保存这最新、最准确的信息
* */
// 迭代NodeList
var divs = document.getElementsByTagName("div"),
i,
len,
div;
for (i=0, len=divs.length; i < len; i++){
div = document.createElement("div");
document.body.appendChild(div);
}

// 选择符API
/*
querySelector(selector) 接收一个CSS选择符,返回与该模式匹配的第一个元素,如果没有找到匹
配元素,返回null
querySelectorAll(selector) 接收一个CSS选择符,返回一个NodeList的实例,没有匹配NodeList就
是空的
matchesSelector(selector) 接收一个CSS选择符,如果调用元素与选择符匹配则返回true否则返回
false
*/

// 元素遍历
/*
* 对于元素间的空格,IE9以及之前版本不会返回文本节点,而其他所有浏览器都会返回文本节点。这
* 样就导致了使用childNodes和firstChild等属性时的行为不一致。
*
* childElementCount 返回子元素(不包括文本节点和注释)的个数
* firstElementChild 指向第一个子元素:firstChild 元素版
* lastElementChild 指向最后一个子元素;lastChild 元素版
* previousElementSibling 前一个同辈元素 previousSibling 元素版
* nextElementSibling 后一个同辈元素 nextSibling 元素版
* */

// HTML5

// 与类相关的扩充
// 通过class名获取NodeList
/*
* getElementsByClassName() 接收一个参数,包含一个或多个类名的字符串,返回带有指定类的所有
* 元素的NodeList。传入多个类名时,类名的先后不重要
* */
// classList
// 在没有classList之前
//删除"user"
//首先 取得类名字符串并拆分成数组
var classNames = div.className.split(/\s+/);
//找到要删的类名
var pos = -1,
i,
len;
for (i=0, len=classNames.length; i < len; i++){
if (classNames[i] === "user"){
pos = i;
break;
}
}
//删除类名
classNames.splice(i,1);
//把剩下的类名拼成字符串并重新设置
div.className = classNames.join(" ");
// 使用classList
/*
* add(value) 将指定字符串添加到列表中。如果已存在就不添加了
* contains(value) 是否存在给定的值 返回 true或false
* remove(value) 从列表中删除给定的字符串
* toggle(value) 如果列表中已存在给定的值,删除它;不存在添加它
* */
div.classList.remove("disabled");
div.classList.add("current");
div.classList.toggle("user");

// 焦点管理
/*
* document.activeElement
* focus()
* document.hsaFocus()
* */

// HTMLDocument的变化
/*
* readyState 属性 loading 正在加载文档 complete 已加载完毕文档
* compatMode 兼容模式 CSS1Compat 标准模式 BackCompat 混杂模式
* head 属性
* charset 字符集属性
* */

// 自定义数据属性
// <div id="myDiv" data-appId="12345" data-myname="Nicholas"></div>
var div = document.getElementById("myDiv");
//获取自定义属性的值
var appId = div.dataset.appId;
var myName = div.dataset.myname;
//设置值
div.dataset.appId = 23456;
div.dataset.myname = "Michael";
//是否存在mynamez值
if (div.dataset.myname){
console.log("Hello, " + div.dataset.myname);
}

// 插入标记
/*
* innerHTML
* outerHtml
* insertAdjacentHTML 接收两各参数 插入位置和HTML文本 第一个参数必须是下面参数之一
* beforebegin 在元素之前插入一个紧邻的同辈元素
* afterbegin 在当前元素之下插入一个新的子元素或在第一个子元素之前再插入新的子元素
* beforeend 在当前元素之下插入一个新的子元素或在第一个子元素之后再插入新的子元素
* afterend 在当前元素之后插入一个紧邻的同辈元素
* */

// scrollIntoView() true
// document.forms[0].scrollIntoView();

// 文档模式
// <meta http-equiv="X-UA-Compatible" content="IE=IEVersion">
// IEVersion 可以是 任意IE版本浏览器如7,8,9
/*
* 强制IE用某种模式渲染
* */

// children 属性
// contains() 方法 判断一个节点是否包含另一个节点

// 插入文本
/*
* innerText
* */

/*
* innerText和textContent返回的内容并不完全一样。innerText会忽略行内样式和脚本,
* 而textContent则会像返回其他文本一样返回行内的样式和脚本代码。
* */
/*
* outerText 属性 作用范围扩大到了调用它的节点,其他和innerText一样
* */

// 滚动
/*
* scrollIntoViewIfNeeded(alignCenter) (Safari,chrome)
* scrollByLines(lineCount) (Safari,chrome)
* scrollByPages(pageCount) (Safari,chrome)
* */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
// dom 变化
// 访问元素的样式
// style
// dom 2 添加的属性和方法
/*
* cssText 访问style特性中的css代码
* length css属性个数
* parentRule 表示CSS信息的CSSRule对象
* getPropertyCSSValue(propertyName) 返回包含给定属性的CSSValue对象
* getPropertyPriority(propertyName) 如果给定了!important返回important 否则返回空字符串
* getPropertyValue(propertyName) 给定属性的字符串值
* item(index) 返回给定位置的CSS属性名称
* removeProperty(propertyName) 从样式中删除给定属性
* setProperty(propertyName,value,priority) 将给定属性设置为相应的值,并加上优先级标志
* (important 或一个空字符串)
* */

// getComputedStyle() 获取style和从css中设置的样式

// 操作样式表

// 元素大小

// 偏移量
/*
* offsetHeight()(边框、内边距、内容)
* offsetWidth()
* offsetLeft()
* offsetTop()
* */
/*
* 所有这些偏移量属性都是只读的,而且每次访问都要重新计算。因此尽量避免重复访问这些值
* */

// 客户区大小
/*
* clientWidth(内容+内边距)
* clientHeight
* */

function getViewport() {
if (document.compatMode === "BackCompat") {
return {
width: document.body.clientWidth,
height: document.body.clientHeight
};
} else {
return {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
};
}
}

// 与偏移量相似,客户区打小也是只读的,每次访问要重新计算

// 滚动大小(包含滚动内容的元素大小)隐藏部分也包含在内
/*
* scrollHeight()
* scrollWidth()
* scrollLeft() 改变滚动位置
* scrollTop()
* */
// 文档总高度
var docHeight = Math.max(document.documentElement.scrollHeight,
document.documentElement.clientHeight);
var docWidth = Math.max(document.documentElement.scrollWidth,
document.documentElement.clientWidth);

// 获取元素大小(跨浏览器)
function getBoundingClientRect(element) {
var scrollTop = document.documentElement.scrollTop;
var scrollLeft = document.documentElement.scrollLeft;
if (element.getBoundingClientRect) {
if (typeof arguments.callee.offset != "number") {
var temp = document.createElement("div");
temp.style.cssText = "position:absolute;left:0;top:0;";
document.body.appendChild(temp);
arguments.callee.offset = -temp.getBoundingClientRect().top - scrollTop;
document.body.removeChild(temp);
temp = null;
}
var rect = element.getBoundingClientRect();
var offset = arguments.callee.offset;
return {
left: rect.left + offset,
right: rect.right + offset,
top: rect.top + offset,
bottom: rect.bottom + offset
};
} else {
var actualLeft = getElementLeft(element);
var actualTop = getElementTop(element);
return {
left: actualLeft - scrollLeft,
right: actualLeft + element.offsetWidth - scrollLeft,
top: actualTop - scrollTop,
bottom: actualTop + element.offsetHeight - scrollTop
}
}
}

// 遍历
// document.createNodeIterator()
// NodeIterator
/*
* root 想作为搜索起点的树中的节点
* whatToShow:表示要访问哪些节点的数字代码
* filter:是一个NodeFilter对象,或者表示应该接受还是拒绝特定节点的函数
* entityReferenceExpansion:布尔值是否要扩展实体引用
* whatToShow:参数是一个位掩码 常量类容定义在NodeFilter中
* NodeFilter.SHOW_ALL 所有节点
* NodeFilter.SHOW_ELEMENT 元素节点
* NodeFilter.SHOW_ATTRIBUTE 特性节点 dom不能使用
* NodeFilter.SHOW_TEXT 文本节点
* NodeFilter.SHOW_COMMENT 注释节点
* NodeFilter.SHOW_DOCUMENT 文档节点
* NodeFilter.SHOW_DOCUMENT_TYPE 文档类型节点
* nextNode()
* previousNode()
* */
// 插入测试dom
var html = `
<div id="div1">
<p><b>Hello</b> world!</p>
<ul>
<li>List item 1</li>
<li>List item 2</li>
<li>List item 3</li>
</ul>
</div>
`
document.body.innerHTML = html
/*var root = document
var filter = {
acceptNode: function(node){
return node.tagName.toLowerCase() === "p" ?
NodeFilter.FILTER_ACCEPT :
NodeFilter.FILTER_SKIP;
}
};
var iterator = document.createNodeIterator(root, NodeFilter.SHOW_ELEMENT,
filter, false);

console.log(iterator.nextNode())
console.log(iterator.previousNode())*/
var div = document.getElementById('div1')
var iterator = document.createNodeIterator(div, NodeFilter.SHOW_ELEMENT, null, false)
var node = iterator.nextNode()
while (node !== null) {
console.log(node.tagName)
node = iterator.nextNode()
}

// TreeWalker
/*
* parentNode()
* firstChild()
* lastChild()
* nextSibling()
* previousSibling()
* */

var div2 = document.getElementById("div1");
var walker = document.createTreeWalker(div2, NodeFilter.SHOW_ELEMENT,
null, false);
walker.firstChild(); // 到p
walker.nextSibling(); // 到ul
var node2 = walker.firstChild(); // 第一个li
while (node2 !== null) {
console.log(node2.tagName); //输出标签名
node2 = walker.nextSibling();
}

// dom中的范围
// 检测 是否支持dom2
var supportsRange = document.implementation.hasFeature("Range", "2.0");
var alsoSupportsRange = (typeof document.createRange === "function");
// 创建
// var range = document.createRange();
// 方法
/*
* startContainer 包含范围的起点
* startOffset 范围在startContainer中起点的偏移量
* endContainer 包含范围终点的节点
* endOffset 范围在endContainer中终点的偏移量
* commonAncestorContainer startContainer、endContainer共同祖先在文档树中位置最深的那个
* */

// 用都没范围实现简单选择
/*
* selectNode() 整个节点
* selectNodeContents() 节点的子节点
* 接收一个dom节点,用节点中的信息填充范围
* */
var range1 = document.createRange(),
range2 = document.createRange();
range1.selectNode(div2)
range2.selectNodeContents(div2)
console.log(range1,range2)
// 方法
/*
* setStartBefore(refNode)
* setStartAfter(refNode)
* setEndBefore(refNode)
* setEndAfter(refNode)
* */

// 用dom范围实现复杂选择
/*
* setStart()
* setEnd()
* 接收节点和偏移量值,setStart节点变成startContainer 偏移量变成startOffset setEnd类似
* */

// 操作DOM范围中的内容

/*
* deleteContents() 删除
* extractContents() 删除并返回删除值
* cloneContents() 创建对象复本
* insertNode() 插入
* */

// 折叠DOM范围
// collapse()

// 比较DOM范围
// compareBoundaryPoints()
/*
* Range.START_TO_START(0)
* Range.START_TO_END(1)
* Range.END_TO_END(2)
* Range.END_TO_START(3)
* */

// 复制DOM范围
// cloneRange()

// 清除DOM范围
// detach()

javascript 资料

现代JavaScript教程 https://zh.javascript.info/

VScode

javascript

windows

快捷键:

多行注释/取消:shift + alt + a

单行注释/取消:ctrl + /