let suits = ["hearts", "spades", "clubs", "diamonds"];
functionpickCard(x: { suit: string; card: number }[]): number; functionpickCard(x: number): { suit: string; card: number }; functionpickCard(x): any { // Check to see if we're working with an object/array // if so, they gave us the deck and we'll pick the card if (typeof x == "object") { let pickedCard = Math.floor(Math.random() * x.length); return pickedCard; } // Otherwise just let them pick the card elseif (typeof x == "number") { let pickedSuit = Math.floor(x / 13); return { suit: suits[pickedSuit], card: x % 13 }; } }
function extend<T, U>(first: T, second: U): T & U { let result = <T & U>{}; for (let id in first) { (<any>result)[id] = (<any>first)[id]; } for (let id in second) { if (!result.hasOwnProperty(id)) { (<any>result)[id] = (<any>second)[id]; } } return result; }
class Person { constructor(public name: string) { } } interface Loggable { log(): void; } class ConsoleLogger implements Loggable { log() { // ... } } var jim = extend(new Person("Jim"), new ConsoleLogger()); var n = jim.name; jim.log();
/** * Extract from T those types that are assignable to U * 从T中提取可分配给U的类型 */ typeExtract<T, U> = T extends U ? T : never;
Omit<T,K>
类似于Exclude,但是更方便. 用于保留一些属性,再排除一些属性.
1 2 3 4 5
/** * Construct a type with the properties of T except for those in type K. * 构造一个除类型K之外的T属性的类型 */ typeOmit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
NonNullable
1 2 3 4 5
/** * Exclude null and undefined from T * 从T中排除null和undefined */ typeNonNullable<T> = T extendsnull | undefined ? never : T;
Parameters
返回类型为 T 的函数的参数类型所组成的数组. 也就是将参数取出来放进数组里.
1 2 3 4 5 6 7 8 9
/** * Obtain the parameters of a function type in a tuple * 在元组中获取构造函数类型的参数 */ typeParameters<T extends (...args: any) => any> = T extends ( ...args: infer P ) => any ? P : never;
/** * Obtain the parameters of a constructor function type in a tuple * 在元组中获取构造函数类型的参数 */ typeConstructorParameters<T extendsnew (...args: any) => any> = T extendsnew (...args: infer P) => any ? P : never;
ReturnType
1 2 3 4 5 6 7 8 9
/** * Obtain the return type of a function type * 获取函数类型的返回类型 */ typeReturnType<T extends (...args: any) => any> = T extends ( ...args: any ) => infer R ? R : any;
InstanceType
1 2 3 4 5 6 7 8 9 10
/** * Obtain the return type of a constructor function type * 获取构造函数类型的返回类型 */
typeInstanceType<T extendsnew (...args: any) => any> = T extendsnew ( ...args: any ) => infer R ? R : any;
ThisType
1 2 3 4 5
/** * Marker for contextual 'this' type * 上下文“this”类型的标记 */ interfaceThisType<T> {}
// callback superagent .post("/api/pet") .send({ name: "Manny", species: "cat" }) // sends a JSON post body .set("X-API-Key", "foobar") .set("accept", "json") .end((err, res) => { // Calling the end function will send the request });
// promise with then/catch superagent.post("/api/pet").then(console.log).catch(console.error);
var cacheModule = require("cache-service-cache-module"); var cache = newcacheModule({ storage: "session" });
// Require superagent-cache-plugin and pass your cache module var superagentCache = require("superagent-cache-plugin")(cache);
superagent .get(uri) .use(superagentCache) .end(function (err, response) { // response is now cached! // subsequent calls to this superagent request will now fetch the cached response });
let throttle = newThrottle({ active: true, // 插件开关 rate: 5, // how many requests can be sent every `ratePer` ratePer: 10000, // number of ms in which `rate` requests may be sent concurrent: 2// 并发数 })
第一次握手:由客户端向服务端发送连接请求 SYN 报文,该报文段中包含自身的数据通讯初始序号,请求发送后,客户端便进入 SYN-SENT 状态。
第二次握手:服务端收到连接请求报文段后,如果同意连接,则会发送一个包含了 ACK 和 SYN 报文信息的应答,该应答中也会包含自身的数据通讯初始序号(在断开连接的“四次挥手”时,ACK 和 SYN 这两个报文是作为两次应答,独立开来发送的,因此会有四次挥手),服务端发送完成后便进入 SYN-RECEIVED 状态。
第三次握手:当客户端收到连接同意的应答后,还要向服务端发送一个确认报文。客户端发完这个报文段后便进入 ESTABLISHED 状态,服务端收到这个应答后也进入 ESTABLISHED 状态,此时连接建立成功。
1、DOM 树的构建是文档加载完成开始的? 构建 DOM 树是一个渐进过程,为达到更好的用户体验,渲染引擎会尽快将内容显示在屏幕上,它不必等到整个 HTML 文档解析完成之后才开始构建 render 树和布局。
2、Render 树是 DOM 树和 CSS 样式表构建完毕后才开始构建的? 这三个过程在实际进行的时候并不是完全独立的,而是会有交叉,会一边加载,一边解析,以及一边渲染。
3、CSS 的解析注意点? CSS 的解析是从右往左逆向解析的,嵌套标签越多,解析越慢。
4、JS 操作真实 DOM 的代价? 用我们传统的开发模式,原生 JS 或 JQ 操作 DOM 时,浏览器会从构建 DOM 树开始从头到尾执行一遍流程。在一次操作中,我需要更新 10 个 DOM 节点,浏览器收到第一个 DOM 请求后并不知道还有 9 次更新操作,因此会马上执行流程,最终执行 10 次。例如,第一次计算完,紧接着下一个 DOM 更新请求,这个节点的坐标值就变了,前一次计算为无用功。计算 DOM 节点坐标值等都是白白浪费的性能。即使计算机硬件一直在迭代更新,操作 DOM 的代价仍旧是昂贵的,频繁操作还是会出现页面卡顿,影响用户体验
二、Virtual-DOM 基础
2.1、虚拟 DOM 的好处
虚拟 DOM 就是为了解决浏览器性能问题而被设计出来的。如前,若一次操作中有 10 次更新 DOM 的动作,虚拟 DOM 不会立即操作 DOM,而是将这 10 次更新的 diff 内容保存到本地一个 JS 对象中,最终将这个 JS 对象一次性 attch 到 DOM 树上,再进行后续操作,避免大量无谓的计算量。所以,用 JS 对象模拟 DOM 节点的好处是,页面的更新可以先全部反映在 JS 对象(虚拟 DOM )上,操作内存中的 JS 对象的速度显然要更快,等更新完成后,再将最终的 JS 对象映射成真实的 DOM,交由浏览器去绘制。
/** * render 将virdual-dom 对象渲染为实际 DOM 元素 */ Element.prototype.render = function () { var el = document.createElement(this.tagName); var props = this.props; // 设置节点的DOM属性 for (var propName in props) { var propValue = props[propName]; el.setAttribute(propName, propValue); }
var children = this.children || []; children.forEach(function (child) { var childEl = child instanceofElement ? child.render() // 如果子节点也是虚拟DOM,递归构建DOM节点 : document.createTextNode(child); // 如果字符串,只构建文本节点 el.appendChild(childEl); }); return el; };
我们通过查看以上 render 方法,会根据 tagName 构建一个真正的 DOM 节点,然后设置这个节点的属性,最后递归地把自己的子节点也构建起来。
diff 算法用来比较两棵 Virtual DOM 树的差异,如果需要两棵树的完全比较,那么 diff 算法的时间复杂度为 O(n^3)。但是在前端当中,你很少会跨越层级地移动 DOM 元素,所以 Virtual DOM 只会对同一个层级的元素进行对比,如下图所示, div 只会和同一层级的 div 对比,第二层级的只会跟第二层级对比,这样算法复杂度就可以达到 O(n)。
子节点的对比算法,例如 p, ul, div 的顺序换成了 div, p, ul。这个该怎么对比?如果按照同层级进行顺序对比的话,它们都会被替换掉。如 p 和 div 的 tagName 不同,p 会被 div 所替代。最终,三个节点都会被替换,这样 DOM 开销就非常大。而实际上是不需要替换节点,而只需要经过节点移动就可以达到,我们只需知道怎么进行移动。
// strictly internal raw: boolean; // contains raw HTML? (server only) isStatic: boolean; // hoisted static node isRootInsert: boolean; // necessary for enter transition check isComment: boolean; // empty comment placeholder? isCloned: boolean; // is a cloned node? isOnce: boolean; // is a v-once node? asyncFactory: Function | void; // async component factory function asyncMeta: Object | void; isAsyncPlaceholder: boolean; ssrContext: Object | void; fnContext: Component | void; // real context vm for functional nodes fnOptions: ?ComponentOptions; // for SSR caching devtoolsMeta: ?Object; // used to store functional render context for devtools fnScopeId: ?string; // functional scope id support
data 属性包含了最后渲染成真实 dom 节点后,节点上的 class,attribute,style 以及绑定的事件
children 属性是 vnode 的子节点
text 属性是文本属性
elm 属性为这个 vnode 对应的真实 dom 节点
key 属性是 vnode 的标记,在 diff 过程中可以提高 diff 的效率
3.1.2、源码创建 VNode 过程
(1)初始化 vue
我们在实例化一个 vue 实例,也即 new Vue( ) 时,实际上是执行 src/core/instance/index.js 中定义的 Function 函数。
1 2 3 4 5 6
functionVue(options) { if (process.env.NODE_ENV !== "production" && !(thisinstanceofVue)) { warn("Vue is a constructor and should be called with the `new` keyword"); } this._init(options); }
// This exported function will be called by `bundleRenderer`. // This is where we perform data-prefetching to determine the // state of our application before actually rendering it. // Since data fetching is async, this function is expected to // return a Promise that resolves to the app instance. exportdefault (context) => { returnnewPromise((resolve, reject) => { const s = isDev && Date.now(); const { app, router, store } = createApp();
if (fullPath !== url) { returnreject({ url: fullPath }); }
//设置路由位置 // set router's location router.push(url);
//等待路由可能的异步钩子 // wait until router has resolved possible async hooks router.onReady(() => { const matchedComponents = router.getMatchedComponents(); // no matched routes if (!matchedComponents.length) { returnreject({ code: 404 }); } //路由上匹配的组件唤醒fetchData钩子 //一个预置钩子匹配一个状态返回promise //当解析完成且状态更新 // Call fetchData hooks on components matched by the route. // A preFetch hook dispatches a store action and returns a Promise, // which is resolved when the action is complete and store state has been // updated. // 使用Promise.all执行匹配到的Component的asyncData方法,即预取数据 Promise.all( matchedComponents.map( ({ asyncData }) => asyncData && asyncData({ store, route: router.currentRoute, }) ) ) .then(() => { isDev && console.log(`data pre-fetch: ${Date.now() - s}ms`); //解析了所有prefetch钩子后,渲染app所需要的state充满了store,
// After all preFetch hooks are resolved, our store is now // filled with the state needed to render the app. // Expose the state on the render context, and let the request handler // inline the state in the HTML response. This allows the client-side // store to pick-up the server-side state without having to duplicate // the initial data fetching on the client.
// This is a factory function for dynamically creating root-level list views, // since they share most of the logic except for the type of items to display. // They are essentially higher order components wrapping ItemList.vue. exportdefaultfunctioncreateListView(type) { return { name: `${type}-stories-view`, //从store中取值 asyncData({ store }) { return store.dispatch("FETCH_LIST_DATA", { type }); },
asyncData 方法被调用,通过 store.dispatch 分发了一个数据预取的事件,接下来我们可以看到通过 FireBase 的 API 获取到 Top 分类的数据,然后又做了一系列的内部事件分发,保存数据状态到 Vuex store,获取 Top 页面的 List 子项数据,最后处理并保存数据到 store.
去了一家公司面试,问了半天技术,答基础不错(因为我没有工作经验).回复说我司非常依赖 SSR,对 SEO 要求很高,搜索引擎排名必须第一,要求我必须尽快做出一份 demo 出来满足他们的最低要求.那么问题来了,我对 SSR 基本只是一知半解,怎么用完全属于白板啊.当时周五,那么只剩周六日两天了,也就是说,两天从不知 SSR 为何物到搞出一个 demo.压力好大.回去路上就开始上网查文档.我一看,这是啥,这又是啥.完全不知所云啊.
//创建渲染器 functioncreateRenderer(bundle, options) { // 调用vue-server-renderer的createBundleRenderer方法创建渲染器, //并设置HTML模板,以后后续将服务端预取的数据填充至模板中 returncreateBundleRenderer( bundle, Object.assign(options, { // for component caching //设置一个缓存 cache: LRU({ max: 1000, maxAge: 1000 * 60 * 15, }), // this is only needed when vue-server-renderer is npm-linked basedir: resolve("./dist"), // recommended for performance runInNewContext: false, }) ); }
let renderer; let readyPromise; //模板路径 const templatePath = resolve("./src/index.template.html"); if (isProd) { //生产环境: //webpack结合vue-ssr-webpack-plugin插件生成的server bundle //服务端渲染的HTML模板 const template = fs.readFileSync(templatePath, "utf-8"); //生产环境的时候这里已经打包好了这个json文件可以直接调用 const bundle = require("./dist/vue-ssr-server-bundle.json");
//client manifests是可选项,允许渲染器自动预加载,渲染添加<script>标签
// The client manifests are optional, but it allows the renderer // to automatically infer preload/prefetch links and directly add <script> // tags for any async chunks used during render, avoiding waterfall requests. const clientManifest = require("./dist/vue-ssr-client-manifest.json"); //vue-server-renderer创建bundle渲染器并绑定server bundle renderer = createRenderer(bundle, { template, clientManifest, }); } else { // 开发环境下,使用dev-server来通过回调把生成在内存中的bundle文件传回 // 通过dev server的webpack-dev-middleware和webpack-hot-middleware实现客户端代码的热更新 //以及通过webpack的watch功能实现服务端代码的热更新
// In development: setup the dev server with watch and hot-reload, // and create a new renderer on bundle / index template update. readyPromise = require("./build/setup-dev-server")( app, templatePath, (bundle, options) => { // 基于热更新,回调生成最新的bundle渲染器 renderer = createRenderer(bundle, options); } ); }
// since this app has no user-specific content, every page is micro-cacheable. // if your app involves user-specific content, you need to implement custom // logic to determine whether a request is cacheable based on its url and // headers. // 1-second microcache. // https://www.nginx.com/blog/benefits-of-microcaching-nginx/
// read template from disk and watch template = fs.readFileSync(templatePath, "utf-8"); chokidar.watch(templatePath).on("change", () => { template = fs.readFileSync(templatePath, "utf-8"); console.log("index.html template updated."); update(); });
//客户端热加载服务 // modify client config to work with hot middleware clientConfig.entry.app = [ "webpack-hot-middleware/client", clientConfig.entry.app, ]; clientConfig.output.filename = "[name].js"; clientConfig.plugins.push( new webpack.HotModuleReplacementPlugin(), new webpack.NoEmitOnErrorsPlugin() );
因为钩子在组件实例还没被创建的时候调用,可以通过传一个回调给next来访问组件实例 。 不过,可以通过传一个回调给 next 来访问组件实例.在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数. 但是回调的执行时机在 mounted 后面,所以在我看来这里对 this 的访问意义不太大,可以放在created或者mounted里面。