JavaScript中一些小妙招

IE浏览器版本过低处理

IE10及以下版本提示升级(使用只有IE10和旧版IE才支持 @cc_on 条件编译语句实现)

<script>/*@cc_on alert("您正在使用的浏览器版本过低,为了您的最佳体验,请先升级浏览器。");window.location.href="http://support.dmeng.net/upgrade-your-browser.html?referrer="+encodeURIComponent(window.location.href); @*/</script>  

由于IE浏览器和双核浏览器会根据网页内容自动选择渲染模式,例如IE11有可能自动采用IE7兼容模式渲染,国产双核浏览器可能会采用IE内核渲染。为了解决这个问题,可以通过 renderer 和 X-UA-Compatible 头部标签要求双核浏览器优先采用 Chrome 内核、IE浏览器优先采用最高版本

<meta name="renderer" content="webkit"/>
<meta name="force-rendering" content="webkit"/>
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1"/>

完整示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8"/>
<meta name="renderer" content="webkit"/>
<meta name="force-rendering" content="webkit"/>
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1"/>
<script>/*@cc_on alert("您正在使用的浏览器版本过低,为了您的最佳体验,请先升级浏览器。");window.location.href="https://support.dmeng.net/upgrade-your-browser.html?referrer="+encodeURIComponent(window.location.href); @*/</script>  
<title>网页标题</title>
<!-- 其他标签 -->
</head>
<body>
<!-- 网页内容 -->
</body>
</html>

参考文章

 

带var和不带var的区别

1、在全局作用域下带var声明一个变量a,全局变量和全局对象window的属性存在映射机制let、const不存在这种映射机制),既声明了一个变量a也在window上添加了一个属性a。(delete window.a 不可删除

2、不带var不带关键字)a = 666 即为赋值表达式,如果沿着作用域链一直找到全局作用域都没找到变量 a,那么此时 a = 666 实际是 window.a = 666 的简写此时a并不是一个变量而只是window一个属性。(delete window.a 可删除

3、例如 console.log(a) 获取a时,此时会先找变量a,没有的话再找window.a

带var是变量,不带var是window属性

//带var
console.log(a)  //undefined 带var变量提升
var a = 666;
delete window.a  //false 不可删除
console.log(a)  //666
//不带var
b = 233;  //实际是window.b = 233;的简写
console.log(b)  //233 此时会先找变量b,没有的话再找window.b
delete window.b  //true 可删除
console.log(b)  //undefined

function变量提升受块级作用域的影响

我们都知道ES6中提供了块级作用域,比如在块级作用域内用let、const声明变量那么该变量离开该块级作用域后就会被回收,也就是说在该块级作用域外无法获取该变量

其实变量提升中的function提升也会受到块级作用域的影响。在现代浏览器中IE11之后),如果在{}代码块中声明函数那么该函数在代码块外部变量提升时只会声明不会定义,但在代码块里面就正常的提升

//不管if语句条件是否成立都会变量提升
console.log(f1)  //undefined 此时f1提升只声明未定义
if(false){
    function f1(){ console.log('f1')}
}

console.log(f2)  //undefined 此时f2提升只声明未定义
if(true){
    console.log(f2)  //f2(){ console.log('f2')} 在代码块中提升正常
    function f2(){ console.log('f2')}
}

 

 

js严格模式"use strict"

顾名思义,js严格模式更加严谨规范js代码的书写。

在当前作用域的第一行添加  "use strict"  即可开启严格模式。全局作用域"use strict"只在当前js文件起作用,其他js文件也想用严格模式也要在第一行加上"use strict"。

严格模式与普通模式下的一些区别

1、严格模式下函数没有arguments.callee,会报错。

2、严格模式下arguments和形参没有映射关系。

3、严格模式下对象重复定义同名属性会提示语法错误。

4、严格模式下函数执行没有明确的调用对象,那么该函数内this指向undefined。

5、严格模式下变量必须声明,不声明就直接赋值会报错。

 

讲讲arguments

1、arguments是函数里的实参集合,是个类数组。

2、arguments.callee是当前函数本身。arguments.callee.caller表示该函数在哪个作用域下执行如果在全局作用域下arguments.callee.caller为null

