开发中常用的JS知识点集锦

快捷导航菜单

 

1、对象的深拷贝(一级属性拷贝和多级属性嵌套拷贝)

ES6的Object.assign函数,和{...xxx}扩展运算符能够实现浅拷贝。JSON.parse(JSON.stringify(xxx))能够实现深拷贝,但是针对要拷贝的属性值如果是函数、时间、undefined、正则表达式以及对象的原型属性和方法都无能为力。因此深拷贝需要自定义函数:

function deepClone(sourceObj){
    if (sourceObj instanceof RegExp) return new RegExp(sourceObj); //正则类型
    if (sourceObj instanceof Date) return new Date(sourceObj);    //时间类型
    if (sourceObj === null || typeof sourceObj !== 'object'){ //基础类型
        return sourceObj;
    }

    //其他引用类型数据, 找出当前数据类型的构造函数,new一个当前类型的空对象
    var obj = new sourceObj.constructor(); 
    for (var key in sourceObj){
        //属于自己的属性才递归深拷贝
        if (sourceObj.hasOwnProperty(key)){
            obj[key] = deepClone(sourceObj[key]);
        }
    }
    return obj;
}

测试代码:

//测试
function Person(name, age){
    this.name = name;
    this.age = age;
}
Person.prototype.myInfo = function(){
    console.log("my name is ", this.name, ", age is ", this.age);
}

var obj = {
    fn: Person,    //函数
    obj: new Person("王大锤", 18),  //对象
    time: new Date("2018-08-18 18:18"),  //时间
    reg: new RegExp('abc'),    //正则
    sign: undefined,    
    arr: ['etf', 99, true]
}
var obj2 = Object.assign({}, obj); //浅拷贝(ES6的Object.assign函数)
var obj3 = {...obj};               //浅拷贝(ES6扩展运算符)
//JSON.stringify深拷贝所有层级属性,但是针对属性值为函数,正则表达式,时间,undefined, 原型对象的方法和属性无能为力
var obj4 = JSON.parse(JSON.stringify(obj));       
var obj5 = deepClone(obj);

console.log("\n**************start*************")
console.log("obj: ", obj, "\n\nobj2: ", obj2, "\n\nobj3: ", obj3, "\n\nobj4: \n", obj4, "\n\nobj5: ", obj5);


obj.obj.name = '罗小虎', obj.obj.age = 27;
obj.arr.push("玉娇龙");
console.log("............update.........")
console.log("obj: ", obj, "\n\nobj2: ", obj2, "\n\nobj3: ", obj3, "\n\nobj4: \n", obj4, "\n\nobj5: ", obj5);

测试结果截图对比一下,发现使用JSON.stringify深拷贝的对象,属性值为函数和undefined的属性直接过滤不见了,然后正则表达式变成了空对象{}, 时间变成了字符串等:

 

2、网络图片转成base64, 在线图片或文件点击下载(隐藏链接)

<div>
            <div onclick="clickMeDownload()">点我下载</div>
            
            <script type="text/javascript">
                /**
                 * 根据远程图片转成base64数据 (远程图片和当前页面不是同一域名时,需要进行web服务器配置,使其可以跨域下载)
                 * @param url      图片链接
                 * @param callback 回调函数
                 */
                function getBase64ByImgUrl(url, callback){
                    let canvas = document.createElement('canvas'),
                        ctx = canvas.getContext('2d'),
                        img = new Image;
                    img.crossOrigin = 'Anonymous';
                    img.onload = function(){
                        canvas.height = img.height;
                        canvas.width = img.width;
                        ctx.drawImage(img,0,0);

                        //获取base64数据
                        let base64 = canvas.toDataURL('image/png');
                        //回调
                        if (callback){
                            callback(base64);
                        }
                        canvas = null;
                    }
                    img.src = url;
                }

                /**
                 * 把base64转成文件流
                 * @param base64     base64数据
                 * @param filename   自定义文件的名字
                 */
                function getFileByBase64(base64, filename){

                    let arr = base64.split(','), mime = arr[0].match(/:(.*?);/)[1],
                        bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);

                    while(n--){
                        u8arr[n] = bstr.charCodeAt(n);
                    }

                    return new File([u8arr], filename, {type:mime});
                }


                /**
                 * 测试例子:点击下载,隐藏下载链接
                 */
                function clickMeDownload(){

                    let imgUrl = 'https://img2018.cnblogs.com/blog/454511/201811/454511-20181114115022054-611805083.png';

                    getBase64ByImgUrl(imgUrl, function(base64){
                        console.log(base64);

                        //创建a标签, 设置a标签的href属性和download属性
                        var aEle = document.createElement('a');
                        aEle.setAttribute('href', base64);
                        aEle.setAttribute('download', 'temp.png');
                        aEle.style.display = 'none'; //隐藏a标签
                        document.body.appendChild(aEle);  //将a标签添加到body里
                        aEle.click();    //触发a标签点击事件

                        document.body.removeChild(aEle);  //下载图片后,移除a标签

                    });
                }
            </script>
        </div>
