关于基础:不容错过的基础设施专场今天4点关于全场景质量协同平台TOne的介绍-第8696期

龙蜥社区自成立以来,汇聚了超过 350 家来自各厂商以及科研院所等笼罩操作系统全产业链的合作伙伴,同时开发者在短短两年多工夫曾经超过 2 万人。如何让开发者的参加更简略、更高效,如何通过开源社区孵化更多优良的开源我的项目,是咱们继续谋求的指标。现诚邀业内开发者参加龙蜥、参加众测共创口头,打造一个高质量、凋敝的社区! 自 5 月开发者服务 devFree MeetUp 举办后,咱们收到许多开发者的反馈,示意想更深刻理解龙蜥社区基础设施。因而,「龙蜥大讲堂」7 月将持续围绕“基础设施”分享 10 余场技术干货演讲,点击下方海报就可领先看。大家提前扫描下方海报二维码进群,参加互动还有龙蜥精美周边等你来拿。 精彩分享中转 今天直播内容介绍2023 年 07 月 06 日(周四)16:00-17:00,龙蜥社区邀请了工业和信息化部电子第五研究所自动化工具负责人、T-One SIG Maintainer 支智昇分享《全场景品质协同平台 T-One》。本次直播,支智昇将次要介绍 T-One 测试平台的现状和各模块根本状况(会交叉演示如何应用 T-One 进行测试工作);电子五所在参加 T-One SIG 中做的的奉献和 T-One 机构版的倒退状况;T-One SIG 倒退打算等。让新退出 T-One SIG 的同学对 T-One 有一个总体的直观印象。对于长期沉闷在 T-One SIG 的同学,能够探讨、交换 T-One 的劣势和优化方向。 调研问卷咱们想理解您对龙蜥大讲堂-直播的意见和倡议,以便后续给您带来更好的内容和体验。问卷实现工夫约 2-3 分钟,咱们将为认真填写的同学,送出龙蜥精美周边一份,拜访链接即可中转问卷界面:https://openanolis.mikecrm.com/D7SuQpU 小龙舒适揭示:本期龙蜥大讲堂直播回放将在直播完结后一周内上线至龙蜥微信视频号及官网(首页-动静-视频)!往期龙蜥大讲堂视频回放已上线至龙蜥视频号(龙蜥社区小龙),欢送观看,有任何疑难欢送随时征询龙蜥助手—小龙(微信:openanolis_assis)。 —— 完 ——

July 6, 2023 · 1 min · jiezi

关于基础:C面向对象程序设计

基础知识一个类的构造 class Stack {public: bool push( const string& ); int size() { return _stack.size(); };private: vector<string> _stack;};所有member function都必须在class主体内申明。至于是否要定义可自在决定如果要在class主体内定义,则该member function主动视为inline函数如果在class外定义须要加上类名::,如Stack::pop(string &elem) 构造函数构造函数名必须与类名雷同且没有返回值 Triangular (int len, int bp){ _length = len; _beg_pos = bp;}能够应用成员初始化列表定义 Triangular (int len, int bp): _length(len), _beg_pos(bp) {}析构函数开释内存资源,不会有返回值和任何参数 ~Matrix(){ delete []_pmat;}mutable(可变)和const(不变)const member function在参数列表后加const将成员函数申明为const member function,其不可扭转class object的内容 int length() const { return _length; } # in classint Triangular::length() const { return _length; } # out classmutable data member对于如计数器等成员变量,通过const member function调用无奈对其批改值,能够用mutable定义该成员变量 ...

March 30, 2022 · 1 min · jiezi

关于基础:C函数

函数根底函数的前置申明只需写出返回值类型和参数列表类型,不许写出参数名称。应为参数名称只有在函数内应用参数时才是必要的。 bool fun1(int, int &);传址/值(Pass By Reference/ Value)援用为对象的别名,两者在内存中地址雷同。援用必须在初始化时赋值。 int a = 1;int &ra = a;C++不容许扭转援用所代表的对象 int a = 1;int b = 2;int &r = a;r = b; // get value of 2 to r/a当以by reference形式将对象作为函数参数传入时,对象自身并不会复制出一份——复制的是对象的地址。应用理由如下: 心愿间接对传入的对象批改升高复制大型对象的额外负担除非心愿在函数内更改参数值,否则倡议在传递内置类型时,不要应用传址形式。传址机制次要用于传递class object

March 20, 2022 · 1 min · jiezi

关于基础:C-Primer-Plus复合类型

指针和自在存储空间指针是一个变量,其存储的是值的地址,而不是值自身 应用惯例变量时,值是指定的量,而地址是派生量,而指针相同 OOP强调的是在运行阶段(而不是编译阶段)进行决策,提供了灵活性,对于内存治理也更加高效 初始化指针int* ptr_a;double* ptr_b;初始化时必须指定所指元素类型,因为对所有指针来说其都是代表一个初始地址,但从该初始地址读多少字节则由指针类型判断 指针注意事项在C++中创立指针时,计算机将调配用来存储地址的内存,但不会调配用来存储指针所指向的数据的内存(指向不确定)。另外,肯定要在对指针解除援用运算符(*)之前,将指针初始化为一个确定的、适当的地址。 指针不是整型,尽管计算机通常把地址当作整数解决。 int* pt;pt = 0xB80000000; //invalid, type mismatchpt = (int*)0xB80000000; //valid, type match应用new来分配内存//typeName * pointer_name = new typeNameint* pn = new int;new运算符依据类型来确定须要多少字节的内存,找到这样的内存并返回 typeName*指申明什么类型的指针,左右两侧的类型必须雷同,否则因为读取的块大小不同而拜访谬误 变量存储在称为栈(stack)的内存区域中,new从被称为堆(heap)或自在存储(free store)的内存区域分配内存 应用delete开释内存int* ps = new int;delete ps;这将删除ps所指向的内存,但不会删除ps自身,ps还可指向其余内存空间 须要留神的是: 不要应用delete来开释不是new调配的内存不要应用delete开释同一个内存两次对空指针(null pointer)应用delete是平安的应用new创立动静数组int* psome = new int [10];delete [] psome;数组间接通过psome[num]拜访 须要留神的是: 如果应用new [] 为数组分配内存,则须要通过delete []来开释如果应用new为一个实体分配内存,则应应用delete来开释

March 9, 2022 · 1 min · jiezi

关于基础:C-Primer-Plus处理数据

变量类型应用sizeof()能够看到类型占用存储空间 类型占用int4B(2B,3B)char1Bshort2Blong4Blong long8Bfloat4Bdouble6B对于char字符类型类型,内存中存储的是其对应的字符编码(如'A'为65),当其作为char类型被cout输入时输入字符变量,为int输入时为其对应的字符编码。这也是cout智能对象的例子。C++中,字符变量用单引号包裹,字符串用多引号包裹 constant限定符通过constant限定符指定的常量不能批改。该当在申明时定义,否则该常量值将不确定。 constant int Months = 12;与#define的区别: constant可指定类型,define不行constant可通过作用域规定将定义限度在特定的函数或文件中类型转换以下状况将造成类型转换: 将一种算术类型的值赋给类一种算术类型的变量表达式中蕴含不同的类型将参数传递给函数初始化和赋值进行的转换潜在的问题(精度缺失,数值异样)若变量的类型无奈示意赋给它的值,将导致没有定义的状况产生(数值异样)以{ }形式初始化时进行的转换(C++11)这种初始化对类型转换的条件更加严格,列表初始化不容许缩窄(narrowing),即变量的类型可能无奈示意赋给它的值。例如不容许将浮点型转换为整型。在不同的整型之间转化或将整型转化为浮点型可能被容许,条件是编译器晓得指标变量可能正确地存储赋给它的值。对于c4,虽66能被char存储,但因为x为变量,之后能够被扭转,编译器无奈确定其是否能被char示意故invalid。表达式中的转换

March 5, 2022 · 1 min · jiezi

关于基础:C-Primer-Plus开始学习C

程序结构一个简略的C++程序如下所示: // myfirst.cpp -- displays a message#include <iostream> // a PREPROCESSOR directiveint main() // function header{ // start of function body using namespace std; // make definitions visible cout << "Come up and C++ me some time."; // message cout << endl; // start a new line cout << "You won’t regret it!" << endl; // more output return 0; // terminate main()} // end of function body以下对于每个局部别离介绍: main函数通常main函数被启动代码(startup code)调用,而启动代码是由编辑器增加到程序中的,是程序与操作系统间的桥梁。该函数头刻画的是main函数与操作系统间的接口头文件名(Header Filenames)C语言中传统是头文件应用扩展名h。对于纯正的C++头文件,无扩展名h,其不只是模式上的变动,没有h的头文件也能够蕴含命名空间命名空间(namespaces)命名空间规定了变量和函数被援用的地位,从而使得两个不同包可有雷同的变量名,只需指定命名空间即可从两个不同中央援用。对于有h扩展名的头文件,如iostream.h,其变量实际上是std::cin和std::cout,扩展名h申明了命名空间。在C++中可不必扩展名h,但须要应用using namespace显式指明命名空间,对于cin和cout,须要应用using namespace std指定函数函数原型:只申明函数返回值类型,参数个数和类型。只形容函数接口。可通过两种形式实现:一是源代码中定义,二是头文件中定义函数定义:蕴含函数的具体实现 ...

February 27, 2022 · 1 min · jiezi

关于基础:C-Primer-Plus预备知识

面向过程与面向对象 面向过程:在C语言中,采纳自顶向下(top_down)准则,使问题满足语言。将一项大工作拆解成一个个小模块,激励开发函数来示意各个工作模块 面向对象:在C++语言中,采纳自下而上(bottom_up)准则,使语言满足问题。从低级组织模式(类)到高级组织模式(程序) 泛型创立独立于类型的代码,C++模板提供了实现这种工作的机制C++代码如何运行 应用文本编辑器编写程序并保留到文件中,这个文件就是源代码编译源代码,将源代码翻译成机器语言,失去指标代码(object code)将指标代码和其余代码链接起来(例如一些规范库)。链接指将指标代码同应用函数的指标代码以及一些规范的启动代码(startup code)组合起来,生成程序的运行阶段版本,即可执行代码 Linux中的编译与链接 g++编译器编译后失去a.out的可执行文件和对应源代码的两个.o文件 g++ test.cxx test_sub.cxx 如果test.cxx文件批改可只编译该文件,而后和test_sub.o文件链接起来 g++ test.cxx test_sub.o

February 27, 2022 · 1 min · jiezi

关于基础:CSS学习之一Css基础入门

一、css写法内联:在dom标签上的style属性中间接书写。外联:在css文件中书写,通过link引入。页内:在style标签中书写。二、选择器1.分类标签/元素选择器:body{};p{};类选择器: .style1{};.style2{} id选择器: #id1{};#id2{} 通配符选择器:*组合选择器:不便定位款式失效的dom标签伪类和伪选择器:实现一些特殊效果2.优先级惯例:!important > 行内款式>ID选择器 > 类选择器 > 标签 > 通配符 > 继承 > 浏览器默认属性**组合:利用权重叠加比拟,权重最大的会优先失效,权重雷同则就近准则。 1、 !import:1000 2、 id选择器:0100 3、 类选择器:0010 4、 标签选择器:0001 5、 *选择器:0000

January 28, 2021 · 1 min · jiezi

一分钟面试题令人挠头的三目运算符

