冰扬

冰扬 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑

放低姿态、努力进取、勇于探索、乐于分享

个人动态

冰扬 赞了回答 · 6月10日

JavaScript字符串单词首字母大写的实现方式

CSS 完全搞定啊 text-transform: capitalize;
这么简单的功能 还用js 就太浪费累

关注 22 回答 11

冰扬 赞了文章 · 2019-08-06

为vue3学点typescript, 解读高级类型

直达

第一课, 体验typescript

第二课, 基础类型和入门高级类型

第三课, 泛型

第四课, 解读高级类型

第五课, 什么是命名空间(namespace)?

回顾

第二课的时候为了更好的讲解基础类型, 所以我们讲解了一部分高级类型, 比如"接口( interface )" / "联合类型( | )" / "交叉类型( & )", 本节课我会把剩余高级类型都讲完.

知识点摘要

本节课主要关键词为: 自动类型推断 / 类型断言 / 类型别名(type) / 映射类型(Pick/Record等...) / 条件类型(extends) / 类型推断(infer)

自动类型推断(不用你标类型了,ts自己猜)

第二课我们讲了那么多基础类型, 大家现在写ts的时候一定会在每个变量后面都加上类型吧? 但是?

现在告诉大家有些情况下你不需要标注类型, ts可以根据你写的代码来自动推断出类型:

赋值字面量给变量

let n = 1; // ts会自动推断出n是number类型
n+=3 // 不报错,因为已知类型

let arr1 = []; // 类型为: any[]
arr1.push(1,'2', {o:3});

let arr = [1]; // 内部要有数字, 才能推断出正确类型
arr.push(2);
自动阅读if条件判断
let n: number|null = 0.5 < Math.random() ? 1:null;
if(null !== n){
    n+=3 // ts知道现在n不是null是number
}
浏览器自带api
document.ontouchstart = ev=>{ // 能自动推断出ev为TouchEvent
    console.log(ev.touches);  // 不报错, TouchEvent上有touches属性
}
typeof

typeof就是js中的typeof, ts会根据你代码中出现的typeof来自动推断类型:

let n:number|string = 0.5 < Math.random()? 1:'1';

// 如果没有typeof, n*=2会报错, 提示没法推断出当前是number类型, 不能进行乘法运算
if('number' === typeof n) {
    n*= 2;
} else  {
    n= '2';
}

注意: 在ts文档中, 该部分的知识点叫做typeof类型保护, 和其他类型推断的内容是分开的, 被写在高级类型/类型保护章节中.

instanceof

ts会根据你代码中出现的instanceof来自动推断类型:

let obj = 0.5 < Math.random() ? new String(1) : new Array(1);
if(obj instanceof String){
    // obj推断为String类型
    obj+= '123'
} else {
    // obj为any[]类型
    obj.push(123);
}

注意: 在ts文档中, 该部分的知识点叫做instanceof类型保护, 和其他类型推断的内容是分开的, 被写在高级类型/类型保护章节中.

类型断言(你告诉ts是什么类型, 他都信)

有些情况下系统没办法自动推断出正确的类型, 就需要我们标记下, 断言有2种语法, 一种是通过"<>", 一种通过"as", 举例说明:

let obj = 0.5 < Math.random() ? 1 : [1]; // number|number[]

// 断言, 告诉ts, obj为数组
(<number[]>obj).push(1);

//等价
(obj as number[]).push(1);

类型别名(type)

类型别名可以表示很多接口表示不了的类型, 比如字面量类型(常用来校验取值范围):

type A = 'top'|'right'|'bottom'|'left'; // 表示值可能是其中的任意一个
type B = 1|2|3;
type C = '红'|'绿'|'黄';
type D = 150;

let a:A = 'none'; // 错误, A类型中没有'none'
更多组合:
interface A1{
    a:number;
}
type B = A1 | {b:string};
type C = A1 & {b:string};

// 与泛型组合
type D<T> = A1 | T[];

索引类型(keyof)

js中的Object.keys大家肯定都用过, 获取对象的键值, ts中的keyof和他类似, 可以用来获取对象类型的键值:

type A = keyof {a:1,b:'123'} // 'a'|'b'
type B = keyof [1,2] // '1'|'2'|'push'... , 获取到内容的同时, 还得到了Array原型上的方法和属性(实战中暂时没遇到这种需求, 了解即可)

可以获得键值, 也可以获取对象类型的值的类型:

type C = A['a'] // 等于type C = 1;
let c:C = 2 // 错误, 值只能是1

映射类型(Readonly, Pick, Record等...)

映射类型比较像修改类型的工具函数, 比如Readonly可以把每个属性都变成只读:

type A  = {a:number, b:string}
type A1 = Readonly<A> // {readonly a: number;readonly b: string;}

打开node_modules/typescript/lib文件夹可以找到lib.es5.d.ts, 在这我们能找到Readonly的定义:

type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

其实不是很复杂, 看了本节课前面前面的内容, 这个很好理解是吧:

  1. 定义一个支持泛型的类型别名, 传入类型参数T.
  2. 通过keyof获取T上的键值集合.
  3. in表示循环keyof获取的键值.
  4. 添加readonly标记.
Partial<T>, 让属性都变成可选的
type A  = {a:number, b:string}
type A1 = Partial<A> // { a?: number; b?: string;}
Required<T>, 让属性都变成必选
type A  = {a?:number, b?:string}
type A1 = Required<A> // { a: number; b: string;}
Pick<T,K>, 只保留自己选择的属性, U代表属性集合
type A  = {a:number, b:string}
type A1 = Pick<A, 'a'> //  {a:number}
Omit<T,K> 实现排除已选的属性
type A  = {a:number, b:string}
type A1 = Omit<A, 'a'> // {b:string}
Record<K,T>, 创建一个类型,T代表键值的类型, U代表值的类型
type A1 = Record<string, string> // 等价{[k:string]:string}
Exclude<T,U>, 过滤T中和U相同(或兼容)的类型
type A  = {a:number, b:string}
type A1 = Exclude<number|string, string|number[]> // number

// 兼容
type A2 = Exclude<number|string, any|number[]> // never , 因为any兼容number, 所以number被过滤掉
Extract<T,U>, 提取T中和U相同(或兼容)的类型
type A  = {a:number, b:string}
type A1 = Extract<number|string, string|number[]> // string
NonNullable<T>, 剔除T中的undefined和null
type A1 = NonNullable<number|string|null|undefined> // number|string
ReturnType<T>, 获取T的返回值的类型
type A1= ReturnType<()=>number> // number
InstanceType<T>, 返回T的实例类型

ts中类有2种类型, 静态部分的类型和实例的类型, 所以T如果是构造函数类型, 那么InstanceType可以返回他的实例类型:

interface A{
    a:HTMLElement;
}

interface AConstructor{
    new():A;
}

function create (AClass:AConstructor):InstanceType<AConstructor>{
    return new AClass();
}
Parameters<T> 获取函数参数类型

返回类型为元祖, 元素顺序同参数顺序.

interface A{
    (a:number, b:string):string[];
}

type A1 = Parameters<A> // [number, string]
ConstructorParameters<T> 获取构造函数的参数类型

Parameters类似, 只是T这里是构造函数类型.

interface AConstructor{
    new(a:number):string[];
}

type A1 = ConstructorParameters<AConstructor> // [number]

extends(条件类型)

T extends U ? X : Y

用来表示类型是不确定的, 如果U的类型可以表示T, 那么返回X, 否则Y. 举几个例子:

type A =  string extends '123' ? string :'123' // '123'
type B =  '123' extends string ? string :123 // string

明显string的范围更大, '123'可以被string表示, 反之不可.

infer(类型推断)

单词本身的意思是"推断", 实际表示在extends条件语句中声明待推断的类型变量. 我们上面介绍的映射类型中就有很多都是ts在lib.d.ts中实现的, 比如Parameters:

type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;

为vue3学点typescript, 解读高级类型

直达

第一课, 体验typescript

第二课, 基础类型和入门高级类型

第三课, 泛型

第四课, 解读高级类型

回顾

第二课的时候为了更好的讲解基础类型, 所以我们讲解了一部分高级类型, 比如"接口( interface )" / "联合类型( | )" / "交叉类型( & )", 本节课我会把剩余高级类型都讲完.

知识点摘要

本节课主要关键词为: 自动类型推断 / 类型断言 / 类型别名(type) / 映射类型(Pick/Record等...) / 条件类型(extends) / 类型推断(infer)

自动类型推断(不用你标类型了,ts自己猜)

第二课我们讲了那么多基础类型, 大家现在写ts的时候一定会在每个变量后面都加上类型吧? 但是?

现在告诉大家有些情况下你不需要标注类型, ts可以根据你写的代码来自动推断出类型:

赋值字面量给变量

let n = 1; // ts会自动推断出n是number类型
n+=3 // 不报错,因为已知类型

let arr1 = []; // 类型为: never[]
arr1.push(1); // 报错,

let arr = [1]; // 内部要有数字, 才能推断出正确类型
arr.push(2);
自动阅读if条件判断
let n: number|null = 0.5 < Math.random() ? 1:null;
if(null !== n){
    n+=3 // ts知道现在n不是null是number
}
浏览器自带api
document.ontouchstart = ev=>{ // 能自动推断出ev为TouchEvent
    console.log(ev.touches);  // 不报错, TouchEvent上有touches属性
}
typeof

typeof就是js中的typeof, ts会根据你代码中出现的typeof来自动推断类型:

let n:number|string = 0.5 < Math.random()? 1:'1';

// 如果没有typeof, n*=2会报错, 提示没法推断出当前是number类型, 不能进行乘法运算
if('number' === typeof n) {
    n*= 2;
} else  {
    n= '2';
}

注意: 在ts文档中, 该部分的知识点叫做typeof类型保护, 和其他类型推断的内容是分开的, 被写在高级类型/类型保护章节中.

instanceof

ts会根据你代码中出现的instanceof来自动推断类型:

let obj = 0.5 < Math.random() ? new String(1) : new Array(1);
if(obj instanceof String){
    // obj推断为String类型
    obj+= '123'
} else {
    // obj为any[]类型
    obj.push(123);
}

注意: 在ts文档中, 该部分的知识点叫做instanceof类型保护, 和其他类型推断的内容是分开的, 被写在高级类型/类型保护章节中.

类型断言(你告诉ts是什么类型, 他都信)

有些情况下系统没办法自动推断出正确的类型, 就需要我们标记下, 断言有2种语法, 一种是通过"<>", 一种通过"as", 举例说明:

