您好!欢迎来到爱源码

爱源码

热门搜索: 抖音快手短视频下载   

JS高级技能 {源码分享}

  • 时间:2022-08-12 01:05 编辑: 来源: 阅读:277
  • 扫一扫,手机访问
摘要:JS高级技能 {源码分享}
1.安全类型检测这个问题就是如何安全地检测一个变量的类型,比如一个变量是否可以是数组。 通常的做法是使用instanceof,如下面的代码所示:let data = [1,2,3];console.log(数组的数据实例);//true,但上述判断在正条件下会失效——也就是在iframe中判断一个父窗口的变量时。 写一个演示来验证一下,如下面主页面的main.html所示:window.global = {arraydata: [1,2,3]} console . log(" array的父数组数据安装:"+(window.global.array的数组数据实例));在iframe.html,判断父窗口的变量类型:console.log("数组的iframe window.parent.global.arraydata实例:"+(数组的window . parent . global . array data实例));iframe中用window.parent获取父窗口的全局window对象,这个对象无论跨域与否都没有问题,然后获取父窗口的变量,再用instanceof进行判断。 最终运行结果如下:可以看到父窗口的判断是正确的,而子窗口的判断是错误的,所以一个变量明明是数组,却不是数组。为什么?由于这是一个只存在于父子窗口中的问题,所以尝试将Array改为父窗口的数组,即window.parent.Array,如下图所示:这次返回true,然后改变其他判断,如上图所示。最后可以知道,根本原因是上图中的最后一个判断:数组!== window.parent.Array它们分别是两个函数。一个由父窗口定义,另一个由子窗口定义。不同内存地址和不同内存地址的对象方程判断不成立,而window . parent . array data . constructor返回父窗口的数组,比较时在子窗口。两个数组不相等,所以判断无效。 那我该怎么办?因为不能用Object的内存地址来判断,所以可以用string来判断。因为字符串是基本类型,所以字符串比较只需要每个字符都相等。 ES5提供了这样一个方法,Object.prototype.toString我们先来试试不同变量的返回值。我们可以看到,如果一个数组返回“[object Array]”,ES5规定了这个函数:即这个函数的返回值以“[object]”开头,后面是变量类型的名称和右括号。 因此,由于它是一个标准的语法规范,这个函数可以用来安全地确定一个变量是否是一个数组。 可以这样写:object . prototype . tostring . call([1,2,3]) = = "[objectarray]"注意要用call,不要直接调用。call的第一个参数是上下文执行上下文,数组作为执行上下文传递给它。 一个有趣的现象是,ES6的class也返回function:因此,我们可以知道class也是function的原型,也就是说class和function本质上是一样的,只是写法不同。 是不是意味着不能再通过instanceof来判断变量类型了?不,当你需要检测父页面的变量类型时,你必须使用这个方法。这个页面的变量还是可以用instanceof或者constructor的方法来判断的,只要你能保证这个变量不会跨页面。 由于大多数人很少写iframe代码,所以没必要采取麻烦的方式,用简单的方式就可以了。 2.惯性加载函数有时需要在代码中进行少量的兼容性判断,或者少量UA的判断,如下面的代码所示://UA的类型getuatype:function(){ let UA = window . navigator . user agent;if(ua . match(/renren/I)){ return 0;} else if(ua . match(/micro messenger/I)){ return 1;} else if(ua . match(/Weibo/I)){ return 2;} return-1;}这个函数用来判断用户在哪个环境下打开网页,从而统计哪个渠道的效果更好。 这类判断有个特点,就是它的结果是死的,无论执行多少次判断都会返回相同的结果,比如用户的UA在这个网页上是不能更改的(调试设置除外)。 所以为了优化,就有了懒人功能。上面的代码可以改成://UA type getua type:function(){ let UA = window . navigator . user agent;if(ua . match(/renren/I)){ pagedata . getua type =()= > 0;返回0;} else if(ua . match(/micro messenger/I)){ pagedata . getua type =()= > 1;返回1;} else if(ua . match(/Weibo/I)){ pagedata . getua type =()= > 2;return 2;} return-1;}每次判断后,重新赋值getUAType函数成为一个新的函数,这个函数直接返回某个变量,这样以后的每次获取都不会用于判断。这是惰性函数的使用。 你说这样几个判断能优化多少时间?这样的一点点时间和用户几乎没有区别。 确实如此,但作为一个有追求的码农,我还是会尽力优化自己的代码,而不仅仅是完成需求。 而当你的优化积累到一定量,就会发生质的变化。 大学的时候C++的老师给我举了个例子,说有个系统很慢。她的一个优化就是把小数的双精度改成单精度,最后快了很多。 其实我们对上面的例子有一个更简单的实现,就是做一个变量保存就行了:让ua = window . navigator . user agent;设ua type = ua . match(/人人/i)?0 : ua.match(/MicroMessenger/i)?1 : ua.match(/weibo/i)?2 : -1;连函数都没写。缺点是即使不使用变量UAType,判断也会执行一次,但是我们认为这个变量被使用的概率还是很高的。 再举一个有用的例子,因为Safari的无痕浏览会禁止本地存储,所以需要做一个兼容性判断:Data.localStorageEnabled = true// Safari的无痕浏览会禁用本地存储try { window . local storage . trysetdata = 1;} catch(e){ data . localstorageenabled = false;} setLocalData: function(key,value){ if(data . localstorageenabled){ window . local storage[key]= value;} else { util.setCookie("_L_" + key,value,1000);}}设置本地数据时,需要判断是否支持localStorage。如果是,请使用本地存储,否则,请使用cookie。 可以使用懒人函数进行修改:setlocaldata: function (key,value){ if(data . localstorageenabled){ util . setlocaldata = function(key,value){ return window . local storage[key];} } else { util . setlocaldata = function(key,value){ return util . get cookie(" _ L _ "+key);} } return util.setLocalData(key,value);}这里if/else判断可以减一次,但是不是特别实惠。毕竟为了减少一个判断,引入了一个惰性函数的概念,你可以权衡一下这个引入是否值得。如果有三五个判断,应该会更好。 3.函数绑定有时需要将一个函数作为参数传递给另一个函数来执行。这时函数的执行上下文往往会发生变化,如下面的代码所示:classdrawtool { constructor(){ this . points =[];} handle mouse click(event){ this . points . push(event . latlng);} init(){ $ map . on(ˇ:点击“是”。,this . handle mouse click);}}在click事件的执行回调中,这并没有指向DrawTool的实例,所以其中的this.points会返回undefined。 第一种方法是使用closure,先缓存这个,把它变成那个:classdrawtool { constructor(){ this . points =[];} handle mouse click(event){ this . points . push(event . latlng);} init(){ let that = this;$ map . on(;点击“是”。,event = > that . handle mouse click(event));}}因为回调函数是用那个执行的,而且那个是指向DrawTool的实例,所以没有问题。 相反,如果没有那个,它就会用这个,所以要看这个指向哪里。 因为我们使用了arrow函数,而arrow函数的this仍然指向父元素的上下文,所以我们可以不用自己创建一个闭包,而只使用这个:init() {$map.on(>点击“是”。,event = > this . handle mouse click(event));}这种方式更简单。第二种方式是使用ES5的bind函数进行绑定,代码如下:init(){ $ map . on(〉点击“是”。,this . handle mouse click . bind(this));}这个bind看起来很神奇,但实际上实现一个bind函数只需要一行代码:function . prototype . bind = function(context){ return()= > This . call(context);}就是返回一个函数,它的this是指向的原函数,然后让它调用(context)绑定执行上下文。 4.cori化cori化是函数和参数值的组合以产生一个新函数。下面的代码假设有一个curry函数:function add(a,b){ return a+b;} let add 1 = add . curry(1);console . log(add 1(5));//6 console . log(add 1(2));// 3如何实现库里的这样一个功能?它的关键点是返回一个函数,这个函数有少量封闭的变量,这些变量记录了创建时的默认参数。然后在执行返回函数时,将新传递的参数和缺省参数拼成一个完整的参数列表来调整原函数,于是有了下面的代码:function . prototype . curry = function(){ let default args = arguments;让那个=这个;return function(){ return that . apply(this,default args . concat(arguments));}};但是因为参数不是数组,也没有concat函数,所以需要把伪数组变成伪数组。可以使用array . prototype . slice:function . prototype . curry = function(){ let slice = array . prototype . slice;let default args = slice . call(arguments);让那个=这个;return function(){ return that . apply(this,default args . concat(slice . call(arguments)));}};现在我们举一个cori化的有用例子。当需要对数组进行降序排序时,需要这样写:let data = [1,5,2,3,10];data.sort((a,b)= > b-a);// [10,5,3,2,1]传递一个函数参数来排序,但是如果你的降序操作比较多,每次都写一个函数参数就有点烦了,所以可以用Coritization来固化这个参数:array . prototype . sort descending = array . prototype . sort . curry((a,b))这样就方便多了:让data = [1,5,2,3,10];data . sort decessing();console.log(数据);// [10, 5, 3, 2, 1]5.防止篡改对象。有时候,你可能害怕自己的对象被人误换了,所以需要保护。 (1)?Object.seal防止新的属性添加和删除。下面的代码说明了当Object.isSealed时,不能添加或删除:使用严格模式时,会抛出异常:(2)Object.freeze冻结对象。属性值不可更改,如下图:同时可以使用object.isFrozen,Object判断当前对象的状态。已发布且Object.isExtensible (3)defineProperty如下图所示冻结单个属性。如果enumerable/writable设置为false,则不会遍历和写入该属性:6。定时器如何实现一个JS版的睡眠功能?因为C/C++/Java等语言都有sleep函数,JS没有。 sleep函数的用途是让线程进入睡眠状态,然后在指定的时间后唤醒。 你不能写一个while循环,不断判断当前时间和开始时间的差值是否达到了指定的时间。因为这样会占用CPU,所以不会休眠。 这个实现相对简单。我们可以用setTimeout+callback:函数sleep(百万秒,回调){settimeout(回调,百万秒);} // sleep 2秒睡眠(2000,()= > console . log(" sleep recover "));但是我的代码不能像瀑布一样用回调写下来,所以我要做一个回调函数作为参数来传值。 于是想到了Promise,现在用Promise重写:函数sleep(百万秒){ return new Promise(resolve = > settimeout(resolve,百万秒));}睡眠(2000)。然后(()= > console . log(" sleep recover "));但是就像仍然没有办法处理上面的问题一样,你仍然需要传递一个函数参数。 虽然使用Promise本质上是一样的,但是它有一个resolve参数,方便你告诉它什么时候异步结束,然后它就可以执行了。尤其是回调比较复杂的时候,用Promise会更方便。 ES7增加了两个新的属性async/await来解决异步的情况,这样异步代码就可以像同步代码一样编写,比如下面的异步版sleep:函数sleep(百万秒){ return new promise(resolve = > settimeout(resolve,百万秒));}异步函数init(){ await sleep(2000);console.log("睡眠恢复");} init();与简单的无极版本相比,睡眠的实现保持不变。 但是,在调试sleep之前添加await,以便下面的代码仅在异步完成sleep之后执行。 同时,您需要将代码逻辑包装在一个异步标记的函数中,该函数将返回一个Promise对象。当其中的异步完成后,您就可以:in it()。然后(()= > console . log(" init finished "));ES7的新属性使我们的代码更加简洁和优雅。 关于计时器的另一个重要话题是setTimeout和setInterval之间的区别。 如下图所示:setTimeout在当前所有执行单元结束时开始计时,setInterval在设置计时器后立即开始计时。 可以用一个实际的例子来说明,这是我在文章JS和多线程中提到的。这里用代码实际运行一下,如下面的代码所示:let script begin = date . now();fun 1();fun 2();//需要执行20ms的工作单元函数act(函数名){console.log(函数名,date . now()-script begin);let begin = date . now();while(date . now()-begin < 20);}函数fun 1(){ let fun 3 =()= > act(" fun 3 ");setTimeout(fun3,0);act(" fun 1 ");}函数fun 2(){ act(" fun 2-1 ");var fun 4 =()= > act(" fun 4 ");setInterval(fun4,20);act(" fun 2-2 ");}这段代码的执行模型如下:控制台输出:与上面的模型分析一致。 然后讨论最后一个话题,函数节流7。函数节流节流的目的是避免触发执行过快,比如:?监听输入触发搜索?倾听调整大小并做出相应的调整?听mousemove来调整位置。我们先来看看1s可以触发多少次resize/mousemove事件,所以我们写了下面的驱动代码:let begin = 0;设count = 0;window . onresize = function(){ count++;let now = date . now();如果(!begin) { begin =现在;返回;} if((now-begin)% 3000 < 60){ console . log(now-begin,count/(now-begin)* 1000);} };当窗口被快速拉动时,resize事件在1s内被触发40次左右:需要注意的是,拉动越快,触发越快。 实际上,你拉得越快,它触发得越慢。因为页面在拉的时候需要重画,所以变化越快,重画的次数就越多,导致触发器越少。 鼠标事件在我电脑的Chrome上1s触发了60次左右:如果需要监控resize事件来进行DOM调整,这个调整需要很长时间,1s需要调整40次,可能会反应不过来,不需要调整那么频繁,需要节流。 如何实现节流?书中是这样实现的:function throttle (method,context){ clear time out(method . tid);method . tid = setTimeout(function(){ method . call(context));}, 100);}每次执行都要设置超时。如果是快速触发,清除最后一个,重新设置,这样就不会快速执行了。 但是,有一个问题,就是这个回调函数永远无法执行。因为不断触发和清除tId,所以有点尴尬。上面代码的本意应该是100ms内最多触发一次,但实际情况是永远无法执行。 这个实现应该叫防抖,不叫节流。 稍微修改一下上面的代码:function throttle (method,context){ if(method . tid){ return;} method . tid = setTimeout(function(){ method . call(context));method . tid = 0;}, 100);}这个实现是正确的,回调最多每100ms执行一次。原理是将setTimeout中的tId设置为0,这样就可以执行下一个触发器。 我们来试试:应该每100ms执行一次,这样才能达到我们的目的。 不过有个小问题,就是每次执行都要延迟100ms。有时,用户可以最大化窗口,并且只触发一个调整大小事件。然而,这个执行仍然要延迟100毫秒。假设你的时间是500ms,那就要延迟半秒,所以这个实现并不理想。 需要优化,如下面的代码所示:function throttle (method,context){//如果是第一次触发,立即执行If(type of method . tid = = " undefined "){ method . call(context);} if(method . tid){ return;} method . tid = setTimeout(function(){ method . call(context));method . tid = 0;}, 100);}先判断是否是第一个触发器,如果是,立即执行。 这解决了上述问题,但是这种实现仍然有问题。因为只是全球第一次,用户最大化后,过一段时间取消最大化后会延迟,第一次触发会执行两次。 那我该怎么办?我想到了一个方法:函数throttle(方法,上下文){if(!method.tId) { method.call(上下文);method . tid = 1;setTimeout(() => method.tId = 0,100);}}每次触发后立即执行,然后设置一个定时器,将tId设置为0。实际效果如下:这个实现比上一个更简洁,可以处理延迟的问题。 ?所以通过节流,1s内执行次数减少到10次,节流时间可以控制,但同时也损失了灵敏度。如果需要高灵敏度,就不要用节流,比如拖动的时候就要用。 如果你拖拽油门会发生什么?用户会发现一张卡接一张卡。作者:Renren.com联储链接:https://juejin.im/post/59ab7b36f265da24934b2470来源:掘金版权归作者所有。 商业转载请联系作者授权,非商业转载请注明出处。


  • 全部评论(0)