面试题 提问:当 a=1,b=2 时,test1 方法的执行结果是什么?思考一分钟。 答案: 解析在使用三目运算符时,尽量保证两个返回值的类型一致,不然会触发类型转换,转换规则如下: 如果返回值 X 和返回值 Y 是同种类型,那么返回类型毫无疑问就是这种类型。如果两个返回值 X 和 Y 的类型不同,那么返回值类型为他们两最接近的父类。举例: // String 和 Boolean 都实现了 Serializable 接口Serializable serializable = a == b ? "true" : Boolean.FALSE;// 所有类都继承了 Object 类Object o = a == b ? new ArrayList<>() : new TernaryOperatorDemo();对于基本数据类型,如果其中一个返回值 X 类型为byte、short或者char,另一个返回值 Y 类型为int,那么若在编译期就能判断出 Y 的取值范围在 X 的取值范围之内,则返回类型为 X 的类型,反之则为 Y 的类型。如果返回值 X 类型不为以上几种,则会触发隐藏类型转换。当基本数据类型和对象数据类型相遇时,三目运算符默认返回结果为基本数据类型。了解以上规则之后,我们再看来一下 test1 方法。 private static void test1(int a, int b) { // 触发隐藏类型转换,int 类型 9 转为 9.0D System.out.println(a == b ? 9.9 : 9); // 编译期判断,98 在 char 之内,转为 b System.out.println(a == b ? 'a' : 98); // 编译期判断,超出char范围,统一转 int System.out.println(a == b ? 'a' : Integer.MAX_VALUE); // 编译期时无法判断 b 的取值,触发隐藏类型转换,统一转 int System.out.println(a == b ? 'a' : b); System.out.println(a != b ? 'a' : b); Map<String, Long> map = new HashMap<>(); map.put("b", 1L); // 基本数据类型和对象数据类型相遇时,默认转为基本数据类, // map.get("a") 返回 null,转为基本数据类型时,报空指针异常 System.out.println(map == null ? -1L : map.get("a"));}作业如何修改 test1 方法,使得代码运行时不抛空指针异常,请至少提供一种解决方案哦。 ...

November 4, 2019 · 1 min · jiezi

先验概率和后验概率最简单理解

先验概率:根据客观事实和统计频率得出的概率。后验概率:在事情发生后,在事情发生这个事实下,判断导致这个事情发生的不同原因的概率。后验概率是根据先验概率推断而来的。假设:根据调查问卷(客观事实)显示,人们在不开心的时候,60%的会选择找他人倾诉,40%的选择不倾诉。 $$p(倾诉)=60% $$ $$p(不倾诉)=40% $$ 另外找他人倾诉的人中60%是女性,40%是男性;不找他人倾诉的人中80%是男性,20%是女性。上述就是先验概率,根据频率估算出来的(存在误差)。 $$p(男|倾诉)=40% $$ $$ p(女|倾诉)=60% $$ $$p(男|不倾诉)=80% $$ $$p(女|不倾诉)=20% $$ 现在,有一个男生不开心,想判断他是否会找人倾诉。根据条件概率公式: $$p(Y/X) = p(X/Y)p(Y)/p(X)$$ 得 $$p(倾诉|男) = p(男|倾诉)p(倾诉)/p(男|倾诉)p(倾诉)+p(男|不倾诉)p(不倾诉)$$ p(倾诉|男)即为后验概率( ̄▽ ̄)。

October 1, 2019 · 1 min · jiezi

关于熵的直观理解

任何粒子的常态都是随机运动,也就是“无序运动”,“熵”这个概念,就是“无需运动”的度量,它反映了事物的不确定性,越不确定的事物,它的熵就越大。 $$熵的公式为:H(X) = -\sum_{i=1}^n p_i*logp_i (其中i\in(1,n)为失误的第i个取值,p_i为取值为第i个值的概率) $$ 比如,对于事物A,它的去取值有3个,取到的概率分别为1/6、1/6、2/3。则该事物的熵为 $$-\frac{1}{6}*log\frac{1}{6}-\frac{1}{6}*log\frac{1}{6}-\frac{2}{3}*log\frac{2}{3}$$ 一般情况下,对于有相同取值个数(假设为2个)的事物B和C,如果B取值的概率分布比C均匀,则B的熵会更大。假设B的两个取值概率均为1/2,而C的取值概率为1/3和2/3: $$-2*\frac{1}{2}*log\frac{1}{2} > -\frac{1}{3}*log\frac{1}{3}-\frac{2}{3}*log\frac{2}{3}$$ 一般情况下,事物取值个数多的熵会比取值个数少的熵大。( ̄▽ ̄) 参考博客:https://www.cnblogs.com/pinar...

September 30, 2019 · 1 min · jiezi

Docker入门基础之应用实战

Docker入门基础之应用实战当我们掌握了Docker镜像和容器的基本用法后,我们现在能做些什么事情呢?现在我们就来看看使用Docker容器如何安装常见的软件,然后运行一个动态网站。 下面我们来学习: 1、安装Nginx2、安装PHP3、使用MySQL服务4、运行wordpress博客安装Nginx运行一个Alpine的容器,选择Alpine作为系统基础镜像是因为Alpine轻巧的体积,基础镜像只有5.53MB,相比ubuntu镜像的88.9MB要小十几倍。 root@ubuntu:~# docker run -it -p 8080:80 alpine sh安装nginx apk add nginx修改nginx配置 vi /etc/nginx/conf.d/default.confdefault.conf内容如下: server { listen 80 default_server; root /home/www; index index.php index.html; }创建Hello World mkdir /home/www && echo "Hello World" > /home/www/index.html创建/run/nginx目录 mkdir /run/nginx启动nginx nginx在浏览器中访问 http://192.168.43.122:8080 nginx安装成功,WEB服务访问正常! 安装PHP现在我们来安装PHP,方法还是一样,使用 apk add 命令来安装php7,php-fpm以及相关扩展。 apk add --no-cache php7 php7-fpm php7-ftp php7-pdo php7-mysqli php7-simplexml php7-xmlwriter php7-zlib php7-imagick php7-memcached php7-sockets php7-mcrypt php7-zip php7-pgsql php7-pdo_odbc php7-odbc php7-curl php7-iconv php7-xml php7-json php7-gd php7-session php7-opcache php7-pdo_sqlite php7-mbstring php7-common php7-pdo_mysql以上顺带安装了很多php扩展,可根据实际需求增减。 ...

July 16, 2019 · 1 min · jiezi

JavaScript奇葩语言特性归纳持续更新中

1. 基本概念1.1 数据类型(1)typeof操作符console.log(typeof null); // “object”用typeof操作符去监测null,得到的类型是object。原因是null被认为是一个空的对象引用。 function func(){ // do something}console.log(typeof func); // "function"function虽然不是6大数据类型中的一种,但却可以被typeof操作符监测出来。原因是函数在ECMAScript中是对象,但函数确实有一些特殊的属性,所以需要用typeof来区分。 (2)Undefinedvar message;console.log(message); // undefinedconsole.log(answer); // 报错console.log(typeof message); // undefinedconsole.log(typeof answer); // undefined声明后未初始化的变量默认值为undefined,想获得一个未声明的变量会报错,这很好理解。但是用typeof操作符监测声明后未初始化的变量和一个未声明的变量都会得到undefined。虽然这两种变量从技术角度看有本质区别,但实际上也不可能对它们做任何操作。所以在声明变量的时候显式地初始化是一个好的选择,这样监测出undefined就知道是未声明的变量了。 (3)Nullnull表示的是一个空对象指针,所以如果一个变量在之后准备保存对象,那么初始化的值为null是最合理的。这样只要检查null值就知道是否已经保存了一个对象的引用了。 console.log(undefined == null); // true用相等操作符(=)去比较undefined和null会得到true的结果。原因是undefined实际上是派生自null。[ 这句话我觉得不用理解, 语言就是这么设计的。 ] (4)BooleanECMAScript中所有的值都有与2个boolean值对应的值: 数据类型转换为true转换为falseBooleantruefalseString任何非空字符空字符Number任何非零数字值0和NaNObject任何对象nullUndefinedn/a(不适用)undefined(5)Numbervar floatNum1 = 1.; // 解析为1var floatNum2 = 10.0; // 解析为10由于保存浮点数值需要的内存空间是保存整数值的两倍,因此ECMAScript会不失时机地将浮点数值转换为整数值。如果小数点后面没有跟任何数字,那么这个数值就可以作为整数值来保存。 console.log(0.1 + 0.2); // 0.30000000000000004浮点数计算不精确。基于IEEE754数值浮点计算产生误差,原因是计算机会先把0.1和0.2转化为二进制,再把相加的结果转化为十进制。在两次转化中产生了误差。 由于内存的限制,ECMAScript并不能保存世界上所有的数值。最小数值为5e-324,保存在Number.MIN_VALUE中;最大数值为1.7976931348623157e+308,保存在Number.MAX_VALUE中。如果某次计算的结果得到了一个超出JavaScript数值范围的值,那么这个数值将被自动转换成特殊的infinity具体来说,如果这个数值是负数,则会被转换成-infinity(负无穷),如果这个数值是正数,则会被转换成Infinity (正无穷)。可使用isFinite()函数来确定一个数值是不是又穷的。 console.log(NaN == NaN); // falseNaN,即非数值(Not a Number)是一个特殊的数值,这个数值用于表示一个本来要返回数值的操作数未返回数值的情况(这样就不会抛出错误了)。NaN本身有两个非同寻常的特点:首先,任何涉及NaN的操作(例如NaN/10)都会返冋NaN,这个特点在多步计算中有可能导致问题。其次,NaN与任何值都不相等,包括NaN本身。 这里我们探讨一个问题就是任何数据除以0只会返回2种结果:1个是NaN,一个是Infinity。下面看几个例子: console.log(0/0); // NaNconsole.log(true/0); // Infinityconsole.log(false/0); // NaNconsole.log("2222"/0); // Infinityconsole.log(-1/0); // -Inifinityconsole.log(undefined/0); // NaNconsole.log(null/0); // NaN从这几个例子中可以归纳出几个结论: ...

July 16, 2019 · 1 min · jiezi

Docker入门基础之容器使用

Docker入门基础之容器使用Docker简介Docker 是一个开源的应用容器引擎,基于Go语言并遵从Apache2.0协议开源。 Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。 容器是完全使用沙箱机制,相互之间不会有任何接口(类似 iPhone 的 app),更重要的是容器性能开销极低。 Ubuntu Docker 安装1、Docker官方安装方法Docker 要求 Ubuntu 系统的内核版本高于 3.10 ,查看本页面的前提条件来验证你的 Ubuntu 版本是否支持 Docker。获取安装包: root@ubuntu:~# wget -qO- https://get.docker.com/ | sh安装完成后有提示: If you would like to use Docker as a non-root user, you should now consider adding your user to the "docker" group with something like: sudo usermod -aG docker runoob Remember that you will have to log out and back in for this to take effect! 启动docker服务 ...

July 16, 2019 · 3 min · jiezi

fn1callcallfn2

描述function fn1(){ console.log(1);}function fn2(){ console.log(2);}fn1.call(fn2); // 输出1fn1.call.call(fn2); // 输出2问题看到这个题目,第一反应是蒙圈的。 fn1.call(fn2); 这个是理解的。fn1.call.call(fn2);这个蒙圈了。 理解有些绕,需要多念叨念叨琢磨琢磨。 call 方法是Function.prototype原型上天生自带的方法,所有的函数都可以调用的。 我觉得 call方法本身没有具体return什么出来,所以是undefined。 Function.prototype.call=function call(context){ // [native code] // call方法的功能 // 1. 把指定函数中的this指向context // 2. 把指定函数执行 // 那么call方法中的this,即为指定函数。也就是说 // 1. 把this中 的 this关键字指向context; // 2. 把指定函数执行this();};fn1.call(fn2);按照上面的理解 call 方法中的this是fn1把call方法中的this(fn1)中的this指向fn2调用 call方法中的this所以调用的是 fn1 ,此时fn1中的 this 指向的是 fn2。但是这个方法里面并没有使用this,而是直接输出了1。 fn1.call.call(fn2);按照上面的理解 call 方法中的 this 是 fn1.call【所有函数都可以调用call,调用的是原型上call方法】把call方法中的this (fn1.call) 中的this 指向fn2调用call方法中的this所以调用的是 fn2(这里有些绕,多念叨念叨琢磨琢磨),此时fn1.call中的this指向的是fn2。它改变了call方法(Function.prototype原型上的call)的this指向。此处调用了call方法中的this,即调用了fn2,输出了2。

