0%

布尔值:

1
let isdone:boolean=false;

数字:

TypeScript里的所有数字都是浮点数。 这些浮点数的类型是number。 除了支持十进制和十六进制字面量,TypeScript还支持ECMAScript 2015中引入的二进制和八进制字面量。

1
2
3
4
let decLiteral:number =6;
let hexLiteral:number=0xf00d;
let binaryLiteral:number=0b1010;
let octalLiteral:number=0o744

字符串:

1
2
3
let name:string="bob";
name="smith";
let name:string=`Gene`;

数组:

  1. 在元素类型后面接上[],表示由此类型元素组成一个数组

    1
    let list: number[]=[1,2,3];

    2.数组泛型,Array<元素类型>

    1
    let list:Array<number>=[1,2,3];

元组Tuple:

元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。 比如,你可以定义一对值分别为stringnumber类型的元组

1
2
3
let x:[string,number];
x=['hello',10];//OK

1
2
console.log(x[0].substr(1));//OK
console.log(x[1].substr(1));//Error,'number' does not have 'substr'

当访问一个越界元素,会使用联合类型替代

1
2
3
x[3]="world";//字符串乐意赋值给(string|number)类型
console.log(x[5].toString());//'string'和'number'都有toString
x[6]=true;//Error,布尔不是(string|number)类型

枚举

enum类型是对JavaScript标准类型数据的一个补充,像C#等其他语言一样,使用枚举类型可以为一组数值赋予友好的名字

1
2
enum Color {Red,Green,Blue}
let c:Color=Color.Green;

任意值

有时候,我们会想要为那些在编程阶段还不清楚类型的变量指定一个类型,这些值可能来自于动态的内容,比如来自用户输入或第三方代码库。这种情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。那么我们可以使用any类型来标记这些变量:

1
2
3
let notSure:any=4;
notSure="maybe a string instead";
notSure=false;//ok,definitely a boolean

在对现有代码进行改写的时候,any类型是十分有用的,它允许你在编译时可选择地包含或移除类型检查。 你可能认为Object有相似的作用,就像它在其它语言中那样。 但是Object类型的变量只是允许你给它赋任意值 - 但是却不能够在它上面调用任意的方法,即便它真的有这些方法:

1
2
3
4
5
let notSure:any=4;
notSure.ifItExists();//ok,ifItExists might exist at runtime
notSure.toFixed();//okay, toFixed exists (but the compiler doesn't check)
let prettySure:Object=4;
prettySure.toFixed();//Error:Property 'toFixed' doesn't exist on type 'object'

当你知道一部分数据的类型时,any类型也是有用的,比如,有一个数组,它包含了不同类型的数据:

1
2
let list:any[]=[1,true,"free"];
list[1]=100;

空值

某种程度上来说,void类型像是与any类型相反,它表示没有任何类型,当一个函数没有返回值时,你通常会见到其返回值类型时void

1
2
3
function warnUser:void{
alert("This is my warning message")
}

声明一个void类型的变量没有什么大用,因为你只能它赋予undefined和null

1
let unusable:void=undefined

Null和Undefined

undefined和null两者各有自己的类型分别叫做undefined和null,和void相似,它们的本身的类型用处不大

1
2
let u:undefined=undefined;
let n:null=null;

默认情况下null和undefined实所有类型的子类型,就是说你可以把null和undefined赋值给number类型的变量

然而,当你指定了–strictNullChecks标记,null和undefined只能赋值给void和它们各自,这能避免很多常见的问题,也许在某处你想传入一个stringnullundefined,你可以使用联合类型string | null | undefined

Nerver

nerver类型表示的是那些永不存在的值的类型。例如,nerver类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型;变量也可能是nerver类型,当它们被永不为真的类型保护锁约束时。nerver类型是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是nerver的子类型或可以赋值给nerver类型,即使any也不可以赋值给nerver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
throw new Error(message);
}