let obj = 0.5 < Math.random() ? 1 : [1]; // number|number[]

// 断言, 告诉ts, obj为数组
(<number[]>obj).push(1);

//等价
(obj as number[]).push(1);

类型别名(type)

类型别名可以表示很多接口表示不了的类型, 比如字面量类型(常用来校验取值范围):

type A = 'top'|'right'|'bottom'|'left'; // 表示值可能是其中的任意一个
type B = 1|2|3;
type C = '红'|'绿'|'黄';
type D = 150;

let a:A = 'none'; // 错误, A类型中没有'none'
更多组合:
interface A1{
    a:number;
}
type B = A1 | {b:string};
type C = A1 & {b:string};

// 与泛型组合
type D<T> = A1 | T[];

索引类型(keyof)

js中的Object.keys大家肯定都用过, 获取对象的键值, ts中的keyof和他类似, 可以用来获取对象类型的键值:

type A = keyof {a:1,b:'123'} // 'a'|'b'
type B = keyof [1,2] // '1'|'2'|'push'... , 获取到内容的同时, 还得到了Array原型上的方法和属性(实战中暂时没遇到这种需求, 了解即可)

可以获得键值, 也可以获取对象类型的值的类型:

type C = A['a'] // 等于type C = 1;
let c:C = 2 // 错误, 值只能是1

映射类型(Readonly, Pick, Record等...)

映射类型比较像修改类型的工具函数, 比如Readonly可以把每个属性都变成只读:

type A  = {a:number, b:string}
type A1 = Readonly<A> // {readonly a: number;readonly b: string;}

打开node_modules/typescript/lib文件夹可以找到lib.es5.d.ts, 在这我们能找到Readonly的定义:

type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

其实不是很复杂, 看了本节课前面前面的内容, 这个很好理解是吧:

  1. 定义一个支持泛型的类型别名, 传入类型参数T.
  2. 通过keyof获取T上的键值集合.
  3. in表示循环keyof获取的键值.
  4. 添加readonly标记.
Partial<T>, 让属性都变成可选的
type A  = {a:number, b:string}
type A1 = Partial<A> // { a?: number; b?: string;}
Required<T>, 让属性都变成必选
type A  = {a?:number, b?:string}
type A1 = Required<A> // { a: number; b: string;}
Pick<T,K>, 只保留自己选择的属性, U代表属性集合
type A  = {a:number, b:string}
type A1 = Pick<A, 'a'> //  {a:number}
Omit<T,K> 实现排除已选的属性
type A  = {a:number, b:string}
type A1 = Omit<A, 'a'> // {b:string}
Record<K,T>, 创建一个类型,T代表键值的类型, U代表值的类型
type A1 = Record<string, string> // 等价{[k:string]:string}
Exclude<T,U>, 过滤T中和U相同(或兼容)的类型
type A  = {a:number, b:string}
type A1 = Exclude<number|string, string|number[]> // number

// 兼容
type A2 = Exclude<number|string, any|number[]> // never , 因为any兼容number, 所以number被过滤掉
Extract<T,U>, 提取T中和U相同(或兼容)的类型
type A  = {a:number, b:string}
type A1 = Extract<number|string, string|number[]> // string
NonNullable<T>, 剔除T中的undefined和null
type A1 = NonNullable<number|string|null|undefined> // number|string
ReturnType<T>, 获取T的返回值的类型
type A1= ReturnType<()=>number> // number
InstanceType<T>, 返回T的实例类型

ts中类有2种类型, 静态部分的类型和实例的类型, 所以T如果是构造函数类型, 那么InstanceType可以返回他的实例类型:

interface A{
    a:HTMLElement;
}

interface AConstructor{
    new():A;
}

function create (AClass:AConstructor):InstanceType<AConstructor>{
    return new AClass();
}
Parameters<T> 获取函数参数类型

返回类型为元祖, 元素顺序同参数顺序.

interface A{
    (a:number, b:string):string[];
}

type A1 = Parameters<A> // [number, string]
ConstructorParameters<T> 获取构造函数的参数类型

Parameters类似, 只是T这里是构造函数类型.

interface AConstructor{
    new(a:number):string[];
}

type A1 = ConstructorParameters<AConstructor> // [number]

extends(条件类型)

T extends U ? X : Y

用来表示类型是不确定的, 如果U的类型可以表示T, 那么返回X, 否则Y. 举几个例子:

type A =  string extends '123' ? string :'123' // '123'
type B =  '123' extends string ? string :123 // string

明显string的范围更大, '123'可以被string表示, 反之不可.

infer(类型推断)

单词本身的意思是"推断", 实际表示在extends条件语句中声明待推断的类型变量. 我们上面介绍的映射类型中就有很多都是ts在lib.d.ts中实现的, 比如Parameters:

type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;

上面声明一个P用来表示...args可能的类型, 如果(...args: infer P)可以表示T, 那么返回...args对应的类型, 也就是函数的参数类型, 反之返回never.

注意: 开始的T extends (...args: any) => any用来校验输入的T是否是函数, 如果不是ts会报错, 如果直接替换成T不会有报错, 会一直返回never.

应用infer

接下来我们利用infer来实现"删除元祖类型中第一个元素", 这常用于简化函数参数, 这有一个我之前的应用

export type Tail<Tuple extends any[]> = ((...args: Tuple) => void) extends ((a: any, ...args: infer T) => void) ? T : never;

总结

多写多练, 很快就上手, 放几个我用ts写的项目当做参考, 抛砖引玉, 加油!

手势库: https://github.com/any86/any-...

命令式调用vue组件: https://github.com/any86/vue-...

工作中常用的一些代码片段: https://github.com/any86/usef...

一个mini的事件管理器: https://github.com/any86/any-...
上面声明一个P用来表示...args可能的类型, 如果(...args: infer P)可以表示T, 那么返回...args对应的类型, 也就是函数的参数类型, 反之返回never.

注意: 开始的T extends (...args: any) => any用来校验输入的T是否是函数, 如果不是ts会报错, 如果直接替换成T不会有报错, 会一直返回never.

应用infer

接下来我们利用infer来实现"删除元祖类型中第一个元素", 这常用于简化函数参数, 这有一个我之前的应用

export type Tail<Tuple extends any[]> = ((...args: Tuple) => void) extends ((a: any, ...args: infer T) => void) ? T : never;

总结

多写多练, 很快就上手, 放几个我用ts写的项目当做参考, 抛砖引玉, 加油!

手势库: https://github.com/any86/any-...

命令式调用vue组件: https://github.com/any86/vue-...

工作中常用的一些代码片段: https://github.com/any86/usef...

一个mini的事件管理器: https://github.com/any86/any-...

查看原文

赞 28 收藏 19 评论 0

冰扬 赞了文章 · 2019-08-06

为vue3学点typescript, 基础类型和入门高级类型

导航

第一课, 体验typescript

第二课, 基础类型和入门高级类型

第三课, 泛型

第四课, 解读高级类型

第五课, 什么是命名空间(namespace)?

很重要

这一节很重要, 可以说是ts的最核心部分, 这一节学完其实就可以开始用ts写代码了, 想想typescript中的type, 再看看标题中的"类型"2字, 所以请大家务必认真.

什么是入门高级类型

因为高级类型的内容比较多, 但是有些基础类型的知识点还必须要用到高级类型的知识讲解才连贯, 所以本节课把最常用的高级类型提前讲解一下, 比如接口/联合类型/交叉类型.

基础类型

ts中基础类型有如下几种:boolean / number / string / object / 数组 / 元组 / 枚举 / any / undefined / null / void / never, 下面我们一一举例学习:

字面量

介绍类型前,有一个前置知识点就是字面量, 字面量的意思就是直接声明, 而非new关键词实例化出来的数据:

// 字面量
const n:number = 123;
const s:string = '456';
const o:object = {a:1,b:'2'};

// 非字面量
const n:Number = new Number(123);
const s:String = new String('456');
const o:Object = new Object({a:1,b:'2'});

通过上面的例子, 大家应该看明白为什么ts中有些类型的开头字母是小写的了吧. 这是因为ts中用小写字母开头的类型代表字面量, 大写的是用来表示通过new实例化的数据.

boolean

布尔类型, 取值只有true / false

const IS_MOBILE:boolean = true;
const IS_TABLE:boolean = false;

number

数字类型, 整数/小数都包括, 同时支持2/8/10/16进制字面量.

let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
let binaryLiteral: number = 0b1010;
let octalLiteral: number = 0o744;

string

字符串类型

let s1:string = 'hello world!';
let s2:string = 'hello ${name}`;

数组

数组有2种表示方式:

第1种, 通过在指定类型后面增加[], 表示该数组内的元素都是该指定类型:

let numbers:number[] = [1,2,3,4,5];
// number|string代表联合类型, 下面的高级类型中会讲
let numbers:(number|string)[] = [1,2,3,4,'5'];

第2种, 通过泛型表示, Array<元素类型>, 泛型会在后面课讲解, 先做了解即可:

let numbers:Array<number> = [1,2,3,4,5];

元组(Tuple)

元组类型表示一个已知元素数量类型数组, 各元素的类型不必相同:

let list1:[number, string] = [1, '2', 3]; // 错误, 数量不对, 元组中只声明有2个元素
let list2:[number, string] = [1, 2]; // 错误, 第二个元素类型不对, 应该是字符串'2'
let list3:[number, string] = ['1', 2]; // 错误, 2个元素的类型颠倒了
let list4:[number, string] = [1, '2']; // 正确

枚举(enum)

枚举是ts中有而js中没有的类型, 编译后会被转化成对象, 默认元素的值从1开始, 如下面的Color.Red的值为1, 以此类推Color.Green为2, Color.Blue为3:

enum Color {Red, Green, Blue}
// 等价
enum Color {Red=1, Green=2, Blue=3}

当然也可以自己手动赋值:

enum Color {Red=1, Green=2, Blue=4}

并且我们可以反向通过值得到他的键值:

enum Color {Red=1, Green=2, Blue=4}
Color[2] === 'Green' // true

看下编译成js后的枚举代码, 你就明白为什么可以反向得到键值:

var Color;
(function (Color) {
    Color[Color["Red"] = 0] = "Red";
    Color[Color["Green"] = 1] = "Green";
    Color[Color["Blue"] = 2] = "Blue";
})(Color || (Color = {}));
// Color的值为: {0: "Red", 1: "Green", 2: "Blue", Red: 0, Green: 1, Blue: 2}

any(任意类型)

