当前位置: 首页 > 编程日记 > 正文

玩转Vuejs--核心原理

一、摘要:

Vuejs是一款前端MVVM框架,利用Vuejs、webpack以及周边一系列生态工具我们可以快速的构建起一个前端应用,网上对于Vue的分析大都是基于各个模块,理解起来不够顺畅,本文将从整个执行过程出发,讲一下Vuejs的核心原理。

二、版本说明:

Vuejs有两种版本,一种是runtime、一种是runtime-with-compiler,对应的渲染有两种写法:

1、render渲染函数:直接写render函数,渲染时将会调用render函数渲染DOM

<div id="demo"></div>//runtime写法var instance = new Vue({data: {hi: "monrning ",name: "Mr zhang"},render(h){ //h vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); };return h('div', this.hi + " " + this.name);}});instance.$mount(document.getElementById("demo"));

2、HTML模板语法:

<div id="demo">{{hi}} {{name}}</div>new Vue({el: "#demo"data: {hi: "monrning ",name: "Mr zhang"}});

runtime就是Vue运行时,很多框架的核心都是改自Vue的运行时,比如mpvue的运行部分,以后会讲。

runtime与runtime-with-compiler的区别:

顾名思义,前者没有compiler,后者有compiler。ompiler是Vue的编译模块,可以将HTML模板转换成对应的AST以及render渲染函数提供给Vue使用,所以本质上可以认为Vue的渲染就是在调用render渲染函数,compiler的作用就是在构造渲染函数。本文不讲compiler部分,只需要知道compiler会将模板构造成render函数即可,后面理解会用到。

三、模块分解:

为了便于理解,本人将Vue的核心分成以下几个部分:数据初始化、数据更新、异步队列、DOM渲染(虚拟DOM)

数据初始化:初始化调用Object.defineProperty对数据进行劫持,进而监听数据的变化,后续的更新、渲染都会依赖这一部分;

数据更新:数据监听实际在数据初始化阶段已经完成了,将这一部分独立出来的原因是数据初始化只做了对数据的set、get进行监听,逻辑的部分需要在数据更新以及渲染中来看;

异步队列:异步队列主要是为了解决在数据更新上触发多个Watcher如何进行更新的问题;

DOM渲染:这一部分包含虚拟DOM,单独作为一个部分,其中虚拟DOM高效的diff算法、patch的概念都很重要;

说明一下,这部分的分解是按照我个人的思路总结出来的几部分(非按顺序执行部分),如果有无法下手的情况可以有意识的按照这几部分来思考一下,仅供大家借鉴使用。

ps:注意上面的初始化和更新仅仅是“数据”的部分,不要跟下面的分析弄混淆。

四、核心原理:

接下来以实际的执行过程来讲一下Vue和核心原理,主要包括两个阶段:初始化阶段、数据更新阶段

以下面的代码为例:

<html lang="en">
<head><meta charset="UTF-8"><title></title><script src="../dist/vue.js"></script>
</head>
<body>
<div id="demo"><div >{{title}}{{title2}}</div><input type="button" @click="testClick" value="click"/>
</div>
</body>
<script>// 这种情况不需要var instance = new Vue({el:"#demo",data: {title: "input MVVM",title2: "input MVVM title2"
    },
    methods: {testClick(){this.title = Math.random();this.title2 = Math.random();}}});
</script>
</html>

首先讲一下初始化阶段