// 推断的返回值类型为never
function fail() {
return error("Something failed");
}

// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
while (true) {
}
}

类型断言:

1.尖括号语法

1
2
let someValue:any="this is a string";
let strlength:number=(<string>someValue).length;

2.as语法

1
2
let someValue:any="this is a string"
let strLength:number=(someValue as string).length;

instanceof

1
2
let arr=[1,2,3]
console.log(arr instanceof Array)

变量.constructor===变量类型

1
2
let arr=[1,2,3]
console.log(arr.constructor===Array)

Array.isArray(变量)

1
2
let arr=[1,2,3]
console.log(Array.isArray(arr))

Object.prototype.toString.call(),返回true则变量时数组类型

1
2
let arr=[1,2,3]
console.log(Object.prototype.toString.call(arr)==='[object Array]')

判断对象原型

1
2
let arr=[1,2,3]
console.log(arr.__proto__===Array.prototype)

Object.getPrototypeOf()来判断是否为数组类型

1
2
let arr=[1,2,3]
console.log(Object.getPrototypeOf(arr)===Array.prototype);//true

isPrototypeOf()判断是否为数组类型

1
2
let arr=[1,2,3]
console.log(Array.prototype.isPrototypeOf(arr))

  • 父子组件之间通信
  • 兄弟组件之间的通信
  • 祖孙与后代组件之间的通信
  • 非关系组件间的通信

整理vue中8种常规的通信方案

  1. 通过 props 传递
  2. 通过 $emit 触发自定义事件
  3. 使用 ref
  4. EventBus
  5. $parent 或$root
  6. attrs 与 listeners
  7. Provide 与 Inject
  8. Vuex

组件通信方案:

  • 通过props传递
  • 通过$emit触发自定义事件
  • 使用ref
  • EventBus
  • $parent或$root
  • attrs与listeners
  • Provide与inject
  • Vuex

props传递数据

  • 适用场景:父组件传递数据给子组件
  • 子组件设置props属性,定义接收父组件传递过来的参数
  • 父组件在使用子组件标签中通过字面量来传递值

Children.vue

1
2
3
4
5
6
7
8
9
10
props:{
//字符串形式
name:String//接收的类型参数
//对象形式
age:{
type:Number,//接收的类型为数值
default:18;//默认值为18
require:true;//age属性必须传递
}
}

Father.vue

1
<Children name='jack' age=18/>

$emit触发自定义事件

  • 适用场景:子组件传递数据给父组件
  • 子组件通过$emit触发自定义事件,$emit第二个参数为传递的数值
  • 父组件绑定监听器获取子组件传递过来的参数
  • 与组件和 prop 一样,事件名提供了自动的大小写转换。如果在子组件中触发一个以 camelCase (驼峰式命名) 命名的事件,你将可以在父组件中添加一个 kebab-case (短横线分隔命名) 的监听器。

Children.vue

methods中

1
this.$emit('add',good)

Father.vue

template中:

1
<Children @add="cartAdd($event)"

ref

父组件在使用子组件的时候设置ref

父组件通过这只子组件ref来获取数据

父组件

1
2
<Children ref='foo'/>
this.$refs.foo//获取子组件实例,通过子组件实例我们可以拿到对应数据

EventBus

  • 使用场景:兄弟组件传值
  • 创建一个中央事件总线EventBus
  • 兄弟组件通过$emit触发自定义事件,$emit第二个参数为传递的数值
  • 另一个兄弟组件通过$on监听自定义事件

Bus.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Bus{
constructor(){
this.events={};
}
on(event,fn){
this.events[event]=this.events[event]||[];
this.events[event].push(fn)

}
emit(event,data){
if(this.events[event]){
this.events[event].forEach(cb=>{
cb(data)
})
}

}
off(event,fn){
if(this.events[event]){
for(let i=0;i<this.events[event].length;i++){
if(this.events[event][i]===fn){
this.events[event].splice(i,1);
break;
}
}
}
}
}

