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命令失效。