any代表任意类型, 也就是说, 如果你不清楚变量是什么类型, 就可以用any进行标记, 比如引入一些比较老的js库, 没有声明类型, 使用的时候就可以标记为any类型, 这样ts就不会提示错误了. 当然不能所有的地方都用any, 那样ts就没有使用的意义了.

let obj:any = {};
// ts自己推导不出forEach中给obj增加了'a'和'b'字段.
['a', 'b'].forEach(letter=>{
    obj[letter] = letter;
});

// 但是因为标记了any, 所以ts认为a可能存在
obj.a = 123

void

void的意义和any相反, 表示不是任何类型, 一般出现在函数中, 用来标记函数没有返回值:

function abc(n:number):void{
    console.log(n);
}

void类型对应2个值, 一个是undefined,一个null:

const n1:void = undefined;
const n2:void = null;

null 和 undefined

默认情况下null和undefined是所有类型的子类型, 比如:

const n1:null = 123;
const n2:undefined = '123';

注意: 这是因为默认情况下的编译选项strictNullChecks为false, 但是为了避免一些奇怪的问题出现, 我还是建议大家设置为true(编译选项设置的内容, 会在后面的课程讲解), 请用精准的类型去标注.

如果一个变量的值确实需要是null或者undefined, 可以像下面这么用, ts会自动根据if/else推导出正确类型:

// 这是"联合类型", 在"高级类型"中会有详细介绍, 表示n可能是undefined也可能是number
let num: undefined|number;

if(Math.random()>0.5) num = 1;

if(undefined !== num) {
    num++;
}

never

never表示不可达, 用文字还真不好描述, 主要使用在throw的情况下:

function error():never{
    throw '错了!';
}

object

object表示非原始类型, 也就是除number/ ss/ boolean/ symbol/ null/ undefined之外的类型:

let o1:object = [];
let o2:object = {a:1,b:2};

但是, 我们实际上基本不用object类型的, 因为他标注的非常不具体, 一般都用接口来标注更具体的对象类型, 请继续看下面的接口的内容.

高级类型入门

通过基础类型组合而来的, 我们可以叫他高级类型. 包含: 交叉类型 / 联合类型 / 接口等等, 当然不止他们3个, 为了不让本节课太长造成读者疲劳, 本节只先讲这3个, 不过不要着急, 下节课会完结高级类型.

接口(interface)

一种定义复杂类型的格式, 比如我们用对象格式存储一篇文章, 那么就可以用接口定义文章的类型:

interface Article {
    title: stirng;
    count: number;
    content:string;
    fromSite: string;
}

const article: Article = {
    title: '为vue3学点typescript(2), 类型',
    count: 9999,
    content: 'xxx...',
    fromSite: 'baidu.com'
}

在这种情况下,当我们给article赋值的时候, 如果任何一个字段没有被赋值或者字段对应的数据类型不对, ts都会提示错误, 这样就保证了我们写代码不会出现上述的小错误.

非必填(?)

还是上面的例子, fromSite的意思是文章来自那个网站,如果文章都是原创的, 该字段就不会有值, 但是如果我们不传又会提示错误, 怎么办?
这时候就需要标记fromSite字段为非必填, 用"?"标记:

interface Article {
    title: stirng;
    count: number;
    content:string;
    fromSite?: string; // 非必填
}

// 不会报错
const article: Article = {
    title: '为vue3学点typescript(2), 类型',
    count: 9999,
    content: 'xxx...',
}

用接口定义函数

接口不仅可以定义对象, 还可以定义函数:

// 声明接口
interface Core {
    (n:number, s:string):[number,string]
}

// 声明函数遵循接口定义
const core:Core = (a,b)=>{
    return [a,b];
}

用接口定义类

先简单看下如何给类定义接口, 后面的课程具体讲类:

// 定义
interface Animate {
    head:number;
    body:number;
    foot:number;
    eat(food:string):void;
    say(word:string):string;
}

// implements
class Dog implements Animate{
    head=1;
    body=1;
    foot=1;
    eat(food){
        console.log(food);
    }
    say(word){
        return word;
    }
}

交叉类型(&)

交叉类型是将多个类型合并为一个类型, 表示"并且"的关系,用&连接多个类型, 常用于对象合并:

interface A {a:number};
interface B {b:string};

const a:A = {a:1};
const b:B = {b:'1'};
const ab:A&B = {...a,...b};

联合类型(|)

交叉类型也是将多个类型合并为一个类型, 表示""的关系,用|连接多个类型:

function setWidth(el: HTMLElement, width: string | number) {
    el.style.width = 'number' === typeof width ? `${width}px` : width;
}

注意: 我这里标记了el为HTMLElement, 可以在typescript的仓库看到ts还定义了很多元素, 请自行浏览(不用背, 用的时候现查),
https://github.com/microsoft/...

总结

如果您看完了上面的所有知识点, 你就可以开始动手造轮子练习了, 加油. 下面是我用ts造的轮子, 如果喜欢请帮忙点下star, 谢谢.

手势库: https://github.com/any86/any-...

命令式调用vue组件: https://github.com/any86/vue-...

工作中常用的一些代码片段: https://github.com/any86/usef...

一个mini的事件管理器: https://github.com/any86/any-...

查看原文

赞 35 收藏 24 评论 0

冰扬 赞了文章 · 2019-08-06

为vue3学点typescript, 基础类型和入门高级类型

导航

第一课, 体验typescript

第二课, 基础类型和入门高级类型

第三课, 泛型

第四课, 解读高级类型

第五课, 什么是命名空间(namespace)?

很重要

这一节很重要, 可以说是ts的最核心部分, 这一节学完其实就可以开始用ts写代码了, 想想typescript中的type, 再看看标题中的"类型"2字, 所以请大家务必认真.

什么是入门高级类型

因为高级类型的内容比较多, 但是有些基础类型的知识点还必须要用到高级类型的知识讲解才连贯, 所以本节课把最常用的高级类型提前讲解一下, 比如接口/联合类型/交叉类型.

基础类型

ts中基础类型有如下几种:boolean / number / string / object / 数组 / 元组 / 枚举 / any / undefined / null / void / never, 下面我们一一举例学习:

字面量

介绍类型前,有一个前置知识点就是字面量, 字面量的意思就是直接声明, 而非new关键词实例化出来的数据:

// 字面量
const n:number = 123;
const s:string = '456';
const o:object = {a:1,b:'2'};

// 非字面量
const n:Number = new Number(123);
const s:String = new String('456');
const o:Object = new Object({a:1,b:'2'});

通过上面的例子, 大家应该看明白为什么ts中有些类型的开头字母是小写的了吧. 这是因为ts中用小写字母开头的类型代表字面量, 大写的是用来表示通过new实例化的数据.

boolean

布尔类型, 取值只有true / false

const IS_MOBILE:boolean = true;
const IS_TABLE:boolean = false;

number

数字类型, 整数/小数都包括, 同时支持2/8/10/16进制字面量.

let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
let binaryLiteral: number = 0b1010;
let octalLiteral: number = 0o744;

string

字符串类型

let s1:string = 'hello world!';
let s2:string = 'hello ${name}`;

数组

数组有2种表示方式:

第1种, 通过在指定类型后面增加[], 表示该数组内的元素都是该指定类型:

let numbers:number[] = [1,2,3,4,5];
// number|string代表联合类型, 下面的高级类型中会讲
let numbers:(number|string)[] = [1,2,3,4,'5'];

第2种, 通过泛型表示, Array<元素类型>, 泛型会在后面课讲解, 先做了解即可:

let numbers:Array<number> = [1,2,3,4,5];

元组(Tuple)

元组类型表示一个已知元素数量类型数组, 各元素的类型不必相同:

let list1:[number, string] = [1, '2', 3]; // 错误, 数量不对, 元组中只声明有2个元素
let list2:[number, string] = [1, 2]; // 错误, 第二个元素类型不对, 应该是字符串'2'
let list3:[number, string] = ['1', 2]; // 错误, 2个元素的类型颠倒了
let list4:[number, string] = [1, '2']; // 正确

枚举(enum)

枚举是ts中有而js中没有的类型, 编译后会被转化成对象, 默认元素的值从1开始, 如下面的Color.Red的值为1, 以此类推Color.Green为2, Color.Blue为3:

enum Color {Red, Green, Blue}
// 等价
enum Color {Red=1, Green=2, Blue=3}

当然也可以自己手动赋值:

enum Color {Red=1, Green=2, Blue=4}

并且我们可以反向通过值得到他的键值:

enum Color {Red=1, Green=2, Blue=4}
Color[2] === 'Green' // true

看下编译成js后的枚举代码, 你就明白为什么可以反向得到键值:

var Color;
(function (Color) {
    Color[Color["Red"] = 0] = "Red";
    Color[Color["Green"] = 1] = "Green";
    Color[Color["Blue"] = 2] = "Blue";
})(Color || (Color = {}));
// Color的值为: {0: "Red", 1: "Green", 2: "Blue", Red: 0, Green: 1, Blue: 2}

any(任意类型)

any代表任意类型, 也就是说, 如果你不清楚变量是什么类型, 就可以用any进行标记, 比如引入一些比较老的js库, 没有声明类型, 使用的时候就可以标记为any类型, 这样ts就不会提示错误了. 当然不能所有的地方都用any, 那样ts就没有使用的意义了.

let obj:any = {};
// ts自己推导不出forEach中给obj增加了'a'和'b'字段.
['a', 'b'].forEach(letter=>{
    obj[letter] = letter;
});

// 但是因为标记了any, 所以ts认为a可能存在
obj.a = 123

void

void的意义和any相反, 表示不是任何类型, 一般出现在函数中, 用来标记函数没有返回值:

function abc(n:number):void{
    console.log(n);
}

void类型对应2个值, 一个是undefined,一个null:

const n1:void = undefined;
const n2:void = null;

null 和 undefined

默认情况下null和undefined是所有类型的子类型, 比如:

const n1:null = 123;
const n2:undefined = '123';

注意: 这是因为默认情况下的编译选项strictNullChecks为false, 但是为了避免一些奇怪的问题出现, 我还是建议大家设置为true(编译选项设置的内容, 会在后面的课程讲解), 请用精准的类型去标注.

如果一个变量的值确实需要是null或者undefined, 可以像下面这么用, ts会自动根据if/else推导出正确类型:

// 这是"联合类型", 在"高级类型"中会有详细介绍, 表示n可能是undefined也可能是number
let num: undefined|number;

if(Math.random()>0.5) num = 1;

if(undefined !== num) {
    num++;
}

never

never表示不可达, 用文字还真不好描述, 主要使用在throw的情况下:

function error():never{
    throw '错了!';
}

object

