Auto.JS简介与教程
什么是Auto.JS? Auto.JS是Android平台上的JavaScript自动化工具。 它的本质是可执行自己编写的简易Javascript脚本的,尤其可以在开启“无障碍模式”的情况下对其他App进行一些操作的一个Android App,便于进行自动化操作。学习成本非常低。 Auto.JS已被黑产广泛使用,以至于作者关闭了官方下载通道。 官网:https://github.com/hyb1996/Auto.js 我们能用它自动化地做什么? 1) 对于黑产 微博:自动注册、远程获取内容、动发微博,点赞关注收藏、评价回复转发 注册类:163邮箱注册,抖音注册 签到类:百度地图签到、大众点评签到、叮咚买菜签到、拼多多签到、云闪付签到积分、支付宝签到积分、京东签到京豆等 2) 对于普通人 启动游戏时自动屏蔽通知、一键与特定联系人微信视频、淘宝双十一一键领猫币等 跟按键精灵的区别?跟Robotium等的区别? 1) VS 按键精灵 无需Root;可直接指定控件并点击,无需识图找坐标 2) VS Robotium 手机无需连电脑即可运行;代码极其简单;无需Eclipse或bat脚本;可在手机上选择不同脚本执行 在自动化操作演示/黑产模拟时,比较有用的功能? 1) APP相关 启动App(通过应用名/包名)、打开App设置页、卸载App等 2) 时序相关 等待指定的Activity(页面)出现、等待指定的App启动、获取当前Activity 3) 控件相关※ 输入:点击/长按含特定文字的控件、滑动特定控件、在第N个输入框输入/追加文字、复制粘贴等; ※控件选择器:通过控件的各种xml属性(如ID、text、desc、包名、位置)选择一个或多个控件并进行操作; 等待:等待指定控件出现 4) 创建界面相关 可编写界面、弹出Toast、弹出对话框、创建悬浮窗 5) 坐标/按键类 点击指定坐标&滑动、模拟点击各种按键(Home、后退键、菜单键、电源键等)等 6) 其他 随机数、网络请求、定时器、多线程、文件操作、找图等常见功能;监听按键&屏幕点击、获取各种设备相关信息(Build.**、音量、震动等等);Linux Shell命令 版本限制 在非Root情况下,Auto.JS只能运行在>=Android7.0以上的系统。 如何编写脚本、运行脚本? 1)安装Auto.JS APK:在手机上安装Auto.js_V4.1.1_Alpha2 注:App会自动引导开启“无障碍服务”。以小米为例,按程序指示,在程序跳转到的页面点击“更多已下载的服务”->“Auto.js”->开启服务 2)在PC上编写 首先安装VSCode,在VS Code中菜单"查看"->"扩展"->输入"Auto.js"或"hyb1996"搜索,即可看到"Auto.js-VSCodeExt"插件,安装即可。请把文件保存为.js,方便代码补全。 3)在PC侧调试 1.jpg 注1:“连接电脑”开关若为蓝色才表示连接成功,否则请确认连接到了同一WIFI,若WIFI环境复杂(比如多个同名WIFI但实为不同路由器),请用笔记本/热点开WIFI 4)脱离电脑运行 先把编写并测试好js文件复制到手机上,在手机上启动Auto.js,点⊕按钮-导入,导入到App里,然后在对应的脚本右边点“▶”即可运行。 5)中止运行 点击Auto.JS 右下角的“×” 或在VSCode里ctrl+shift+p然后选”Stop” 官方文档 https://hyb1996.github.io/AutoJs-Docs/ 注:使用选择器时无需加UiSelector. 脚本范例 https://github.com/bjc5233/autojs https://github.com/bayson/autojs https://github.com/hyb1996/Auto.js/tree/master/app/src/main/assets/sample 作者:RedB 链接:https://www.jianshu.com/p/4602db0618df 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
View Details用Python实现自动化操作Android手机
一、【必须】安装adb工具 adb全称Android Debug Bridge,是Android系统的调试工具。 下并安装ADB Installer v1.4.3,下载链接:http://pan.webscraping.cn:8000/index.php/s/7kDAJUOmKEa1h4N 安装完成后,启动一个新的cmd窗口,输入adb devices,若无错误提示则表明安装成功。 Ubuntu下安装adb可以参考这篇文章:http://bernaerts.dyndns.org/linux/74-ubuntu/354-ubuntu-xenial-android-adb-fastboot-qtadb) 二、【可选】安装UI Automator Viewer辅助工具 为了使用UI Automator Viewer这个辅助分析工具,我们需要先安装Android SDK,步骤如下: 1. 下载并安装Java 8 2. 下载并安装Google Android SDK 3. 启动Android SDK Manager,选择并安装Android SDK Platform-tools. 4. 双击uiautomatorviewer.bat,启动UI Automator Viewer,点击第二个图标获取设备截图及相关UI信息,如下图所示。 三、【主角】AndroidViewClient AndroidViewClient是用纯Python编写的Android应用程序自动测试框架,它不依赖其它程序(例如 monkeyrunner, jython)。AndroidViewClient在底层是通过调用adb命令实现对Android设备的控制,因此在本文的一开始就先介绍了adb的安装。 开始下文之前,假设你已经安装配置好Python运行环境,否则请先安装Python 2.7(注意:AndroidViewClient不兼容Python3)。 1. 安装AndroidViewClient 项目主页:https://github.com/dtmilano/AndroidViewClient 推荐用easy_install安装: easy_install --upgrade androidviewclient 安装详细说明见这里:https://github.com/dtmilano/AndroidViewClient/wiki#installation PS:依赖库比较多,安装需要有点耐心。 2. 测试安装是否成功 下载https://github.com/dtmilano/AndroidViewClient/archive/master.zip包,解压并切换到examples目录下,执行python check-import.py,如果没有问题,会输出OK。 3. 写一个例子 实现这样一个功能: 点击屏幕微信图标启动微信,点击第一个联系人/群,发送一个报时消息。 代码如下: view plaincopy to clipboardprint? # coding: utf-8 # 点击屏幕微信图标启动微信,点击第一个联系人/群,发送一个报时消息 import sys import os import re import time from com.dtmilano.android.viewclient import ViewClient def test(): # 连接手机 device, serialno = ViewClient.connectToDeviceOrExit() vc = ViewClient(device, serialno) # 按HOME键 device.press('KEYCODE_HOME') time.sleep(3) # 找到微信图标 vc.dump() weixin_button = vc.findViewWithTextOrRaise(u'微信') # 点击微信图标 weixin_button.touch() time.sleep(10) # 找到第一个联系人/群 # 可以使用UI Automator Viewer查看到对应第一个联系人/群的resource-id为"com.tencent.mm:id/auj" vc.dump() group_button = vc.findViewByIdOrRaise("com.tencent.mm:id/auj") # 点击进群 group_button.touch() time.sleep(5) # 找到输入框并输入当前时间 vc.dump() […]
View DetailsC# this关键字的四种用法(转)
用法一 this代表当前类的实例对象
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 29 30 31 32 33 34 35 36 |
namespace Demo { public class Test { private string scope = "全局变量"; public string getResult() { string scope = "局部变量"; // this代表Test的实例对象 // 所以this.scope对应的是全局变量 // scope对应的是getResult方法内的局部变量 return this.scope + "-" + scope; } } class Program { static void Main(string[] args) { try { Test test = new Test(); Console.WriteLine(test.getResult()); } catch (Exception ex) { Console.WriteLine(ex); } finally { Console.ReadLine(); } } } } |
用法二 用this串联构造函数
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 29 30 31 32 33 34 35 36 |
namespace Demo { public class Test { public Test() { Console.WriteLine("无参构造函数"); } // this()对应无参构造方法Test() // 先执行Test(),后执行Test(string text) public Test(string text) : this() { Console.WriteLine(text); Console.WriteLine("有参构造函数"); } } class Program { static void Main(string[] args) { try { Test test = new Test("张三"); } catch (Exception ex) { Console.WriteLine(ex); } finally { Console.ReadLine(); } } } } |
用法三 为原始类型扩展方法
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
namespace Demo { public static class Extends { // string类型扩展ToJson方法 public static object ToJson(this string Json) { return Json == null ? null : JsonConvert.DeserializeObject(Json); } // object类型扩展ToJson方法 public static string ToJson(this object obj) { var timeConverter = new IsoDateTimeConverter { DateTimeFormat = "yyyy-MM-dd HH:mm:ss" }; return JsonConvert.SerializeObject(obj, timeConverter); } public static string ToJson(this object obj, string datetimeformats) { var timeConverter = new IsoDateTimeConverter { DateTimeFormat = datetimeformats }; return JsonConvert.SerializeObject(obj, timeConverter); } public static T ToObject<T>(this string Json) { return Json == null ? default(T) : JsonConvert.DeserializeObject<T>(Json); } public static List<T> ToList<T>(this string Json) { return Json == null ? null : JsonConvert.DeserializeObject<List<T>>(Json); } public static DataTable ToTable(this string Json) { return Json == null ? null : JsonConvert.DeserializeObject<DataTable>(Json); } public static JObject ToJObject(this string Json) { return Json == null ? JObject.Parse("{}") : JObject.Parse(Json.Replace(" ", "")); } } class Program { static void Main(string[] args) { try { List<User> users = new List<User>{ new User{ID="1",Code="zs",Name="张三"}, new User{ID="2",Code="ls",Name="李四"} }; // list转化json字符串 string json = users.ToJson(); // string转化List users = json.ToList<User>(); // string转化DataTable DataTable dt = json.ToTable(); } catch (Exception ex) { Console.WriteLine(ex); } finally { Console.ReadLine(); } } } public class User { public string ID { get; set; } public string Code { get; set; } public string Name { get; set; } } } |
用法四 索引器(基于索引器封装EPList,用于优化大数据下频发的Linq查询引发的程序性能问题,通过索引从list集合中查询数据)
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; namespace MyDemo.Web { /// <summary> /// EPList 支持为List创建索引 /// </summary> /// <typeparam name="T">类型</typeparam> public class EPList<T> { #region 成员变量 /// <summary> /// 索引 /// </summary> private List<string[]> m_Index = new List<string[]>(); /// <summary> /// 缓存数据 /// </summary> private Dictionary<string, List<T>> m_CachedData = new Dictionary<string, List<T>>(); /// <summary> /// List数据源 /// </summary> private List<T> m_ListData = new List<T>(); /// <summary> /// 通过索引值取数据 /// </summary> /// <param name="indexFields">索引字段</param> /// <param name="fieldValues">字段值</param> /// <returns></returns> public List<T> this[string[] indexFields] { get { string key = string.Join(",", indexFields); if (m_CachedData.ContainsKey(key)) return m_CachedData[key]; return new List<T>(); } } #endregion #region 公共方法 /// <summary> /// 创建索引 /// </summary> /// <param name="indexFields">索引字段</param> public void CreateIndex(string[] indexFields) { if (m_Index.Contains(indexFields)) return; m_Index.Add(indexFields); } /// <summary> /// 添加 /// </summary> /// <param name="record">记录</param> public void Add(T record) { m_ListData.Add(record); m_Index.ForEach(indexFields => { string key = getKey(record, indexFields); if (m_CachedData.ContainsKey(key)) { m_CachedData[key].Add(record); } else { List<T> list = new List<T> { record }; m_CachedData.Add(key, list); } }); } #endregion #region 私有方法 /// <summary> /// 获取值 /// </summary> /// <param name="record">记录</param> /// <param name="fieldName">字段名</param> /// <returns></returns> private object getValue(T record, string fieldName) { Type type = typeof(T); PropertyInfo propertyInfo = type.GetProperty(fieldName); return propertyInfo.GetValue(record, null); } /// <summary> /// 获取Key /// </summary> /// <param name="record">记录</param> /// <param name="indexFields">索引字段</param> private string getKey(T record, string[] indexFields) { List<string> values = new List<string>(); foreach (var field in indexFields) { string value = Convert.ToString(getValue(record, field)); values.Add(field + ":" + value); } return string.Join(",", values); } /// <summary> /// 获取Key /// </summary> /// <param name="indexFields">索引字段</param> /// <param name="fieldValues">字段值</param> /// <returns></returns> private string getKey(string[] indexFields, object[] fieldValues) { if (indexFields.Length != fieldValues.Length) return string.Empty; for (int i = 0; i < indexFields.Length; i++) { fieldValues[i] = indexFields[i] + ":" + fieldValues[i]; } string key = string.Join(",", fieldValues); return key; } #endregion } } |
给EPList创建索引,并添加数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
private EPList<SysDepartInfo> GetEPListData() { EPList<SysDepartInfo> eplist = new EPList<SysDepartInfo>(); eplist.CreateIndex(new string[] { "ParentId" }); string sql = "select Id,ParentId,Code,Name from SysDepart"; SqlHelper.ExecuteReader(sql, null, (reader) => { SysDepartInfo record = new SysDepartInfo(); record.Id = Convert.ToString(reader["Id"]); record.ParentId = Convert.ToString(reader["ParentId"]); record.Code = Convert.ToString(reader["Code"]); record.Name = Convert.ToString(reader["Name"]); eplist.Add(record); }); return eplist; } |
通过索引高效查询数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
/// <summary> /// 获取子节点 /// </summary> /// <param name="data"></param> /// <param name="parentId"></param> private IEnumerable<TreeInfo> CreateChildren(EPList<SysDepartInfo> data, TreeInfo node) { string id = node == null ? "0" : node.id; List<TreeInfo> childNodes = new List<TreeInfo>(); // ParentId字段上创建了索引,所以这里就可以通过索引值直接取出下一层子节点数据,避免Linq查询引发的效率问题 var indexValues = new string[] { "ParentId:" + id }; var childData = data[indexValues]; childData.ForEach(record => { var childNode = new TreeInfo { id = record.Id, text = record.Code + " " + record.Name }; childNodes.Add(childNode); childNode.children = CreateChildren(data, childNode); }); return childNodes.OrderBy(record => record.text); } |
from:https://www.cnblogs.com/yellowcool/p/7908607.html
View Details移动端touchstart,touchmove,touchend
touchstart:手指触摸到一个 DOM 元素时触发。 touchmove:手指在一个 DOM 元素上滑动时触发。 touchend:手指从一个 DOM 元素上移开时触发。 touchcancel:当系统停止跟踪触发触发 event.touches 当前触摸屏幕的触摸点数组 event.changedTouches 移动的触摸点信息数组 event.targetTouches 只包含放在元素上的触摸信息数组 touch对象代表一个触点,通过event.touches[0]获取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
{ screenX: 511, screenY: 400,//触点相对于屏幕左边沿的Y坐标 clientX: 244.37899780273438, clientY: 189.3820037841797,//相对于可视区域 pageX: 244.37, pageY: 189.37,//相对于HTML文档顶部,当页面有滚动的时候与clientX=Y 不等 force: 1,//压力大小,是从0.0(没有压力)到1.0(最大压力)的浮点数 identifier: 1036403715,//一次触摸动作的唯一标识符 radiusX: 37.565673828125, //能够包围用户和触摸平面的接触面的最小椭圆的水平轴(X轴)半径 radiusY: 37.565673828125, rotationAngle: 0,//它是这样一个角度值:由radiusX 和 radiusY 描述的正方向的椭圆,需要通过顺时针旋转这个角度值,才能最精确地覆盖住用户和触摸平面的接触面 target: {} // 此次触摸事件的目标element } |
from:https://www.cnblogs.com/fanbulaile/p/10880232.html
View Details简单理解Vue中的nextTick
Vue中的nextTick涉及到Vue中DOM的异步更新,感觉很有意思,特意了解了一下。其中关于nextTick的源码涉及到不少知识,很多不太理解,暂且根据自己的一些感悟介绍下nextTick。 一、示例 先来一个示例了解下关于Vue中的DOM更新以及nextTick的作用。 模板
1 2 3 4 5 6 7 8 9 |
<div class="app"> <div ref="msgDiv">{{msg}}</div> <div v-if="msg1">Message got outside $nextTick: {{msg1}}</div> <div v-if="msg2">Message got inside $nextTick: {{msg2}}</div> <div v-if="msg3">Message got outside $nextTick: {{msg3}}</div> <button @click="changeMsg"> Change the Message </button> </div> |
Vue实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
new Vue({ el: '.app', data: { msg: 'Hello Vue.', msg1: '', msg2: '', msg3: '' }, methods: { changeMsg() { this.msg = "Hello world." this.msg1 = this.$refs.msgDiv.innerHTML this.$nextTick(() => { this.msg2 = this.$refs.msgDiv.innerHTML }) this.msg3 = this.$refs.msgDiv.innerHTML } } }) |
点击前 点击后 从图中可以得知:msg1和msg3显示的内容还是变换之前的,而msg2显示的内容是变换之后的。其根本原因是因为Vue中DOM更新是异步的(详细解释在后面)。 二、应用场景 下面了解下nextTick的主要应用的场景及原因。 在Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中 在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进Vue.nextTick()的回调函数中。与之对应的就是mounted()钩子函数,因为该钩子函数执行时所有的DOM挂载和渲染都已完成,此时在该钩子函数中进行任何DOM操作都不会有问题 。 在数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,这个操作都应该放进Vue.nextTick()的回调函数中。 具体原因在Vue的官方文档中详细解释: Vue 异步执行 DOM 更新。只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部尝试对异步队列使用原生的 Promise.then 和MessageChannel,如果执行环境不支持,会采用 setTimeout(fn, 0)代替。 例如,当你设置vm.someData = 'new value’,该组件不会立即重新渲染。当刷新队列时,组件会在事件循环队列清空时的下一个“tick”更新。多数情况我们不需要关心这个过程,但是如果你想在 DOM 状态更新后做点什么,这就可能会有些棘手。虽然 Vue.js 通常鼓励开发人员沿着“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们确实要这么做。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用Vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用。 三、nextTick源码浅析 作用 Vue.nextTick用于延迟执行一段代码,它接受2个参数(回调函数和执行回调函数的上下文环境),如果没有提供回调函数,那么将返回promise对象。 源码
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
/** * Defer a task to execute it asynchronously. */ export const nextTick = (function () { const callbacks = [] let pending = false let timerFunc function nextTickHandler () { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() } } // the nextTick behavior leverages the microtask queue, which can be accessed // via either native Promise.then or MutationObserver. // MutationObserver has wider support, however it is seriously bugged in // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It // completely stops working after triggering a few times... so, if native // Promise is available, we will use it: /* istanbul ignore if */ if (typeof Promise !== 'undefined' && isNative(Promise)) { var p = Promise.resolve() var logError = err => { console.error(err) } timerFunc = () => { p.then(nextTickHandler).catch(logError) // in problematic UIWebViews, Promise.then doesn't completely break, but // it can get stuck in a weird state where callbacks are pushed into the // microtask queue but the queue isn't being flushed, until the browser // needs to do some other work, e.g. handle a timer. Therefore we can // "force" the microtask queue to be flushed by adding an empty timer. if (isIOS) setTimeout(noop) } } else if (!isIE && typeof MutationObserver !== 'undefined' && ( isNative(MutationObserver) || // PhantomJS and iOS 7.x MutationObserver.toString() === '[object MutationObserverConstructor]' )) { // use MutationObserver where native Promise is not available, // e.g. PhantomJS, iOS7, Android 4.4 var counter = 1 var observer = new MutationObserver(nextTickHandler) var textNode = document.createTextNode(String(counter)) observer.observe(textNode, { characterData: true }) timerFunc = () => { counter = (counter + 1) % 2 textNode.data = String(counter) } } else { // fallback to setTimeout /* istanbul ignore next */ timerFunc = () => { setTimeout(nextTickHandler, 0) } } return function queueNextTick (cb?: Function, ctx?: Object) { let _resolve callbacks.push(() => { if (cb) { try { cb.call(ctx) } catch (e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } }) if (!pending) { pending = true timerFunc() } if (!cb && typeof Promise !== 'undefined') { return new Promise((resolve, reject) => { _resolve = resolve }) } } })() |
首先,先了解nextTick中定义的三个重要变量。 callbacks 用来存储所有需要执行的回调函数 pending 用来标志是否正在执行回调函数 timerFunc 用来触发执行回调函数 接下来,了解nextTickHandler()函数。
1 2 3 4 5 6 7 8 |
function nextTickHandler () { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() } } |
这个函数用来执行callbacks里存储的所有回调函数。 接下来是将触发方式赋值给timerFunc。 先判断是否原生支持promise,如果支持,则利用promise来触发执行回调函数; 否则,如果支持MutationObserver,则实例化一个观察者对象,观察文本节点发生变化时,触发执行所有回调函数。 如果都不支持,则利用setTimeout设置延时为0。 最后是queueNextTick函数。因为nextTick是一个即时函数,所以queueNextTick函数是返回的函数,接受用户传入的参数,用来往callbacks里存入回调函数。 上图是整个执行流程,关键在于timeFunc(),该函数起到延迟执行的作用。从上面的介绍,可以得知timeFunc()一共有三种实现方式。 Promise MutationObserver setTimeout 其中Promise和setTimeout很好理解,是一个异步任务,会在同步任务以及更新DOM的异步任务之后回调具体函数。 下面着重介绍一下MutationObserver。 MutationObserver是HTML5中的新API,是个用来监视DOM变动的接口。他能监听一个DOM对象上发生的子节点删除、属性修改、文本内容修改等等。 调用过程很简单,但是有点不太寻常:你需要先给他绑回调:
1 |
var mo = new MutationObserver(callback) |
通过给MutationObserver的构造函数传入一个回调,能得到一个MutationObserver实例,这个回调就会在MutationObserver实例监听到变动时触发。 这个时候你只是给MutationObserver实例绑定好了回调,他具体监听哪个DOM、监听节点删除还是监听属性修改,还没有设置。而调用他的observer方法就可以完成这一步:
1 2 3 4 |
var domTarget = 你想要监听的dom节点 mo.observe(domTarget, { characterData: true //说明监听文本内容的修改。 }) |
[…]
View DetailsDQL、DML、DDL、DCL的概念与区别
SQL(Structure Query Language)语言是数据库的核心语言。 SQL的发展是从1974年开始的,其发展过程如下: 1974年—--由Boyce和Chamberlin提出,当时称SEQUEL。 1976年—--IBM公司的Sanjase研究所在研制RDBMS SYSTEM R 时改为SQL。 1979年—--ORACLE公司发表第一个基于SQL的商业化RDBMS产品。 1982年—--IBM公司出版第一个RDBMS语言SQL/DS。 1985年—--IBM公司出版第一个RDBMS语言DB2。 1986年—--美国国家标准化组织ANSI宣布SQL作为数据库工业标准。 SQL是一个标准的数据库语言,是面向集合的描述性非过程化语言。 它功能强,效率高,简单易学易维护(迄今为止,我还没见过比它还好 学的语言)。然而SQL语言由于以上优点,同时也出现了这样一个问题: 它是非过程性语言,即大多数语句都是独立执行的,与上下文无关,而 绝大部分应用都是一个完整的过程,显然用SQL完全实现这些功能是很困 难的。所以大多数数据库公司为了解决此问题,作了如下两方面的工作: (1)扩充SQL,在SQL中引入过程性结构;(2)把SQL嵌入到高级语言中, 以便一起完成一个完整的应用。 二. SQL语言的分类 SQL语言共分为四大类:数据查询语言DQL,数据操纵语言DML,数据定义语言DDL,数据控制语言DCL。 1. 数据查询语言DQL 数据查询语言DQL基本结构是由SELECT子句,FROM子句,WHERE 子句组成的查询块: SELECT <字段名表> FROM <表或视图名> WHERE <查询条件> 2 .数据操纵语言DML 数据操纵语言DML主要有三种形式: 1) 插入:INSERT 2) 更新:UPDATE 3) 删除:DELETE 3. 数据定义语言DDL 数据定义语言DDL用来创建数据库中的各种对象—--表、视图、 索引、同义词、聚簇等如: CREATE TABLE/VIEW/INDEX/SYN/CLUSTER | | | | | 表 视图 索引 同义词 簇 DDL操作是隐性提交的!不能rollback 4. 数据控制语言DCL 数据控制语言DCL用来授予或回收访问数据库的某种特权,并控制 数据库操纵事务发生的时间及效果,对数据库实行监视等。如: 1) GRANT:授权。 2) ROLLBACK [WORK] TO [SAVEPOINT]:回退到某一点。 回滚—ROLLBACK 回滚命令使数据库状态回到上次最后提交的状态。其格式为: SQL>ROLLBACK; 3) COMMIT [WORK]:提交。 在数据库的插入、删除和修改操作时,只有当事务在提交到数据 库时才算完成。在事务提交前,只有操作数据库的这个人才能有权看 […]
View DetailsMySQL锁总结
锁是计算机协调多个进程或线程并发访问某一资源的机制。锁保证数据并发访问的一致性、有效性;锁冲突也是影响数据库并发访问性能的一个重要因素。锁是Mysql在服务器层和存储引擎层的的并发控制。 加锁是消耗资源的,锁的各种操作,包括获得锁、检测锁是否是否已解除、释放锁等。 锁机制 共享锁与排他锁 共享锁(读锁):其他事务可以读,但不能写。 排他锁(写锁) :其他事务不能读取,也不能写。 粒度锁 MySQL 不同的存储引擎支持不同的锁机制,所有的存储引擎都以自己的方式显现了锁机制,服务器层完全不了解存储引擎中的锁实现: MyISAM 和 MEMORY 存储引擎采用的是表级锁(table-level locking) BDB 存储引擎采用的是页面锁(page-level locking),但也支持表级锁 InnoDB 存储引擎既支持行级锁(row-level locking),也支持表级锁,但默认情况下是采用行级锁。 默认情况下,表锁和行锁都是自动获得的, 不需要额外的命令。 但是在有的情况下, 用户需要明确地进行锁表或者进行事务的控制, 以便确保整个事务的完整性,这样就需要使用事务控制和锁定语句来完成。 不同粒度锁的比较: 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。 这些存储引擎通过总是一次性同时获取所有需要的锁以及总是按相同的顺序获取表锁来避免死锁。 表级锁更适合于以查询为主,并发用户少,只有少量按索引条件更新数据的应用,如Web 应用 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。 最大程度的支持并发,同时也带来了最大的锁开销。 在 InnoDB 中,除单个 SQL 组成的事务外, 锁是逐步获得的,这就决定了在 InnoDB 中发生死锁是可能的。 行级锁只在存储引擎层实现,而Mysql服务器层没有实现。 行级锁更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理(OLTP)系统 页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。 MyISAM 表锁 MyISAM表级锁模式: 表共享读锁 (Table Read Lock):不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求; 表独占写锁 (Table Write Lock):会阻塞其他用户对同一表的读和写操作; MyISAM 表的读操作与写操作之间,以及写操作之间是串行的。当一个线程获得对一个表的写锁后, 只有持有锁的线程可以对表进行更新操作。 其他线程的读、 写操作都会等待,直到锁被释放为止。 默认情况下,写锁比读锁具有更高的优先级:当一个锁释放时,这个锁会优先给写锁队列中等候的获取锁请求,然后再给读锁队列中等候的获取锁请求。 (This ensures that updates to a table are not “starved” even when there is heavy SELECT activity for the table. However, if there are many updates for […]
View Details什么是分布式锁
概述 为了防止分布式系统中的多个进程之间相互干扰,我们需要一种分布式协调技术来对这些进程进行调度。而这个分布式协调技术的核心就是来实现这个分布式锁。 为什么要使用分布式锁 成员变量 A 存在 JVM1、JVM2、JVM3 三个 JVM 内存中 成员变量 A 同时都会在 JVM 分配一块内存,三个请求发过来同时对这个变量操作,显然结果是不对的 不是同时发过来,三个请求分别操作三个不同 JVM 内存区域的数据,变量 A 之间不存在共享,也不具有可见性,处理的结果也是不对的 注:该成员变量 A 是一个有状态的对象 如果我们业务中确实存在这个场景的话,我们就需要一种方法解决这个问题,这就是分布式锁要解决的问题 分布式锁应该具备哪些条件 在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行 高可用的获取锁与释放锁 高性能的获取锁与释放锁 具备可重入特性(可理解为重新进入,由多于一个任务并发使用,而不必担心数据错误) 具备锁失效机制,防止死锁 具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败 分布式锁的实现有哪些 Memcached:利用 Memcached 的 add 命令。此命令是原子性操作,只有在 key 不存在的情况下,才能 add 成功,也就意味着线程得到了锁。 Redis:和 Memcached 的方式类似,利用 Redis 的 setnx 命令。此命令同样是原子性操作,只有在 key 不存在的情况下,才能 set 成功。 Zookeeper:利用 Zookeeper 的顺序临时节点,来实现分布式锁和等待队列。Zookeeper 设计的初衷,就是为了实现分布式锁服务的。 Chubby:Google 公司实现的粗粒度分布式锁服务,底层利用了 Paxos 一致性算法。 通过 Redis 分布式锁的实现理解基本概念 分布式锁实现的三个核心要素: 加锁 最简单的方法是使用 setnx 命令。key 是锁的唯一标识,按业务来决定命名。比如想要给一种商品的秒杀活动加锁,可以给 key 命名为 “lock_sale_商品ID” 。而 value 设置成什么呢?我们可以姑且设置成 1。加锁的伪代码如下:
1 2 |
setnx(lock_sale_商品ID,1) |
当一个线程执行 setnx 返回 1,说明 key 原本不存在,该线程成功得到了锁;当一个线程执行 setnx 返回 0,说明 key […]
View Details两个月新增 80 万行代码,Linux 内核维护为什么不会崩?
8 月初,当 Linux 5.8 RC 版本开放测试时,大多数的新闻都聚焦于它的大小,称其为“史上最大的内核版本”。正如 Linus Torvalds 本人指出的那样,“尽管没有任何一件事情能脱颖而出……但 5.8 似乎是我们有史以来最大的发行版之一。” 确实,刚刚发布的 Linux 内核 5.8 RC 具有超过 14,000 个 commit,约 80 万行新代码以及大约 100 名新贡献者。要知道,距离 5.7 正式版发布才仅仅过去了约 2 个月的时间。Linux 内核维护者 Steven Rostedt 认为,5.8 之所以变得如此之大,很有可能是因为 COVID-19 疫情让很多人难以出门旅行,所有人都因此能够在这期间完成比平时更多的工作。 Rostedt 表示,从一个经验丰富的 Linux 内核贡献者和维护者的角度来看,5.8 RC 发行版特别令人震惊的并不是它的大小,而是它的空前规模对于那些正在维护它的人来说却没有造成困扰,“我认为这是因为 Linux 具有比世界上任何软件项目都好的工作流程。” 拥有最佳的工作流程意味着什么?对 Rostedt 而言,这归结为 Linux 内核开发人员随着时间的推移建立的一系列基本规则,以使他们能够持续不断地大规模、可靠地发展 Linux 内核项目。Rostedt 站在一个 Linux 内核资深维护者的角度,为我们分享了庞大的 Linux 内核项目 30 年来是如何有条不紊地运转的。 第一个关键因素是 Git 首先让我们从 Linux 项目的历史来看。在该项目的早期(1991-2002年),人们只能直接将补丁发送给 Linus Torvalds。准确地说,Linus 从项目的子维护者那里获取补丁,而这些子维护者从其他代码贡献者那里获取补丁。随着 Linux 内核变得越来越大,代码越来越复杂,很快他们就发现,一切都变得很难扩展和跟踪,并且项目将始终面临合并不兼容代码的风险。 这导致 Linus 开始探索包括 BitKeeper 在内的各种版本管理工具。BitKeeper 是一种最早的分布式版本管理的方法,其他的版本管理系统通常使用签出/修改/签入协议,而 BitKeeper 则向所有人提供整个仓库的副本,并允许开发人员将其变更发送出去以进行合并。Linux 在 2002 年开始短暂地采用了 BitKeeper,但是由于其本身是一个专有软件,被认为不符合社区对开源工作的信念,于是该工具在 2005 年停止使用。为了寻找替代品,Linus 消失了一段时间,并带着 git 回来了,后者成为了更强大的分布式版本管理系统,并且是管理流程的第一个重要实例化。Git 的出现使 Linux 开发在今天依然运转良好。 Rostedt 为我们列出了 Linux […]
View Detailsgit blame 查看文件修记录
打个比方 我们有个文件夹project project里面有一堆文件夹 aaa,bbb,ccc,我们需要找到aaa文件夹的其中某个文件file.txt的修改记录 我们可以再project上开启git操作 然后 输入 git blame -L 11,12 aaa/file.txt -L 11,12 含义是该文件的第11行和12行的修改记录 from:https://www.cnblogs.com/1179929172-zh/p/12938344.html
View Details