View Code

 

3、常用CSS样式记录

3.1 给定标签宽高,内容超出限制宽高后,以省略点展示,示例:

<div class="text-box">宽度500px, 高度50px,超出高度后后缀...展示。这俩人是现今世上,手段最高明的摸金校尉,都有万夫不挡之勇,神鬼莫测之机,兼有云长之忠,翼德之猛,子龙之勇,孔明之智,那面古镜一定就是他们从云南掏出来的。</div>
View Code
/* 6、超出限制宽高后省略点展示
            多行后显示省略点,只应用于-webkit内核; 移动端浏览器基本都是WebKit内核的,所以该样式适用于移动端
        */
        .text-box{
            margin-top: 20px; width: 500px; height: 50px; border:1px dashed #999; color: #999; line-height: 25px;
            overflow: hidden; text-overflow: ellipsis; display: -webkit-box;
            -webkit-line-clamp: 2; /* 这个是设置最多展示几行 */
            -webkit-box-orient: vertical; word-break:break-all;
        }
View Code

3.2  一个标签,配合before和after属性,设置出外圆边框,内三角形图标

<span class="video-icon"></span>
/* 5、视屏图标icon */
        .video-icon { display: inline-block; width: 30px; height: 30px; border: 2px solid blue; border-radius: 30px; position: relative; cursor: pointer; }
        .video-icon:after {
            position: absolute; left: 12px; top: 7px; content: '';
            border-width:8px 0 8px 10px; border-style:solid;
            border-color:transparent transparent transparent blue;
        }
View Code

3.3  单选切换:两个按钮,只能选中一个,左边按钮的左边和右边按钮的右边圆角

<div class="grade_box"><div class="grade_first grade_sel">一级分类</div><div class="grade_second">二级分类</div></div>
        <script type="text/javascript">
            var gradeList = (document.getElementsByClassName('grade_box')[0]).children;

            for (var i = 0; i < gradeList.length; i++){
                var obj = gradeList[i];

                obj.setAttribute('index', i);

                obj.onclick = function(){
                    var index = this.getAttribute('index');

                    console.log(this);

                    if (index == 0){
                        gradeList[0].className = 'grade_first grade_sel';
                        gradeList[1].className = 'grade_second';
                    }
                    else{
                        gradeList[0].className = 'grade_first';
                        gradeList[1].className = 'grade_second grade_sel';
                    }
                }
            }
        </script>