资讯详情页最新发布上方横幅
最新发布的资讯信息
【域名/主机/服务器|】qq邮箱提醒在哪里打开(2024-06-04 18:58)
【技术支持|常见问题】1556原创ng8文章搜索页面不齐(2024-05-01 14:43)
【技术支持|常见问题】1502企业站群-多域名跳转-多模板切换(2024-04-09 12:19)
【技术支持|常见问题】1126完美滑屏版视频只能显示10个(2024-03-29 13:37)
【技术支持|常见问题】响应式自适应代码(2024-03-24 14:23)
【技术支持|常见问题】1126完美滑屏版百度未授权使用地图api怎么办(2024-03-15 07:21)
【技术支持|常见问题】如何集成阿里通信短信接口(2024-02-19 21:48)
【技术支持|常见问题】算命网微信支付宝产品名称年份在哪修改?风水姻缘合婚配对_公司起名占卜八字算命算财运查吉凶源码(2024-01-07 12:27)
【域名/主机/服务器|】帝国CMS安装(2023-08-20 11:31)
【技术支持|常见问题】通过HTTPs测试Mozilla DNS {免费源码}(2022-11-04 10:37)

联系我们
Q Q:375457086
Q Q:526665408
电话:0755-84666665
微信:15999668636
联系客服
企业客服1 企业客服2 联系客服
86-755-84666665
手机版
手机版
扫一扫进手机版
返回顶部