object表示非原始类型, 也就是除number/ ss/ boolean/ symbol/ null/ undefined之外的类型:

let o1:object = [];
let o2:object = {a:1,b:2};

但是, 我们实际上基本不用object类型的, 因为他标注的非常不具体, 一般都用接口来标注更具体的对象类型, 请继续看下面的接口的内容.

高级类型入门

通过基础类型组合而来的, 我们可以叫他高级类型. 包含: 交叉类型 / 联合类型 / 接口等等, 当然不止他们3个, 为了不让本节课太长造成读者疲劳, 本节只先讲这3个, 不过不要着急, 下节课会完结高级类型.

接口(interface)

一种定义复杂类型的格式, 比如我们用对象格式存储一篇文章, 那么就可以用接口定义文章的类型:

interface Article {
    title: stirng;
    count: number;
    content:string;
    fromSite: string;
}

const article: Article = {
    title: '为vue3学点typescript(2), 类型',
    count: 9999,
    content: 'xxx...',
    fromSite: 'baidu.com'
}

在这种情况下,当我们给article赋值的时候, 如果任何一个字段没有被赋值或者字段对应的数据类型不对, ts都会提示错误, 这样就保证了我们写代码不会出现上述的小错误.

非必填(?)

还是上面的例子, fromSite的意思是文章来自那个网站,如果文章都是原创的, 该字段就不会有值, 但是如果我们不传又会提示错误, 怎么办?
这时候就需要标记fromSite字段为非必填, 用"?"标记:

interface Article {
    title: stirng;
    count: number;
    content:string;
    fromSite?: string; // 非必填
}

// 不会报错
const article: Article = {
    title: '为vue3学点typescript(2), 类型',
    count: 9999,
    content: 'xxx...',
}

用接口定义函数

接口不仅可以定义对象, 还可以定义函数:

// 声明接口
interface Core {
    (n:number, s:string):[number,string]
}

// 声明函数遵循接口定义
const core:Core = (a,b)=>{
    return [a,b];
}

用接口定义类

先简单看下如何给类定义接口, 后面的课程具体讲类:

// 定义
interface Animate {
    head:number;
    body:number;
    foot:number;
    eat(food:string):void;
    say(word:string):string;
}

// implements
class Dog implements Animate{
    head=1;
    body=1;
    foot=1;
    eat(food){
        console.log(food);
    }
    say(word){
        return word;
    }
}

交叉类型(&)

交叉类型是将多个类型合并为一个类型, 表示"并且"的关系,用&连接多个类型, 常用于对象合并:

interface A {a:number};
interface B {b:string};

const a:A = {a:1};
const b:B = {b:'1'};
const ab:A&B = {...a,...b};

联合类型(|)

交叉类型也是将多个类型合并为一个类型, 表示""的关系,用|连接多个类型:

function setWidth(el: HTMLElement, width: string | number) {
    el.style.width = 'number' === typeof width ? `${width}px` : width;
}

注意: 我这里标记了el为HTMLElement, 可以在typescript的仓库看到ts还定义了很多元素, 请自行浏览(不用背, 用的时候现查),
https://github.com/microsoft/...

总结

如果您看完了上面的所有知识点, 你就可以开始动手造轮子练习了, 加油. 下面是我用ts造的轮子, 如果喜欢请帮忙点下star, 谢谢.

手势库: https://github.com/any86/any-...

命令式调用vue组件: https://github.com/any86/vue-...

工作中常用的一些代码片段: https://github.com/any86/usef...

一个mini的事件管理器: https://github.com/any86/any-...

查看原文

赞 35 收藏 24 评论 0

冰扬 赞了文章 · 2019-06-03

2019-我的前端面试题

2019的5月9号,离发工资还有1天的时候,我的领导亲切把我叫到办公室跟我说:'阿郭,我们公司要倒闭了,钱是没有的啦,为了不耽误你,你赶紧出去找工作吧'。听到这话,我虎躯一震,这已经是第2个月没工资了。

公司真尼玛黑,明天就要发工资,今天才告诉我说你要倒闭。领导难道是今天忽然想起来要倒闭吗?

算了,也没其他办法了。于是才工作半年的我,又填写了简历,开始了默默的找工作之旅。

我是从5月中旬开始找工作的,到现在半个月了。先说下我面试情况,我一共面试了10家公司。其中4家公司确定拿到了offer。还有一家公司1面通过了,邀约我参加2面。但是面试了这么久挺累的,就拒绝了。

以下是我面试过程中遇到的一些面试题,我整理出来。每个问题也附上相关的资料,方便自己使用。如果对你们有帮助的,也希望点个赞吧。

html篇

在我面试的众多公司里,只有58同城的1面问到相关问题,其他公司压根没问。

1.html4和html5的DOCTYPE有什么区别,以及他们的作用?

答案:HTML <!DOCTYPE> 声明

css篇

问css的公司也很少,只有那些需要做h5的公司才问下。

1.你用过flex布局吗?你简单说几个flex的属性?比如左对齐的属性是啥?右对齐的属性是啥?

flex是个很简单的知识点,花5分钟时间把教程看一遍,肯定就没问题了。

Flex 布局语法教程

2.css3实现动画的方式有那些?

我的回答:有transition tranform animation。 这样回答已经没问题了,从来没有人再追问了。如果你需要更深入了解可以点击以下教程查看.

CSS3的动画属性

3.你移动端是如何做适配的?

我的回答:早期的时候我是通过计算设计稿和屏幕的比例,动态的设置device-width和initial-scale,再通过px定位来实现移动端适配。后来改用rem,淘宝的flexbile出来之后,我就用flexbile。现在都是用css新的单位vw适配的。

这样回答,基本上就没问题了,也没有追问。感兴趣可以看下这两个教程

移动端适配问题解决方案

移动端适配总结

4.如何实现垂直居中

我的回答:如果是行内元素的话,就设置父类的text-align和子类的vertical-align(实际应用中vertical-align并不怎么有效)。我个人比较常用的设置父元素为相对定位,再设置子元素绝对定位,top和left为50%,transform为-50% -50%。有时候也用flex。

这样回答,我个人感觉就行了。没那么多花里胡哨的东西。以下是知识点的补充,刚兴趣的可以看下,这个大佬的教程。

Vertical-Align,你应该知道的一切

16种方法实现水平居中垂直居中

5.盒装模型和怪异模型有什么区别,如何在现代浏览器中实现怪异模型?

我的回答:盒装模型的宽度等于content-width,不包括边框和padding。怪异模型的宽度等于内容宽度+padding+边框。在现代浏览器中设置box-sizingborder-box 就可以实现怪异模型。

标准模式与怪异模式对于渲染页面的影响

6.如何实现圣杯布局?

css经典布局——圣杯布局

7. 简单说下bfc是啥,以及如何形成一个bfc

学习 BFC (Block Formatting Context)

总结:面试中关于css的问题非常少,常问的也就1-4。5,6,7只被问过一次。

js篇

1.说下es6里你常用的功能?

这问题10个公司面试,10个公司的人都问。我都不知道问这些有啥意义。

我的回答:我先说我常用的啊,定义变量的时候用let、const,定义函数用箭头函数,数组新增了一些方法,比如from、of、includes。字符串新增了一些方法padStart、padEnd、includes。扩展运算符、解构赋值。接下里就是最常用的promise和async和await。不怎么常用的是set、map、weekset、weekmap、Symbol、proxy等。

ECMAScript 6 入门

2.说一说let、const和var的区别?

我就不写自己的答案了,有更好的问题,我就给你们推荐更好的文章。

let 和 var的区别

3.数组foreach和map的区别?

我的回答:foreach是直接修改原数组的,map是修改之后返回一个新的修改过数组,原数组不变。

4.call、apply、bind的区别?

this、apply、call、bind

5.你是怎么实现js继承的,不使用class,写个你最常用的继承方式?

深入JavaScript继承原理

6.js的原型链是啥?

JavaScript原型与原型链

7.说一说js的闭包是啥?手写一个闭包的案例

JavaScript 闭包

8.说一说promise是啥?

可以参考一下两篇文章:

面试精选之Promise

解读Promise内部实现原理

9.你能不能手写一个promise?

我感觉这简直是变态题,这个是我一年前面试爱奇艺的时候,面试官要求的,我没写出来。

你能手写一个Promise吗?Yes I promise

10.谈一谈你对async和await的理解?

我的回答:async和await是对generator函数更优雅的实现,能够以同步的方式写异步的方法。async定义在函数前面,函数执行后返回一个promise。await后面只能跟一个promise函数。

更详细的问题参考下面教程

理解 async/await

11.手写一个冒泡排序或者快速排序

js数据结构和算法(9)-排序算法

12.手写一个js的单例模式,并且说下你都在哪些地方会用到?

JavaScript设计模式

13.如何把一个多维数组转为1维数组,比如[1,2,[3,[4,[5]]]]

JavaScript 学习笔记 - 多维数组变为一维数组

14.正则表达式的回溯是啥?

一年前爱奇艺面试题

你真的懂JavaScript的正则吗?

正则表达式回溯法原理

15.Object.create()和new object()和{}的区别?

58同城面试题

Object.create()和new object()和{}的区别

16.for in和Object.keys的区别?

JavaScript中in操作符(for..in)、Object.keys()和Object.getOwnPropertyNames()的区别

17.箭头函数与普通函数的区别

详解箭头函数和普通函数的区别以及箭头函数的注意事项、不适用场景

18.js的事件循环机制是啥?

Js 的事件循环(Event Loop)机制以及实例讲解

总结:我面试的话基本上问了这些问题,promise是重中之重,面试必问。

vue篇

1.vue数据双向绑定原理的原理是啥?

这问题都被问烂了。

面试题:你能写一个Vue的双向数据绑定吗

模拟 Vue 手写一个 MVVM

2.vue是如何传参的?

又是一个烂大街的问题

Vue 组件间通信六种方式(完整版)

3.说下vuex是什么?

Vuex Demo 讲解

4.vue-router是怎么实现路由的?路由守卫是什么?

前端路由简介以及vue-router实现原理

Vue的钩子函数-路由导航守卫、keep-alive、生命周期钩子

5.你常用的element-ui的组件有那些?

我的回答:表单,tab,日历,布局,列表,分页,树形结构,弹层等。

Element

6.vue中权限是怎么设置的?

手摸手,带你用vue撸后台 系列二(登录权限篇)

7.在vue开发中你遇到过什么难题?你又是怎么解决的?