在入口中执行挂载

1
2
3
4
5
6
7
8
9
10
11
//main.js
import {createApp} from 'vue'
import App from './App.vue'
//引入事件类
import EventBus from 'lib/Bus.js'
const $bus=new Bus()
//挂载
//1使用provide
app.provide('$bus',$bus);
//2 挂载到this上
app.config.globalProperties.$bus=$bus

在组件中引入并使用

1
2
3
4
5
export default{
created(){
this.$bus.emit('ButtonCreated')
}
}

在setup中使用

setup中无法访问到应用实例的this,所以用provide/inject

1
2
3
4
5
6
7
8
import {inject} from 'vue'
export default {
setup(){
const $bus=inject('$bus')
$bus.emit('ButtonSetup')
}
}

$parent或$root

通过共同父辈$parent或者$root搭建通信桥梁

兄弟组件

this.$parent.on(‘add’,this.add);

另一个兄弟组件

this.$parent.emit(‘add’)

$attrs 与$ listeners

  • 适用场景:祖先传递数据给子孙
  • 设置批量向下传属性$attrs$listeners
  • 包含了父级作用域中不作为 prop 被识别 (且获取) 的特性绑定 ( class 和 style 除外)。
  • 可以通过 v-bind="$attrs" 传⼊内部组件

在vue2.4中,为了解决该需求,引入了$attrs$listeners,新增了inheritAttrs选项。我们只需要在B组件中对引入的C组件增加下面两个属性即可绑定所有的属性和事件。

1
2
<C v-bind="$attrs" v-on="$listeners"></C>

A组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<div>
<h2>组件A 数据项:{{myData}}</h2>
<B @changeMyData="changeMyData" :myData="myData"></B>
</div>
</template>
<script>
import B from "./B";
export default {
data() {
return {
myData: "100"
};
},
components: { B },
methods: {
changeMyData(val) {
this.myData = val;
}
}
};
</script>

B组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div>
<h3>组件B</h3>
<C v-bind="$attrs" v-on="$listeners"></C>
</div>
</template>
<script>
import C from "./C";
export default {
components: { C },
};
</script>


C组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div>
<h5>组件C</h5>
<input v-model="myc" @input="hInput" />
</div>
</template>
<script>
export default {
props: { myData: { String } },
created() {
this.myc = this.myData; // 在组件A中传递过来的属性
console.info(this.$attrs, this.$listeners);
},
methods: {
hInput() {
this.$emit("changeMyData", this.myc); // // 在组件A中传递过来的事件
}
}
};
</script>


参考:https://juejin.cn/post/6844903828098138120

provide与inject

在祖先组件定义provide属性,并返回传递的值

在后代组件通过inject接收组件传递过来的值

祖先组件:

1
2
3
4
5
6
provide(){
return {
foo:'foo'

}
}

后代组件:

1
inject:['foo'];//获取到祖先组件传递过来的值

具体:https://v3.cn.vuejs.org/guide/component-provide-inject.html#%E5%A4%84%E7%90%86%E5%93%8D%E5%BA%94%E6%80%A7

Vuex

  • 适用场景: 复杂关系的组件数据传递
  • Vuex作用相当于一个用来存储共享变量的容器
  • state用来存放共享变量的地方
  • getter,可以增加一个getter派生状态,(相当于store中的计算属性),用来获得共享变量的值
  • mutations用来存放修改state的方法。
  • actions也是用来存放修改state的方法,不过action是在mutations的基础上进行。常用来做一些异步操作

#小结

  • 父子关系的组件数据传递选择 props$emit进行传递,也可选择ref
  • 兄弟关系的组件数据传递可选择$bus,其次可以选择$parent进行传递
  • 祖先与后代组件数据传递可选择attrslisteners或者 ProvideInject
  • 复杂关系的组件数据传递可以通过vuex存放共享的变量

