1.androidå¼å设置å±è½å½å¶
2.java.lang.reflect.InvocationTargetException
3.nodejs EventEmitter 源码分析
4.Go语言源码阅读分析(3)-- unsafe
5.Golang源码剖析panic与recover,看不懂你打我好了
6.浅谈Vue3响应式原理与源码解读
androidå¼å设置å±è½å½å¶
项ç®å¼åä¸ï¼ä¸ºäºç¨æ·ä¿¡æ¯çå®å ¨ï¼ä¼æç¦æ¢é¡µé¢è¢«æªå±ãå½å±çéæ±ãè¿ç±»èµæï¼å¨ç½ä¸æå¾å¤ï¼ä¸è¬é½æ¯éè¿è®¾ç½®ActivityçFlag解å³ï¼å¦ï¼
//ç¦æ¢é¡µé¢è¢«æªå±ãå½å±getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
è¿ç§è®¾ç½®å¯è§£å³ä¸è¬çé²æªå±ãå½å±çéæ±ã
å¦æ页é¢ä¸æå¼¹åºPopupwindowï¼å¨å½å±è§é¢ä¸çæææ¯ï¼
éPopupwindowåºå为é»è²
ä½Popupwindowåºåä»ç¶æ¯å¯ä»¥çå°ç
å¦ä¸é¢ä¸¤å¼ Gifå¾æ示ï¼
æªè®¾ç½®FLAG_SECUREï¼å½å±çææï¼å¦ä¸å¾ï¼gitå¾çä¸é´çæ°´å°å¿½ç¥ï¼ï¼
设置äºFLAG_SECUREä¹åï¼å½å±çææï¼å¦ä¸å¾ï¼gitå¾çä¸é´çæ°´å°å¿½ç¥ï¼ï¼
åå åæ
çå°äºä¸é¢çææï¼æ们å¯è½ä¼æçé®PopupWindowä¸åDialogæèªå·±çwindow对象ï¼èæ¯ä½¿ç¨WindowManager.addViewæ¹æ³å°Viewæ¾ç¤ºå¨Activityçªä½ä¸çãé£ä¹ï¼Activityå·²ç»è®¾ç½®äºFLAG_SECUREï¼ä¸ºä»ä¹å½å±æ¶è¿è½çå°PopupWindowï¼
æ们å éè¿getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);æ¥åæä¸æºç ï¼
1ãWindow.java
//windowå¸å±åæ°private final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();//æ·»å æ è¯public void addFlags(int flags) {
setFlags(flags, flags);
}//éè¿mWindowAttributes设置æ è¯public void setFlags(int flags, int mask) { final WindowManager.LayoutParams attrs = getAttributes();
attrs.flags = (attrs.flags&~mask) | (flags&mask);
mForcedWindowFlags |= mask;
dispatchWindowAttributesChanged(attrs);
}//è·å¾å¸å±åæ°å¯¹è±¡ï¼å³mWindowAttributespublic final WindowManager.LayoutParams getAttributes() { return mWindowAttributes;
}
éè¿æºç å¯ä»¥çå°ï¼è®¾ç½®windowå±æ§çæºç é常ç®åï¼å³ï¼éè¿windowéçå¸å±åæ°å¯¹è±¡mWindowAttributes设置æ è¯å³å¯ã
2ãPopupWindow.java
//æ¾ç¤ºPopupWindowpublic void showAtLocation(View parent, int gravity, int x, int y) {
mParentRootView = new WeakReference<>(parent.getRootView());
showAtLocation(parent.getWindowToken(), gravity, x, y);
}//æ¾ç¤ºPopupWindowpublic void showAtLocation(IBinder token, int gravity, int x, int y) { if (isShowing() || mContentView == null) { return;
}
TransitionManager.endTransitions(mDecorView);
detachFromAnchor();
mIsShowing = true;
mIsDropdown = false;
mGravity = gravity;
//å建Windowå¸å±åæ°å¯¹è±¡
final WindowManager.LayoutParams p =createPopupLayoutParams(token);
preparePopup(p);
p.x = x;
p.y = y;
invokePopup(p);
}//å建Windowå¸å±åæ°å¯¹è±¡protected final WindowManager.LayoutParams createPopupLayoutParams(IBinder token) { final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
p.gravity = computeGravity();
p.flags = computeFlags(p.flags);
p.type = mWindowLayoutType;
p.token = token;
p.softInputMode = mSoftInputMode;
p.windowAnimations = computeAnimationResource(); if (mBackground != null) {
p.format = mBackground.getOpacity();
} else {
p.format = PixelFormat.TRANSLUCENT;
} if (mHeightMode < 0) {
p.height = mLastHeight = mHeightMode;
} else {
p.height = mLastHeight = mHeight;
} if (mWidthMode < 0) {
p.width = mLastWidth = mWidthMode;
} else {
p.width = mLastWidth = mWidth;
}
p.privateFlags = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH
| PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
p.setTitle("PopupWindow:" + Integer.toHexString(hashCode())); return p;
}//å°PopupWindowæ·»å å°Windowä¸private void invokePopup(WindowManager.LayoutParams p) { if (mContext != null) {
p.packageName = mContext.getPackageName();
} final PopupDecorView decorView = mDecorView;
decorView.setFitsSystemWindows(mLayoutInsetDecor);
setLayoutDirectionFromAnchor();
mWindowManager.addView(decorView, p); if (mEnterTransition != null) {
decorView.requestEnterTransition(mEnterTransition);
}
}
éè¿PopupWindowçæºç åæï¼æ们ä¸é¾çåºï¼å¨è°ç¨showAtLocationæ¶ï¼ä¼åç¬å建ä¸ä¸ªWindowManager.LayoutParamså¸å±åæ°å¯¹è±¡ï¼ç¨äºæ¾ç¤ºPopupWindowï¼è该å¸å±åæ°å¯¹è±¡ä¸å¹¶æªè®¾ç½®ä»»ä½é²æ¢æªå±Flagã
å¦ä½è§£å³
åå æ¢ç¶æ¾å°äºï¼é£ä¹å¦ä½å¤çå¢ï¼
åå头åæä¸Windowçå ³é®ä»£ç ï¼
//éè¿mWindowAttributes设置æ è¯public void setFlags(int flags, int mask) { final WindowManager.LayoutParams attrs = getAttributes();
attrs.flags = (attrs.flags&~mask) | (flags&mask);
mForcedWindowFlags |= mask;
dispatchWindowAttributesChanged(attrs);
}
å ¶å®åªéè¦è·å¾WindowManager.LayoutParams对象ï¼å设置ä¸flagå³å¯ã
ä½æ¯PopupWindow并没æåActivityä¸æ ·æç´æ¥è·å¾windowçæ¹æ³ï¼æ´å«è¯´è®¾ç½®Flagäºãæ们ååæä¸PopupWindowçæºç ï¼
//å°PopupWindowæ·»å å°Windowä¸private void invokePopup(WindowManager.LayoutParams p) { if (mContext != null) {
p.packageName = mContext.getPackageName();
}
final PopupDecorView decorView = mDecorView;
decorView.setFitsSystemWindows(mLayoutInsetDecor);
setLayoutDirectionFromAnchor(); //æ·»å View
mWindowManager.addView(decorView, p); if (mEnterTransition != null) {
decorView.requestEnterTransition(mEnterTransition);
}
}
æ们è°ç¨showAtLocationï¼æç»é½ä¼æ§è¡mWindowManager.addView(decorView, p);
é£ä¹æ¯å¦å¯ä»¥å¨addViewä¹åè·åå°WindowManager.LayoutParamså¢ï¼
çæ¡å¾ææ¾ï¼é»è®¤æ¯ä¸å¯ä»¥çãå 为PopupWindow并没æå ¬å¼è·åWindowManager.LayoutParamsçæ¹æ³ï¼èä¸mWindowManagerä¹æ¯ç§æçã
å¦ä½æè½è§£å³å¢ï¼
æ们å¯ä»¥éè¿hookçæ¹å¼è§£å³è¿ä¸ªé®é¢ãæ们å 使ç¨å¨æ代çæ¦æªPopupWindowç±»çaddViewæ¹æ³ï¼æ¿å°WindowManager.LayoutParams对象ï¼è®¾ç½®å¯¹åºFlagï¼ååå°è·å¾mWindowManager对象å»æ§è¡addViewæ¹æ³ã
é£é©åæï¼
ä¸è¿ï¼éè¿hookçæ¹å¼ä¹æä¸å®çé£é©ï¼å 为mWindowManageræ¯ç§æ对象ï¼ä¸åPublicçAPIï¼è°·æåç»å级Androidçæ¬ä¸ä¼èèå ¶å ¼å®¹æ§ï¼æ以æå¯è½åç»Androidçæ¬ä¸æ¹äºå ¶å称ï¼é£ä¹æ们éè¿åå°è·å¾mWindowManager对象ä¸å°±æé®é¢äºãä¸è¿ä»å代çæ¬çAndroidæºç å»çï¼mWindowManager被æ¹çå çä¸å¤§ï¼æ以hookä¹æ¯å¯ä»¥ç¨çï¼æ们尽éå代ç æ¶èèä¸è¿ç§é£é©ï¼é¿å 以ååºé®é¢ã
public class PopupWindow {
...... private WindowManager mWindowManager;
......
}
èaddViewæ¹æ³æ¯ViewMangeræ¥å£çå ¬å ±æ¹æ³ï¼æ们å¯ä»¥æ¾å¿ä½¿ç¨ã
public interface ViewManager{ public void addView(View view, ViewGroup.LayoutParams params); public void updateViewLayout(View view, ViewGroup.LayoutParams params); public void removeView(View view);
}
åè½å®ç°
èèå°hookçå¯ç»´æ¤æ§åæ©å±æ§ï¼æ们å°ç¸å ³ä»£ç å°è£ æä¸ä¸ªç¬ç«çå·¥å ·ç±»å§ã
package com.ccc.ddd.testpopupwindow.utils;
import android.os.Handler;
import android.view.WindowManager;
import android.widget.PopupWindow;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class PopNoRecordProxy implements InvocationHandler { private Object mWindowManager;//PopupWindowç±»çmWindowManager对象
public static PopNoRecordProxy instance() { return new PopNoRecordProxy();
} public void noScreenRecord(PopupWindow popupWindow) { if (popupWindow == null) { return;
} try { //éè¿åå°è·å¾PopupWindowç±»çç§æ对象ï¼mWindowManager
Field windowManagerField = PopupWindow.class.getDeclaredField("mWindowManager");
windowManagerField.setAccessible(true);
mWindowManager = windowManagerField.get(popupWindow); if(mWindowManager == null){ return;
} //å建WindowManagerçå¨æ代ç对象proxy
Object proxy = Proxy.newProxyInstance(Handler.class.getClassLoader(), new Class[]{ WindowManager.class}, this); //æ³¨å ¥å¨æ代ç对象proxyï¼å³ï¼mWindowManager对象ç±proxy对象æ¥ä»£çï¼
windowManagerField.set(popupWindow, proxy);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { //æ¦æªæ¹æ³mWindowManager.addView(View view, ViewGroup.LayoutParams params);
if (method != null && method.getName() != null && method.getName().equals("addView")
&& args != null && args.length == 2) { //è·åWindowManager.LayoutParamsï¼å³ï¼ViewGroup.LayoutParams
WindowManager.LayoutParams params = (WindowManager.LayoutParams) args[1]; //ç¦æ¢å½å±
setNoScreenRecord(params);
}
} catch (Exception ex) {
ex.printStackTrace();
} return method.invoke(mWindowManager, args);
} /
*** ç¦æ¢å½å±
*/
private void setNoScreenRecord(WindowManager.LayoutParams params) {
setFlags(params, WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
} /
*** å 许å½å±
*/
private void setAllowScreenRecord(WindowManager.LayoutParams params) {
setFlags(params, 0, WindowManager.LayoutParams.FLAG_SECURE);
} /
*** 设置WindowManager.LayoutParams flagå±æ§ï¼åèç³»ç»ç±»Window.setFlags(int flags, int mask)ï¼
*
* @param params WindowManager.LayoutParams
* @param flags The new window flags (see WindowManager.LayoutParams).
* @param mask Which of the window flag bits to modify.
*/
private void setFlags(WindowManager.LayoutParams params, int flags, int mask) { try { if (params == null) { return;
} params.flags = (params.flags & ~mask) | (flags & mask);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
Popwindowç¦æ¢å½å±å·¥å ·ç±»ç使ç¨ï¼ä»£ç 示ä¾ï¼
//å建PopupWindow
//æ£å¸¸é¡¹ç®ä¸ï¼è¯¥æ¹æ³å¯æ¹æå·¥åç±»
//æ£å¸¸é¡¹ç®ä¸ï¼ä¹å¯èªå®ä¹PopupWindowï¼å¨å ¶ç±»ä¸è®¾ç½®ç¦æ¢å½å±
private PopupWindow createPopupWindow(View view, int width, int height) {
PopupWindow popupWindow = new PopupWindow(view, width, height); //PopupWindowç¦æ¢å½å±
PopNoRecordProxy.instance().noScreenRecord(popupWindow); return popupWindow;
} //æ¾ç¤ºPopupwindow
private void showPm() {
View view = LayoutInflater.from(this).inflate(R.layout.pm1, null);
PopupWindow pw = createPopupWindow(view,ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
pw1.setFocusable(false);
pw1.showAtLocation(this.getWindow().getDecorView(), Gravity.BOTTOM | Gravity.RIGHT, PopConst.PopOffsetX, PopConst.PopOffsetY);
}
å½å±ææå¾ï¼
java.lang.reflect.InvocationTargetException
æ好æ¯ç¨mainæ¹æ³å¼ä¸ªæµè¯ç±» å«æµè¯ä¸daoæ¹æ³ æ¯å¦å¥½ç¨ ç¶åå¨è¯è¯actionä¸çæ¹æ³ å¦ædaoæ¹æ³å¥½ç¨çè¯ å¼å¸¸å°±å¨actionä¸ ç¶åå¨é个æé¤é误
å ä¸ºä½ æ²¡åæºç è¿ä¹åå¼å¸¸çè¯ å¾é¾æ¾åå
nodejs EventEmitter 源码分析
EventEmitter 是 Node.js 中的事件管理器核心逻辑简单,主要聚焦于事件与函数或函数数组之间的关联。在 v..1 版本中,核心逻辑在实例的 _events 属性上展开,该属性是疯狂打地鼠php源码一个对象,其键为事件名称,值为事件对应的函数或函数数组。所有方法均围绕 _events 展开。
构造函数初始化 _events 属性,若实例本身未定义,则执行此操作。此操作涉及对实例原型的引用,通过 ObjectGetPrototypeOf 的使用来实现。函数 on 允许用户注册事件监听器,逻辑简单明了:判断同名事件是否已注册,无则注册;已有则将新监听器加入已有函数数组中。emit 方法触发事件,根据事件名称获取对应函数或函数数组,使用 ReflectApply 调用。此方法与 Function.prototype.apply 类似,但提供了更简洁的实现。
off 方法与 on 方法相似,但逻辑相反。它获取事件监听器,若为函数,则直接删除;若为数组,则遍历删除指定监听器。此方法同样简洁,直接操作事件列表。
Reflect API 的北京源码使用在不同版本的 EventEmitter 中逐渐增多,例如将 Object.keys 替换为 Reflect.ownKeys,以更好地处理 Symbol 类型的事件名。反射方法,如 Reflect.apply,尽管在 V8 中源码显得复杂,但其执行逻辑与 Function.prototype.apply 相似,性能上并无显著提升,但提升了代码的可读性。
在最新版本 v.5.0 中,EventEmitter 的实现中采用 Reflect.ownKeys 更为合理,因为此方法能有效避免返回数组中无 Symbol 的问题。EventEmitter 的构造函数与 Stream 的关系展示了如何利用继承来扩展功能。Stream 通过继承 EventEmitter,实现了更简洁的 class 写法,未来可能进一步简化。
此外,文章还讨论了私有属性的使用,以及简易版 EventEmitter 的实现。简易版 EventEmitter 基本逻辑简洁,但不包含参数校验、异常处理和性能优化等生产环境所需的功能。实际生产环境中的 EventEmitter 实现则需额外处理这些复杂情况。
Go语言源码阅读分析(3)-- unsafe
Go语言的unsafe包提供了一套打破类型安全限制的操作,但使用时需谨慎,因为它可能导致代码无法移植。包内主要包含unsafe.go文件和一些声明,实际实现和测试用例并未提供。关键内容如下: 1. Pointer类型:可以转换为任何类型的指针,以及Uintptr类型,这种转换允许直接读写内存,ventorapp源码风险极高,需谨慎使用。 - 可以将任意类型转换为Pointer类型,但转换后不能长于原类型,且要求内存布局一致。例如,将float转换为uint的函数`Floatbits`。 - Pointer可以转换为uintptr,但这种转换仅用于内存地址的打印,且不能直接从uintptr恢复为Pointer,除非是枚举类型。 2. 偏移指针:用于访问结构体字段或数组元素,需确保指针不会超出原始对象的内存范围。 3. syscall调用:在syscall包中,某些函数需要在同一条语句中进行指针到uintptr的转换,以确保指针指向的对象在调用时仍然有效。 4. reflect包使用:reflect.Value.Pointer和UndafeAddr返回的都是uintptr,应在获取后立即转换为Pointer,避免对象被GC回收。 5. 反射结构体转换:例如StringHeader和SliceHeader的Data字段,仅在指向活动切片或字符串时有效。 总之,unsafe包的使用需遵循特定的规则和限制,不当使用可能导致程序不稳定或移植问题。接下来的计划是研究reflect包。Golang源码剖析panic与recover,看不懂你打我好了
哈喽,大家好,我是asong,今天与大家来聊一聊go语言中的lhr源码"throw、try.....catch{ }"。如果你之前是一名java程序员,我相信你一定吐槽过go语言错误处理方式,但是这篇文章不是来讨论好坏的,我们本文的重点是带着大家看一看panic与recover是如何实现的。上一文我们讲解了defer是如何实现的,但是没有讲解与defer紧密相连的recover,想搞懂panic与recover的实现也没那么简单,就放到这一篇来讲解了。废话不多说,直接开整。
Go 语言中panic 关键字主要用于主动抛出异常,类似 java 等语言中的 throw 关键字。panic 能够改变程序的控制流,调用 panic 后会立刻停止执行当前函数的剩余代码,并在当前 Goroutine 中递归执行调用方的 defer;
Go 语言中recover 关键字主要用于捕获异常,让程序回到正常状态,类似 java 等语言中的 try ... catch 。recover 可以中止 panic 造成的程序崩溃。它是一个只能在 defer 中发挥作用的函数,在其他作用域中调用不会发挥作用;
recover只能在defer中使用这个在标准库的注释中已经写明白了,我们可以看一下:
这里有一个要注意的点就是recover必须要要在defer函数中使用,否则无法阻止panic。最好的验证方法是先写两个例子:
运行我们会发现example2()方法的panic是没有被recover住的,导致整个程序直接crash了。这里大家肯定会有疑问,为什么直接写recover()就不能阻止panic了呢。我们在 详解defer实现机制(附上三道面试题,我不信你们都能做对)讲解了defer实现原理,一个重要的源码道歉知识点**defer将语句放入到栈中时,也会将相关的值拷贝同时入栈。**所以defer recover()这种写法在放入defer栈中时就已经被执行过了,panic是发生在之后,所以根本无法阻止住panic。
通过运行结果可以看出panic不会影响defer函数的使用,所以他是安全的。
这里我开了两个协程,一个协程会发生panic,导致程序崩溃,但是只会执行自己所在Goroutine的延迟函数,所以正好验证了多个 Goroutine 之间没有太多的关联,一个 Goroutine 在 panic 时也不应该执行其他 Goroutine 的延迟函数。
其实我们在实际项目开发中,经常会遇到panic问题, Go 的 runtime 代码中很多地方都调用了 panic 函数,对于不了解 Go 底层实现的新人来说,这无疑是挖了一堆深坑。我们在实际生产环境中总会出现panic,但是我们的程序仍能正常运行,这是因为我们的框架已经做了recover,他已经为我们兜住底,比如gin,我们看一看他是怎么做的。
我们先来写个简单的代码,看看他的汇编调用:执行go tool compile -N -l -S main.go就可以看到对应的汇编码了,我们截取部分片段分析:
上面重点部分就是画红线的三处,第一步调用runtime.deferprocStack创建defer对象,这一步大家可能会有疑惑,我上一文忘记讲个这个了,这里先简单概括一下,defer总共有三种模型,编译一个函数里只会有一种defer模式。在讲defer实现机制时,我们一起看过defer的结构,其中有一个字段就是_panic,是触发defer的作用,我们来看看的panic的结构:
简单介绍一下上面的字段:
上面的pc、sp、goexit我们单独讲一下,runtime包中有一个Goexit方法,Goext能够终止调用它的goroutine,其他的goroutine是不受影响的,goexit也会在终止goroutine之前运行所有延迟调用函数,Goexit不是一个panic,所以这些延迟函数中的任何recover调用都将返回nil。如果我们在主函数中调用了Goexit会终止该goroutine但不会返回func main。由于func main没有返回,因此程序将继续执行其他gorountine,直到所有其他goroutine退出,程序才会crash。
下面就开始我们的重点吧~。
在讲defer实现机制时,我们一起看过defer的结构,其中有一个字段就是_panic,是触发defer的作用,我们来看看的panic的结构:简单介绍一下上面的字段:上面的pc、sp、goexit我们单独讲一下,runtime包中有一个Goexit方法,Goext能够终止调用它的goroutine,其他的goroutine是不受影响的,goexit也会在终止goroutine之前运行所有延迟调用函数,Goexit不是一个panic,所以这些延迟函数中的任何recover调用都将返回nil。如果我们在主函数中调用了Goexit会终止该goroutine但不会返回func main。由于func main没有返回,因此程序将继续执行其他gorountine,直到所有其他goroutine退出,程序才会crash。写个简单的例子:运行上面的例子你就会发现,即使在主goroutine中调用了runtime.Goexit,其他goroutine是没有任何影响的。所以结构中的pc、sp、goexit三个字段都是为了修复runtime.Goexit,这三个字段就是为了保证该函数的一定会生效,因为如果在defer中发生panic,那么goexit函数就会被取消,所以才有了这三个字段做保护。看这个例子:
英语好的可以看一看这个: github.com/golang/go/is...,这就是上面的一个例子,这里就不过多解释了,了解就好。
接下来我们再来看一看gopanic方法。
gopanic的代码有点长,我们一点一点来分析:
根据不同的类型判断当前发生panic错误,这里没什么多说的,接着往下看。
上面的代码都是截段,这些部分都是为了判断当前defer是否可以使用开发编码模式,具体怎么操作的就不展开了。
在第三部分进行defer内联优化选择时会执行调用延迟函数(reflectcall就是这个作用),也就是会调用runtime.gorecover把recoverd = true,具体这个函数的操作留在下面讲,因为runtime.gorecover函数并不包含恢复程序的逻辑,程序的恢复是在gopanic中执行的。先看一下代码:
这段代码有点长,主要就是分为两部分:
第一部分主要是这个判断if gp._panic != nil && gp._panic.goexit && gp._panic.aborted { ... },正常recover是会绕过Goexit的,所以为了解决这个,添加了这个判断,这样就可以保证Goexit也会被recover住,这里是通过从runtime._panic中取出了程序计数器pc和栈指针sp并且调用runtime.recovery函数触发goroutine的调度,调度之前会准备好 sp、pc 以及函数的返回值。
第二部分主要是做panic的recover,这也与上面的流程基本差不多,他是从runtime._defer中取出了程序计数器pc和栈指针sp并调用recovery函数触发Goroutine,跳转到recovery函数是通过runtime.call进行的,我们看一下其源码(src/runtime/asm_amd.s 行):
因为go语言中的runtime环境是有自己的堆栈和goroutine,recovery函数也是在runtime环境执行的,所以要调度到m->g0来执行recovery函数,我们在看一下recovery函数:
在recovery 函数中,利用 g 中的两个状态码回溯栈指针 sp 并恢复程序计数器 pc 到调度器中,并调用 gogo 重新调度 g , goroutine 继续执行,recovery在调度过程中会将函数的返回值设置为1。这个有什么作用呢? 在deferproc函数中找到了答案:
当延迟函数中recover了一个panic时,就会返回1,当 runtime.deferproc 函数的返回值是 1 时,编译器生成的代码会直接跳转到调用方函数返回之前并执行 runtime.deferreturn,跳转到runtime.deferturn函数之后,程序就已经从panic恢复了正常的逻辑。
在这里runtime.fatalpanic实现了无法被恢复的程序崩溃,它在中止程序之前会通过 runtime.printpanics 打印出全部的 panic 消息以及调用时传入的参数。
这就是这个逻辑流程,累死我了。。。。
结尾给大家发一个小福利,哈哈,这个福利就是如果避免出现panic,要注意这些:这几个是比较典型的,还有很多会发生panic的地方,交给你们自行学习吧~。
好啦,这篇文章就到这里啦,素质三连(分享、点赞、在看)都是笔者持续创作更多优质内容的动力!
浅谈Vue3响应式原理与源码解读
Vue3响应式原理的核心在于数据劫持、依赖收集和依赖更新,主要通过Proxy与Reflect这两个ES6新特性实现。首先,理解响应式,它涉及数据变化触发函数自动更新的过程,如视图依赖数据,数据变动则自动刷新视图。副作用函数就是那些引用外部数据的函数,如Vue中的effect函数。
实现响应式的基本步骤是,当数据发生变化时,能够自动调用与之相关的副作用函数。Vue2通过Object.defineProperty进行数据劫持,而Vue3则利用Proxy的set和get拦截器,结合Reflect API,动态跟踪和更新依赖。reactive函数是Vue3响应式的核心,它会创建一个代理对象,通过baseHandlers中的get和set方法进行依赖收集与更新,其中依赖收集在effect.ts中的track()方法中处理。
ref则用于定义基本数据类型的响应式,其源码在packages/reactivity/src/ref.ts。总的来说,Vue3响应式原理的实现是借助Proxy的代理功能,配合Reflect进行数据操作的拦截和反射,从而实现实时响应数据变化的效果。
深入理解这些原理,可以参考Vue官方文档和JavaScript.info的相关内容。