这个是个开放式的问题,经常被问道,但是又不好回答。我自己回答的是自己开发vue组件面临的问题。模模糊糊的说过去了,对方我也不知道他听懂没听懂。反正胡乱回答过去之后,他就没有再问了。

vue插件开发、文档书写、github发布、npm包发布一波流

8.vue是如何编译模板的?

模拟 Vue 手写一个 MVVM

9.请你说下vue的生命周期是啥?以及每个生命周期都做了啥事?

vue生命周期详解

10.在你做过的那么多项目里,说一个你最有成就感的项目?

这个必问。这个一定要想好,可以从技术、沟通、业务紧急度等方面选一个最让你自豪的项目说下。

总结:关于vue其实没多少考点,但是又经常被问。对了还有,每次也都会问你开发vue几年了,这个你知道强调你用的很熟就可以了。

webpack篇

1.webpack是什么?说下你常用的webpack的loader和plugin是啥?

webpack4介绍与总结

2.webpack的loader和plugin的区别是啥?

webpack loader和plugin编写

webpack中loader和plugin的概念理解

3.webpack如何打包多页应用?

webpack4 多页面,多环境配置

4.如何做webpack优化?

Webpack4优化之路

5.如何使用webpack实现增量打包?

webpack增量打包多页应用

6.commonjs和amd的区别?

前端模块化:CommonJS,AMD,CMD,ES6

总结:关于webpack的面试题,也就这些了。很多面试官都喜欢问webpack,webpack也是面试中的重点。

http相关

1.你是怎么解决跨域问题的?

烂大街的问题。

前端跨域整理

2.强缓存是什么?协议缓存是什么?

HTTP----HTTP缓存机制

总结:关于http的面试题就这两个,面试前准备下就好了

其他

1.先做下自我介绍吧

(必问)这个环节是非常重要的,我以前都不怎么准备,想到哪说到哪,后来我才意识到这是不对的,因为这是每个面试都必问的。所以非常有必要,搞一个标准答案出来。其次,你的简历通常都是好几页,里面都是密密麻麻的问题,想让面试官从简历里了解你有多优秀,那是痴心妄想。所以这时候,自我介绍就非常重要了。

自我介绍时长:大概2-5分钟左右。

1.需要介绍你的地点和学校,因为很多公司很看重这两项,

2.介绍你的亮点,比如我,我大学拿过好几个奖学金,这就是亮点。工作里,我擅长写文档,带过团队这也是我的亮点。

3.介绍你用过的技术,擅长什么。

4.介绍你以前的公司和工作内容。

5.介绍你的性格和优点。

其他的尽量不要多说,言多必失

2.为什么你要离职?

(必问)这个很考察你的情商,你千万不要说跟上一家公司领导有问题,也不要说你身体不好。这个问题也是面试必问的,你一定要找个好的理由,或者编一个好的理由。不要让人觉得你很不稳定,很不好相处。

如果你之前是个大公司,你可以说职业瓶颈,你想进步,但是发现工作中用到的技术就那么多,怎么都突破不了,所以你想换个工作。或者说官僚主义严重等等。如果是个小公司,你就可以说拖欠工资,公司倒闭等等。这个你可以好好想想。(哎,我就是公司倒闭,拖欠了3个月工资)

3.你上一家公司的薪资待遇?

(必问)。有些公司是需要打印上一家公司的银行流水的,所以回答这个问题要慎重。

如果不需要打印流水,你报上一家公司工资的时候,比你理想工资低1-2k就好了,不用说实话。完全不用担心对方到时候打电话核对的问题。你上一家公司的hr如果不是蠢到家了,他是不会说你实际工资的。

如果需要打印流水,你先找下你朋友里有没有工资比较高的,有的话,就按他的工资报。打印流水的时候就让他打印一份给你。打印流水是不需要银行签名的,所以你随便搞就行了。如果你没有这样的朋友,那就老实写实际工资吧。

需不需要打印流水,面试的时候填来访登记表的时候,上面都有写明的,注意看就行了。

顺便说下背景调查的事,写你上一家公司的领导或者同事的时候,如果你有关系比较好的同事,就写你同事名字。如果没有你就写你好朋友或者亲人的就可以。对方查不出来的。

4.上一家工作中遇到过什么难题?

这个不算很常问,但是最好也先想下。跟hr聊的时候,尽量说技术上的难题。跟技术聊的时候,尽量说沟通问题。没有问题,你最好也要提前编一个问题。不建议现场编,很容易圆不过来。

5.未来职业规划

(必问)这个你只需要回答5年内的规划就可以了,再远了,你就说太远了,现在制定也不理想,需要往后不断完善。5年规划,你也要有个目标,一般的模式是前2年精研技术,比如精研webpack原理,学习数据结构和算法等等,要说出点东西。后3年要学习管理,比如研究架构,学习团队管理等。要点是,要明确,既有技术又有管理,让别人知道你是个有理想的人。

我的回答:我想找一个稳定的有前途的公司,先用1-2年的时候,巩固下技术。我现在学了很多工具,每个工具都用得很熟,但是我对原理的东西比较模糊,都是零星的了解。所以未来1-2年打算把技术好好研究一下。2年后我希望能转到管理岗位,能够做更大的更多的事情。未来几年我也会坚持写博客,坚持做开源插件,希望能帮助更多的人,结交更多的朋友,也希望能树立自己的品牌,成为某一方面的大牛。

6.业余时间干什么?

这个不多问,但是最好还是准备下。看书、参加团建都是比较好的回答。不要老说自己很宅。

7.你对996/加班怎么看?

我觉得加班都是为了更好的完成公司的工作,是为了给公司带来更好的业绩。公司业绩好,那么我们员工自然也会更好。当然了,从个人角度讲,加班也是一种锻炼,可以多挣点钱。所以我觉得需要加班的时候就加班,以公司的义务为重。

我的内心却是草泥马,老子才不加班。但是不能说,以先拿offer为准,有了offer你就有信心,再面试你就没那么怕了。

总结:面试官常问的问题就那么几个,就不要临场发挥了,多准备下吧。

8.说下你有什么优点和缺点?

尽量积极健康向上吧。

我的回答:

1.我觉得我是蛮努力的人,从初中到高中包括大学我都很努力的学习。大学时很努力的学习功课,业余时间都是泡在图书馆里,学习各种知识,努力了解这个世界。现在工作了,也经常看各种书记和教程,不断的丰富自己,提高的自己的技能和经验。

2.我觉得我也是个挺有想法的人,在我工作过的公司,有很多项目都是我提出来的。比如项目代码优化,比如搞工作流,比如搞培训,我还贡献过开源项目。我不禁有想法,还很努力的把自己的想法付诸行动。

3.最后我觉得我还是一个挺负责的人。工作里我愿意多承担事情,很想把所有的事情都做好。比如最近,虽然我离职了,但是上一家公司有些项目需要我修改,我还在积极的帮他们改。

谈下你的缺点:

我的缺点大部分都是跟我个人有关的。

1.宅一点,空闲时间我更喜欢在家看电影看书,或者去图书馆。很偶尔才会约朋友出去玩,除了谈事情,我也不太喜欢经常找别人闲聊天。

2.还有一点是焦虑吧,现在前端技术发展很快,涉及的东西又特别多。我有时候会紧张,觉得自己进步的太慢,进步的也不够。另外没工作的时候,我也会紧张,我会觉得对不起公司,好像在白占公司便宜。

薪资

我单独着重说下薪资。

薪资不等于工资!!!

薪资不等于工资!!!

薪资不等于工资!!!

重要的事说3遍。

薪资=工资+年终奖+x薪+公积金+社保。

在你报工资的时候,请先问清楚是多少薪,有没有年终奖,社保是多少。

在你报工资的时候,请先问清楚是多少薪,有没有年终奖,社保是多少。

在你报工资的时候,请先问清楚是多少薪,有没有年终奖,社保是多少。

重要的事情又说了3遍。你不问,hr是刻意给你隐瞒的。

hr让你填的是工资。比如你在ab两家公司都填20k,a公司是12薪,b公司是13薪,这一年就差了2w块钱。还有公积金,有的公积金只有几百,有的是工资的5%,有的是工资的12%。你如果每个公司都要价相同,实际到手的钱可能差别很大。所以要慎重。

另外一点,谈工资的时候一定要自信,一定要自信,一定要自信。等到谈工资的时候,90%,你已经是他们心中的理想人选了,所以不用担心再被刷掉了。

hr问题

为什么我要着重谈下hr问题呢。因为我对hr有偏见,长期的工作经验,我发现hr真是一个非常不值得信任的人群。

hr对你的作用就1个——剥削你。

在我10次面试里有2次是被hr哄骗过去的。面试前hr说不是996,你要的薪资是没问题的。但是一面试,面试官就会说我们是996,你能接受不?你要的薪资太高的,我们这边可能满足不了你。真的很浪费时间,很让人气愤。

其次是薪资问题。我很不理解,工资又不是hr的,hr却总是想法设法压低你的工资,为了压低你的工资,他们打击你或者恐吓你。毫无原则压低你工资。

最后交友问题,当你入职一家公司后,有时候hr会突然跟你走得很近,找你聊天啊,跟你开玩笑啊。别信,要不就是有活动需要你帮忙了,要不就是他们想从你口中得到一些其他员工的状况。别当真。

当然我这样说,也不要一棒子打死。也许有些hr很好吧。要跟hr保持适度的距离,不要交心。

看准网

推荐一个网站,看准网,这个网站是不收费的。去面试前最好查下你要面试的公司,如果评论很差,你就不要去了,浪费时间。

看准网

几个小建议

1.不做去小公司,不要去小公司,不要去小公司

2.尽量不要996,人生还有许多其他事情可以做

3.多拿offer,多看看,不要有心理负担

4.谈工资的时候,一定要自信。

在找工作的时候,运气比能力更重要,希望你有好的运气!!!

查看原文

赞 113 收藏 87 评论 7

冰扬 赞了文章 · 2019-04-22

容易混淆-论query和params在前后端中的区别

前言

最近在学node,试着做一个前后端都有的项目
然后就遇到了query和parmas这俩兄弟
你说他们俩长得也不像吧
可这用法实在是太类似了
这不,专门写篇文章来区分这哥俩
分别会从vue路由Node接收两个角度讲

vue路由中的传参

假设我们现在需要实现一个路由切换,点击之切换到W组件
并传递一个id值和一个age

我们运用router-link来写
然后一连串的疑惑就产生了

<router-link :to="{ A: 'xxx', query: { xx:'xxx' }}" />
<router-link :to="{ A: 'xxx', parmas: { xx:'xxx' }}" />
routes:{ ??? }