July 3, 2019 · 1 min · jiezi

二Scala基础知识

Scala基础知识谁适合阅读本教程? 本教程适合想从零开始学习 Scala 编程语言的开发人员,强烈建议你有一定的 JAVA 基础,因为 Scala 从 JAVA 演化而来学习任务1、解释器 2、基础语法 3、函数 4、条件与循环 2.1、Scala解释器2.1.1、 交互模式顾名思义就是在命令行调用scala的基础环境,我们在第一篇演示的命令行输出Hello,World!即是。当然Scala在交互模式下能够做很多的事情。在这里我们只做简单的输出演示。 CTRL+C退出交互模式 2.1.2、脚本模式类似于我们在命令行通过 <font color=#7A67EE>java *.java执行</font>一样,我们通过记事本创建一个文件HelloWorld.scala,存储在指定目录,我存储在了/Users/sunliangliang/Desktop/temp这个目录下,代码如下 object HelloWorld { /* 这是我的第一个Scala项目, * 输出"Hello,Scala!" */ def main(args: Array[String]) { println("Hello, Scala!") }}在命令行执行 scala /Users/sunliangliang/Desktop/temp/HelloWorld.scala,看到如下输出结果 2.2、基础语法2.2.1、基础内容普及区分大小写:大小写敏感,即Scala和scala在程序中含义不同类名:首字母大写,驼峰式方法名:首字母小写,驼峰文件名:文件名要和对象/类名匹配包名:全小写常量名:全大写行结尾: 行结尾的";"可省略空格与缩进:运算符前后保留一个空格如: def main(args: Array[String]): Unit = { print("Hello world") }语法规范基本和java一样,其他可参考java相关规范。 2.2.2、变量与常量1、常量的声明:var 常量名 :数据类型 = 值 scala采用数据类型在后面的方式 var str : String = "Scala"2、变量的声明:val 变量名 : 数据类型 = 值 ,如下图 ...

June 14, 2019 · 3 min · jiezi

JavaScript之call理解

Function.prototype.call()概念call()方法使用一个指定的this值和单独给出的一个或多个参数来调用一个函数。 注意:该方法的语法和作用与apply()方法类似,只有一个区别,就是call()方法接受的是一个参数列表,而apply()方法接受的是一个包含多个参数的数组。JavaScript中的每一个Function对象都有一个apply()方法和一个call()方法,它们的语法分别为:JavaScript Demo: Function.call() function Product(name, price) { this.name = name; this.price = price;}function Food(name, price) { Product.call(this, name, price); this.category = 'food';}console.log(new Food('cheese', 5).name); // 输出: cheese浅显易懂的理解,示例中表达式 Product.call(this, name, price)就等价于 this.Product(name, price)this在此处显然指向对象Food,进一步代入this指向对象和参数为 Food.Product('cheese', 5)语法fun.call(thisArg, arg1, arg2, ...)语法个人理解为:fun.call(thisArg, arg1, arg2, …) 就相当于 thisArg.fun(arg1, arg2, …)而不论this.Arg初始指向对的是否为对象参数: thisArg在fun函数运行时指定的this值。需要注意的是,指定的this值并不一定是该函数执行时真正的this值,如果这个函数在 [非严格模式]的this值会自动指向全局对象(浏览器中就是 window 对象),同时值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象。arg1, arg2, …指定的参数列表。 返回值: 使用调用者提供的this值和参数调用该函数的返回值。若该方法没有返回值,则返回undefined。 描述: call()允许为不同的对象分配和调用属于一个对象的函数/方法。 call()提供新的this值给当前调用的函数/方法。你可以使用call来实现继承:写一个方法,然后让另外一个新的对象来继承它(而不是在新对象中再写一次这个方法)。 示例使用call方法调用父构造函数在一个子构造函数中,你可以通过调用父构造函数的call方法来实现继承,类似于Java中的写法。下例中,使用Food和Toy构造函数创建的对象实例都会拥有在Product构造函数中添加的name属性和price属性,但category属性是在各自的构造函数中定义的。 function Product(name, price) { this.name = name; this.price = price;}function Food(name, price) { Product.call(this, name, price); this.category = 'food';}function Toy(name, price) { Product.call(this, name, price); this.category = 'toy';}var cheese = new Food('feta', 5);var fun = new Toy('robot', 40);使用call方法调用匿名函数在下例中的for循环体内,我们创建了一个匿名函数,然后通过调用该函数的call方法,将每个数组元素作为指定的this值执行了那个匿名函数。这个匿名函数的主要目的是给每个数组元素对象添加一个print方法,这个print方法可以打印出各元素在数组中的正确索引号。当然,这里不是必须得让数组元素作为this值传入那个匿名函数(普通参数就可以),目的是为了演示call的用法。 ...

June 13, 2019 · 2 min · jiezi

Java对象在JVM中的生命周期

概念在Java中,对象的生命周期包括以下几个阶段: 创建阶段(Created)应用阶段(In Use)不可见阶段(Invisible)不可达阶段(Unreachable)收集阶段(Collected)终结阶段(Finalized)对象空间重分配阶段(De-allocated) Java对象在JVM中的生命周期当你通过new语句创建一个java对象时,JVM就会为这个对象分配一块内存空间,只要这个对象被引用变量引用了,那么这个对象就会一直驻留在内存中,否则,它就会结束生命周期,JVM会在合适的时候回收它所占用的内存。 伪代码: class Teacher: //属性 String name; Set<Student> students; //有参构造函数 Teacher(String tname, Set<Student> students ); //setter,getter省略 class Student: //属性 String name; Teacher teachrer; //有参构造函数 Student(String sname,Teacher teacher); //setter,getter省略main: Teacher t = new Teacher("张三",new hashSet()); Student s = new Studnet("李四",null); s.getTeacher(t); t.getStudnet().add(s); s = null; t = null;创建了一个Teacher对象和一个个Student对象,并且定义了2个引用变量t,s,分别引用了Teacher对象,Student对象 建立了Teacher对象和Student对象的双向关联关系,表示Student对象的teacher属性引用了Teacher对象,Teacher对象的students集合存放了Student对象的引用。 把s变量置为了null,s变量不再引用Student对象了,但是Teacher对象的students仍然存放着Student对象的引用,那么Student对象就会结束生命周期;把t变量置为null后,Teacher不再被任何引用变量所引用,自然Teacher对象的引用也不存在了,也就结束了周期。 虽然最后Teacher和Student之前还存在着双向引用,但是在程序中并没有任何引用变量来引用这两个对象,程序也就无法访问到这个两个变量,最终还是被JVM的垃圾回收器当作无用的对象所回收。参考文档:Java对象的生命周期

May 18, 2019 · 1 min · jiezi

javascript 面向对象(实现继承的几种方式)