3、arguments与函数形参存在映射关系非严格模式下),但这种映射机制在函数执行一开始就固定了,在函数执行过程中映射关系不可改变,比如函数形参有2个,但函数执行只传1个实参,那么该函数arguments只与第一个形参存在映射关系

//形参2个,实参2个
function fn(a, b){
    arguments[1] = 9;
    console.log(b);
}
fn(1, 2)  //9 此时形参b与arguments[1]是映射关系
//形参2个,实参1个
function fn(a, b){
    arguments[1] = 9;
    console.log(b);
}
fn(1)  //undefined 此时形参b和arguments不存在映射关系

 

 

日期对象用toLocaleString()显示成24小时制

new Date().toLocaleString()
//输出 2018/5/9 上午12:31:59

new Date().toLocaleString('chinese',{hour12:false})
//输出 2018/5/9 00:33:25

 

对象的深度复制

代码如下

function deepCopy(obj){
    //如果是非对象直接返回,包括function(特殊)
    if(typeof obj !== 'object') return obj;
    //如果是null
    if(obj === null) return null;
    //如果是日期对象
    if(obj instanceof Date) return new Date(obj);
    //如果是正则对象
    if(obj instanceof RegExp) return new RegExp(obj);
    //不直接定义[]或{}是为了要保留该对象的原型链
    var tempObj = new obj.constructor();
    for(var key in obj){
        tempObj[key] = typeof obj[key] == 'object' ? deepCopy(obj[key]) : obj[key];
    }  
    return tempObj;
}

 

数字格式化

非正则方法

var num = '1234567890.12';

function priceFmt1(str){
    var arr1 = str.split('.');
    var n = arr1[0].length % 3;
    var arr2 = arr1[0].split('');
    for(var i=n; i<arr2.length; i+=4){
        arr2.splice(i, 0, ',');
    }
    return arr2.join('')+'.'+arr1[1];
}

console.log(priceFmt1(num))     //1,234,567,890.12

正则方法

首先了解正则中的?=,?!    参考文章

//匹配后面是'国人'的中字
console.log('中国人'.replace(/中(?=国人)/, 'zhong'));      //zhong国人
console.log('中国话'.replace(/中(?=国人)/, 'zhong'));      //中国话

//匹配前面是'中国'的人字
console.log('中国人'.replace(/(?<=中国)人/, 'ren'));      //中国ren
console.log('法国人'.replace(/(?<=中国)人/, 'ren'));      //法国人

//匹配后面不是'国人'的中字
console.log('中国人'.replace(/中(?!国人)/, 'zhong'));      //中国人
console.log('中国话'.replace(/中(?!国人)/, 'zhong'));      //zhong国话

//匹配前面不是'中国'的人字
console.log('中国人'.replace(/(?<!中国)人/, 'ren'));      //中国人
console.log('法国人'.replace(/(?<!中国)人/, 'ren'));      //法国ren
var num = '1234567890.12';