对于queryparmas来说

  1. A用name还是path?
  2. routes要怎么写?
  3. url长什么样?
  4. 会有什么隐藏的坑么

query

<router-link :to="{ name: 'W', query: { id:'1234',age:'12' }}"/>
<router-link :to="{ path: '/W', query: { id:'1234',age:'12' }}"/>

namepath都可以用

前者的routes基于name设置

{
  path: '/hhhhhhh', //这里可以任意
  name: 'W',  //这里必须是W
  component: W
}

然后就把path匹配添加到url上去

http://localhost:8080/#/hhhhhhh?id=1234&age=12

后者基于path来设置routes

{
  path: '/W', //这里必须是W
  name: 'hhhhhhhh',  //这里任意
  component: W
}

url:

http://localhost:8080/#/W?id=1234&age=12

这两种方法,都可以自定义path的样式,
不过一个是在router-link to里面定义,一个则是在routes里面定义

在接收参数的时候都是使用this.$route.query.id

parmas

<router-link :to="{ name: 'W', params: { id:'1234',age:'12' }}"/>

这里只能用name不能用path,不然会直接无视掉params中的内容

然后在routes中添加

{
      path:'/W/:id/:age',
      name:'W',
      component:W
}

这里的name与上面router-link中的name保持一致

url就取决于这个path的写法

http://localhost:8080/#/W/1234/12

注意,path里面的/w可以任意写,写成/hhhhh也可以
但是!
/:id/:age不能省略,且不能改名字

不写的话,第一次点击可以实现组件跳转
且可以通过this.$route.parmas.id获取到传过来的id值,但如果
刷新页面,传过来的id值和age值就会丢失

从这也能看出params比query严格

Node中的req.query和req.params

在后端中,要接受前端的axios请求
于是我们又碰到了这哥俩

什么样的axios请求对应什么样的接受方式?
还有不止是req.query,req.params,又混进来一个req.body
好家伙,乱成一锅粥

假设前端现在用axios向后端发送一个请求,发送id值请求后端的数据

req.query

axios.get(`/api/?id=1234`)

或者