1、原型链继承核心: 将父类的实例作为子类的原型缺点: 父类新增原型方法/原型属性,子类都能访问到,父类一变其它的都变了 function Person (name) { this.name = name; }; Person.prototype.getName = function () { //对原型进行扩展 return this.name; }; function Parent (age) { this.age = age; }; Parent.prototype = new Person(‘老明’); //这一句是关键 //通过构造器函数创建出一个新对象,把老对象的东西都拿过来。 Parent.prototype.getAge = function () { return this.age; };// Parent.prototype.getName = function () { //可以重写从父类继承来的方法,会优先调用自己的。// console.log(222);// }; var result = new Parent(22); console.log(result.getName()); //老明 //调用了从Person原型中继承来的方法(继承到了当前对象的原型中) console.log(result.getAge()); //22 //调用了从Parent原型中扩展来的方法2、构造继承基本思想借用构造函数的基本思想就是利用call或者apply把父类中通过this指定的属性和方法复制(借用)到子类创建的实例中。因为this对象是在运行时基于函数的执行环境绑定的。也就是说,在全局中,this等于window,而当函数被作为某个对象的方法调用时,this等于那个对象。call、apply 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。所以,这个借用构造函数就是,new对象的时候(new创建的时候,this指向创建的这个实例),创建了一个新的实例对象,并且执行Parent里面的代码,而Parent里面用call调用了Person,也就是说把this指向改成了指向新的实例,所以就会把Person里面的this相关属性和方法赋值到新的实例上,而不是赋值到Person上面,所以所有实例中就拥有了父类定义的这些this的属性和方法。因为属性是绑定到this上面的,所以调用的时候才赋到相应的实例中,各个实例的值就不会互相影响了。核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)缺点: 方法都在构造函数中定义, 只能继承父类的实例属性和方法,不能继承原型属性/方法,无法实现函数复用,每个子类都有父类实例函数的副本,影响性能 function Person (name) { this.name = name; this.friends = [‘小李’,‘小红’]; this.getName = function () { return this.name; } };// Person.prototype.geSex = function () { //对原型进行扩展的方法就无法复用了// console.log(“男”);// }; function Parent = (age) { Person.call(this,‘老明’); //这一句是核心关键 //这样就会在新parent对象上执行Person构造函数中定义的所有对象初始化代码, // 结果parent的每个实例都会具有自己的friends属性的副本 this.age = age; }; var result = new Parent(23); console.log(result.name); //老明 console.log(result.friends); //[“小李”, “小红”] console.log(result.getName()); //老明 console.log(result.age); //23 console.log(result.getSex()); //这个会报错,调用不到父原型上面扩展的方法3、组合继承组合继承(所有的实例都能拥有自己的属性,并且可以使用相同的方法,组合继承避免了原型链和借用构造函数的缺陷,结合了两个的优点,是最常用的继承方式)核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后再通过将父类实例作为子类原型,实现函数复用缺点:调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了) function Person (name) { this.name = name; this.friends = [‘小李’,‘小红’]; }; Person.prototype.getName = function () { return this.name; }; function Parent (age) { Person.call(this,‘老明’); //这一步很关键 this.age = age; }; Parent.prototype = new Person(‘老明’); //这一步也很关键 var result = new Parent(24); console.log(result.name); //老明 result.friends.push(“小智”); // console.log(result.friends); //[‘小李’,‘小红’,‘小智’] console.log(result.getName()); //老明 console.log(result.age); //24 var result1 = new Parent(25); //通过借用构造函数都有自己的属性,通过原型享用公共的方法 console.log(result1.name); //老明 console.log(result1.friends); //[‘小李’,‘小红’]4、寄生组合继承核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点缺点:堪称完美,但实现较为复杂 function Person(name) { this.name = name; this.friends = [‘小李’,‘小红’]; } Person.prototype.getName = function () { return this.name; }; function Parent(age) { Person.call(this,“老明”); this.age = age; } (function () { var Super = function () {}; // 创建一个没有实例方法的类 Super.prototype = Person.prototype; Parent.prototype = new Super(); //将实例作为子类的原型 })(); var result = new Parent(23); console.log(result.name); console.log(result.friends); console.log(result.getName()); console.log(result.age); ...

April 2, 2019 · 2 min · jiezi

JS中的逻辑运算符&&、||,位运算符|,&

1、JS中的||符号:运算方法: 只要“||”前面为false,不管“||”后面是true还是false,都返回“||”后面的值。 只要“||”前面为true,不管“||”后面是true还是false,都返回“||”前面的值。总结:真前假后2、JS中的&&符号:运算方法: 只要“&&”前面是false,无论“&&”后面是true还是false,结果都将返“&&”前面的值; 只要“&&”前面是true,无论“&&”后面是true还是false,结果都将返“&&”后面的值;总结:假前真后弄懂了以上说的还应该知道: js的6个蛋蛋:在js逻辑运算中,0、”“、null、false、undefined、NaN都会判为false,其他都为true。举个栗子:3、位运算符:|运算方法: 两个位只要有一个为1,那么结果都为1。否则就为0继续举栗子|运算符还能进行取整运算4、位运算符:&运算方法: 两个数值的个位分别相与,同时为1才得1,只要一个为0就为0。还是举栗子:

March 27, 2019 · 1 min · jiezi

chanel 使用与原理 二

有了上篇的基本了解,可以翻阅源码了涉及的数据结构// Go/src/runtime/chan.gotype hchan struct { qcount uint // total data in the queue dataqsiz uint // size of the circular queue buf unsafe.Pointer // points to an array of dataqsiz elements elemsize uint16 closed uint32 elemtype *_type // element type sendx uint // send index recvx uint // receive index recvq waitq // list of recv waiters sendq waitq // list of send waiters // lock protects all fields in hchan, as well as several // fields in sudogs blocked on this channel. // // Do not change another G’s status while holding this lock // (in particular, do not ready a G), as this can deadlock // with stack shrinking. lock mutex}type waitq struct { first *sudog last *sudog}// sudog represents a g in a wait list, such as for sending/receiving// on a channel.//// sudog is necessary because the g ↔ synchronization object relation// is many-to-many. A g can be on many wait lists, so there may be// many sudogs for one g; and many gs may be waiting on the same// synchronization object, so there may be many sudogs for one object.//// sudogs are allocated from a special pool. Use acquireSudog and// releaseSudog to allocate and free them.type sudog struct { // The following fields are protected by the hchan.lock of the // channel this sudog is blocking on. shrinkstack depends on // this for sudogs involved in channel ops. g *g // isSelect indicates g is participating in a select, so // g.selectDone must be CAS’d to win the wake-up race. isSelect bool next *sudog prev *sudog elem unsafe.Pointer // data element (may point to stack) // The following fields are never accessed concurrently. // For channels, waitlink is only accessed by g. // For semaphores, all fields (including the ones above) // are only accessed when holding a semaRoot lock. acquiretime int64 releasetime int64 ticket uint32 parent *sudog // semaRoot binary tree waitlink *sudog // g.waiting list or semaRoot waittail *sudog // semaRoot c *hchan // channel}makechan()当 make(chan int,3)带有设置缓存大小的参数,则会分配一段连续空间,buf 指向这段内存空间c.buf = add(unsafe.Pointer(c), hchanSize) 分配 hchanSize 大小的空间,其中常量 hchanSize hchanSize = unsafe.Sizeof(hchan{}) + uintptr(-int(unsafe.Sizeof(hchan{}))&(maxAlign-1))func makechan(t *chantype, size int) *hchan { elem := t.elem // compiler checks this but be safe. ··· var c *hchan // 返回的是指针 switch { case size == 0 || elem.size == 0: // Queue or element size is zero. c = (*hchan)(mallocgc(hchanSize, nil, true)) // Race detector uses this location for synchronization. c.buf = c.raceaddr() case elem.kind&kindNoPointers != 0: // Elements do not contain pointers. // Allocate hchan and buf in one call. c = (*hchan)(mallocgc(hchanSize+uintptr(size)*elem.size, nil, true)) c.buf = add(unsafe.Pointer(c), hchanSize) // <============ default: // Elements contain pointers. c = new(hchan) c.buf = mallocgc(uintptr(size)*elem.size, elem, true) // <============== } ··· return c}chansend()我们截取不同情况的代码段。如果 chanel 为 nil如果 chanel 为空,即没有用 make 分配内存,那么会调用 gopark 方法if c == nil { if !block { return false } gopark(nil, nil, waitReasonChanSendNilChan, traceEvGoStop, 2) throw(“unreachable”) }而 gopark 方法会将当前的 goroutine 休眠,然后回调通过参数传来的 unlockf 方法。注意看回上面的代码,调用 gopark 方法时传递的 unlockf 参数为 nil,所以会一直休眠。// Puts the current goroutine into a waiting state and calls unlockf.// If unlockf returns false, the goroutine is resumed.// unlockf must not access this G’s stack, as it may be moved between// the call to gopark and the call to unlockf.// Reason explains why the goroutine has been parked.// It is displayed in stack traces and heap dumps.// Reasons should be unique and descriptive.// Do not re-use reasons, add new ones.func gopark(unlockf func(*g, unsafe.Pointer) bool, lock unsafe.Pointer, reason waitReason, traceEv byte, traceskip int) { if reason != waitReasonSleep { checkTimeouts() // timeouts may expire while two goroutines keep the scheduler busy } mp := acquirem() gp := mp.curg status := readgstatus(gp) if status != _Grunning && status != _Gscanrunning { throw(“gopark: bad g status”) } mp.waitlock = lock mp.waitunlockf = *(*unsafe.Pointer)(unsafe.Pointer(&unlockf)) gp.waitreason = reason mp.waittraceev = traceEv mp.waittraceskip = traceskip releasem(mp) // can’t do anything that might move the G between Ms here. mcall(park_m)}这时, Go 语言启动的时候会有一个 goroutine sysmon 一直检测系统的运行情况,其中有一个方法 checkdead(),当检测到所有 goroutine 都处于休眠,即死锁,便抛出错误。// /Go/src/runtime/proc.gofunc checkdead() { … throw(“all goroutines are asleep - deadlock!”) }如果 chanel 已经被关闭了直接引发 panic: lock(&c.lock) if c.closed != 0 { unlock(&c.lock) panic(plainError(“send on closed channel”)) }如果能发送数据发送数据还分三种情况:1 当前 hchan 的 recvq 接收队列上已经有 goroutine 阻塞 lock(&c.lock) ··· if sg := c.recvq.dequeue(); sg != nil { // Found a waiting receiver. We pass the value we want to send // directly to the receiver, bypassing the channel buffer (if any). send(c, sg, ep, func() { unlock(&c.lock) }, 3) return true }send 方法判断到接收方 sudog 的 elem 字段存有对应的内存空间地址值的话,调用 sendDirect 方法。这里啰嗦一下,sudog 的 elem 是在 func chanrecv 方法中赋值的,将接收方 goroutine 用来接收数据的栈空间地址赋值给 elem。// send processes a send operation on an empty channel c.// The value ep sent by the sender is copied to the receiver sg.// The receiver is then woken up to go on its merry way.// Channel c must be empty and locked. send unlocks c with unlockf.// sg must already be dequeued from c.// ep must be non-nil and point to the heap or the caller’s stack.func send(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) { ··· if sg.elem != nil { sendDirect(c.elemtype, sg, ep) sg.elem = nil } gp := sg.g unlockf() gp.param = unsafe.Pointer(sg) if sg.releasetime != 0 { sg.releasetime = cputicks() } goready(gp, skip+1)}sendDirect 方法中 memmove 方法直接拷贝 t.size 个字节到目的内存空间。func sendDirect(t *_type, sg *sudog, src unsafe.Pointer) { // src is on our stack, dst is a slot on another stack. // Once we read sg.elem out of sg, it will no longer // be updated if the destination’s stack gets copied (shrunk). // So make sure that no preemption points can happen between read & use. dst := sg.elem typeBitsBulkBarrier(t, uintptr(dst), uintptr(src), t.size) // No need for cgo write barrier checks because dst is always // Go memory. memmove(dst, src, t.size)}2 当前 hchan.buf 还有可用空间:将数据放到 buffer 里面。 if c.qcount < c.dataqsiz { // Space is available in the channel buffer. Enqueue the element to send. qp := chanbuf(c, c.sendx) if raceenabled { raceacquire(qp) racerelease(qp) } typedmemmove(c.elemtype, qp, ep) c.sendx++ if c.sendx == c.dataqsiz { c.sendx = 0 } c.qcount++ unlock(&c.lock) return true }3 当前 hchan.buf 已满:阻塞当前 goroutine // Block on the channel. Some receiver will complete our operation for us. gp := getg() mysg := acquireSudog() mysg.releasetime = 0 if t0 != 0 { mysg.releasetime = -1 } // No stack splits between assigning elem and enqueuing mysg // on gp.waiting where copystack can find it. mysg.elem = ep mysg.waitlink = nil mysg.g = gp mysg.isSelect = false mysg.c = c gp.waiting = mysg gp.param = nil c.sendq.enqueue(mysg) goparkunlock(&c.lock, waitReasonChanSend, traceEvGoBlockSend, 3)goparkunlock 方法将当前 goroutine 休眠,并且释放锁资源// Puts the current goroutine into a waiting state and unlocks the lock.// The goroutine can be made runnable again by calling goready(gp).func goparkunlock(lock *mutex, reason waitReason, traceEv byte, traceskip int) { gopark(parkunlock_c, unsafe.Pointer(lock), reason, traceEv, traceskip)}参考文章http://legendtkl.com/2017/08/… ...

March 16, 2019 · 6 min · jiezi

Python 多维List创建的问题

背景最近在学Python,我觉得学习一个新语言最好的方式就是写一个简单的项目,所以就打算写一个简单的俄罗斯方块游戏。那么在写的过程中遇到了一个小问题。 def init(self, width = 10, height = 30): self.width, self.height = width, height self.board_size = [width, height]我用一个二维List来记录游戏空间的状态,game_boardx代表一个格子,0代表这格子是空的,1代表不是。很显然,初始化的时候应该将所有的格子都赋值为0。查询了一下List的相关文档,发现可以用[0] * n这样的方式来快速创建特定长度的List,因此很自然的写出了下面这行代码。 self.game_board = [[0] * height] * width查看一下结果,确实创建了长宽符合预期、值全部都是0的一个二维List,感觉没什么问题,就接着往下写了。问题但这两天在写消除方法的时候,使用最下面3排全是1,第四排中间是1其他全是0,这样消除完成之后应该还剩1个1掉落到第一排。但测试过程中发现无论如何都会导致所有的1都被消除了,一开始我还以为是消除的算法有问题,但后来在每一步过程中都监控整个game_board的状态时才发现,当一横排有一个值为1的时候,这一横排所有值都自动变成1了。解决很自然的就想到,这是由于List对象的引用产生的。[0] * height产生了长度为height并且内容全是0的List,由于0是个int,是基础数据类型,因此这样使用是正确的。但用这个List去进行* width操作时,产生的都是这个List的引用,而不是新建了width个List,所以修改其中任意一个就全部都修改了。经过查阅Python文档,将代码修改为: self.game_board = [([0] * height) for i in range(width)]测试发现问题解决。总结其实是很基础的问题,对于Python *这个运算符不够了解,因此想当然觉得代表了对对象进行深拷贝。而且可能前端做多了,对数据结构这种基础不太敏感了吧,还是要多做练习啊。

January 18, 2019 · 1 min · jiezi

前端基本功-常见概念(三)

前端基本功-常见概念(一) 点这里前端基本功-常见概念(二) 点这里前端基本功-常见概念(三) 点这里1.HTML / XML / XHTMLhtml:超文本标记语言,显示信息,不区分大小写xhtml:升级版的html,区分大小写xml:可扩展标记语言被用来传输和存储数据2.AMD/CMD/CommonJs/ES6 ModuleAMD:AMD规范采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。AMD是requirejs 在推广过程中对模块定义的规范化产出,提前执行,推崇依赖前置。用define()定义模块,用require()加载模块,require.config()指定引用路径等首先我们需要引入require.js文件和一个入口文件main.js。main.js中配置require.config()并规定项目中用到的基础模块。 /** 网页中引入require.js及main.js / <script src=“js/require.js” data-main=“js/main”></script> / main.js 入口文件/主模块 / // 首先用config()指定各模块路径和引用名 require.config({ baseUrl: “js/lib”, paths: { “jquery”: “jquery.min”, //实际路径为js/lib/jquery.min.js “underscore”: “underscore.min”, } }); // 执行基本操作 require([“jquery”,“underscore”],function($,){ // some code here });引用模块的时候,我们将模块名放在[]中作为reqiure()的第一参数;如果我们定义的模块本身也依赖其他模块,那就需要将它们放在[]中作为define()的第一参数。 // 定义math.js模块 define(function () { var basicNum = 0; var add = function (x, y) { return x + y; }; return { add: add, basicNum :basicNum }; }); // 定义一个依赖underscore.js的模块 define([‘underscore’],function(){ var classify = function(list){ _.countBy(list,function(num){ return num > 30 ? ‘old’ : ‘young’; }) }; return { classify :classify }; }) // 引用模块,将模块放在[]内 require([‘jquery’, ‘math’],function($, math){ var sum = math.add(10,20); $("#sum").html(sum); });CMD:seajs 在推广过程中对模块定义的规范化产出,延迟执行,推崇依赖就近require.js在申明依赖的模块时会在第一之间加载并执行模块内的代码: define([“a”, “b”, “c”, “d”, “e”, “f”], function(a, b, c, d, e, f) { // 等于在最前面声明并初始化了要用到的所有模块 if (false) { // 即便没用到某个模块 b,但 b 还是提前执行了 b.foo() } });CMD是另一种js模块化方案,它与AMD很类似,不同点在于:AMD 推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行。此规范其实是在sea.js推广过程中产生的。 / CMD写法 / define(function(require, exports, module) { var a = require(’./a’); //在需要时申明 a.doSomething(); if (false) { var b = require(’./b’); b.doSomething(); } }); / sea.js / // 定义模块 math.js define(function(require, exports, module) { var $ = require(‘jquery.js’); var add = function(a,b){ return a+b; } exports.add = add; }); // 加载模块 seajs.use([‘math.js’], function(math){ var sum = math.add(1+2); });CommonJs:Node.js是commonJS规范的主要实践者,它有四个重要的环境变量为模块化的实现提供支持:module、exports、require、global。实际使用时,用module.exports定义当前模块对外输出的接口(不推荐直接用exports),用require加载模块。 // 定义模块math.js var basicNum = 0; function add(a, b) { return a + b; } module.exports = { //在这里写上需要向外暴露的函数、变量 add: add, basicNum: basicNum } // 引用自定义的模块时,参数包含路径,可省略.js var math = require(’./math’); math.add(2, 5); // 引用核心模块时,不需要带路径 var http = require(‘http’); http.createService(…).listen(3000);commonJS用同步的方式加载模块。在服务端,模块文件都存在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,限于网络原因,更合理的方案是使用异步加载。ES6 Module:ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,旨在成为浏览器和服务器通用的模块解决方案。其模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。/ 定义模块 math.js /var basicNum = 0;var add = function (a, b) { return a + b;};export { basicNum, add };/ 引用模块 /import { basicNum, add } from ‘./math’;function test(ele) { ele.textContent = add(99 + basicNum);}如上例所示,使用import命令的时候,用户需要知道所要加载的变量名或函数名。其实ES6还提供了export default命令,为模块指定默认输出,对应的import语句不需要使用大括号。这也更趋近于ADM的引用写法。/ export default **///定义输出export default { basicNum, add };//引入import math from ‘./math’;function test(ele) { ele.textContent = math.add(99 + math.basicNum);}ES6的模块不是对象,import命令会被 JavaScript 引擎静态分析,在编译时就引入模块代码,而不是在代码运行时加载,所以无法实现条件加载。也正因为这个,使得静态分析成为可能。ES6 模块与 CommonJS 模块的差异CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。运行时加载: CommonJS 模块就是对象;即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。- 编译时加载: ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,import时采用静态命令的形式。即在import时可以指定加载某个输出值,而不是加载整个模块,这种加载称为“编译时加载”。CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。本节参考文章:前端模块化:CommonJS,AMD,CMD,ES63.ES5的继承/ES6的继承ES5的继承时通过prototype或构造函数机制来实现。ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.apply(this))。ES6的继承机制完全不同,实质上是先创建父类的实例对象this(所以必须先调用父类的super()方法),然后再用子类的构造函数修改this。具体的:ES6通过class关键字定义类,里面有构造方法,类之间通过extends关键字实现继承。子类必须在constructor方法中调用super方法,否则新建实例报错。因为子类没有自己的this对象,而是继承了父类的this对象,然后对其进行加工。如果不调用super方法,子类得不到this对象。ps:super关键字指代父类的实例,即父类的this对象。在子类构造函数中,调用super后,才可使用this关键字,否则报错。区别:(以SubClass,SuperClass,instance为例)ES5中继承的实质是:(那种经典寄生组合式继承法)通过prototype或构造函数机制来实现,先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.apply(this))。先由子类(SubClass)构造出实例对象this然后在子类的构造函数中,将父类(SuperClass)的属性添加到this上,SuperClass.apply(this, arguments)子类原型(SubClass.prototype)指向父类原型(SuperClass.prototype)所以instance是子类(SubClass)构造出的(所以没有父类的[[Class]]关键标志)所以,instance有SubClass和SuperClass的所有实例属性,以及可以通过原型链回溯,获取SubClass和SuperClass原型上的方法ES6中继承的实质是:先创建父类的实例对象this(所以必须先调用父类的super()方法),然后再用子类的构造函数修改this先由父类(SuperClass)构造出实例对象this,这也是为什么必须先调用父类的super()方法(子类没有自己的this对象,需先由父类构造)然后在子类的构造函数中,修改this(进行加工),譬如让它指向子类原型(SubClass.prototype),这一步很关键,否则无法找到子类原型(注,子类构造中加工这一步的实际做法是推测出的,从最终效果来推测)然后同样,子类原型(SubClass.prototype)指向父类原型(SuperClass.prototype)所以instance是父类(SuperClass)构造出的(所以有着父类的[[Class]]关键标志)所以,instance有SubClass和SuperClass的所有实例属性,以及可以通过原型链回溯,获取SubClass和SuperClass原型上的方法静态方法继承实质上只需要更改下SubClass.__proto__到SuperClass即可本节参考文章:链接4.HTTP request报文/HTTP response报文请求报文响应报文请求行 请求头 空行 请求体状态行 响应头 空行 响应体HTTP request报文结构是怎样的首行是Request-Line包括:请求方法,请求URI,协议版本,CRLF首行之后是若干行请求头,包括general-header,request-header或者entity-header,每个一行以CRLF结束请求头和消息实体之间有一个CRLF分隔根据实际请求需要可能包含一个消息实体 一个请求报文例子如下:GET /Protocols/rfc2616/rfc2616-sec5.html HTTP/1.1Host: www.w3.orgConnection: keep-aliveCache-Control: max-age=0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36Referer: https://www.google.com.hk/Accept-Encoding: gzip,deflate,sdchAccept-Language: zh-CN,zh;q=0.8,en;q=0.6Cookie: authorstyle=yesIf-None-Match: “2cc8-3e3073913b100"If-Modified-Since: Wed, 01 Sep 2004 13:24:52 GMTname=qiu&age=25请求报文HTTP response报文结构是怎样的首行是状态行包括:HTTP版本,状态码,状态描述,后面跟一个CRLF首行之后是若干行响应头,包括:通用头部,响应头部,实体头部响应头部和响应实体之间用一个CRLF空行分隔最后是一个可能的消息实体 响应报文例子如下:HTTP/1.1 200 OKDate: Tue, 08 Jul 2014 05:28:43 GMTServer: Apache/2Last-Modified: Wed, 01 Sep 2004 13:24:52 GMTETag: “40d7-3e3073913b100"Accept-Ranges: bytesContent-Length: 16599Cache-Control: max-age=21600Expires: Tue, 08 Jul 2014 11:28:43 GMTP3P: policyref=“http://www.w3.org/2001/05/P3P/p3p.xml"Content-Type: text/html; charset=iso-8859-1{“name”: “qiu”, “age”: 25}响应报文5.面向对象的工厂模式/构造函数工厂模式集中实例化了对象,避免实例化对象大量重复问题//工厂模式function createObject(a,b){ var obj = new Object(); //集中实例化 obj.a = a; obj.b = b; obj.c = function () { return this.a + this.b; }; return obj; //返回实例化对象}var box = createObject(‘abc’,10);var box1 = createObject(‘abcdef’,20);alert(box.c()); //返回abc10alert(box1.c()); //返回abcdef20//构造函数function Create(a,b) { this.a =a; this.b =b; this.c = function () { return this.a + this.b; };}var box = new Create(‘abc’,10);alert(box.run()); //返回abc10构造函数相比工厂模式:没有集中实例化没有返回对象实例直接将属性和方法赋值给this解决了对象实例归属问题构造函数编写规范:构造函数也是函数,但是函数名的第一个字母大写必须使用new运算符 + 函数名(首字母大写)例如:var box = new Create();构造函数和普通函数的区别:普通函数,首字母无需大写构造函数,用普通函数调用方式无效查看归属问题,要创建两个构造函数:function Create(a,b) { this.a =a; this.b =b; this.c = function () { return this.a + this.b; };}function DeskTop(a,b) { this.a =a; this.b =b; this.c = function () { return this.a + this.b; };}var box = new Create(‘abc’,10);var box1 = new DeskTop(‘def’,20);alert(box instanceof Object);//这里要注意:所有的构造函数的对象都是Object.alert(box instanceof Create); //truealert(box1 instanceof Create); //falsealert(box1 instanceof DeskTop); //true6. new Promise / Promise.resolve()Promise.resolve()可以生成一个成功的PromisePromise.resolve()语法糖例1:Promise.resolve(‘成功’)等同于new Promise(function(resolve){resolve(‘成功’)})例2:var resolved = Promise.resolve(‘foo’);resolved.then((str) => console.log(str);//foo)相当于var resolved = new Promise((resolve, reject) => { resolve(‘foo’)});resolved.then((str) => console.log(str);//foo)Promise.resolve方法有下面三种形式:Promise.resolve(value);Promise.resolve(promise);Promise.resolve(theanable);这三种形式都会产生一个新的Promise。其中:第一种形式提供了自定义Promise的值的能力,它与Promise.reject(reason)对应。两者的不同,在于得到的Promise的状态不同。第二种形式,提供了创建一个Promise的副本的能力。第三种形式,是将一个类似Promise的对象转换成一个真正的Promise对象。它的一个重要作用是将一个其他实现的Promise对象封装成一个当前实现的Promise对象。例如你正在用bluebird,但是现在有一个Q的Promise,那么你可以通过此方法把Q的Promise变成一个bluebird的Promise。实际上第二种形式可以归在第三种形式中。本节参考文章:ES6中的Promise.resolve()推荐阅读:性感的Promise…7.伪类 / 伪元素伪类伪类 用于当已有元素处于的某个状态时,为其添加对应的样式,这个状态是根据用户行为而动态变化的。当用户悬停在指定的元素时,我们可以通过 :hover 来描述这个元素的状态。虽然它和普通的 CSS 类相似,可以为已有的元素添加样式,但是它只有处于 DOM 树无法描述的状态下才能为元素添加样式,所以将其称为伪类。伪元素伪元素 用于创建一些不在文档树中的元素,并为其添加样式。我们可以通过 :before 来在一个元素前增加一些文本,并为这些文本添加样式。虽然用户可以看到这些文本,但是这些文本实际上不在文档树中。本节参考文章:前端面试题-伪类和伪元素、总结伪类与伪元素8.DOMContentLoaded / loadDOM文档加载的步骤为:解析HTML结构。DOM树构建完成。//DOMContentLoaded加载外部脚本和样式表文件。解析并执行脚本代码。加载图片等外部文件。页面加载完毕。//load触发的时机不一样,先触发DOMContentLoaded事件,后触发load事件。原生js// 不兼容老的浏览器,兼容写法见jQuery中ready与load事件,或用jQuerydocument.addEventListener(“DOMContentLoaded”, function() { // …代码…}, false);window.addEventListener(“load”, function() { // …代码…}, false);jQuery// DOMContentLoaded$(document).ready(function() { // …代码…});//load$(document).load(function() { // …代码…});head 中资源的加载head 中 js 资源加载都会停止后面 DOM 的构建,但是不影响后面资源的下载。css资源不会阻碍后面 DOM 的构建,但是会阻碍页面的首次渲染。body 中资源的加载body 中 js 资源加载都会停止后面 DOM 的构建,但是不影响后面资源的下载。css 资源不会阻碍后面 DOM 的构建,但是会阻碍页面的首次渲染。DomContentLoaded 事件的触发上面只是讲了 html 文档的加载与渲染,并没有讲 DOMContentLoaded 事件的触发时机。直截了当地结论是,DOMContentLoaded 事件在 html文档加载完毕,并且 html 所引用的内联 js、以及外链 js 的同步代码都执行完毕后触发。大家可以自己写一下测试代码,分别引用内联 js 和外链 js 进行测试。load 事件的触发当页面 DOM 结构中的 js、css、图片,以及 js 异步加载的 js、css 、图片都加载完成之后,才会触发 load 事件。注意:页面中引用的js 代码如果有异步加载的 js、css、图片,是会影响 load 事件触发的。video、audio、flash 不会影响 load 事件触发。推荐阅读:再谈 load 与 DOMContentLoaded本节参考文章:DOMContentLoaded与load的区别、事件DOMContentLoaded和load的区别9. 为什么将css放在头部,将js文件放在尾部因为浏览器生成Dom树的时候是一行一行读HTML代码的,script标签放在最后面就不会影响前面的页面的渲染。那么问题来了,既然Dom树完全生成好后页面才能渲染出来,浏览器又必须读完全部HTML才能生成完整的Dom树,script标签不放在body底部是不是也一样,因为dom树的生成需要整个文档解析完毕。我们再来看一下chrome在页面渲染过程中的,绿色标志线是First Paint的时间。纳尼,为什么会出现firstpaint,页面的paint不是在渲染树生成之后吗?其实现代浏览器为了更好的用户体验,渲染引擎将尝试尽快在屏幕上显示的内容。它不会等到所有HTML解析之前开始构建和布局渲染树。部分的内容将被解析并显示。也就是说浏览器能够渲染不完整的dom树和cssom,尽快的减少白屏的时间。假如我们将js放在header,js将阻塞解析dom,dom的内容会影响到First Paint,导致First Paint延后。所以说我们会 将js放在后面,以减少First Paint的时间,但是不会减少DOMContentLoaded被触发的时间。本节参考文章:DOMContentLoaded与load的区别10.clientheight / offsetheightclientheight:内容的可视区域,不包含border。clientheight=padding+height-横向滚动轴高度。这里写图片描述offsetheight,它包含padding、border、横向滚动轴高度。 offsetheight=padding+height+border+横向滚动轴高度scrollheight,可滚动高度,就是将滚动框拉直,不再滚动的高度,这个很好理解。 It includes the element’s padding, but not its border or margin.本节参考文章:css clientheight、offsetheight、scrollheight详解11.use strict 有什么意义和好处使调试更加容易。那些被忽略或默默失败了的代码错误,会产生错误或抛出异常,因此尽早提醒你代码中的问题,你才能更快地指引到它们的源代码。防止意外的全局变量。如果没有严格模式,将值分配给一个未声明的变量会自动创建该名称的全局变量。这是JavaScript中最常见的错误之一。在严格模式下,这样做的话会抛出错误。消除 this 强制。如果没有严格模式,引用null或未定义的值到 this 值会自动强制到全局变量。这可能会导致许多令人头痛的问题和让人恨不得拔自己头发的bug。在严格模式下,引用 null或未定义的 this 值会抛出错误。不允许重复的属性名称或参数值。当检测到对象中重复命名的属性,例如:var object = {foo: “bar”, foo: “baz”};)或检测到函数中重复命名的参数时,例如:function foo(val1, val2, val1){})严格模式会抛出错误,因此捕捉几乎可以肯定是代码中的bug可以避免浪费大量的跟踪时间。使 eval() 更安全。在严格模式和非严格模式下, eval() 的行为方式有所不同。最显而易见的是,在严格模式下,变量和声明在 eval() 语句内部的函数不会在包含范围内创建(它们会在非严格模式下的包含范围中被创建,这也是一个常见的问题源)。在 delete 使用无效时抛出错误。 delete 操作符(用于从对象中删除属性)不能用在对象不可配置的属性上。当试图删除一个不可配置的属性时,非严格代码将默默地失败,而严格模式将在这样的情况下抛出异常。本节参考文章:经典面试题(4)12.常见 JavaScript 内存泄漏意外的全局变量JavaScript 处理未定义变量的方式比较宽松:未定义的变量会在全局对象创建一个新变量。在浏览器中,全局对象是 window 。function foo(arg) { bar = “this is a hidden global variable”;}真相是:function foo(arg) { window.bar = "this is an explicit global variable";}函数 foo 内部忘记使用 var ,意外创建了一个全局变量。此例泄漏了一个简单的字符串,无伤大雅,但是有更糟的情况。另一种意外的全局变量可能由 this 创建:function foo() { this.variable = "potential accidental global";}// Foo 调用自己,this 指向了全局对象(window)// 而不是 undefinedfoo();在 JavaScript 文件头部加上 ‘use strict’,可以避免此类错误发生。启用严格模式解析 JavaScript ,避免意外的全局变量。被遗忘的计时器或回调函数在 JavaScript 中使用 setInterval 非常平常。一段常见的代码:var someResource = getData();setInterval(function() { var node = document.getElementById(‘Node’); if(node) { // 处理 node 和 someResource node.innerHTML = JSON.stringify(someResource)); }}, 1000);此例说明了什么:与节点或数据关联的计时器不再需要,node 对象可以删除,整个回调函数也不需要了。可是,计时器回调函数仍然没被回收(计时器停止才会被回收)。同时,someResource 如果存储了大量的数据,也是无法被回收的。对于观察者的例子,一旦它们不再需要(或者关联的对象变成不可达),明确地移除它们非常重要。老的 IE 6 是无法处理循环引用的。如今,即使没有明确移除它们,一旦观察者对象变成不可达,大部分浏览器是可以回收观察者处理函数的。观察者代码示例:var element = document.getElementById(‘button’);function onClick(event) { element.innerHTML = ’text’;}element.addEventListener(‘click’, onClick);对象观察者和循环引用注意事项老版本的 IE 是无法检测 DOM 节点与 JavaScript 代码之间的循环引用,会导致内存泄漏。如今,现代的浏览器(包括 IE 和 Microsoft Edge)使用了更先进的垃圾回收算法,已经可以正确检测和处理循环引用了。换言之,回收节点内存时,不必非要调用 removeEventListener 了。脱离 DOM 的引用有时,保存 DOM 节点内部数据结构很有用。假如你想快速更新表格的几行内容,把每一行 DOM 存成字典(JSON 键值对)或者数组很有意义。此时,同样的 DOM 元素存在两个引用:一个在 DOM 树中,另一个在字典中。将来你决定删除这些行时,需要把两个引用都清除。var elements = { button: document.getElementById(‘button’), image: document.getElementById(‘image’), text: document.getElementById(’text’)};function doStuff() { image.src = ‘http://some.url/image'; button.click(); console.log(text.innerHTML); // 更多逻辑}function removeButton() { // 按钮是 body 的后代元素 document.body.removeChild(document.getElementById(‘button’)); // 此时,仍旧存在一个全局的 #button 的引用 // elements 字典。button 元素仍旧在内存中,不能被 GC 回收。}此外还要考虑 DOM 树内部或子节点的引用问题。假如你的 JavaScript 代码中保存了表格某一个 <td> 的引用。将来决定删除整个表格的时候,直觉认为 GC 会回收除了已保存的 <td> 以外的其它节点。实际情况并非如此:此 <td> 是表格的子节点,子元素与父元素是引用关系。由于代码保留了 <td> 的引用,导致整个表格仍待在内存中。保存 DOM 元素引用的时候,要小心谨慎。闭包闭包是 JavaScript 开发的一个关键方面:匿名函数可以访问父级作用域的变量。避免滥用本节参考文章:4类 JavaScript 内存泄漏及如何避免13.引用计数 / 标记清除js垃圾回收有两种常见的算法:引用计数和标记清除。引用计数就是跟踪对象被引用的次数,当一个对象的引用计数为0即没有其他对象引用它时,说明该对象已经无需访问了,因此就会回收其所占的内存,这样,当垃圾回收器下次运行就会释放引用数为0的对象所占用的内存。标记清除法是现代浏览器常用的一种垃圾收集方式,当变量进入环境(即在一个函数中声明一个变量)时,就将此变量标记为“进入环境”,进入环境的变量是不能被释放,因为只有执行流进入相应的环境,就可能会引用它们。而当变量离开环境时,就标记为“离开环境”。垃圾收集器在运行时会给储存在内存中的所有变量加上标记,然后会去掉环境中的变量以及被环境中的变量引用的变量的标记,当执行完毕那些没有存在引用 无法访问的变量就被加上标记,最后垃圾收集器完成清除工作,释放掉那些打上标记的变量所占的内存。 function problem() { var A = {}; var B = {}; A.a = B; B.a = A;}引用计数存在一个弊端就是循环引用问题(上边)标记清除不存在循环引用的问题,是因为当函数执行完毕之后,对象A和B就已经离开了所在的作用域,此时两个变量被标记为“离开环境”,等待被垃圾收集器回收,最后释放其内存。分析以下代码: function createPerson(name){ var localPerson = new Object(); localPerson.name = name; return localPerson; } var globalPerson = createPerson(“Junga”); globalPerson = null;//手动解除全局变量的引用在这个????中,变量globalPerson取得了createPerson()函数的返回的值。在createPerson()的内部创建了一个局部变量localPerson并添加了一个name属性。由于localPerson在函数执行完毕之后就离开执行环境,因此会自动解除引用,而对于全局变量来说则需要我们手动设置null,解除引用。不过,解除一个值的引用并不意味着自动回收该值所占用的内存,解除引用真正的作用是让值脱离执行环境,以便垃圾收集器下次运行时将其收回。本节参考文章:JavaScript的内存问题14.前后端路由差别1.后端每次路由请求都是重新访问服务器2.前端路由实际上只是JS根据URL来操作DOM元素,根据每个页面需要的去服务端请求数据,返回数据后和模板进行组合。本节参考文章:2018前端面试总结…15.window.history / location.hash通常 SPA 中前端路由有2种实现方式:window.historylocation.hash下面就来介绍下这两种方式具体怎么实现的一.history1.history基本介绍window.history 对象包含浏览器的历史,window.history 对象在编写时可不使用 window 这个前缀。history是实现SPA前端路由是一种主流方法,它有几个原始方法:history.back() - 与在浏览器点击后退按钮相同history.forward() - 与在浏览器中点击按钮向前相同history.go(n) - 接受一个整数作为参数,移动到该整数指定的页面,比如go(1)相当于forward(),go(-1)相当于back(),go(0)相当于刷新当前页面如果移动的位置超出了访问历史的边界,以上三个方法并不报错,而是静默失败在HTML5,history对象提出了 pushState() 方法和 replaceState() 方法,这两个方法可以用来向历史栈中添加数据,就好像 url 变化了一样(过去只有 url 变化历史栈才会变化),这样就可以很好的模拟浏览历史和前进后退了,现在的前端路由也是基于这个原理实现的。2.history.pushStatepushState(stateObj, title, url) 方法向历史栈中写入数据,其第一个参数是要写入的数据对象(不大于640kB),第二个参数是页面的 title, 第三个参数是 url (相对路径)。stateObj :一个与指定网址相关的状态对象,popstate事件触发时,该对象会传入回调函数。如果不需要这个对象,此处可以填null。title:新页面的标题,但是所有浏览器目前都忽略这个值,因此这里可以填null。url:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个网址。关于pushState,有几个值得注意的地方:pushState方法不会触发页面刷新,只是导致history对象发生变化,地址栏会有反应,只有当触发前进后退等事件(back()和forward()等)时浏览器才会刷新这里的 url 是受到同源策略限制的,防止恶意脚本模仿其他网站 url 用来欺骗用户,所以当违背同源策略时将会报错3.history.replaceStatereplaceState(stateObj, title, url) 和pushState的区别就在于它不是写入而是替换修改浏览历史中当前纪录,其余和 pushState一模一样4.popstate事件定义:每当同一个文档的浏览历史(即history对象)出现变化时,就会触发popstate事件。注意:仅仅调用pushState方法或replaceState方法 ,并不会触发该事件,只有用户点击浏览器倒退按钮和前进按钮,或者使用JavaScript调用back、forward、go方法时才会触发。另外,该事件只针对同一个文档,如果浏览历史的切换,导致加载不同的文档,该事件也不会触发。用法:使用的时候,可以为popstate事件指定回调函数。这个回调函数的参数是一个event事件对象,它的state属性指向pushState和replaceState方法为当前URL所提供的状态对象(即这两个方法的第一个参数)。5.history实现spa前端路由代码<a class=“api a”>a.html</a><a class=“api b”>b.html</a> // 注册路由 document.querySelectorAll(’.api’).forEach(item => { item.addEventListener(‘click’, e => { e.preventDefault(); let link = item.textContent; if (!!(window.history && history.pushState)) { // 支持History API window.history.pushState({name: ‘api’}, link, link); } else { // 不支持,可使用一些Polyfill库来实现 } }, false) }); // 监听路由 window.addEventListener(‘popstate’, e => { console.log({ location: location.href, state: e.state }) }, false)popstate监听函数里打印的e.state便是history.pushState()里传入的第一个参数,在这里即为{name: ‘api’}二.Hash1.Hash基本介绍url 中可以带有一个 hash http://localhost:9000/#/a.htmlwindow 对象中有一个事件是 onhashchange,以下几种情况都会触发这个事件:直接更改浏览器地址,在最后面增加或改变#hash;通过改变location.href或location.hash的值;通过触发点击带锚点的链接;浏览器前进后退可能导致hash的变化,前提是两个网页地址中的hash值不同。2.Hash实现spa前端路由代码 // 注册路由 document.querySelectorAll(’.api’).forEach(item => { item.addEventListener(‘click’, e => { e.preventDefault(); let link = item.textContent; location.hash = link; }, false) }); // 监听路由 window.addEventListener(‘hashchange’, e => { console.log({ location: location.href, hash: location.hash }) }, false)本节参考文章:vue 单页应用(spa)前端路由实现原理 ...

January 13, 2019 · 5 min · jiezi

java小心机(5)| 浅谈类成员初始化顺序

类成员什么时候会被初始化呢?一般来说:“类的代码在初次使用时才被加载”,加载过程包括了初始化。比如说new A()调用构造函数时,类中全部成员都会被初始化。但对于static域(包括静态成员变量、静态代码块、静态方法),当某个static域被调用时,类中的的所有staict就会被初始化,按照定义顺序(即书写顺序)初始化,且只会初始化一次(N个实例共用)。static域的初始化优先级要优于普通成员(即非静态域)下文代码例子名称解释:静态域:静态代码块、静态成员变量非静态域:非静态代码块、非静态成员变量(ps:成员方法不包含在里面,因为方法只能讲加载而非初始化)在没有继承父类的情况下:class HelloA { public HelloC helloC1 = new HelloC(“普通成员C1 构造函数前”); public HelloA() { System.out.println(“构造函数A”); } public HelloC helloC2 = new HelloC(“普通成员C2 构造函数后”); static { System.out.println(“静态块A”); } public HelloC helloC3 = new HelloC(“普通成员C3 静态块后”); { System.out.println(“非静态块A”); } public HelloC helloC4 = new HelloC(“普通成员C4 非静态块后”); public static HelloC helloC5 = new HelloC(“静态成员C5”); public static void main(String[] args) { new HelloA(); }}class HelloC { public HelloC(String str) { System.out.println(str); }}//out:静态块A静态成员C5普通成员C1 构造函数前普通成员C2 构造函数后普通成员C3 静态块后非静态块A普通成员C4 非静态块后 构造函数C构造函数A可以看出,初始化顺序为:静态域 -> 非静态域 -> 构造函数以上面优先级并按所定义的顺序初始化(即书写顺序)在有继承父类的情况下:class HelloA extends HelloB{ public HelloA() { System.out.println(“子类:构造函数A”); } static { System.out.println(“子类:静态块A”); } { System.out.println(“子类:非静态块A”); } public static void main(String[] args) { new HelloA(); }}class HelloB { public HelloB() { System.out.println(“父类:构造函数B”); } static { System.out.println(“父类:静态块B”); } { System.out.println(“父类:非静态块B”); }}//out:父类:静态块B子类:静态块A父类:非静态块B父类:构造函数B子类:非静态块A子类:构造函数A从结果可以看出,初始化顺序为:父类静态域->子类静态域->父类非静态域->父类构造函数->子类非静态域->子类构造函数这里说明一点:这是初始化顺序,不等同于语句程序的执行过程。因此在上面的初始化顺序里没有成员方法(静态或者非静态都没有),这是因为成员方法都是调用了才执行,虽然静态方法已经被加载进了方法区,但初始化过程中并没有执行过。推荐阅读:「 趣图 」HTTP状态码共享单车,要凉凉了?点击阅读原文,阅读「java小心机」系列文章您的点赞、转发是对我最大的支持! THANDKSEnd -一个立志成大腿而每天努力奋斗的年轻人伴学习伴成长,成长之路你并不孤单! ...

January 9, 2019 · 1 min · jiezi

java小心机(6)| 多态的一些坑

对于"多态"的概念,想必大家都很熟悉了,但我们还是来回顾一下吧class Actor { public void act(){ System.out.println(“Actor”); }}class HappyActor extends Actor{ @Override public void act() { System.out.println(“HappyActor”); }}class SadActor extends Actor{ @Override public void act() { System.out.println(“SadActor”); }}public class Test { public static void main(String[] args) { Actor hActor = new HappyActor();//向上转型 Actor sActor = new SadActor();//向上转型 hActor.act(); sActor.act(); }}//out:HappyActorSadActor上面例子中,HappyActor和SadActor都继承Actor并重写了act方法,在main函数中,两个Actor对象调用相同方法产生不同的结果,就称为"多态"。在设计程序中,“多态"可使代码变得灵活,有时候还是很好使得,但也有一些坑在里面,下面就给你一一道来坑1 “覆盖"私有方法public class PrivateOverride { private void f(){ System.out.println(“private f()”); } public static void main(String[] args){ PrivateOverride po = new Derived(); po.f(); }}class Derived extends PrivateOverride{ public void f(){ System.out.println(“public f()”); }}//out:private f()我们期望输出的是public f(),但结果并非我们所想。PrivateOverride中的f()方法是私有的,对它的子类是不可见的,Derived 的f()方法无法覆盖它。 结论:无法覆盖私有方法。需要注意,子类中方法切勿与父类中的私有方法同名,否则会容易混淆。坑2 域与静态方法不存在多态1. 域class SuperClass { public String field = “Super field”; public String getField() { return field; }}class SubClass extends SuperClass { public String field = “Sub field”; @Override public String getField() { return field; } public String getSuperField() { return super.getField(); }}public class FieldAccess { public static void main(String[] args) { SuperClass sup = new SubClass();//向上转型 System.out.println(“sup.field = " + sup.field + “, sup.getField() = " + sup.getField()); SubClass sub = new SubClass(); System.out.println(“sub.field = " + sub.field + “, sub.getField() = " + sub.getField() +”, sub.getSuperField() = " + sub.getSuperField()); }}//out:sup.field = Super field, sup.getField() = Sub fieldsub.field = Sub field, sub.getField() = Sub field, sub.getSuperField() = Super field域的访问操作是由编译器解析的,所以不是多态的。(扩展:多态是由动态绑定实现的,即是在程序运行期对数据做绑定)SuperClass中的field和SubClass中的field分配了不同的内存空间,在SubClass中实际上包含了两个field:一个是自己的和从父类SuperClass中继承的。子类SubClass中可使用super.field显示地指明SuperClass中的field。2. 静态方法class StaticSuper{ public static String staticGet(){ return “Base staticGet()”; } public String dynamicGet(){ return “Base dynamicGet()”; }}class StaticSub extends StaticSuper{ public static String staticGet(){ return “Base staticGet()”; } @Override public String dynamicGet(){ return “Base dynamicGet()”; }}public class StaticMethod { public static void main(String[] args) { StaticSuper sup = new StaticSub(); System.out.println(sup.staticGet()); System.out.println(sup.dynamicGet()); }}//out:Base staticGet()Base dynamicGet()静态方法无法被覆盖,无法实现多态。总结:域与静态方法都无法实现多态。需要注意,尽可能不对父类和子类的域使用相同的名字,无法覆盖的方法不使用相同名字,以免造成混淆。推荐阅读:老同学又来借钱了「 深入浅出 」集合Set你、我您的点赞、转发是对我最大的支持! THANDKSEnd -一个立志成大腿而每天努力奋斗的年轻人伴学习伴成长,成长之路你并不孤单! ...

January 9, 2019 · 2 min · jiezi

「 深入浅出 」集合Set

系列文章「 深入浅出 」集合List 「 深入浅出 」java集合Collection和MapSet继承自Collection接口,不能包含有重复元素。本篇文章主要讲Set中三个比较重要的实现类:HashSet、TreeSet。SetSet是一个存储无序且不重复元素的集合。在使用Set集合的时候,应该注意两点为Set集合里的元素的实现类重写equals()和hashCode()方法()若传入重复的元素,重复元素会被忽略(可以用于做集合的去重)扩展判断两个元素相等的标准:两个对象通过equals()方法比较相等,并且两个对象的hashCode()方法返回值也相等。HashSetHashSet是Set接口的典型实现,是哈希表结构,主要利用HashMap的key来存储元素,计算插入元素的hashCode来获取元素在集合中的位置,因此具有很好的存取和查找性能。主要特点1.不允许出现重复元素2.存储的元素是无序的3.不是同步的,如果多个线程同时访问一个HashSet,则必须通过代码来保证其同步。4.集合元素值可以是null。HashSet基本操作public class HashSetTest { public static void main(String[] agrs){ //创建HashSet集合: Set<String> hashSet = new HashSet<String>(); System.out.println(“HashSet初始容量大小:"+hashSet.size()); //元素添加: hashSet.add(“my”); hashSet.add(“name”); hashSet.add(“is”); hashSet.add(“ken”); hashSet.add(“hello”); hashSet.add(“everyone”); System.out.println(“HashSet容量大小:"+hashSet.size()); //迭代器遍历: Iterator<String> iterator = hashSet.iterator(); while (iterator.hasNext()){ String str = iterator.next(); System.out.print(str+” “); } System.out.print("\n”); //元素删除: hashSet.remove(“ken”); System.out.println(“HashSet元素大小:” + hashSet.size()); hashSet.clear(); System.out.println(“HashSet元素大小:” + hashSet.size()); //集合判断: boolean isEmpty = hashSet.isEmpty(); System.out.println(“HashSet是否为空:” + isEmpty); boolean isContains = hashSet.contains(“hello”); System.out.println(“HashSet是否为空:” + isContains); }}//out:HashSet初始容量大小:0HashSet容量大小:6ken everyone name is hello my HashSet元素大小:5HashSet元素大小:0HashSet是否为空:trueHashSet是否为空:false细节注意事项可看以下例子://重写类A的equals方法总是返回true,但没有重写其hashCode()方法。//不能保证当前对象是HashSet中的唯一对象class A { @Override public boolean equals(Object obj) { return true; }}//重写类B的hashCode()方法总是返回1,但没有重写其equals()方法。//不能保证当前对象是HashSet中的唯一对象class B { @Override public int hashCode() { return 1; }}//重写重写类C的hashCode()方法总是返回2,且有重写其equals()方法class C { @Override public int hashCode() { return 2; } @Override public boolean equals(Object obj) { return true; }}public class HashSetTest { public static void main(String[] args) { HashSet books = new HashSet(); //分别向books集合中添加两个A对象,两个B对象,两个C对象 books.add(new A()); books.add(new A()); books.add(new B()); books.add(new B()); books.add(new C()); books.add(new C()); System.out.println(books); }}//out[B@1, B@1, C@2, A@3bc257, A@785d65]可以看出,只有当同时重写了equals方法和hashCode方法时,才能按照自己的意图判断对象是否相等,否则均判定为不相等,可加入Set集合中。TreeSet与HashSet集合类似,TreeSet也是基于Map来实现,其底层结构为红黑树(特殊的二叉查找树)与HashSet不同的是,TreeSet具有排序功能,分为自然排序(123456)和自定义排序两类,默认是自然排序具有如下特点:对插入的元素进行排序,是一个有序的集合(主要与HashSet的区别)底层使用红黑树结构,而不是哈希表结构允许插入Null值不允许插入重复元素线程不安全TreeSet基本操作public class TreeSetTest { public static void main(String[] agrs){ TreeSet<String> treeSet = new TreeSet<String>(); System.out.println(“TreeSet初始化容量大小:"+treeSet.size()); //元素添加: treeSet.add(“my”); treeSet.add(“name”); treeSet.add(“ken”); treeSet.add(“hello”); treeSet.add(“world”); treeSet.add(“1”); treeSet.add(“2”); treeSet.add(“3”); System.out.println(“TreeSet容量大小:” + treeSet.size()); System.out.println(“TreeSet元素顺序为:” + treeSet.toString()); System.out.println(“遍历元素升序:”); //迭代器遍历:升序 Iterator<String> iteratorAesc = treeSet.iterator(); while(iteratorAesc.hasNext()){ String str = iteratorAesc.next(); System.out.print(str + " “); } System.out.println("\n”); System.out.println(“遍历元素降序:”); //迭代器遍历:降序 Iterator<String> iteratorDesc = treeSet.descendingIterator(); while(iteratorDesc.hasNext()){ String str = iteratorDesc.next(); System.out.print(str + " “); } System.out.println("\n”); //元素获取:实现NavigableSet接口 String firstEle = treeSet.first();//获取TreeSet头节点: System.out.println(“TreeSet头节点为:” + firstEle); // 获取指定元素之前的所有元素集合:(不包含指定元素) SortedSet<String> headSet = treeSet.headSet(“ken”); System.out.println(“ken节点之前的元素为:"+headSet.toString()); //获取给定元素之间的集合:(包含头,不包含尾) SortedSet subSet = treeSet.subSet(“1”,“hello”); System.out.println(“1–hello之间节点元素为:"+subSet.toString()); //集合判断: boolean isEmpty = treeSet.isEmpty(); System.out.println(“TreeSet是否为空:"+isEmpty); boolean isContain = treeSet.contains(“who”); System.out.println(“TreeSet是否包含who元素:"+isContain); //元素删除: boolean kenRemove = treeSet.remove(“ken”); System.out.println(“ken元素是否被删除”+kenRemove); //集合中不存在的元素,删除返回false boolean whoRemove = treeSet.remove(“who”); System.out.println(“who元素是否被删除”+whoRemove); //删除并返回第一个元素:如果set集合不存在元素,则返回null String pollFirst = treeSet.pollFirst(); System.out.println(“删除的第一个元素:"+pollFirst); //删除并返回最后一个元素:如果set集合不存在元素,则返回null String pollLast = treeSet.pollLast(); System.out.println(“删除的最后一个元素:"+pollLast); treeSet.clear();//清空集合 }}//out:TreeSet初始化容量大小:0TreeSet容量大小:8TreeSet元素顺序为:[1, 2, 3, hello, ken, my, name, world]遍历元素升序:1 2 3 hello ken my name world 遍历元素降序:world name my ken hello 3 2 1 TreeSet头节点为:1ken节点之前的元素为:[1, 2, 3, hello]1–hello之间节点元素为:[1, 2, 3]TreeSet是否为空:falseTreeSet是否包含who元素:falsejiaboyan元素是否被删除falsewho元素是否被删除false删除的第一个元素:1删除的最后一个元素:worldTreeSet元素排序我们讲到了TreeSet是一个有序集合,可以对集合元素排序,其中分为自然排序和自定义排序自然排序(正序与反序)public class TreeSetTest { public static void main(String[] agrs){ TreeSet<String> treeSetString = new TreeSet<String>(); treeSetString.add(“a”); treeSetString.add(“z”); treeSetString.add(“d”); treeSetString.add(“b”); System.out.println(“字母正序:” + treeSetString.toString()); System.out.println(“字母反序:” + treeSetString.descendingSet().toString()); TreeSet<Integer> treeSetInteger = new TreeSet<Integer>(); treeSetInteger.add(1); treeSetInteger.add(24); treeSetInteger.add(23); treeSetInteger.add(6); System.out.println(“数字正序:” + treeSetInteger.toString()); System.out.println(“数字反序:” + treeSetInteger.descendingSet().toString()); }}//out字母顺序:[a, b, d, z]数字顺序:[1, 6, 23, 24]自定义排序当使用的是自己定义的类时,就需要做一些特殊处理,否则会报错Exception in thread “main” java.lang.ClassCastException,有两种实现方式1.实现Comparable接口public class Person implements Comparable<Person>{ private String name; private Integer age; public Person(){} public Person(String name,Integer age){ this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public int compareTo(Person person) { //如果name长度一样,则比较年龄的大小 //需要考虑相等的情况,否则相等的情况只显示先加入集合的元素 if(this.age.equals(person.age)){ return 1; } return this.age - person.age; } @Override public String toString() { return “Person{” + “name=’” + name + ‘'’ + “, age=” + age + ‘}’; } public static void main(String[] agrs){ TreeSet<Person> treeSet = new TreeSet<Person>(); //排序对象: Person p1 = new Person(“ken”,18); Person p2 = new Person(“xiaoming”,15); Person p3 = new Person(“laowang”,15); Person p4 = new Person(“zhangsan”,25); //添加到集合: treeSet.add(p1); treeSet.add(p2); treeSet.add(p3); treeSet.add(p4); System.out.println(“TreeSet集合顺序为:"+treeSet); }}//out:TreeSet集合顺序为:[Person{name=‘xiaoming’, age=15}, Person{name=‘laowang’, age=15}, Person{name=‘ken’, age=18}, Person{name=‘zhangsan’, age=25}]2.实现Comparetor<t style=“margin: 0px; padding: 0px; max-width: 100%; overflow-wrap: break-word !important; font-size: inherit; color: inherit; line-height: inherit;">接口,并重写compare方法</t>//自定义Person类的比较器:public class PersonComparator implements Comparator<Person> { @Override public int compare(Person p1, Person p2) { //比较名字长度,从大到小排序 //需要考虑相等的情况,否则相等的情况只显示先加入集合的元素 if(p2.getName().length() == p1.getName().length()){ return 1; } return p2.getName().length() - p1.getName().length(); }}测试程序public static void main(String[] agrs){ TreeSet<Person> treeSet = new TreeSet<Person>(new PersonComparator()); //排序对象: Person p1 = new Person(“ken”,18); Person p2 = new Person(“xiaoming”,15); Person p3 = new Person(“laowang”,15); Person p4 = new Person(“zhangsan”,25); //添加到集合: treeSet.add(p1); treeSet.add(p2); treeSet.add(p3); treeSet.add(p4); System.out.println(“TreeSet集合顺序为:"+treeSet); }//outTreeSet集合顺序为:[Person{name=‘xiaoming’, age=15}, Person{name=‘zhangsan’, age=25}, Person{name=‘laowang’, age=15}, Person{name=‘ken’, age=18}]后续文章将对java集合中的具体实现类进行深入了解。有兴趣的话可以观看后续内容,进一步了解java集合内容。推荐阅读: 个人网站模板推荐如何在GitHub上大显身手?IDEA热部署插件JRebel坚持日更:7天您的点赞、转发是对我最大的支持! THANDKSEnd -一个立志成大腿而每天努力奋斗的年轻人伴学习伴成长,成长之路你并不孤单! ...

January 9, 2019 · 3 min · jiezi

Java基础差,需要怎么补

本文首发于本博客 猫叔的博客,转载请申明出处感谢粉丝的提问:Java基础差,需要怎么补?我整体的总结了一下,大致分为以下的几个点说一下:1、善于使用搜索引擎现在的网上资源基本是不缺的,缺的是你去搜索的积极性,你需要善于使用搜索引擎,去查找你想要的答案,类似百度、必应等大型搜索引擎,或者是去一些技术的问答平台提问。对于Java的一些基础知识,你可以轻而易举的找到对应相关的demo与实战,同时其针对性也强。2、整理学习目录如果你是一个自学Java的初学者,那么你其实更加需要一个细致的学习目录来帮你快速的整理入门流程,如下是我参考一些网络教程的自学目录。开发环境搭建(开发工具IDE)数据类型与运算符流程控制数组类和对象继承多态抽象类和接口异常如果你想要更高级一点的目录集合框架与泛型实用类lang、util输入、输出和反射注解和多线程网络编程技术XML技术你可能还需要一些扩展知识Spring框架MySql、OracleLinux、WindowHibernate、Struts2、SpringBoot等框架3、有规律的学习与笔记在你根据目录学习的过程中,我想你需要且一定要有一份在线或者线下的笔记,记录你的学习心得还有对技术的理解体会,甚至你可以借助一些你所理解的比喻来加深你对这一技术的学习等。比如我从在校带实验室的时候就一直保持笔记的习惯,我也一直保持到现在。4、反复与实战强化可能你在学习完一项技能后,或者一个基础知识后,开始上手,而且对应的demo也写得很顺利,但是一定要反复的练习,就像学习英语一样,你要不停的学习,最后在学习了几天后,可以自己和自己介绍这个技术并自己写一个实例来使用这个技术,那么你在未来都将记住这一技术的基本使用方式。浅显的说了一下,可能你对于基础是对于web应用,其实基本的思路也是差不多的,没有什么是捷径,唯一有的就是不停的学习与摸索。加油,各位!公众号:Java猫说现架构设计(码农)兼创业技术顾问,不羁平庸,热爱开源,杂谈程序人生与不定期干货。

January 4, 2019 · 1 min · jiezi