View Code
.grade_box { width: 300px; height: 40px; }
        .grade_box div {
            display: inline-block; height: 100%; width: 50%; line-height: 40px; text-align: center; background-color: #f4f4f4; color: #ff7800; border: 1px solid #ff7800; box-sizing: border-box; cursor: pointer;
        }
        .grade_box .grade_sel { background-color: #ff7800; color: #fff; }
        .grade_box .grade_first { border-top-left-radius: 40px; border-bottom-left-radius: 40px; }
        .grade_box .grade_second { border-top-right-radius: 40px; border-bottom-right-radius: 40px; }
View Code

3.4 按钮背景颜色渐变

<div class="bg_change_btn">4、按钮背景颜色渐变</div>
.bg_change_btn{
            width: 200px; height: 50px; line-height: 50px; border-radius: 50px; margin-top: 30px; text-align: center;
            background-image: linear-gradient(-45deg, orange 0%, yellow 100%), linear-gradient( green, black); cursor: pointer;
        }
View Code

 

4、JS中某些数字加减乘除会出现多位小数点现象,精度丢失

举例:

console.log("0.1+0.2 = ", 0.1+0.2);   //0.1+0.2 =  0.30000000000000004
console.log("0.1+0.7 = ", 0.1+0.7);   //0.1+0.7 =  0.7999999999999999
console.log("0.2+0.7 = ", 0.2+0.7)    //0.2+0.7 =  0.8999999999999999
console.log("1.1+0.3 = ", 1.1+0.3)    //1.1+0.3 =  1.4000000000000001

console.log("1.7-0.1 = ", 1.7-1.1);   //1.7-0.1 =  0.5999999999999999
console.log("1.7-1.2 = ", 1.7-1.3);   //1.7-1.2 =  0.3999999999999999
console.log("1.7-1.4 = ", 1.7-1.4);   //1.7-1.4 =  0.30000000000000004

console.log("1.10*100 =", 1.10*100);  //1.10*100 = 110.00000000000001
console.log("1.11*100 =", 1.11*100);  //1.11*100 = 111.00000000000001
console.log("1.12*100 =", 1.12*100);  //1.12*100 = 112.00000000000001
console.log("1.13*100 =", 1.13*100);  //1.13*100 = 112.99999999999999
console.log("1.14*100 =", 1.14*100);  //1.14*100 = 113.99999999999999
console.log("1.15*100 =", 1.15*100);  //1.15*100 = 114.99999999999999
console.log("1.16*100 =", 1.16*100);  //1.16*100 = 115.99999999999999

console.log("0.7/0.1 = ", 0.7/0.1);   //0.7/0.1 =  6.999999999999999
console.log("0.6/0.1 = ", 0.6/0.1);   //0.6/0.1 =  5.999999999999999

解决方式1,使用Math.round函数处理,假设数字变量名为num, 格式为:Math.round(num*100) / 100:

console.log("0.1+0.2 = ", Math.round((0.1+0.2)*100)/100);   //0.1+0.2 =  0.3
console.log("0.1+0.7 = ", Math.round((0.1+0.7)*100)/100);   //0.1+0.7 =  0.8
console.log("0.2+0.7 = ", Math.round((0.2+0.7)*100)/100)    //0.2+0.7 =  0.9
console.log("1.1+0.3 = ", Math.round((1.1+0.3)*100)/100)    //1.1+0.3 =  1.4

console.log("1.7-0.1 = ", Math.round((1.7-0.1)*100)/100);   //1.7-0.1 =  1.6
console.log("1.7-1.2 = ", Math.round((1.7-1.2)*100)/100);   //1.7-1.2 =  0.5
console.log("1.7-1.4 = ", Math.round((1.7-1.4)*100)/100);   //1.7-1.4 =  0.3

console.log("1.10*100 =", Math.round((1.10*100)*100)/100);  //1.10*100 = 110
console.log("1.11*100 =", Math.round((1.11*100)*100)/100);  //1.11*100 = 111
console.log("1.12*100 =", Math.round((1.12*100)*100)/100);  //1.12*100 = 112
console.log("1.13*100 =", Math.round((1.13*100)*100)/100);  //1.13*100 = 113
console.log("1.14*100 =", Math.round((1.14*100)*100)/100);  //1.14*100 = 114
console.log("1.15*100 =", Math.round((1.15*100)*100)/100);  //1.15*100 = 115
console.log("1.16*100 =", Math.round((1.16*100)*100)/100);  //1.16*100 = 116

console.log("0.7/0.1 = ", Math.round((0.7/0.1)*100)/100);   //0.7/0.1 =  7
console.log("0.6/0.1 = ", Math.round((0.6/0.1)*100)/100);   //0.6/0.1 =  6

 

解决方式2:使用parseInt函数,假设数字变量名为num, 格式为:parseInt(num*100 + 0.1)/100

console.log("0.1+0.2 = ", parseInt((0.1+0.2)*100+0.1)/100);   //0.1+0.2 =  0.3
console.log("0.1+0.7 = ", parseInt((0.1+0.7)*100+0.1)/100);   //0.1+0.7 =  0.8
console.log("0.2+0.7 = ", parseInt((0.2+0.7)*100+0.1)/100)    //0.2+0.7 =  0.9
console.log("1.1+0.3 = ", parseInt((1.1+0.3)*100+0.1)/100)    //1.1+0.3 =  1.4

console.log("1.7-0.1 = ", parseInt((1.7-0.1)*100+0.1)/100);   //1.7-0.1 =  1.6
console.log("1.7-1.2 = ", parseInt((1.7-1.2)*100+0.1)/100);   //1.7-1.2 =  0.5
console.log("1.7-1.4 = ", parseInt((1.7-1.4)*100+0.1)/100);   //1.7-1.4 =  0.3

console.log("1.10*100 =", parseInt((1.10*100)*100+0.1)/100);  //1.10*100 = 110
console.log("1.11*100 =", parseInt((1.11*100)*100+0.1)/100);  //1.11*100 = 111
console.log("1.12*100 =", parseInt((1.12*100)*100+0.1)/100);  //1.12*100 = 112
console.log("1.13*100 =", parseInt((1.13*100)*100+0.1)/100);  //1.13*100 = 113
console.log("1.14*100 =", parseInt((1.14*100)*100+0.1)/100);  //1.14*100 = 114
console.log("1.15*100 =", parseInt((1.15*100)*100+0.1)/100);  //1.15*100 = 115
console.log("1.16*100 =", parseInt((1.16*100)*100+0.1)/100);  //1.16*100 = 116

console.log("0.7/0.1 = ", parseInt((0.7/0.1)*100+0.1)/100);   //0.7/0.1 =  7
console.log("0.6/0.1 = ", parseInt((0.6/0.1)*100+0.1)/100);   //0.6/0.1 =  6
View Code

 

5、call、apply和bind使用方法

  call、apply、bind的存在都是为了可以改变函数运行时的“上下文”,也就是函数的this对象指向。使用格式为:

  • call使用格式:函数.call(上下文,  参数1,  参数2, ...)   ==   fn.call(obj,  arg1,  arg2, ....)
  • apply使用格式(参数写在数组里):函数.apply(上下文,  [参数1,  参数2, ...])   ==   fn.apply(obj, [arg1,  arg2,  ...])
  • bind使用格式:函数.bind(上下文, 参数1, 参数2, ...)()   ==   函数.bind(上下文)(参数1, 参数2, ...)   ==   函数.bind(上下文, 参数1)(参数2, ...)   

说明:这三个方法的第一个参数都是this要指向的对象,第二个及后面的参数就是要调用函数的参数。call和apply是立即调用,而bind方法会创建一个新函数(称为绑定函数),所以bind方法使用时还需要再调用一下。下面看一些例子:

var obj = {
    name: "王大锤",
    sayName: function(){
        console.log("我是" + this.name);
    }
}
var obj2 = { name: "罗小虎" }

obj.sayName();            //我是王大锤
obj.sayName.call(obj2);   //我是罗小虎
obj.sayName.apply(obj2);  //我是罗小虎
obj.sayName.bind(obj2)(); //我是罗小虎
var obj = {
    name: "王大锤",
    sayName: function(age, sex){
        console.log("我是" + this.name + ", 年龄" + age + ", 性别" + sex);
    }
}
var obj2 = { name: "玉娇龙" }

obj.sayName(27, "男");              //我是王大锤, 年龄27, 性别男
obj.sayName.call(obj2, 18, "女");   //我是玉娇龙, 年龄18, 性别女
obj.sayName.apply(obj2, [19, "女"]); //我是玉娇龙, 年龄19, 性别女
obj.sayName.bind(obj2)(20, "女");   //我是玉娇龙, 年龄20, 性别女
obj.sayName.bind(obj2, 21)("女");   //我是玉娇龙, 年龄21, 性别女
obj.sayName.bind(obj2, 22, "女")(); ////我是玉娇龙, 年龄22, 性别女

 

 将伪数组转成数组,Object对象本来没有slice函数,但是Array有。然后可以通过call、apply、bind将Array的slice函数指向Object对象,让Object对象可以调用slice函数:

//将伪数组转成数组
var obj = { 0:99, 1: '王大锤', 2:true, 3: 0.27, 4: 'hello', length: 5 }
console.log( Array.prototype.slice.call(obj) );       //[99, "王大锤", true, 0.27, "hello"]
console.log( Array.prototype.slice.call(obj, 1, 3) ); //["王大锤", true]
console.log( Array.prototype.slice.apply(obj) );      //[99, "王大锤", true, 0.27, "hello"]
console.log( Array.prototype.slice.apply(obj, [0, 2]) ); //[99, "王大锤"]
console.log( Array.prototype.slice.bind(obj)() );       //[99, "王大锤", true, 0.27, "hello"]
console.log( Array.prototype.slice.bind(obj)(0, 3) );  //[99, "王大锤", true]
//获取数组中的最大值和最小值
console.log( Math.max.call(null, 11, 99, 22, 33) );   //99
console.log( Math.min.call(null, 11, 99, 22, 33) );   //11
console.log( Math.max.apply(null, [11, 99, 22, 33]) ); //99
console.log( Math.min.apply(null, [11, 99, 22, 33]) ); //11
console.log( Math.max.bind(null)(11, 99, 22, 33) );   //99
console.log( Math.min.bind(null)(11, 99, 22, 33) );   //11

 

6、定义类和继承的标准实现方式

6.1 定义类的方式有多种:工厂方式、构造函数方式、原型方式、混合的构造函数+原型方式、动态原型方法。目前使用最广泛的是混合的构造函数+原型方式。此外,动态原型方法也很留行,在功能上与混合的构造函数+原型方式等价。

混合的构造函数+原型方式(即用构造函数定义对象的所有非函数属性,用原型方式定义对象的函数属性(即方法))示例代码:

function Car(sColor,iDoors,iMpg) {
  this.color = sColor;
  this.doors = iDoors;
  this.mpg = iMpg;
  this.drivers = new Array("Mike","John");
}

Car.prototype.showColor = function() {
  alert(this.color);
};

var oCar1 = new Car("red",4,23);
var oCar2 = new Car("blue",3,25);

oCar1.drivers.push("Bill");

alert(oCar1.drivers);    //Mike,John,Bill
alert(oCar2.drivers);    //Mike,John

动态原型方法的基本想法和混合的构造函数+原型方式相同,即在构造函数内定义非函数属性,而函数属性则利用原型属性定义。示例代码:

function Car(sColor,iDoors,iMpg) {
  this.color = sColor;
  this.doors = iDoors;
  this.mpg = iMpg;
  this.drivers = new Array("Mike","John");
  
  if (typeof Car._initialized == "undefined") {
    Car.prototype.showColor = function() {
      alert(this.color);
    };
    
    Car._initialized = true;
  }
}

 

应用示例:

  JavaScript的字符串是不可变的,即它们的值不能改变,请考虑下面的代码:

var str = "hello ";
str += "world";

实际上,这段代码在幕后执行的步骤如下:

  1. 创建存储 "hello" 的字符串。
  2. 创建存储 "world" 的字符串。
  3. 创建存储连接结果的字符串。
  4. 把str的当前内容复制到结果中。
  5. 把 "world" 复制到结果中。
  6. 更新str,使它指向结果。

每次完成字符串都会执行步骤2到6,使用这种操作非常消耗资源。如果重复这一过程几百次,甚至几千次,就会造成性能问题。解决办法使用Array对象存储字符串,然后用join()方法(参数是空字符串)创建最后的字符串,示例代码:

function StringBuffer () {
  this._strings_ = new Array();
}

StringBuffer.prototype.append = function(str) {
  this._strings_.push(str);
};

StringBuffer.prototype.toString = function() {
  return this._strings_.join("");
};

//测试演示
var buffer = new StringBuffer ();
buffer.append("hello ");
buffer.append("world");

console.log( buffer.toString() ); //hello world

 

6.2 JavaScript的继承机制实现也有多种方式:对象冒充、call//apply方法、原型链、混合方式。最好的方式是使用混合方式,我们知道创建类的最好方式是用构造函数定义属性,用原型定义方法。这种方式同样适用于继承机制,用对象冒充继承构造函数的属性,用原型链继承prototype对象的方法,示例代码:

function ClassA(sColor) {
    this.color = sColor;
}

ClassA.prototype.sayColor = function () {
    alert(this.color);
};

function ClassB(sColor, sName) {
    ClassA.call(this, sColor);
    this.name = sName;
}

ClassB.prototype = new ClassA();
ClassB.prototype.constructor = ClassB; //修正ClassB原型的构造函数
ClassB.prototype.sayName = function () { alert(this.name); }; //新增一个属于ClassB独有的方法

6.3  ES6新增加的创建类和继承的写法:

class ClassA{
    constructor(sColor){
        this.color = sColor;
    }

    sayColor(){
        console.log('color is ', this.color);
    }
}

//继承
class ClassB extends ClassA{
    constructor(sColor, sName){
        super(sColor); //调用父级
        this.name = sName;
    }

    //属于ClassB的方法
    sayName(){
        console.log(this.name, ' color is ', this.color);
    }
}

 

posted @ 2018-11-20 19:39  谈晓鸣  阅读(763)  评论(0编辑  收藏  举报