组件data定义函数与对象的区别

在我们定义好一个组件的时候,vue最终都会通过Vue.extend()构成组件实例

1
2
3
4
5
6
function Component(){

}
Component.prototype.data={
count:0
}

创建两个组件实例:

1
2
3
4
5
const componentA=new Component()
const componentB=new Component();
console.log(componentB.data.count);//0
componentA.data.count=1;
console.log(componentB.data.count)//1

产生这样的原因是两者共用了同一个内存地址,compoentA修改的内容,同样对componentB产生了影响

如果采用函数形式,则不会出现这种情况(函数返回的对象内存地址并不相同)

1
2
3
4
5
6
7
8
function Component(){
this.data = this.data()
}
Component.prototype.data = function (){
return {
count : 0
}
}
1
2
3
4
console.log(componentB.data.count)  // 0
componentA.data.count = 1
console.log(componentB.data.count) // 0

vue组件可能有多个实例,采用函数返回一个全新data形式,使每个实例对象的数据不会受到其他实例对象数据的污染

原理:

vue初始化data的代码时,data的定义可以是函数或者对象

1
2
3
4
5
6
7
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
...
}

但是组件在创建的时候,会进行选项的合并,自定义组件会进入mergeOptions进行选择合并

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Vue.prototype._init = function (options?: Object) {
...
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
...
}

定义data会进行数据校验

这时候vm实例为undefined,进入if判断,若data类型不是function,则出现警告提示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
strats.data = function (
parentVal: any,
childVal: any,
vm?: Component
): ?Function {
if (!vm) {
if (childVal && typeof childVal !== "function") {
process.env.NODE_ENV !== "production" &&
warn(
'The "data" option should be a function ' +
"that returns a per-instance value in component " +
"definitions.",
vm
);

return parentVal;
}
return mergeDataOrFn(parentVal, childVal);
}
return mergeDataOrFn(parentVal, childVal, vm);
};

总结:

  • 根实例对象data可以是对象也可以是函数(根实例是单例),不会产生数据污染情况
  • 组件实例对象data必须是函数,目的是为了防止多个组件实例对象之间共用一个data,产生数据污染,采用函数的形式,initData时会将其作为工厂函数返回全新data对象

  • new Vue 的时候调用_init方法
  1. 定义$set,$get,$delete,$watch等方法
  2. 定义$on,$off,$emit等事件
  3. 定义_update,$forceUpdate,$destroy生命周期
  • 调用$mount进行页面的挂载,会解析template:
  1. 将html文档解析后生成ast抽象语法树
  2. 将ast解析成字符串
  3. 生成render函数,挂载到vm上后,会再次调用mount方法
  • mount方法中
  1. 会调用mountComponent渲染组件
  2. 执行beforeMount钩子
  3. 定义updateComponent渲染页面视图方法(updateComponent方法主要执行在vue初始化声明的render,update方法,render的作用是生成vnode)
  4. 监听组件数据,一旦发生变化,触发beforeUpdate生命钩子
  • _update主要功能是调用patch,将vnode转换为真实DOM,并且更新到页面中

参考链接:

https://vue3js.cn/interview/vue/new_vue.html#%E4%B8%89%E3%80%81%E7%BB%93%E8%AE%BA

柯里化:把接受多个参数的函数变换成接收一个单一参数(最初函数的第一个参数)的函数,并且返回接收余下的参数而且返回结果的新函数的技术

柯里化的通用实现方法:

1
2
3
4
5
6
7
8
9
10
11
12
const curry=(fn,...args)=>{
return args.length<fn.length//函数参数个数可以通过函数.length属性访问,
?(..._args)=>curry(fn,...args,..._args)//传入参数小于原始函数fn参数的个数时,继续对当前函数进行柯里化,返回一个接受所有参数(当前参数和剩余参数)的函数
:fn(...args)//否则直接执行函数
}
function add1(a,b,c,d){
return a+b+c+d;
}
var add=curry(add1)
console.log(add(1,2,3,4))//10
console.log(add(1)(2)(3)(4));//10
console.log(add(1,2)(3,4));//10

