其他知识 本篇文章包含了 vue源码知识 和 其他特色功能展示
vue源码部分核心解析 由于是单独提取需要上下相互学习
初始化data new Vue的时候调用会调用_init方法,定义 $set、 $get 、$delete、$watch 等方法,调用$mount进行页面的挂载,挂载的时候主要是通过mountComponent方法,定义updateComponent更新函数,执行render生成虚拟DOM,_update将虚拟DOM生成真实DOM结构,并且渲染到页面中 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 new Vue ({ el :"#app" , data :{ }, data ( ){ return { msg :"hello" } } watch :{} }) function vue (options ){ this ._init (options) } initMixin (vue)lifeyvleMinin (vue)renderMin (Vue )initGlobApi (Vue )export default vue
初始化init 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 export function initMixin (Vue ){ Vue .prototype ._init = function (options ){ let vm = this vm.$options = merfeOptions (Vue .options ,options) callHook (vm,'beforeCreates' ) vm.$options = mergerOption (Vue .options ) initState (vm) callHook (vm,;'creates' ) if (vm.$options .el ){ vm.$mount(vm.$options .el ) } } Vue .prototype .$mount = function (el ){ let vm = this el = document .querySelector (el) let options = vm.$options if (!options.render ){ let template= options.template if (!template && el){ el=el.outerHTML } } } }
初始化状态 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 export function initState (vm ){ let ops = vm.$options if (opts.props ){ initProps (vm) } if (opts.data ){ initData (vm) } if (opts.watch ){ initWatch (vm) } if (opts.compoted ){ initCompoted (vm) } if (opts.methods ){ initMethods (vm) } } function initProps (vm ){}function initData (vm ){ let data = vm.$options .data data = typeof data == 'function' ? data.call (vm):data observer (data) } function initWatch (vm ){}function initCompoted (vm ){}function initMethods (vm ){}
vue中的data data分为两种形式 分为函数和对象 在远吗中通过typeof 判断是函数还是对象 将返回的结果传递给了observer函数进行类型判断取值 是把传入值所有的属性(包括嵌套属性)递归 进行响应式defineReactive,又通过 object.definProperty(缺点: 对象中的一个属性进行劫持) 定义get set方法 难点是对数组的劫持 首先对数组进行遍历 并获取数组原来的方法 并继承 然后对数组中所有能改变数组自身的方法,如 push、pop 等这些方法进行重写。重写后的方法会先执行它们本身原有的逻辑,并对能增加数组长度的 3 个方法 push、unshift、splice 方法做了判断,获取到插入的值,然后把新添加的值变成一个响应式对象
1 2 3 let data = vm.$options .data data = typeof data === 'function' ?data.call (vm):data observer (data)
data 中处理对象数组 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 export function observe (data ){ if (typeod data != 'object' || data == null ){ return data } return new Observer (data) } class Observer { constructor (value ){ Object .defineProperty (value,"__ob__" ,{ enumerable :false , value :this }) if (Array .isArray (value)){ value.__proto__ == ArrayMethods this .observeArray (value) }else { this .walk (value) } } walk (data ){ let keys = object.keys (data) for (let i=0 ;i<keys.length ;i++){ let key =keys[i] let value = data[i] defineReactive (data,key,value) } } observeArray (value ){ for (let let i=0 ;i<value.length ;i++){ observer (value[i]) } } } function definReactive (data,key,value ){ observer (value) object.definProperty (data,key,{ get ( ){ return data[key] }, set (newValue ){ if (newValue == value) return ; observer (value) value = newValue } }) }
data 中处理数组 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 let oldArrayProtoMethods = Array .prototype export let ArrayMethods = Object .create (oldArrayProtoMethods)let methods = [ 'push' , 'pop' , 'unshift' , 'shift' , 'splice' ] methods.forEach ((item )=> { ArrayMethods [item] = function (...args ){ let result = oldArrayProtoMethods[item].apply (this ,args) let inserted Switch (item){ case 'push' : case 'unshift' : inserted = args break ; case 'splice' : inserted = args.splice () } let ob = this .__ob__ if (inserted){ ob.observeArray (inserted) } return result } })
vue模板编译 创建了一个baseCompile 函数 通过document.querySelector获取到html 通过正则解析 将template模板里面属性 指定等等 解析成ast树 并进行遍历 节点进行标记 生成render函数 render函数将模版内容生成对应的vnode 通过diff算法中的patch 等到渲染视图中的vnode 新旧节点的对比 创建真实的dom节点插入视图中完成渲染 */
获取html转为ast语法 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 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 /vue首次渲染 =>> 先初始化数据 =》 讲模板进行编译 export function initMixin (Vue ){ Vue .prototype ._init = function (options ){ let vm = this vm.$options = options initState (vm) if (vm.$options .el ){ vm.$mount(vm.$options .el ) } } Vue .prototype .$mount = function (el ){ let vm = this el = document .querySelector (el) let options = vm.$options if (!options.render ){ let template= options.template if (!template && el){ el=el.outerHTML let ast = compileToFunction (el) options.render = render } } mounetComponent (vm,el ){ } } const ncname = `[a-zA-Z_][\\-\\..-9_a-zA-Z]*` ;const qunameCapture = `((?:${ncname} \\:)?${ncnme} )` ;const startTagOpen = new RegEp (`^<${quameCapture} ` );const endTag = new RegExp (`^<\\/${qnameCapture} [^>]*>` );const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s "'=<>` ]+)))?/ ;const startTagClose = /^\s*(/ \?)>/;const defaultTagRE = /\{\((?:.|\r?\n)+?)\}/g function createAstElement (tag,attrs ){ return { tagName, attrs, children :[] type :1 , parent :null } } let root;let creatParent;let stack = []function start (tag,attrs ){ let element = createAstElement (tag,attrs) if (!root){ root = element } creatParent = element stack.push (element) } function charts (text ){ text= text.replace (/a/g ,'' ) if (text){ createParent.children .push ({type :3 ,text}) } } function end ( ){ let element = stack.pop () creatParent = stack[stack.length -1 ] if (creatParent){ element.parent .creaateParent .tag creaateParent.children .push (element) } } function parseHTML (html ){ while (html){ let textEnd = html.indexOf ('<' ) if (textEnd === 0 ){ const startTagMatch = parseStartTag () if (startTagMatch){ start (startTagMatch.tagName ,startTagMatch.attrs ) continue ; } let endTagMatch = html.match (endTag) if (endTagMatch){ advance (endTagMatch[0 ].length ) end (endTagMatch[1 ]) continue ; } } let text if (textEnd>0 ){ text = html.substring (0 ,textEnd) } if (text){ advance (text.length ) charts (text) } } function parseStartTag ( ){ const start = html.match (startTagOpen) if (start){ let match = { tagName :start[1 ], attrs :[], } advance (stsrt[0 ].length ) let attr let end while ((!end = html.match (startTagClose)) && (attrs=html.match (attribute))){ march.attrs .push ({name :attr[1 ],vale :attrs[3 ]||attrs[4 ]||attrs[3 ]}) advance (attr[0 ]) brack; } if (end) { advance (end[0 ].length ) } } } function advance (n ){ html = html.substring (n) } return root } export function compileToFunction (el ){ let ast= parseHTML (el) let code =generate (ast) }
ast语法变为render函数 配合上面使用
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 const defaultTagRE = /\{((?:.|\r?\n)+?)\}\}/g function genPorps (attrs ){ let str ='' ; for (let i =0 ;i<attrs.length ;i++){ let attr = attr[i]; if (attr.name == 'style' ){ let obj = {} attr.vale .split (';' ).forEach (item => { let [key,val] =item.split (':' ) obj[key] = val }) attr.value = obj } str+= `${attr.name} :${JSON .stringify(attr.value)} ` } return `{${str.slice(0 ,-1 )} }` } function genChildren (el ){ let children = el.children if (children){ return children.map (child =>gen (child)).join (',' ) } } function gen (node ){ if (node.type === 1 ){ return generate (node) }else { let text = node.text if (!defaultTagRE.test (text)){ return `_v(${JSON .stringify(text)} )` } let tokens = [] let lastindex = defaultTagRE.lasteIndex = 0 let match while (match = defaultTagRE.exec (text)){ let index = match.index if (index>lastindex){ tokens.push (JSON .stringify (text.slice (lastindex,index))) } tokens.push (`_s(${match[1 ].trim()} )` ) lastindex = index+ match[0 ].length if (lastindex<text.length ){ tokens.push (JSON .sttingify (text.slice (lastindex))) } return `_v(${tokens.join('+' )} )` } } } export function generate (el ){ let children = genChildren (el) let code = `_c(${el.tag} ,${el.attrs.length?`${genPorps(el.attrs)} ` :'null' } ,${children?`${children} ` :'null' } )` return code }
render字符串从函数到dom 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 let ast = parseHTML (el)let code = generate (ast)let render = new Function (`with(this){return ${code} }` ) return render export function mountComponent (vm,el ){ callHook (vm,'beforMounted' ) vm._updata (vm._render ()) callHook (vm,'mounted' ) } export function lifeyvleMinin (vue ){ Vue .prototype ._updata = function (vnode ){ let vm = this vm.$el =patch (vm.$el ,vnode) } } export function callHook (vm,hook ){ const handlers = vm.$options [hook] if (handlers){ for (let i=0 ;i<headers.length ;i++){ handlers[i].call (this ) } } } export function renderMin (Vue ){ Vue .prototype ._c = function ( ){ return createElement (...arguments ) } Vue .prototype ._v = function (text ){ return createText (text) } Vue .prototype ._s = function (value ){ return val == null ? "" :(typeof val == 'object' )?JSON .stringify (val):val } Vue .prototype ._render = function ( ){ let vm =this let render = vm.$options .render let vnode = = render.call (this ) } } function createElement (tag,data = {},...children ){ return vnode (tag,data,data.key ,children) } function createText (text ){ return vnode (undefined ,undefined ,undefined ,undefined ,text) } function vnode (tag,data,key,children,text ){ return { tag, data, key, children, text } } export function patch (oldVnode,vnode ){ let el = createEl (vnode) let parentEl = oldVnode.parentNode parentEl.insertBefore (el,oldVnode.nextsibling ) parentEl.removeChild (oldVnode) return el } function createEl (vnode ){ let {tag,children,key,data,text} = vnode if (typeof tag == 'string' ){ vnode.el = document .createElement (tag) if (children.length >0 ){ children.forEach (child => { vnode.el .appendChild (createEl (child)) }) } }else { vnode.el = document .createTextNode (text) } return vnode.el }
vue源码后续待更新
diff算法 diff 算法是一种通过同层的树节点进行比较的高效算法,比较只会在同层级进行, 不会跨层级比较 在diff比较的过程中,循环从两边向中间比较 原理:当数据发生改变时,set方法会调用Dep.notify通知所有订阅者Watcher,订阅者就会调用patch给真实的DOM打补丁,更新相应的视图 通过isSameVnode进行判断,相同则调用patchVnode方法 patchVnode做了以下操作: 找到对应的真实dom,称为el 如果都有都有文本节点且不相等,将el文本节点设置为Vnode的文本节点 如果oldVnode有子节点而VNode没有,则删除el子节点 如果oldVnode没有子节点而VNode有,则将VNode的子节点真实化后添加到el 如果两者都有子节点,则执行updateChildren函数比较子节点 updateChildren主要做了以下操作: 设置新旧VNode的头尾指针 新旧头尾指针进行比较,循环向中间靠拢,根据情况调用patchVnode进行patch重复流程、调用createElem创建一个新节点,从哈希表寻找 key一致的VNode 节点再分情况操作
vue中的双向数据绑定 new Vue()首先执行初始化,在Observe函数中对data执行响应化处理,同时对模板执行编译,找到其中动态绑定的数据,从data中获取并初始化视图,同时定义⼀个更新函数和Watcher,将来对应数据变化时Watcher会调用更新函数 因为datra中的某个属性会多次出现 定义了一个dep 管理watcher 将来data中数据⼀旦发生变化,会首先找到对应的Dep,通知所有Watcher执行更新函数
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 class Vue { constructor (options ) { this .$options = options; this .$data = options.data ; observe (this .$data ); proxy (this ); new Compile (options.el , this ); } } function observe (obj ) { if (typeof obj !== "object" || obj == null ) { return ; } new Observer (obj); } class Observer { constructor (value ) { this .value = value; this .walk (value); } walk (obj ) { Object .keys (obj).forEach ((key ) => { defineReactive (obj, key, obj[key]); }); } } class Compile { constructor (el, vm ) { this .$vm = vm; this .$el = document .querySelector (el); if (this .$el ) { this .compile (this .$el ); } } compile (el ) { const childNodes = el.childNodes ; Array .from (childNodes).forEach ((node ) => { if (this .isElement (node)) { console .log ("编译元素" + node.nodeName ); } else if (this .isInterpolation (node)) { console .log ("编译插值⽂本" + node.textContent ); } if (node.childNodes && node.childNodes .length > 0 ) { this .compile (node); } }); } isElement (node ) { return node.nodeType == 1 ; } isInterpolation (node ) { return node.nodeType == 3 && /\{\{(.*)\}\}/ .test (node.textContent ); } } class Watcher { constructor (vm, key, updater ) { this .vm = vm this .key = key this .updaterFn = updater Dep .target = this vm[key] Dep .target = null } update ( ) { this .updaterFn .call (this .vm , this .vm [this .key ]) } } class Dep { constructor ( ) { this .deps = []; } addDep (dep ) { this .deps .push (dep); } notify ( ) { this .deps .forEach ((dep ) => dep.update ()); } } class Watcher { constructor (vm, key, updateFn ) { Dep .target = this ; this .vm [this .key ]; Dep .target = null ; } } function defineReactive (obj, key, val ) { this .observe (val); const dep = new Dep (); Object .defineProperty (obj, key, { get ( ) { Dep .target && dep.addDep (Dep .target ); return val; }, set (newVal ) { if (newVal === val) return ; dep.notify (); }, }); }
js运行机制 javaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事 avaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。 事件循环是js实现异步的一种方法,也是js的执行机制。 javascript的执行和运行 执行和运行有很大的区别,javascript在不同的环境下,比如node,浏览器,Ringo等等,执行方式是不同的。而运行大多指javascript解析引擎,是统一的。 js 轮询机制 所有任务都在主线程上执行,形成一个执行栈。 2、主线程发现有异步任务,如果是微任务就把他放到微任务的消息队列里,如果是宏任务就把他 放到宏任务的消息队列里。 3、执行栈所有同步任务执行完毕。 4、执行微任务队列,之后再执行宏任务队列。
浏览器跨域原理 核心思想:浏览器的script、img、iframe标签是不受同源策略限制的,所以通过script标签引入一个js文件,这个js文件载入成功后会执行我们在url参数中指定的callback函数,并把我们需要的json数据作为参数传入。在服务器端,当req.params参数中带有callback属性时,则把数据作为callback的参数执行,并拼接成一个字符串后返回。 优点:兼容性好,简单易用,支持浏览器与服务器双向通信 缺点:只支持GET请求,且只支持跨域HTTP请求这种情况(不支持HTTPS) 在js中,直接用XMLHttpRequest请求不同域上的数据时,是不可以的。但在页面上引入不同域上的js脚本文件却是可以的,jsonp正是利用这个特性来实现的。
CORS请求原理 CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
核心思想:在服务器端通过检查请求头部的origin,从而决定请求应该成功还是失败 修改document.domain来跨子域 浏览器都有一个同源策略,限制其一:不能通过ajax的方法去请求不同源中的文档; 其二:浏览器中不同域的框架间不能进行js的交互操作。 不同的框架之间(父子或同辈),能够获取到彼此的window对象的,但不能使用获取到的window对象的属性和方法(html5中的postMessage方法是一个例外,还有些浏览器如ie6也可以使用top、parent等少数几个属性)。
实用方法 复制文本到切板 1 2 const copy = (text )=>navigator.clipboard .writeText (text) copy ("你好" )
获取某个日期相当于当年的第几天 1 2 const time = (date )=>Math .floor ((date - new Date (date.getFullYear (),0 ,0 ))/100. /60 /60 /24 ) console .log (time (new Date (2023 ,12 ,30 ))
解析url参数 1 2 3 4 5 6 7 const parseurl = (url )=>{ q={} url.replace (/([^?&=]+)=([^&+])/g ,(_,k,v )=> (q[k]=v)) return q } console .log (parseurl ("http://a.com/?a=1&b=2" )) console .log (parseurl ("a=1&b=2" ));
生成随机颜色 1 2 3 const color = ( ) => '#' +Math .floor (Math .random () * 0xffffff ).toString (16 ).padEnd (6 ,'0' ); console .log (color ());
去掉字符串中的元素标记 1 2 3 4 5 6 7 8 9 const removetap = (tag ) => new DOMParser ().parseFromString (tag,'text/html' ).body .textContent || ' ' ; console .log (removetap ("<div>55555555</div>" )) `` ` #### 获取对象的基本数据类型 ` `` js const getType = (tag ) => { return Object .prototype .toString .call (tag).slice (8 ,-1 ) } console .log (getType (()=> {}));
反转字符串 1 2 3 4 const reverString = (str ) =>{ return str.split ('' ).reverse ().join ("" ) } console .log (reverString ("efesgfrgrd" ))
获取两个数字之间的随机数 1 2 3 4 const random =(min,max )=>{ return Math .floor (Math .random () * (max-min+1 )+min) } console .log (random (5 ,8 ))
检查日期是否合法 1 2 3 4 const ifTimeDate = (...time ) =>{ return !Number .isNaN (new Date (...time).valueOf ()) } console .log (ifTimeDate (" 1999 03:16:18" ));
检查日期是否在两个日期之间 1 2 3 4 5 6 7 const ifData = (data,min,max )=>{ return +max>= +data && data >= +min } const data = new Date ("2010-12-8" )const min = new Date ("2003-05-6" )const max = new Date ("2008-05-16" )console .log (ifData (data,min,max))
判断此页面标签是否激活 1 2 const isTabView =( )=>!document .hidden isTabView ()
判断一段字符串是否是url 1 2 3 4 const isUrl = (url ) =>{ return /^(http|https):\/\/([\w.]+\/?)\S*/ .test (url) } console .log (isUrl ("http://baidu.com" ))
打乱数组顺序 1 2 3 let arr = [1 ,2 ,3 ,4 ,5 ]arr = arr.sort (()=> 0.5 - Math .random ()) console .log (arr);
去除数字之外的所有字符 1 2 3 const str = "5415sfs5f15sdf48sdf5ds6666666668ss5ds88" const number = str.replace (/\D/g ,'' )console .log (number);
删除数组重复项 1 2 3 let arr = [1 ,2 ,2 ,3 ,5 ,5 ,8 ]const arr2 = (arr )=>[...new Set (arr)]console .log (arr2 (arr));
过滤数组为false得值 1 2 3 let arr = ['undefined' ,0 ,2 ,false ]const arr2 = arr.filter (Boolean )console .log (arr2);
滚动到页面顶部 1 2 3 const scrllToTop = ( ) =>{ window .scrollTo ({top :0 ,left :0 ,behavior :"smooth" }) }
滚动到页面底部 1 2 3 const scrllToTop = ( ) =>{ window .scrollTo ({top :document .documentElement .offsetHeight ,left :0 ,behavior :"smooth" }) }
调用摄像头 1 2 3 4 5 document .getElementById ("btn_start" ).addEventListener ('click' ,function ( ){ navigator.mediaDevices .getUserMedia ({video :true }).then (stream => { document .getElementById ("video" ).srcObject = stream }) })
y隐藏手机号中间四位 1 2 3 4 const tel = '15237903806' const reg = /^(\d{3})\d{4}(\d{4})$/ const str = tel.replace (reg,"$1****$2" )console .log (str)
禁止浏览器缩放 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 window .addEventListener ( "mousewheel" , function (event ) { if (event.ctrlKey === true || event.metaKey ) { event.preventDefault (); } }, { passive : false } ); window .addEventListener ( "DOMMouseScroll" , function (event ) { if (event.ctrlKey === true || event.metaKey ) { event.preventDefault (); } }, { passive : false } );
vue自定义拖拽指令 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 Vue .directive ('drag' , (el ) => { const oDiv = el const minTop = oDiv.getAttribute ('drag-min-top' ) const ifMoveSizeArea = 20 oDiv.onmousedown = e => { let target = oDiv while (window .getComputedStyle (target).position !== 'absolute' && target !== document .body ) { target = target.parentElement } document .onselectstart = () => { return false } if (!target.getAttribute ('init_x' )) { target.setAttribute ('init_x' , target.offsetLeft ) target.setAttribute ('init_y' , target.offsetTop ) } const initX = parseInt (target.getAttribute ('init_x' )) const initY = parseInt (target.getAttribute ('init_y' )) const disX = e.clientX - target.offsetLeft const disY = e.clientY - target.offsetTop document .onmousemove = e => { const l = e.clientX - disX const t = e.clientY - disY target.style .left = l + 'px' target.style .top = (t < minTop ? minTop : t) + 'px' if (Math .abs (l - initX) > ifMoveSizeArea || Math .abs (t - initY) > ifMoveSizeArea) { target.setAttribute ('dragged' , '' ) } else { target.removeAttribute ('dragged' ) } } document .onmouseup = e => { document .onmousemove = null document .onmouseup = null document .onselectstart = null } return false } })
调用摄像头 1 2 3 4 5 document .getElementById ("btn_start" ).addEventListener ('click' ,function ( ){ navigator.mediaDevices .getUserMedia ({video :true }).then (stream => { document .getElementById ("video" ).srcObject = stream }) })