function priceFmt2(str){
    var arr1 = str.split('.');
    var s = arr1[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
    return s+'.'+arr1[1];
}

console.log(priceFmt2(num))     //1,234,567,890.12
//(\d{3})+ 匹配一个或多个连续三位数字,即连续3位数字或连续6位数字或9位如此类推
//(\d{3})+(?!\d) 即匹配后面不是数字的连续3位数字或6位数字或...
//\B(?=(\d{3})+(?!\d)) 即匹配后面是(后面不是数字的连续3位数字或6位数字...)的非单词边界

 

用正则简单实现压缩js文件

const fs = require('fs');

function compressJS(filePath){
    let content = fs.readFileSync(filePath, 'utf-8');
    // 匹配前面有字符的空格、匹配空格、匹配带引号的
    let reg = /(\w+\s|\s|\'.+\'|\".+\")/g;
    let compressContent = content.replace(reg, function(){
        if(/(\"|\')/.test(arguments[0])){   //带引号的不改变
            return arguments[0];
        }else if(/(var|let|const|function|return|else)/.test(arguments[0])){//关键字后面的空格要保留
            return arguments[0];
        }else if(arguments[0].length > 0){      //不是关键字后面的空格去除
            return arguments[0].slice(0, -1);
        }else{      //其余的空格去除
            return '';
        }
    });
    fs.writeFile(filePath, compressContent, err => {
        if(err) return console.log('文件压缩出错');
        console.log('文件压缩成功');
    })
}

 

重写console.log

其实重写之后最终调用的还是原来的console.log,只是利用闭包将console.log指向另一个函数,而在这个函数里执行console.log.call()

console.log = (function(oriLogFunc){
	return function(str){
		oriLogFunc.call(console, 'Sam--'+str);
	}
})(console.log);

console.log('hello')    //Sam--hello

 

ObjectId转为标准时间

思路:取id的前八位,按16进制转10进制,再乘上1000。

function _id2time(_id) {
    return new Date(parseInt(_id.toString().substring(0, 8), 16) * 1000);
}

 

函数防抖与节流

防抖与节流的相同之处都是限制回调函数的调用频率。不同之处是:

防抖,在一段时间内连续触发事件时不执行回调,最后一次触发事件才执行回调函数。举例,input事件。

节流,连续触发事件,但每个一段时间才执行回调函数。举例,scroll事件。

/**
 * @param {function} fn 事件回调函数
 * @param {number} delay 延时
 * @method 函数防抖
 */
function debounce(fn, delay){
    var timer;
    return function(e){
        var _this = this;
        clearTimeout(timer);
        timer = setTimeout(function(){
            fn.apply(_this, e);
        }, delay)
    }
}

/**
* @params {function} fn 事件回调函数
* @params {number} delay 延时
* @methods 函数节流
*/        
function throttle(fn, delay){
    var lastTime = new Date();
    return function(e){
        // 如果距离上次执行时间太短则不执行函数
        if(new Date() - lastTime < delay) return;
        lastTime = new Date();
        fn.call(this, e);
    }
}

 

鼠标滚轮事件

 IE、chrome监听的是wheelDelta,向下滚动其值为-120;向上滚动其值为120

 FF监听的是detail,向下滚动其值为3;向上滚动其值为-3

//判断鼠标滚轮滚动方向
//FF,火狐浏览器会识别该方法
if (window.addEventListener) window.addEventListener('DOMMouseScroll', wheel, false);
window.onmousewheel = document.onmousewheel = wheel;//W3C
//统一处理滚轮滚动事件
function wheel(event) {
    var delta = 0;
    if (!event) event = window.event;
    if (event.wheelDelta) {//IE、chrome浏览器使用的是wheelDelta,并且值为“正负120”
        delta = event.wheelDelta / 120;
        if (window.opera) delta = -delta;//因为IE、chrome等向下滚动是负值,FF是正值,为了处理一致性,在此取反处理
    } else if (event.detail) {//FF浏览器使用的是detail,其值为“正负3”
        delta = -event.detail / 3;
    }
    if (delta) handle(delta);
}
//上下滚动时的具体处理函数
function handle(delta) {
    if (delta < 0) {//向下滚动
        
    } else {//向上滚动
        
    }
}

 

数组乱序Math.random()

arr.sort(function() {
    return Math.random() - 0.5;
})

 

+运算中的隐式类型转换

由于JavaScript弱类型的语言特性,会导致频繁发生隐式类型转换

但隐式类型转换有他的规则:

  • js在进行加法运算的时候, 会先推测两个操作数是不是number,如果是,则直接相加得出结果。
  • 如果其中有一个操作数为string,则将另一个操作数隐式的转换为string,然后进行字符串拼接得出结果。
  • 如果操作数为对象或者是数组这种复杂的数据类型,那么就将两个操作数都转换为字符串,进行拼接 。
  • 如果操作数是像boolean这种的简单数据类型,那么就将操作数转换为number相加得出结果。
  • null转成number为0,undefined转成number为NaN
+[]    //0 []转成number
[] + {}    //"[object Object]" 两边都转成string
{} + []    //0 {}被js解析器认为代码块了,所以实则为+[]

参考文章

 

返回顶部

(function backToTop(){
    var currentScroll = document.documentElement.scrollTop || document.body.scrollTop;
    if (currentScroll > 0) {
        // ie浏览器中4.5会渲染成5px,用数值取整处理
        window.scrollTo (0, Math.floor(currentScroll - (currentScroll/10)));
        window.requestAnimationFrame(backToTop);
    }
})();
window.scrollTo({
	left: 0,
	top: 0,
	behavior: "smooth"
})

 

匿名函数设置函数名问题

1、本该是匿名的函数如果设置了函数名,那么该函数名是这个函数内部私有的,外部无法调用

什么叫本该匿名的函数呢?

//普通函数声明
function fn(){...}
//这样创建的函数就不是匿名函数,外部可以用函数名fn()来调用

 

//函数表达式
let a = function(){...}
//这样创建函数,表达式右边就本该是个匿名函数
//如果将该匿名函数命名,例如
a = function fn(){...}
//那么就是我们要说的问题,上面表达式中的 fn 只能在函数内部调用,外部要调用该函数就只能 a()

//还有比如像自执行函数、回调函数也是
(function fn(){
    //fn 只能内部使用
})();
setTimeout(function fn(){
    //fn 只能内部使用
}, 1000)

2、本该匿名的函数如果设置了函数名,那么该函数名就类似在函数内部用 const 声明的常量,该函数名所储存的值不可修改严格模式下会报错

(function fn(){
    fn = 20;
    console.log(fn)  //function fn(){...},并不是20说明不改变
})();

(function fn(){
    "use strict"
    fn = 20;
    console.log(fn)  //报错Assignment to constant variable
})();

3、上面说了类似 const 声明,但跟 const 有所不同的是,在函数内部可以再次声明

 

(function fn(){
    "use strict"
    let fn = 20;
    console.log(fn)  //20
})();
//或者形参
(function fn(fn){
    "use strict"
    console.log(fn)  //20
})(20);

常见三种排序方法

冒泡排序

思路:

从第一个开始,跟下一个两两比较(arr[i]arr[i+1]),如果当前比下一个大(arr[i] > arr[i+1])则交换位置,这样一轮下来就得出了最大的在最后位置。

再进行下一轮比较,不过上一轮得出的最大数就不用比了。

function bubbleSort(arr){
  //一共需要进行arr.length-1轮    
  for(let i=0; i<arr.length-1; i++){
    //每一轮需要比较arr.length-1-i次  
    for(let j=0; j<arr.length-1-i; j++){
      if(arr[j] > arr[j+1]){
        let temp = arr[j+1];
        arr[j+1] = arr[j];
        arr[j] = temp;
      }
    } 
  }
}

 

插入排序

思路:

创建一个新数组tempArr,先从原数组arr中拿出一项 A 插进tempArr中,然后再把原数组arr剩下的每一项 a 依次跟tempArr中的每一项 b (从tempArr最右边开始)比较,如果 a > b 则将 a 插入 b 的下一位,如果 a 没有比tempArr中的任何一项大,那么将 a 插入到tempArr中的第一位。

function insertSort(arr){
  //创建一个新数组tempArr,先从原数组arr中拿出一项插进tempArr中,这里就拿原数组arr中的第一位
  let tempArr = [arr[0]];
  //遍历原数组剩下的
  for(let i=1; i<arr.length; i++){
    //与tempArr中的每一项比较  
    for(let j=tempArr.length-1; j>=0; j--){
      if(arr[i] >= tempArr[j]){
        tempArr.splice(j+1, 0, arr[i]);
        break;  //插入tempArr了就可以了
      }
      //如果没有比tempArr中的任何一项大,那就插入到tempArr的第一位
      j == 0 ? tempArr.unshift(arr[i]) : null;
    }
  }
  return tempArr;
}

 

快速排序

思路:

创建 left 和 right 两个空数组,拿出原数组中间位置那一项 c ,遍历原数组剩下的每一项与 c 比较,如果比 c 小就插入到 left 中,反之则插入到 right 中,最后返回 left.concat(c, right) 。如果 left 或 right 的长度大于1,那么就继续递归。

function quickSort(arr){
  let left = [];
  let right = [];
  //原数组中间位置那一项
  let c = arr.splice(Math.floor(arr.length/2), 1);
  for(let i=0; i<arr.length; i++){
    //如果比c小,放进left,反之放进right  
    arr[i] < c ? left.push(arr[i]) : right.push(arr[i]);
  }
  //如果left或right的长度大于1,则继续递归
  left.length > 1 ? left = quickSort(left) : null;
  right.length > 1 ? right = quickSort(right) : null;
  return left.concat(c, right);
}

 

add(1)(2)(3)

这是一道JS面试题

题目:

用js 实现一个无限极累加的函数, 形如:
add(1) //=> 1;
add(1)(2) //=> 3; 
add(1)(2)(3) //=> 6; 
add(1)(2)(3)(4) //=> 10; 
以此类推...

分析:

add(1)(2)输出3?? 简单,就是一个闭包,add函数返回一个函数。

 

function add(n){
    return function(m){
        return n + m;
    }
}
console.log(add(1)(2))  //3

然后呢?add(1)(2)(3)呢?那么里面应该继续返回函数。先来看看 add(1) 怎么输出1。已知 add() 返回一个函数,但输出时要是一个值。恩,答案一目了然,这是一个JS基础知识点,console.log(fn) 时,实际会调用 fn 的 toString 方法,所以我们可以重写 fn 的 toString 方法来达成目的

function add(n){
    return function(m){
        return n + m;
    }
}
console.log(add(1))  //function(m){ return n + m; }
//重写toString
function add(n){
    function more(m){
        return n + m;
    }
    more.toString = function(){
        return n;
    }
    return more;
}
console.log(add(1))  //1

 

好了,add(1) 可以了。那么add(1)(2)(3)...呢,上面已经分析出了,明显函数里面还要不断得返回函数,想想,add(1)、add(1)(2)和add(1)(2)(3)...都返回同一个函数,而这个函数 toString() 的值却在累增

function add(n){
    let sum = n;
    function more(m){
        //toString()的值sum在不断累增
        sum += m;
        return more;
    }
    more.toString = function(){
        return sum;
    }
    return more;
}
add(1) //1;
add(1)(2) //3; 
add(1)(2)(3) //6; 
add(1)(2)(3)(4) //10; 

 

写着写着就成功了。不过要记住,add(1)(2)(3)...的返回值依然是个函数,只是该函数在输出打印(调用toString)时是一个值。既然 toString() 出来的是个值,那自然也是可以进行一些计算的。

typeof add(1)(2)(3)  //function
add(1)(2)(3) + 9  //15
add(1)(2)(3) - 9  //-3
add(1)(2)(3) + 'aa'  //'6aa'
add(1)(2)(3) - 'aa'  //NaN
add(1)(2)(3) + [1,2,3]  //"61,2,3"

 

字符串slice、substring、substr的区别

slice(n, m)

1、m必须大于n,否则返回空字符串

2、传入负数相当于传入(字符串.length+负数),计算后的参数还是要遵守第二个比第一个参数大,否则返回空字符串。

let str = 'hello world haha';
str.slice(2, 5)  //"llo"
//第二个参数小于第一个参数,返回空字符串
str.slice(5, 2)  //""
str.slice(-5, -2)  //" ha"
str.slice(5, -2)  //" world ha"
//第二个参数小于第一个参数,返回空字符串
str.slice(-2, -5)  //""

 

substring(n, m)

1、会自动判断n和m的大小,比如 m < n 就相当于slice(m, n)。反正n和m谁大谁小都可以

2、传入负数相当于传入0,然后再遵循上面第一条。

 

let str = 'hello world haha';
str.substring(2, 5)  //"llo"
//如果第一个参数比第二个大
str.substring(5, 2)  //"llo"  =str.substring(2, 5)=str.slice(2, 5)
//如果传入负数
str.substring(5, -2)  //"hello"  =str.substring(5, 0)=str.slice(0, 5)
str.substring(-5, -2)  //""  =str.substring(0, 0)=str.slice(0,0)

substr(n, m)

1、这里n和m不存在大小关系,n是起始索引,m是截取个数。

2、当n为负数时也会像slice()一样进行(字符串.length+负数)计算,当m为负数时像substring()一样相当于传入0

let str = 'hello world haha';
str.substr(2, 5)  //"llo w"
//如果第一个参数为负数
str.substr(-5, 2)  //" h"
//如果第二个参数为负数
str.substr(5, -2)  //""

 

Node.js版本管理器nvm

参考文章    参考文章

1、windows版的nvm安装前需要卸载完已安装的Node.js程序(在控制面板卸载就可以)。

2、下载安装包,github下载地址

3、点击安装就可以了(注意安装路径不要出现中文或空格),安装完后即可在命令行工具使用nvm命令。

4、使用nvm install 版本号 安装Node.js程序,nvm use 版本号 切换到该版本,如果npm命令失效

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值