柯里化的作用:

参数复用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 正常正则验证字符串 reg.test(txt)

// 函数封装后
function check(reg, txt) {
return reg.test(txt)
}

check(/\d+/g, 'test') //false
check(/[a-z]+/g, 'test') //true

// Currying后
function curryingCheck(reg) {
return function(txt) {
return reg.test(txt)
}
}

var hasNumber = curryingCheck(/\d+/g)
var hasLetter = curryingCheck(/[a-z]+/g)

hasNumber('test1') // true
hasNumber('testtest') // false
hasLetter('21212') // false

第一个参数reg进行了复用

提前返回

举个例子,兼容现代浏览器以及IE浏览器的事件添加方法,我们正常情况可能这样写:

1
2
3
4
5
6
7
8
9
10
11
12
var addEvent=function(el,type,fn,capture){
if(window.addEventListener){
el.addEventListener(type,function(e){
fn.call(el,e);
},capture);
}
else if(window.attachEvent){
el.attachEvent("on"+type,function(e){
fn.call(el,e);
})
}
}

上面的方法我们每次使用addEvent为元素添加事件的时候,都会走一遍if…else if…,其实只要判断一次,用柯里化

1
2
3
4
5
6
7
8
9
10
11
12
var addEvent=(function(){
if(window.addEventListener){
el.addEventListener(type,function(e){
fn.call(el,e);
},capture);
}
else if(window.attachEvent){
el.attachEvent("on"+type,function(e){
fn.call(el,e);
})
}
})();

延迟执行

比如bind方法的实现机制就是柯里化:

1
2
3
4
5
6
7
Function.prototype.bind=function(context){
var self=this;
var args=Array.prototype.slice.call(arguments,1);
return funtion(){
return self.apply(context,args);
}
}

参考文章:

https://juejin.cn/post/6844904093467541517

https://www.zhangxinxu.com/wordpress/2013/02/js-currying/

https://www.jianshu.com/p/2975c25e4d71

1
2
3
4
5
6
7
8
9
10
11
is=function(x,y){
if(x===y){//如果x===y并且+0和-0=>+0!==0false -0!==0false 1/+0===1/-0false返回false
return x!==0||y!==0||1/x===1/y
}else{
//解决NaN===NaN为false,NaN!==NaNtrue NaN!==NaN true 返回true
return x!==x&&y!==y;
}
}
console.log(is(0,0))//true
console.log(is(+0,-0))//false
console.log(is(NaN,NaN))//true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Object.defineProperty(Object,'assign',{
value:
function (target,...args){
if(target===null)return new TypeError("Can't convert null or undefined to object")
//目标对象统一为引用数据类型
const to=Object(target);
for(let i=0;i<args.length;i++){
//每一个源对象
const nextSource=args[i];
if(nextSource!==null){
//使用for...in 和hasOwnProperty双重判断,确保只拿到本身的属性,方法(不包含继承的)
for(const nextKey in nextSource){
if(Object.prototype.hasOwnProperty.call(nextSource,nextKey)){
to[nextKey]=nextSource[nextKey];
}
}
}
}
return to;
},
//不可枚举
enumerable:false,
writable:true,
configurable:true
})

1
2
3
4
5
6
7
8
9
10
11
Function.prototype._call=function(context=window,...args){//如果没有传入对象,则会默认this指向window
if(typeOf context !=='function'){
throw Error('Type Error')
}
const fn=Symbol('thisObj');
context[fn]=this;//给目标对象增加一个属性,并且将this赋给该属性
let res=context[fn](ars);//通过临时属性调用该函数并返回结果
delete context[fn];//删除该临时属性
return res;

}