axios.get(`/api`,{ params:{id:'1234' })

在前端里面,router怎么发送的就怎么收
query发送的就用this.$route.query接收
params发送的就用this.$route.params接收

但是在这里,虽然第二种方式里面有params
但这两种我们都要用req.query.id来获取里面的id

router.get('/api',function(req,res){
console.log(req.query.id)
.......
})

req.params

那如果直接把id值写进发送的url里面呢

axios.get(`/api/1234`)

看这个形式有没有觉得很眼熟
它跟上面paramsurl非常像
我们就反向操作一下

router.get('/api/:id',function(req,res){
    console.log(req.params.id)
    .......
    })

如果它是这么请求的

axios.get(`/api/1234-12`)

中间用-或者&隔开
那我们也可以在获取时的路径上这么写

router.get('/api/:id-:age',function(req,res){
    console.log(req.params.id)
    console.log(req.params.age)
    .......
    })

req.body

上面两个都是处理get请求的
而这位小兄弟就是用来处理post请求的
(需要安装body-parser中间件)

axios.post(`/api`,{ id:'1234' })

我们就用req.body来接收

router.get('/api',function(req,res){
console.log(req.body.id)
.......
})

总结

我们归纳了queryparams在前端路由以及后端接收中的区别
容易混淆的东西还是得多写,多总结

希望这篇文章对大家分清它们的使用场景有所帮助

查看原文

赞 20 收藏 16 评论 1

冰扬 评论了文章 · 2019-03-06

另辟蹊径:vue单页面,多路由,前进刷新,后退不刷新

目的:vue-cli构建的vue单页面应用,某些特定的页面,实现前进刷新,后退不刷新,类似app般的用户体验。
注: 此处的刷新特指当进入此页面时,触发ajax请求,向服务器获取数据。不刷新特指当进入此页面时,不触发ajax请求,而是使用之前缓存的数据,以便减少服务器请求,用户体验更流畅。

项目需求:

任何技术的探索,都来自项目的需求。之前经手的一个项目是微信端商城,使用的是传统的mvc模式,利用的是jq+js,因此对于商城的项目需求比较熟悉。目前在学习vue,练手一个商城,遇到之前经常提及而无法很好解决的需求。有些页面需要前进刷新,后退不刷新。比如,从商城的【首页】-->【详情页】-->【订单提交页】,每次打开新页面都需要获取新数据,但是按下返回键后,就不需要再获取新数据了,而滚动条还保留在之前的位置。最常见的操作是从【首页】-->【详情页】,然后在从【详情页】-->【首页】,如此反复。
实例如图:
首页详情页订单提交页

前人经验:

前人栽树,后人好乘凉。技术圈的分享一直都在蓬勃发展。遇到问题,我们可以尽情去搜索,去寻找大佬的足迹。针对上述需求,看到一个分享vue-router 之 keep-alive,比较符合我的需求,但是使用到我的项目上发现,稍微有点不适合。此分享技术要点,比较适合两个页面之前的跳转,返回。而我的页面是多个路由(2+)之间的跳转,返回。无奈,只能去自己探索发现。不过此技术要点给了我很好的启发,特此感谢作者。@ RoamIn

实现思路:

注:demo中,index页面包含三个链接导航。page1-->page2-->page3.依次前进,每次前进到一个新页面都需要获取数据,而按下后退键后,从page3返回到page2,page2不再获取新数据,而是使用之前缓存的数据。从page2返回到page1时,page1不再获取新数据,而是使用之前的数据。所以,page1和page2需要缓存,page3不需要缓存。可以把page1想象成首页,page2想象成详情页,page3想象成订单提交页。这样方便理解。

  • 利用keep-alive 缓存需要缓存的页面

    • 在app.vue中改写router-view

      <keep-alive>
          <router-view v-if="$route.meta.keepAlive">
              <!-- 这里是会被缓存的视图组件,比如 page1,page2 -->
          </router-view>
      </keep-alive>
      
      <router-view v-if="!$route.meta.keepAlive">
          <!-- 这里是不被缓存的视图组件,比如 page3 -->
      </router-view>
    • 在router/index.js中添加路由元信息,设置需要缓存的页面

      routes: [{
              path: '/',
              name: 'index',
              component: index,
              meta: {
                  keepAlive: false, //此组件不需要被缓存
              }
          },
          {
              path: '/page1',
              name: 'page1',
              component: page1,
              meta: {
                  keepAlive: true, //此组件需要被缓存
                  
              }
          },
          {
              path: '/page2',
              name: 'page2',
              component: page2,
              meta: {
                  keepAlive: true, // 此组件需要被缓存
                 
              }
          },
          {
              path: '/page3',
              name: 'page3',
              component: page3,
              meta: {
                  keepAlive: false, // 此组件不需要被缓存
              }
          }
      ]
    • 钩子函数的执行顺序

      • 不使用keep-alive
        beforeRouteEnter --> created --> mounted --> destroyed

      • 使用keep-alive
        beforeRouteEnter --> created --> mounted --> activated --> deactivated
        再次进入缓存的页面,只会触发beforeRouteEnter -->activated --> deactivated 。created和mounted不会再执行。我们可以利用不同的钩子函数,做不同的事。务必理解上述钩子函数的执行时机和执行顺序,本教程的核心就依赖于此钩子函数
        activated和deactivated是使用keep-alive后,vue中比较重要的两个钩子函数,建议详细了解下

  • 需缓存的页面的写法

    注:demo中的page1和page2,这两个页面都需要缓存,思路一样,以下以page1为例,page2不再赘述。
    示例文件:components/page1.vue

    • data中初始化一个str字符串,存放从后台获取的数据

         data() {
           return {
             msg: "我是第一个页面",
             str: ""  // 加载页面后执行获取数据的方法,插入到此
           };
         }
    • methods中创建一个方法,模拟从后台获取数据

         methods: {
           getData() {
             // getData方法,模拟从后台请求数据
             this.str = "我是通过调用方法加载的数据。。。";
           }
         }
    • 修改router/index.js中的配置

      • 每次进入页面,我们都需要知晓是从哪个页面进来的,用以判断是否需要获取数据。以这个page1页面为例,当我们知晓是从page2过来的,我们就可以认为是用户操作了返回键,这时page1页面就不需要再获取新数据了,使用之前缓存的数据就可以了。如果是从别的页面过来的,我们就需要获取数据。

      • 我们可以通过beforeRouteEnter这个钩子函数中的from参数判断是从哪个页面过来的,这个参数执行时,组件实例还没创建,所有不能在data中定义变量。我们可以在路由中定义一个变量,用来判断。

      • 在router/index.js的meta中添加isBack变量,默认false

           {
                path: '/page1',
                name: 'page1',
                component: page1,
                meta: {
                    keepAlive: true, //此组件需要被缓存
                    isBack:false, //用于判断上一个页面是哪个
                }
            },
            {
                path: '/page2',
                name: 'page2',
                component: page2,
                meta: {
                    keepAlive: true, // 此组件需要被缓存
                    isBack:false, //用于判断上一个页面是哪个
                }
            },
    • beforeRouteEnter中判断是从哪个页面过来的

      • 判断是从哪个路由过来的,如果是page2过来的,表明当前页面不需要刷新获取新数据,直接用之前缓存的数据即可

          beforeRouteEnter(to, from, next) {
            // 路由导航钩子,此时还不能获取组件实例 `this`,所以无法在data中定义变量(利用vm除外)
            // 参考 https://router.vuejs.org/zh-cn/advanced/navigation-guards.html
            // 所以,利用路由元信息中的meta字段设置变量,方便在各个位置获取。这就是为什么在meta中定义isBack
            // 参考 https://router.vuejs.org/zh-cn/advanced/meta.html
            if(from.name=='page2'){
                to.meta.isBack=true;
                //判断是从哪个路由过来的,
                //如果是page2过来的,表明当前页面不需要刷新获取新数据,直接用之前缓存的数据即可
            }
        
            next();
          },
    • activated中执行getData这个获取数据的方法

      • 因为这个页面需要缓存。只有第一次进入时才会执行created和mounted方法,再次进入就不执行了。而activated每次进入都执行,所以在这个钩子函数中获取数据。

        activated() {
          if(!this.$route.meta.isBack){
            // 如果isBack是false,表明需要获取新数据,否则就不再请求,直接使用缓存的数据
            this.getData();
          }
          // 恢复成默认的false,避免isBack一直是true,导致下次无法获取数据
          this.$route.meta.isBack=false
        
        },
    • 这样就可以了?

      • 当这样设置完毕后,你执行起来,貌似是可以了。第一次进入page1,能获取新数据,从page2返回时,不再获取新数据了,而是使用之前缓存的数据。但这样还有一个问题,当用户从page1进入page2后,因为某种原因,手动刷新了page2的页面。这时再返回到page1,发现之前缓存的数据丢失了,而且也没有再重新获取。所以我们还需要再添加一个判断条件,当用户手动刷新页面后,再返回时就需要重新获取数据了。

      • 如何添加这个条件,判断用户是否刷新了页面呢?我们知道,当使用keep-alive后,只有第一次进入后会触发created钩子函数,再次进入就不再执行了。当用户刷新了页面,这个钩子函数就会又执行,所以,我们可以利用这个小技巧来做点文章。

      • data中定义变量isFirstEnter用来判断是否第一次进入,或是否刷新了页面,默认false

           data() {
             return {
               msg: "我是第一个页面",
               str: "",  // 加载页面后执行获取数据的方法,插入到此
               isFirstEnter:false // 是否第一次进入,默认false
             };
           },
      • created中把isFirstEnter变为true,说明是第一次进入或刷新了页面

           created() {
             this.isFirstEnter=true;
             // 只有第一次进入或者刷新页面后才会执行此钩子函数
             // 使用keep-alive后(2+次)进入不会再执行此钩子函数
           },
      • activated中增加判断条件

           activated() {
             if(!this.$route.meta.isBack || this.isFirstEnter){
                 // 如果isBack是false,表明需要获取新数据,否则就不再请求,直接使用缓存的数据
                 // 如果isFirstEnter是true,表明是第一次进入此页面或用户刷新了页面,需获取新数据
                 this.str=''// 把数据清空,可以稍微避免让用户看到之前缓存的数据
                 this.getData();
             }
             // 恢复成默认的false,避免isBack一直是true,导致下次无法获取数据
             this.$route.meta.isBack=false
             // 恢复成默认的false,避免isBack一直是true,导致每次都获取新数据
             this.isFirstEnter=false;
         
           },
      • 这样应该就可以了

  • 不需要缓存页面的写法

    注:demo中的page3,这个页面不需要缓存,该怎么写就怎么写,不需要做特别的设置。

其它设置:

使用keep-alive后,可能有点小问题:第二个页面可能继承第一个页面的滚动条的高度。(在我项目中遇到的)
比如:page1向下滚动后,再进入page2,这时page2的滚动条可能是之前的高度,可能不会在顶部。

  • 解决方法一
    每次离开记录滚动条的高度,再次进入时根据项目需要再恢复之前的高度,或者置顶。

  • 解决方法二(推荐)
    router/index.js中添加如下代码(如不理解,请看参考链接)
    参考:HTML5 History 模式     滚动行为

      mode: 'history',
      scrollBehavior(to, from, savedPosition) {
          if (savedPosition) {
              return savedPosition
          } else {
              return {
                  x: 0,
                  y: 0
              }
          }
      }

疑问点:

在此次demo练习中,打印了一下钩子函数的执行顺序,发现一个疑问点(我对钩子函数理解也很浅显):
从page1进入page2时,先执行了page2的beforeRouteEnter和created方法,然后才执行page1的deactivated方法。
所以我把这两个初始化设置,放在了activated里面,而没有放在deactivated中

    this.$route.meta.isBack=false;
    this.isFirstEnter=false;

钩子函数执行顺序

结束语:

为了解决这个前进刷新后退不刷新问题,让我整整苦恼了一周时间,想了很多方法,也没能解决。最后综合各个大佬经验,试验了很多次,才归结出这个比较‘low’的方法。
目前,我也是vue小白,也在探索着前进,如果这个方法能解决你遇到的难题,我很高兴。如果你认为的确很low,求轻喷。
demo在下方的GitHub中,欢迎star。
也欢迎大家提供意见和建议,谢谢大家

GitHub

查看原文

冰扬 评论了文章 · 2019-01-21

🔥 掘金小册 markdown 转换器

20190121001820.png

项目地址:掘金小册 markdown 转换器,欢迎 star 蟹蟹

购买过的掘金小册,通过谷歌 pupeteer 自动爬取 html 文档,并将 html 文档转换为 markdown 格式的文件。

安装方式

首先 clone 此项目:

git clone https://github.com/oliyg/juejinxiaoce.git

然后安装依赖:

npm i

推荐使用 nrm 管理镜像源,使用淘宝镜像:nrm use taobao

手动下载 chromium:

chromium 官网下载

将 chromium 解压缩,连同 chrome-win 文件夹一同放入项目根目录中去

最后项目目录如下:

20190120235001.png

使用方法

新建 .env 文件到根目录,并根据 .env.example 填写掘金登录用户名和密码以及要爬取的小册ID信息:

20190120235150.png

小册ID见 URL 链接:

20190120235353.png

.env 文件修改完毕后执行 npm start 等待出现消息 all done. enjoy. 完成转换,效果如下:

20190121000703.png

20190121000715.png

免责

  • 该项目仅作为技术讨论,学习和研究使用

隐私

  • 该项目不会存储和发送任何用户隐私数据

License

The MIT License (MIT)
Copyright (c) 2019 OliverYoung

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.

查看原文

冰扬 评论了文章 · 2019-01-14

玩转控制台,看看那些你不知道的Console用法

前言

作为前端工程师,我们每天都离不开用控制台调试代码,console.log也成了我们最常用的命令,那除了用console.log,还有许多console的方法可以使用,熟练掌握它们,可以让我们在控制台看到的信息更美观准确,也会大大提高我们的开发效率哦,下面就跟小肆一起来看看吧.

Chrome的控制台

大部分常用浏览器都有各自的控制台,不过小肆用着最习惯的还是Chrome的控制台,打开chrome,win系统按F12,mac系统按command+option+J就可以呼出控制台了,切换到Console标签就能看到如下信息:
图1
我们可以看到,baidu还在控制台给我们留了个小彩蛋,我想这个彩蛋也是为我们程序员同学留的吧。让我们先学第一个命令清除控制台来开始吧。

清除控制台

在chorme下清除控制台的方法有很多:

  • 输入console.clear()命令或clear()命令
  • 使用快捷键 Control + JCommand + K
  • 点击控制台左上角第二个图标 🚫

显示信息的命令

console.log('技术放肆聊')     // 输出普通信息
console.info('技术放肆聊')    // 输出提示信息
console.warn('技术放肆聊')    // 输出警告信息
console.error('技术放肆聊')   // 输出错误信息
console.debug('技术放肆聊')   // 输出调试信息

console.logconsole.infoconsole.debug这三个命令可以理解为一个,我们只需要用console.log就行,并且chrome还不支持console.debug命令。

console.warn命令输出警告信息,信息前带有黄色警告符号。
console.error输出错误信息,信息前带有红色错误符号,表示出错,同时会显示错误发生的堆栈。
上段代码在chrome控制台输出效果如下:
图2

在safari输出效果如下:
图3

占位符

console上述的命令支持printf的占位符格式,支持的占位符有:字符(%s)、整数(%d或%i)、浮点数(%f)和对象(%o):

占位符作用
%s字符串
%d or %i整数
%f浮点数
%o可展开的DOM
%O列出DOM的属性
%c根据提供的css样式格式化字符串
//字符(%s)
console.log("%s","技术放肆聊");
//整数(%d或%i)
console.log("%d年%d月%d日",2019,1,6); 
//浮点数(%f)
console.log("PI=%f",3.1415926);

显示效果如下:
图4

%o、%O 都是用来输出 Object 对象的,对普通的 Object 对象,两者没区别,但是打印dom节点时就不一样了:
图5

%c 占位符是最常用的。使用 %c 占位符时,对应的后面的参数必须是 CSS 语句,用来对输出内容进行 CSS 渲染。常见的输出方式有两种:文字样式、图片输出。

图6

信息分组

console.group()用于将显示的信息分组,可以把信息进行折叠和展开。
console.groupEnd()结束内联分组
图7

将对象以树状结构展现

console.dir()可以显示一个对象所有的属性和方法.
图8

显示某个节点的内容

console.dirxml()用来显示网页的某个节点(node)所包含的html/xml代码
图9

判断变量是否是真

console.assert()用来判断一个表达式或变量是否为真,
此方法接受两个参数,第一个参数是表达式,第二个参数是字符串。只有当第一个参数为false,才会输出第二个参数,否则不会有任何结果。
图10

计时功能

console.time()console.timeEnd(),用来显示代码的运行时间

console.time("控制台计时器");
for(var i = 0; i < 10000; i++){
    for(var j = 0; j < 10000; j++){}       
}
console.timeEnd("控制台计时器");

图11

性能分析performance profile

console.profile()console.proileEnd()用来分析程序各个部分的运行时间,找出瓶颈所在。

function All(){
    for(var i = 0; i < 10; i++){
        funcA(100);
    }
    funcB(1000);
}
function funcA(count){
    for(var i = 0; i < count; i++){};
}
function funcB(count){
    for(var i = 0; i < count; i++){};
}
console.profile("性能分析器");
All();
console.profileEnd();

详细的信息在chrome控制台里的"profile"选项里查看

console.count()统计代码被执行的次数

function myFunction(){
    console.count("myFunction 被执行的次数");
}
myFunction();       //myFunction 被执行的次数: 1
myFunction();       //myFunction 被执行的次数: 2
myFunction();       //myFunction 被执行的次数: 3

console.table表格显示方法

-w948

总结

合理的利用console的各种方法,会使我们的调试过程更加愉悦,今天的分享就到这里了,记得右下角点好看呦!

技术放肆聊公众号,每日干货,最前沿的技术知识,扫描下方二维码关注:

技术放肆聊

查看原文

冰扬 评论了文章 · 2019-01-07

小程序生命周期——小程序探索

小程序App生命周期

  • 小程序App生命周期是在app.js里面调用的,App(Object)函数用来注册一个小程序,接受一个 Object 参数,指定其小程序的生命周期回调
  • App() 必须在 app.js 中调用,必须调用且只能调用一次,不然会出现无法预期的后果

clipboard.png

以上应该一眼就能看明白,以下主要讲讲前台、后台定义和运行机制等

1 . 前台、后台定义

  • 当用户点击左上角关闭,或者按了设备 Home 键离开微信,小程序并没有直接销毁,而是进入了后台onHide;当再次进入微信或再次打开小程序,又会从后台进入前台onShow。需要注意的是:只有当小程序进入后台一定时间,或者系统资源占用过高,才会被真正的销毁。

2 . 运行机制

  • 小程序启动会有两种情况,一种是「冷启动」,一种是「热启动」。 假如用户已经打开过某小程序,然后在一定时间内再次打开该小程序,此时无需重新启动,只需将后台态的小程序切换到前台onShow,这个过程就是热启动;冷启动指的是用户首次打开或小程序被微信主动销毁后再次打开的情况,此时小程序需要重新加载启动onLauch
  • 小程序没有重启的概念
  • 当小程序进入后台,客户端会维持一段时间的运行状态,超过一定时间后(目前是5分钟)会被微信主动销毁
  • 在 iOS 上,当微信客户端在一定时间间隔内(目前是 5 秒)连续收到两次及以上系统内存告警时,会主动进行小程序的销毁,并提示用户 「该小程序可能导致微信响应变慢被终止」。建议小程序在必要时使用 wx.onMemoryWarning 监听内存告警事件,进行必要的内存清理。

3 . 更新机制

  • 小程序冷启动时如果发现有新版本,将会异步下载新版本的代码包,并同时用客户端本地的包进行启动,即新版本的小程序需要等下一次冷启动才会应用上。 如果需要马上应用最新版本,可以使用 wx.getUpdateManager API 进行处理
  • 小程序强制更新
//注意,小程序的更新的api需要基础库在1.9.90以上
const updateManager = wx.getUpdateManager()
updateManager.onCheckForUpdate(function (res) {
    // 请求完新版本信息的回调,省略回调会报错,如下图
    console.log(res.hasUpdate)
})
updateManager.onUpdateReady(function () {
    wx.showModal({
        title: '更新提示',
        content: '新版本已经准备好,是否重启应用?',
        success: function (res) {
            if (res.confirm) {
                // 新的版本已经下载好,调用 applyUpdate 应用新版本并重启
                updateManager.applyUpdate()
            }
        }
    })
})
updateManager.onUpdateFailed(function () {
    // 新的版本下载失败
    wx.showModal({
        title: '更新提示',
        content: '新版本下载失败',
        showCancel:false
    })
})

clipboard.png

3 . 再次打开逻辑

  • 用户打开小程序的预期有以下两类场景:1、打开首页 2、打开指定页面
  • 现在要打开的是首页,如果上一次退出的时候是首页,则保留状态;否则,清空原来的页面栈,打开首页(相当于执行 wx.reLaunch 到首页)
  • 现在要打开的是指定页面,不管上次在什么页面,清空原来的页面栈,打开指定页面(相当于执行 wx.reLaunch 到指定页)

小程序Page生命周期

  • Page(Object) 函数用来注册一个页面。接受一个 Object 类型参数,其指定页面的初始数据、生命周期回调、事件处理函数等

clipboard.png

引用一张不错的图片来让你一目了然,图片来源于网络

clipboard.png

补充:app.onPageNotFound(Object)

基础库 1.9.90 开始支持,低版本需做兼容处理。
  • 小程序要打开的页面不存在时触发,也可以使用 wx.onPageNotFound 绑定监听
  • 开发者可以在回调中进行页面重定向,但必须在回调中同步处理,异步处理(例如 setTimeout 异步执行)无效
  • 主要用于轮播、魔方等动态指定跳转页面的场景,避免出现页面不存在的情况
App({
  onPageNotFound(res) {
    // 可以封装一个小程序跳转函数,智能解决tabbar页面跳转的问题
    wx.redirectTo({
      url: 'pages/...'
    })
  }
})

生命周期执行顺序

生命周期执行顺序
分别了解了App和Page的生命周期函数,那他们之间有何关联

吐槽一句,好多文章说什么app的生命周期函数onLauch可能会在page的onLoad之后触发,搞得我一脸懵逼,说话这么不严谨,真的好吗???

先看看正常的生命周期

App({
  onLaunch() {
     console.log('app---onLaunch');
  }, 
  onShow() {
    console.log('app---onShow');
  },
  onHide() {
    console.log('app---onHide');
  }
})

Page({

  onLoad(options) {
    console.log('page---onLoad');
  },

  onReady() {
    console.log('page---onReady');
  },

  onShow() {
    console.log('page---onShow');
  },

  onHide() {
    console.log('page---onHide');
  },

  onUnload() {
    console.log('page---onUnload');
  }

})

控制台输出

clipboard.png

应该说永远是这个顺序,现在再加点代码,就用官方的例子

App({
  onLaunch() {
     console.log('app---onLaunch');
      // 获取用户信息
    wx.getSetting({
      success: res => {
        if (res.authSetting['scope.userInfo']) {
          // 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框
          wx.getUserInfo({
            success: res => {
              // 可以将 res 发送给后台解码出 unionId
              this.globalData.userInfo = res.userInfo
              console.log('app---onLaunch---success');
              // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
              // 所以此处加入 callback 以防止这种情况
              if (this.userInfoReadyCallback) {
                this.userInfoReadyCallback(res)
              }
            }
          })
        }
      }
    })
  }
})

Page({
  onLoad(options) {
    console.log('page---onLoad');
    if (app.globalData.userInfo) {
      this.setData({
        userInfo: app.globalData.userInfo,
        hasUserInfo: true
      })
    } else if (this.data.canIUse) {
      // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
      // 所以此处加入 callback 以防止这种情况
      app.userInfoReadyCallback = res => {
        this.setData({
          userInfo: res.userInfo,
          hasUserInfo: true
        })
      }
    } else {
      // 在没有 open-type=getUserInfo 版本的兼容处理
      wx.getUserInfo({
        success: res => {
          app.globalData.userInfo = res.userInfo
          this.setData({
            userInfo: res.userInfo,
            hasUserInfo: true
          })
        }
      })
    }
  }
})

控制台输出

clipboard.png

  • 可以看出小程序生命周期函数并没有错乱,只是加了异步操作
  • 你永远不知道异步操作会排在正常周期的哪一个位置
解决方案
例子中已经给出了callback的方案,现在也可以使用promise来解决

1 . callback

  • 解释一波:Page页面判断当前app.globalData.userInfo是否有值
  • 如果有,说明异步操作很顺利,当做同步往下操作即可
  • 如果没有,则定义一个app方法(回调函数)app.userInfoReadyCallback = res => {...}
  • 因为Page.onLoad没有顺利拿到app.globalData.userInfo,说明App页面请求success异步操作滞后了
  • 此时APP页面不仅会给globalData.userInfo赋值,还会执行Page页面定义的回调方法,完成业务逻辑

以上例子是官方对于授权之后的操作流程示例,实际使用替换自己的初始化函数和业务逻辑

注意事项
app.onLaunch全局只触发一次,也就是打开的第一个页面会有异步问题,打开第二个页面肯定是可以拿到初始化数据的,但是小程序可以从不同场景进入,可能打开的并非是首页,这时就需要给每一个可能被第一次就打开的页面,写回调函数,暂时没想到类似vue那种设置全局路由回调的方案

2 . promise

  • 最终效果应该和回调一样,之前小程序不支持promise,一直没有好好用过,等以后单独研究~

Page实例生命周期

先来看一张很熟悉的图
官方原话(以下内容你不需要立马完全弄明白,不过以后它会有帮助)

clipboard.png

咋一看,什么鬼,好吧~我听你的,以后再说~

  • 转眼间,以后就到啦!好在有大佬解释过了,借花献佛,谁让我是搬运工呢(最后会贴上参考文章链接)

Page实例由两大线程组成

  • 负责界面的线程(view thread)和服务线程(appservice thread),各司其职又互相配合
  • 界面线程有四大状态:
  1. 初始化状态:初始化界面线程所需要的工作,包括工作机制,基本和我们开发者没有关系,等初始化完毕就向 “服务线程”发送初始化完毕信号,然后进入等待传回初始化数据状态
  2. 首次渲染状态:收到“服务线程”发来的初始化数据后(就是 json和js中的data数据),就开始渲染小程序界面,渲染完毕后,发送“首次渲染完毕信号”给服务线程,并将页面展示给用户
  3. 持续渲染状态:此时界面线程继续一直等待“服务线程”通过this.setdata()函数发送来的界面数据,只要收到就重新局部渲染,也因此只要更新数据并发送信号,界面就自动更新
  4. 结束状态:你懂得
  • 服务线程五大状态:
  1. 初始化状态:无需和其他模块交流,跟小程序开发也没多大关联,此阶段就是启动服务线程所需的基本功能,比如信号发送模块。系统的初始化工作完毕,就调用自定义的onload和onshow,

然后等待界面线程的“界面线程初始化完成”信号。
onload是只会首次渲染的时候执行一次,onshow是每次界面切换都会执行,简单理解,这就是唯一差别

  1. 等待激活状态:接收到“界面线程初始化完成”信号后,将初始化数据发送给“界面线程”,等待界面线程完成初次渲染
  2. 激活状态:收到界面线程发送来的“首次渲染完成”信号后,就进入激活状态既程序的正常运行状态,并调用自定义的onReady()函数。

此状态下就可以通过 this.setData 函数发送界面数据给界面线程进行局部渲染,更新页面

  1. 后台运行状态:如果界面进入后台,服务线程就进入后台运行状态,从目前的官方解读来说,这个状态挺奇怪的,和激活状态是相同的,也可以通过setdata函数更新界面的

总结一下

小程序的一生

  • 打开小程序 -> app.onLaunch -> app.onShow -> Page.onLoad -> Page.onShow -> Page.onReady
  • 进入下一个页面 -> Page.onHide -> Next.onLoad -> Next.onShow -> Next.onReady
  • 返回上一个页面 -> Next.onUnload -> Page.onShow
  • 离开小程序 -> app.onHide
  • 再次进入 -> app未销毁 ->app.onShow 否则从头开始(销毁判断看上文运行机制)

注意:Tabbar页面初始化之后不会被销毁,也就是只会执行一次 onLoad 函数

只触发一次的,一般都是用来初始化操作

  • onLaunch:初始化全局数据,注意异步问题
  • onLoad:初始化页面数据
  • onReady:代表页面已经准备妥当,界面内容的修改,最好放在这里,如wx.setNavigationBarTitle
  • onUnload:清除定时器,因为所有页面的脚本逻辑都跑在同一个JsCore线程

触发多次的,一般用来改变状态

  • onShow:刷新
  • onHide:重置

参考文献

http://www.wxapp-union.com/ar...
http://www.wxapp-union.com/ar...
https://developers.weixin.qq....
查看原文

认证与成就

  • 获得 289 次点赞
  • 获得 16 枚徽章 获得 0 枚金徽章, 获得 6 枚银徽章, 获得 10 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2017-08-25
个人主页被 878 人浏览