js知识点整理

37 阅读5分钟

立即执行函数&作用域:点击第几个弹出几

// 有问题的代码:
var list = document.getElementById("list");
var li = list.children;
for(var i = 0 ;i<li.length;i++){
  li[i].onclick=function(){
    alert(i);  // 结果总是3.而不是0,1,2
    }
}
// 方案1:立即执行函数
var list = document.getElementById("list");
var li = list.children;
for(var i = 0 ;i<li.length;i++){
    (function(j) {li[j].onclick=function(){
        alert(j);  // 结果是0,1,2
    }})(i)
}
// 方案2:let 块级作用域
var list = document.getElementById("list");
var li = list.children;
for(let i = 0 ;i<li.length;i++){
  li[i].onclick=function(){
    alert(i);  // 结果是0,1,2
    }
}

for...in VS for...of

  • for...in包含对象,获取到的是下标或者键名。for...of不能用于对象,拿到的是元素值。
  • 可遍历对象的例如: for...in, Object.keys/values/entires

null VS undefined

  • null:是object的一个特殊值,表示对象不是有效的对象,是一个不存在的对象的占位符。
  • undefined:声明了变量,但是没有给变量赋值,或者访问对象上不存在的属性
  • typeof null === 'object' / typeof undefined === 'undefined'
  • Number(null) ==> 0 / Number(undefined) ==> NaN

instanceof VS typeof

  • typeof可检测 number、string、boolean、function、undefined、object。
  • instanceof是对原型链进行判断,可以分别array和object

原型链

事件循环、宏任务、微任务

递归

// 阶乘
function factorial(num){
    return num == 1 ? 1 : num * factorial( -- num )
}
factorial(5)  // 120// 累加
function sum(num){
    return num == 1 ? 1 : num + sum(num - 1)
}
sum(10)  // 55// 递归实现类似reduce求和
function sum( ...args ){
    return args.length == 0 ? 0 : args.pop() + sum( ...args)
};
​
sum( 1,3,5,7,9 ); // 25

this指向问题

  • this是一个指针型变量,它动态指向当前函数的运行环境
  • 他永远指向所在函数的真实调用者,如果没有调用者,就指向全局window
  • 箭头函数中的this,指向的是函数定义位置的上下文this
  • 如果有new关键字,this指向new出来的那个函数
  • 普通函数、匿名函数、立即执行函数、回调函数的this都是指向window
  • 对象下的函数,谁调用就指向谁
  • dom回调,this指向绑定事件的对象。

改变this指向的三种方法call、bind、apply

  • 共同点

    • 三个函数的第一个参数都是改变this的指针
  • 不同点

    • call和apply都会自动执行,bind不会立刻指向,需要手动调用一次,有两次传参的机会(可以bind时候传入参数,也可以在调用的时候传入参数。如果都有参数,则以先传入的为主,按顺位取参)
    • call和bind都可以接收多个参数,apply只接收两个参数,第二个参数是一个数组。
  • 代码示例

// 用=改变dom事件中的this指向
<body>
    <button>hd</button>
    <button>cms</button>
</body>
<script>
    function show(){
        alert(this.innerHTML)
    }
    let btns = document.querySelectorAll('button');
    for(var i = 0; i< btns.length; i ++){
        btns[i].addEventListener('click', (event)=>{
            // call 改变this指向,将show的this指向成event.target 
            // call apply 都会立即执行,bind不会立即执行,要手动调一次
            // call bind都可以有多个入参,apply的入参只有两个,第二个是数组
            show.call(event.target)
        })
    }
</script>
// 用Math取数组中最大值
let arr = [1,4,3,5,3,2];
// let max = Math.max(...arr);
// Math.max本身并不支持数组,调用apply将数组作为参数,打散后,传给Math.max。这样参数就从数组变成了打散之后的数据了。目的就是打散数组,传给Math.max,所以不关注this指向,可以指定为null。
let max = Math.max.apply(null, arr)  // apply第二个入参为数组,用bind、call的话,要将arr打散。
console.log( max );
// 构造函数的方法继承
function Request(){
    this.get = function(params){
        let str = Object.keys(params).map( key => `${ key }=${params[key]}`).join('&');
        return `https://${this.url}?${str}`
    }
}

function Article(){
    this.url = 'articles/list';
    // bind 不会立即执行,如果是要用bind的话,就需要手动执行一次。用call或者apply则不需要。
    // 此处 执行Request函数,往this中追加一个get属性,后边在访问属性的时候,才可以拿到。
    Request.bind(this)();
}
function News(){
    this.url = 'articles/list';
    Request.call(this);
}
let a = new Article();
let n = new News();

console.log( n.get({ id: 2, cat: 'news' }));
console.log( a.get({ id: 1, cat: 'js'}) );
// bind不会立刻执行,例如代码所举例所用,在点击的时候才会执行
<body>
    <button>hd</button>
</body>
<script>
    let btn = document.querySelector('button');
    btn.addEventListener('click', function(event){
        console.log( this.url + event.target.innerHTML ); // baidu.comhd
    }.bind({ url: 'baidu.com' }))
</script>
// 利用bind随机变化dom的背景颜色
<body>
    <button>hd</button>
</body>
<script>
    function Color(elem){
        this.elem = elem;
        this.colors = ['#0189ff', '#04899da', '#ff3322'];
        this.run = function(){
            setInterval(function(){
                let i = Math.floor(Math.random()* this.colors.length);
                this.elem.style.background = this.colors[i]
            }.bind(this), 1000)
        }
    }
    let c = new Color(document.body);
    c.run();
</script>

闭包

定义:闭包就是能够读取其他函数内部变量的函数。

作用:让我们可以间接访问到函数内部的变量,延长变量的使用寿命,减少命名空间的污染。

特性:1)函数嵌套函数。 2)函数内部可以引用外部的参数和变量。 3)参数和变量不会被垃圾机制回收

缺点:1)滥用闭包可能导致大量变量不会被垃圾回收,消耗内存,甚至导致卡顿。2)函数内部的没有被释放,占有内存的时间长,容易造成内存泄露。

解决方案:注意编码习惯,在退出函数之前,将不再使用的变量及时的释放。

// 缺点以及优化方案的代码示例
function fn1(){
    var arr = new Array[100000]
    function fn2(){
        console.log(arr.length)
    }
    return fn2
}
var f = fn1()
f()
// 代码的缺陷之处就在于创建了一个十万个元素的数组,存在了内存中,没有释放,造成了内存浪费。
// 优化代码示例:在使用之后,手动清空变量,释放内存
var f = fn1()
f()
f = null //让内部函数成为垃圾对象,从而回收闭包
// 最简单的闭包函数
function hd(){
    let n = 1;
    return function(){
        console.log( ++n );
    }
};
// hd返回一个函数,在外部被接收。hd中的n也就被保存了下来。在执行a函数时候,实现累加的效果
let a = hd();
a();  // 2
a();  // 3
// 下边代码与上边同理,只是多嵌套了一层
function hd(){
    let n = 1;
    return function(){
        let m = 1;
        // 多嵌套一层,相同的原理,都是为了把内部变量保存下来,实现累加的效果
        return function(){
            console.log( ++m );
        }
    }
};
// hd返回一个函数,在外部被接收。hd中的n也就被保存了下来。在执行a函数时候,实现累加的效果
let a = hd()();
a();  // 2
a();  // 3
// 构造函数中作用域的使用形态
function Hd(){
    let n = 1;
    this.add = function(){
        console.log( ++n );
    }
};
let a = new Hd(); // a对象中含有add属性,add又使用到了n。作用域内的值被保留了下来
a.add();
a.add();