1、首先进行“数据的初始化”,代码如下:

  function initData (vm) {var data = vm.$options.data;data = vm._data = typeof data === 'function'? getData(data, vm): data || {};if (!isPlainObject(data)) {data = {};warn('data functions should return an object:\n' +'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',vm);}// proxy data on instancevar keys = Object.keys(data);var props = vm.$options.props;var methods = vm.$options.methods;var i = keys.length;while (i--) {var key = keys[i];{if (methods && hasOwn(methods, key)) {warn(("Method \"" + key + "\" has already been defined as a data property."),vm);}}if (props && hasOwn(props, key)) {warn("The data property \"" + key + "\" is already declared as a prop. " +"Use prop default value instead.",vm);} else if (!isReserved(key)) {proxy(vm, "_data", key); //将data代理到Vue实例上}}// observe dataobserve(data, true /* asRootData */);}

可以看到数据初始化包括两个过程:

(1)、将 data 代理到Vue实例上,之后 data 中的数据可以直接通过Vue实例 this 来设置或者获取,代理过程如下:

 vm._data = typeof data === 'function'? getData(data, vm): data || {};//遍历data,设置代理
proxy(vm, "_data", key);    //proxy 过程function proxy (target, sourceKey, key) {sharedPropertyDefinition.get = function proxyGetter () {return this[sourceKey][key]};sharedPropertyDefinition.set = function proxySetter (val) {this[sourceKey][key] = val;};Object.defineProperty(target, key, sharedPropertyDefinition);}

ps: 这里先将数据挂在了vm._data 上,之后再代理到 vm 实例上,前者主要是为了暴漏 $data 使用,最终可以获取数据的地方有三个: vm (实例)、vm._data、vm.$data;

(2)、劫持数据,进行数据的初始化,底层使用大家了解最多Object.defineProperty(官方宣称3.0后这部分会使用Proxy来代替)

上面的 observe(data, true /* asRootData */) 最终会调用 defineReactive 进行初始化,直接看这部分:

//{title: 'MVVM'} 
function
defineReactive$$1 (obj,//{title: 'MVVM'}key,//titleval,customSetter,shallow) {var dep = new Dep();//依赖收集器 每个key一个 var property = Object.getOwnPropertyDescriptor(obj, key);if (property && property.configurable === false) {return}// cater for pre-defined getter/settersvar getter = property && property.get;var setter = property && property.set;if ((!getter || setter) && arguments.length === 2) {val = obj[key];}var childOb = !shallow && observe(val);Object.defineProperty(obj, key, { //进行劫持enumerable: true,configurable: true,get: function reactiveGetter () {var value = getter ? getter.call(obj) : val;if (Dep.target) { dep.depend(); //依赖收集器将当前watcher收集到依赖中if (childOb) {childOb.dep.depend();if (Array.isArray(value)) {dependArray(value);}}}return value},set: function reactiveSetter (newVal) {var value = getter ? getter.call(obj) : val;/* eslint-disable no-self-compare */if (newVal === value || (newVal !== newVal && value !== value)) {return}/* eslint-enable no-self-compare */if (customSetter) {customSetter();}// #7981: for accessor properties without setterif (getter && !setter) { return }if (setter) {setter.call(obj, newVal);} else {val = newVal;}childOb = !shallow && observe(newVal);dep.notify(); //通知订阅当前key的所有watcher}});}

这部分是最基本的部分,必须理解,直接上图:

①、Observer会调用defineReactive对数据的每个key进行劫持;

②、defineReactive会为当前key定义get、set方法,以及创建一个Dep实例,Dep可以称为依赖收集器;

③、当(watcher)在获取数据时,如: let title = data.title,此时就会触发 'title' 对应的 get方法,如果Dep.target有对应的watcher,那么通过dep.depend() 将当前watcher加入到  'title' 对应的dep中,最后返回 title 的值。

这里有必要看下Dep的源码以及解释下Dep.target的含义

  var uid = 0;/*** A dep is an observable that can have multiple* directives subscribing to it.*/var Dep = function Dep () {this.id = uid++;this.subs = [];};Dep.prototype.addSub = function addSub (sub) {this.subs.push(sub);};Dep.prototype.removeSub = function removeSub (sub) {remove(this.subs, sub);};Dep.prototype.depend = function depend () {if (Dep.target) {Dep.target.addDep(this);}};Dep.prototype.notify = function notify () {// stabilize the subscriber list firstvar subs = this.subs.slice();if (!config.async) {// subs aren't sorted in scheduler if not running async// we need to sort them now to make sure they fire in correct// ordersubs.sort(function (a, b) { return a.id - b.id; });}for (var i = 0, l = subs.length; i < l; i++) {subs[i].update();}};// The current target watcher being evaluated.// This is globally unique because only one watcher// can be evaluated at a time.Dep.target = null;var targetStack = [];function pushTarget (target) {targetStack.push(target);Dep.target = target;}function popTarget () {targetStack.pop();Dep.target = targetStack[targetStack.length - 1];}

这里Dep.target相当于一个上下文的作用(公共变量),用来存储当前正在执行的watcher实例。获取数据时判断如果Dep.target(当前watcher)存在,dep.depend() 就会将当前正在获取数据的watcher加入到依赖之中;

④、当数据发生变化时,如:data.title = 'xxxx',此时会触发 'title' 对应的set方法,通过dep.notity() 通知对应dep中的watcher,watcher再进行更新;

以上就是数据初始化的过程,总结来说就是对数据进行劫持,并为每个key建立一个依赖,获取数据时依赖收集对应的watcher,数据变化时通知对应的watcher进行更新。

2.之后进行数据的挂载,主要包括两个部分:模版解析、创建渲染watcher完成渲染

模版解析:compiler部分

调用compiler中的compiletoFunctions将模版解析为render渲染函数,(模板解析是运行时比较消耗性能的部分,如果已经编译过Vue会将结果缓存起来使用)

render函数将会传递给下面的渲染watcher渲染DOM使用(获取数据、创建对应的DOM、绑定事件),直观的看一下render的逻辑。

(function anonymous(
) {
with(this){return _c('div',{attrs:{"id":"demo"}},[_c('div',[_v("\n    "+_s(title)+"\n    "+_s(title2)+"\n    ")]),_v(" "),_c('input',{attrs:{"type":"button","value":"click"},on:{"click":testClick}})])}
})

创建渲染watcher,渲染DOM:渲染watcher划重点,Vue有很多个watcher,但只有一个watcher负责渲染,就是渲染watcher

首先讲一下Watcher,看下源码:

var uid$2 = 0;/*** A watcher parses an expression, collects dependencies,* and fires callback when the expression value changes.* This is used for both the $watch() api and directives.*/var Watcher = function Watcher (vm,expOrFn,cb,options,isRenderWatcher) {this.lazy = options.lazy;.....this.id = ++uid$2; // uid for batching
  .....this.expression = expOrFn.toString();// parse expression for getterif (typeof expOrFn === 'function') {this.getter = expOrFn;} else {this.getter = parsePath(expOrFn);.....}this.value = this.lazy? undefined: this.get();};/*** Evaluate the getter, and re-collect dependencies.*/Watcher.prototype.get = function get () {pushTarget(this); //将当前watcher 赋值到 Dep.targetvar value;var vm = this.vm;......value = this.getter.call(vm, vm); //调用watcher传入的回调函数......popTarget();this.cleanupDeps();return value};/*** Add a dependency to this directive.*/  Watcher.prototype.addDep = function addDep (dep) {var id = dep.id;if (!this.newDepIds.has(id)) {this.newDepIds.add(id);this.newDeps.push(dep);if (!this.depIds.has(id)) {dep.addSub(this);}}};Watcher.prototype.update = function update () {/* istanbul ignore else */if (this.lazy) {this.dirty = true;} else if (this.sync) {this.run();} else {queueWatcher(this); //将 watcher加入异步队列中}};/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
Watcher.prototype.run = function run () {if (this.active) {var value = this.get();
    ......
  }
    ......
}

Watcher接受五个参数,现在只关注前两个即可,一个是vm实例,一个是执行函数expOrFn。

Watcher在构造函数初始化时会执行 this.get() 方法,this.get()会执行两个操作:pushTargt(this)、执行回调函数expOrFn

①、pushTargt(this):将当前的watcher实例赋值给Dep.target(见上面Object.defineProperty中的get方法),也即是此时 Dep.target = 'renderWatcher';

②、执行回调函数expOrFn:如果此时回调函数中如果有属获取数据的动作,将会先触发Object.defineProperty中的get方法,将Dep.target(当前watcher) 加入到依赖中,之后整个回调函数执行完毕。

过程如图:

这里的 pushTarget 以及 Dep.target 在看源码的时候是比较难懂的,主要是语义上没有写清(如果对应写成 pushWatcher 以及 globalWatcher 可能会更清楚一些,作者应该是想写的高内聚一些)。

接下来看一下渲染watcher,渲染watcher顾名思义就是负责渲染的watcher,说白了就是回调函数会执行上面的render渲染函数进行DOM渲染

updateComponent = function () {
    vm._update(vm._render(), hydrating); //_render 会调用上面render进行DOM的绘制
  };

new Watcher(vm, updateComponent, noop, {before: function before () {if (vm._isMounted && !vm._isDestroyed) {callHook(vm, 'beforeUpdate');}}}, true /* isRenderWatcher */);

那么根据上面Watcher逻辑,此处渲染watcher初始化时首先会将Dep.target切换到当前watcher上,之后执行回调updateComponent。updateComponent实际上就是执行render函数,render函数获取并订阅数据,之后创建DOM完成渲染。

由于已经订阅了数据,数据在发生变化时就会通知渲染watcher重新进行渲染,之后反映到DOM上。

下面是 updateComponent -> render 执行的逻辑,createElement 将会使用虚拟DOM来创建,最后映射到真实DOM,虚拟DOM的技术有机会单独来讲,不在此处展开。

with(this){return _c('div',{attrs:{"id":"demo"}},[_c('div',[_v("\n    "+_s(title)+"\n    "+_s(title2)+"\n    ")]),_v(" "),_c('input',{attrs:{"type":"button","value":"click"},on:{"click":testClick}})])}

vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); };

总结一下:watcher说白了就是用了哪个数据就会订阅哪个数据,数据变化时就会收到对应通知,未使用到的数据变化就不会受到通知。如渲染watcher,模版中使用了title、title2,那么当title、title2变化时就会通知渲染watcher进行更新,其它各种watcher同理(lazy的有些特殊,后面再讲);

接下来讲一下数据更新阶段

当数据发生变化时,如例子中testClick触发数据变化,过程为:

testClick(){this.title = Math.random();this.title2 = Math.random();}

1.数据更新触发data中的set,依赖收集器会通知Watcher进行更新:

此处首先执行的是this.title = Math.random(),由于渲染watcher使用了title进行渲染,那么此处title的变化就会通知渲染watcher进行更新

//defineReactive中数据的set
dep.notify(); Dep.prototype.notify = function notify () {// stabilize the subscriber list firstvar subs = this.subs.slice();if (!config.async) {// subs aren't sorted in scheduler if not running async// we need to sort them now to make sure they fire in correct// ordersubs.sort(function (a, b) { return a.id - b.id; });}for (var i = 0, l = subs.length; i < l; i++) {subs[i].update();//watcher更新}
};

这里只有一个watcher使用了title,所以subs中就是一个渲染watcher,渲染watcher就会更新。

接下来看下watcher的更新逻辑:

Watcher.prototype.update = function update () {/* istanbul ignore else */if (this.lazy) {this.dirty = true;} else if (this.sync) {this.run();} else {queueWatcher(this);}};

这里可以看到watcher的update不会直接执行watcher的逻辑(例子中为渲染DOM),而是执行了一个queueWatcher方法(前面的逻辑判断先忽略),queueWatcher会将watcher放入一个队列queue中,之后通过nextTick来执行,这就是我前面讲到的异步队列

function queueWatcher (watcher) {var id = watcher.id;console.log('watcherId='+ id + 'exporession=' + watcher.expression);if (has[id] == null) {//console.log('watcherId='+ id + 'exporession=' + watcher.expression);has[id] = true;if (!flushing) {queue.push(watcher);} else { // if already flushing, splice the watcher based on its id// if already past its id, it will be run next immediately.var i = queue.length - 1;while (i > index && queue[i].id > watcher.id) {i--;}queue.splice(i + 1, 0, watcher);}// queue the flushif (!waiting) {waiting = true;if (!config.async) {flushSchedulerQueue();return}nextTick(flushSchedulerQueue);}}}

nextTick可以简单当成一个异步函数来看,比如setTimeout,主要目的就是把要执行的操作放到下一个执行周期中,比如例子中testClick整个执行周期完成后才会执行此处的逻辑,watcher中的逻辑才会真正执行,这样就避免了多次触发watcher以及触发了多个watcher更新造成的重复渲染问题。

var queue = [ watcher ];//nextTick flushSchedulerQueue
setTimetout(function(){for(var i = 0; i < queue.length; i++){queue[i].run(); //watcher.run()
  }
} , 0);

ps:这里的异步队列中涉及到了nextTick以及多个watcher执行顺序的问题,本文为了方便理解只讲了一种watcher--渲染watcher,后面讲其它watcher的时候一起来讲。

以上就是数据更新阶段的逻辑。

五、总结:

本文从Vue的初始化以及更新两个方面出发讲了下Vue的核心逻辑,主要目的是为了帮助大家了解整个流程,介绍过程屏蔽了不少内容,后面有机会再展开。另外,如果有没看过源码可以从我上面划分的几个部分来开,可能会事半功倍。

转载于:https://www.cnblogs.com/DevinnZ/p/10562505.html

相关文章:

【C++】拷贝控制与资源管理

1. 拷贝控制与资源管理 管理类外资源的类必须定义拷贝控制成员。如P447中所见&#xff0c;这种类需要通过析构函数来释放对象所分配的资源。一旦一个类需要析构函数&#xff0c;那么几乎可确定它也需要一个拷贝构造函数和一个拷贝赋值函数。 明确拷贝语义&#xff1a;可以定义…

leangoo V5.4.2版上线

本次更新增加了“卡片编辑面板内显示成员、截止日期、工作量”的功能。同时我们也进行了大量的功能优化&#xff0c;以下是此次更新详情&#xff1a; 1. 新增“卡片编辑面板内显示成员、截止日期、工作量”功能 本次更新后 &#xff0c;您在卡片编辑面板内添加成员&#xff0c;…

差分边缘检测实现

#include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/opencv.hpp> using namespace cv; // 图像差分操作 void diffOperation(const cv::Mat srcImage, cv::Mat& edgeXImage,cv::Mat& edgeYImage) {cv::Mat…

2.1:CGPROGRAM

文章著作权归作者所有。转载请联系作者&#xff0c;并在文中注明出处&#xff0c;给出原文链接。 本系列原更新于作者的github博客&#xff0c;这里给出链接。 前言 经过前面两个章节的铺垫&#xff0c;我们对渲染以及Unity Shaderlab相关的知识已经有了大概的认识&#xff0c;…

【OpenCV】OpenCV中积分图函数与应用

OpenCV中积分图函数与应用 参考资料 opencv 查找integral&#xff0c;目前网上大部分的资料来自于opencv https://docs.opencv.org/master/d7/d1b/group__imgproc__misc.html#gadeaf38d7701d7ad371278d663c50c77dhttps://blog.csdn.net/jia20003/article/details/52710751ht…

django学习笔记【003】创建第一个带有model的app

【1】python应用程序要连接mysql有多个驱动程序可供选择&#xff1a; 1、MySQLdb 这个只支持python2.x 所以在这里就不说了&#xff1b; 2、mysqlclient 下载地址   https://pypi.python.org/pypi/mysqlclient/1.3.9 3、MySQL Connector/python 这个是mysql官方主推的mysql驱…

图像非极大值抑制 Sobel 边缘实现

bool SobelVerEdge(cv::Mat srcImage, cv::Mat& resultImage) {CV_Assert(srcImage.channels() 1);srcImage.convertTo(srcImage, CV_32FC1);// 水平方向的 Sobel 算子cv::Mat sobelx (cv::Mat_<float>(3,3) << -0.125, 0, 0.125,-0.25, 0, 0.25,-0.125, 0, …

第四次作业 (日期和jieba库的运用)

设计题1&#xff1a; 设计一个本月份日历&#xff0c;输出格式如下&#xff1a; 要求&#xff1a; 1.初始化start_day&#xff0c;end_day两个日期 from datetime import datetime start_daydatetime(2019,4,1) end_daydatetime(2019,4,30) 其它时间数据生成要用datetime或date…

【C++】LINK类型错误分析记录

LINK类型错误 情况1&#xff1a; 根据生成路径&#xff0c;查找是否成功生成静态库/动态库&#xff0c;一般在./bin文件中。 情况2&#xff1a; 是否在CMakeLists中target_link_libraries中添加链接静态库操作 情况3&#xff1a; 是都存在类模板&#xff0c;需要实例化&a…

eBay宣布发布全新的购买和销售APIs

eBay最近宣布发布两款全新的购买和销售APIs。这些APIs旨在促进eBay产品在第三方应用程序中的更好集成。eBay于10月19日在他们的博客上发表了几篇文章&#xff0c;不仅详细介绍了这些全新的购买和销售APIs提供的功能&#xff0c;而且还详细地总结了他们公司从SOAP&#xff08;简…

Sobel 边缘实现

#include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include "opencv2/imgproc/imgproc.hpp" #include <iostream> using namespace cv; // 非极大值抑制实现sobel竖直细化边缘 bool SobelVerEdge(cv::Mat srcImage, cv::…

vue下实现textarea类似密码框的功能之探索input输入框keyup,keydown,input事件的触发顺序...

项目中引入element的input框组件&#xff0c;触发事件必须要加上.native <el-input placeholder"请输入" type"textarea" v-model"valueText" keyup.native"keyUp(valueText,$event)" keydown.native"keyDown($event)" …

【C++】动态内存管理/move/以及移动构造与移动赋值运算符

文章目录1 .对象移动与右值引用 实际应用过程中遇到的问题及其解决方案c中临时变量不能作为非const的引用参数2. 动态内存管理类3. 对象移动与右值引用4. 移动构造与移动复制运算符1 .对象移动与右值引用 实际应用过程中遇到的问题及其解决方案 问题描述&#xff1a; bool Cr…

图像直接卷积 Sobel 边缘实现

bool sobelEdge(const cv::Mat& srcImage, cv::Mat& resultImage,uchar threshold) {CV_Assert(srcImage.channels() 1);// 初始化水平核因子Mat sobelx (Mat_<double>(3, 3) << 1, 0,-1, 2, 0, -2, 1, 0, -1);// 初始化垂直核因子Mat sobely (Mat_&…

JSON.parse解析特殊字符报错解决方案

2019独角兽企业重金招聘Python工程师标准>>> 具体案例&#xff1a; 页面点击”下一任务” 会去请求后台&#xff0c;这里出现的问题是有虚拟任务的时候。然后会返回一个map&#xff0c;也就是如下图中回调函数中的data。 当该map里存有以下字符的时候&#xff1a; \…

MySQL数据库字符集和整理

MySQL数据库字符集和整理(2009-11-20 22:23:37)mysql数据库 it 其实这个表在MySQL数据库中通过phpMyAdmin就能看到&#xff0c;icech只是把表格整理了一下方便大家使用&#xff0c;如果要更换数据库的字符集&#xff0c;心里有数。其中有三种utf8_general_ci、utf8_unicode_ci…

【SLAM后端】—— ceres优化相机位姿求解

求解结果如下&#xff1a; mat 初始化&#xff0c;eigenvalue初始化 Mat K ( Mat_<double> ( 3,3 ) << 520.9, 0, 325.1, 0, 521.0, 249.7, 0, 0, 1 );Eigen::Matrix<float,3,1> vd_3d;v_3d << 3, 2, 1;求解目标函数结构体构造与实例 struct CurveFi…

SPOJ 1811 LCS [后缀自动机]

题意&#xff1a; 求两个串的最大连续子串 一个串建SAM&#xff0c;另一个串在上面跑 注意如果走了Suffix Link&#xff0c;sum需要更新为t[u].val1 Suffix Link有点像失配吧&#xff0c;当前状态s走不了了就到Suffix Link指向的状态fa上去&#xff0c;fa是s的后缀所以是可行的…

图像卷积下非极大值抑制 Sobel 的实现

bool sobelOptaEdge(const cv::Mat& srcImage, cv::Mat& resultImage, int flag) {CV_Assert(srcImage.channels() 1);// 初始化sobel水平核因子cv::Mat sobelX (cv::Mat_<double>(3, 3) << 1, 0, -1,2, 0, -2, 1, 0, -1);// 初始化sebel垂直核因子cv::…

was unable to refresh its cache! status = Cannot execute request on any known server

出现这种错误是因为: Eureka服务注册中心也会将自己作为客户端来尝试注册它自己&#xff0c;所以我们需要禁用它的客户端注册行为。 在 yml中设置 eureka.client.register-with-eurekafalse eureka.client.fetch-registryfalse 但在服务端是要这是为false的&#xff0c;在客…

【C++】浅析析构函数(基类中)为什么要写成虚基类?

为什么有了虚析构函数&#xff0c;就能先调用子类的析构函数&#xff1f; class A {virtual ~A(){} };class B : A {virtual ~B(){} };A *p new B(); delete p; 唯一差别是&#xff0c;每个析构函数结束时会自动&#xff08;隐含地&#xff09;调上父类的析构函数&#xff0…

Roberts 边缘检测

#include <opencv2/opencv.hpp> // roberts算子实现 cv::Mat roberts(cv::Mat srcImage) {cv::Mat dstImage srcImage.clone();int nRows dstImage.rows;int nCols dstImage.cols;for (int i 0; i < nRows - 1; i){for (int j 0; j < nCols - 1; j){// 根据公…

vector、map删除当前记录

map<string, string> sMap; map<string, string>::iterator iter; for(iter sMap.begin();iter ! sMap.end();/* iter */) {sMap.erase(iter); }注意下列错误表达&#xff1a;1. for(iter sMap.begin();iter ! sMap.end(); iter ) {sMap.erase(iter); } 错误原因…

1-2 postman工具简介

postman提供了一个多窗口和多选项卡页面用于发送和接受请求&#xff0c;postman努力保持整洁和灵活&#xff0c;提供更多的空间&#xff0c;满足用户的需要。他很简单&#xff0c;能满足大部分接口的测试&#xff0c;性价比特别高。如图所示&#xff1a; 1.侧边栏 postman的侧边…

【C++】重载运算符(一)

1.1 重载运算符特点 重载运算符本质上是一次函数调用 除了operator() 运算符调用外&#xff0c;其他重载运算符不能含有默认参数。 当重载的运算符是成员函数时&#xff0c;this绑定到左侧运算对象。成员运算符函数&#xff08;显式&#xff09;的参数数量比运算对象少一个。…

javaScript的调试(二)

2019独角兽企业重金招聘Python工程师标准>>> 一、Firebug Firebug是Firefox浏览器的调试工具&#xff0c;只要我们在Firefox中安装了Firebug应用&#xff0c;就可以按F12或右击鼠标开启调试 那么我们就先来看一下如何在Firefox中安装了Firebug应用&#xff0c;一图剩…

Prewitt 边缘检测

#include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include "opencv2/imgproc/imgproc.hpp" #include <iostream> // prewitt算子实现 cv::Mat prewitts(cv::Mat img, bool verFlag false) {img.convertTo(img, CV_32FC…

[swift 进阶]读书笔记-第十一章:互用性 C11P1 实践:封装 CommonMark

第十一章&#xff1a;互用性 Interoperability 前言&#xff1a; swift 的最大优点就是与C 或者 OC 混编的时候稳的一匹 本章主要讲了swift和C之间的一些知识点。 11.1 实践:封装 CommonMark Hands-On: Wrapping CommonMark 这一小节更像是一个教程。教你如何封装C语言中的Comm…

【Cmake】Cmake学习记录

Cmake学习记录 1.1 常例 add_library(gen_reference_infogen_reference_info/gen_reference_info.hgen_reference_info/gen_reference_info.cc ) target_link_libraries(gen_reference_infoparamsimage_preprocessorcenter_extractorremapperdescriptor_generator${OpenCV_LI…

MySQL(三)用正则表达式搜索

正则表达式是用来匹配文本的特殊的串&#xff08;字符集合&#xff09;&#xff0c;将一个模式&#xff08;正则表达式&#xff09;与一个文本串进行比较&#xff1b; 所有种类的程序设计语言、文本编辑器、操作系统等都支持正则表达式&#xff0c;正则表达式用正则表达式语言来…