JS(重点难点)

目录

1、闭包

1.1 变量作用域

1.2 什么是闭包

1.3 闭包的缺陷

1.4 作用域链

2、js中的真假判断

3、js函数中的隐藏参数

4、立即执行函数

4.1 特点

4.2 写法

4.3 错误写法

4.4 与闭包的常用结合

5、变量的提升

6、函数的提升

7、递归

8、原型

9、数组

9.1 创建数组

9.2 基本方法

10、判断数据类型

11、事件

11.1 绑定事件

11.2 事件传播机制

11.3 阻止冒泡

12、cookie

13、es6关于对象的新特性

13.1 扩展运算符

13.2 对象初始化

13.3 新增方法

14、Map

15、for in 和for of的区别

16、什么是event loop

 结束语:


1、闭包

1.1 变量作用域

理解闭包,我们需要首先理解变量的作用域。变量的作用域可以理解为变量的使用范围。

function func(){
    console.log(num1); // 报错使用未定义的变量,程序终止
    num1 =3;
    console.log(num1);
}
func();

function func(){
    num1 =3; // 不加 var 变量提升为全局
    console.log(num1);
}
console.log(num1); // 报错
func();

function func(){
    num1 =3; // 不加 var 变量提升为全局
    console.log(num1);
}
func();
console.log(num1); // 3

function func(){
    console.log(num1); // undefined
    var num1 =3;
    console.log(num1); // 3
}
func();

var num1 =1;
function func(){
    console.log(num1); // undefined
    var num1 =3; // 此变量的声明会提升
    console.log(num1); // 3
}
console.log(num1); // 1

function func(){
    num1 =3; // 不加 var 变量提升为全局
    console.log(num1);
    for(var i=0; i<5; i++){
​
    }
    console.log(i); // 5
}
func();

js中变量的作用域分为两种:(ES6之后新增了块级作用域)

  • 全局作用域:在script标签中直接定义的属性和方法。在全局作用域中声明的变量和函数,都属于window对象。

  • 局部作用域:局部变量属于某个函数。在函数中定义的变量成为局部变量。

    • 调用函数时创建函数作用域,函数执行完毕之后,函数作用域销毁

    • 每调用一次函数就会创建一个新的函数作用域,它们之间是相互独立的

    var a = 0; // 全局变量
    function func(){
        var b =1; // 局部变量
        d = 3; // 全局变量: 不声明直接使用,会出现变量的提升
        {
            var c = 2; // 块级变量,使用var和let会有不同的结果。let会阻止变量的提升。
        }
        console.log(c);
    }
    func(); // 放在输出d的代码后,会有不同的效果。
    console.log(b);
    console.log(d);

1.2 什么是闭包

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

正常情况下,我们在函数的外部是无法读取到函数内部的变量。

那我们把这个变量变成全局变量不就解决了吗?但是全局变量就意味着此变量可以被所有函数访问,安全性低。

这个时候就需要用到闭包。

  • 外层方法中定义一个局部变量

  • 外层方法中定义一个内层方法,此方法修改局部变量,并返回局部变量

  • 外层方法将内层方法作为返回值返回

  • 在外部调用外层方法,并接受内层方法对象。

  • 在外部调用内层方法,可以获取或者修改外层方法中的局部变量

    function func(){
        var count =1; // 局部变量
        function addCount(){
            count++;
            return count;
        }
        function reduceCount(){
            count--;
            return count;
        }
        return [addCount,reduceCount];
    }
    var funArray = func();
    funArray[0]();
    funArray[1]();

有了闭包,函数内部的变量,外部无法直接访问,但是可以通过暴露出来的方法进行操作。

1.3 闭包的缺陷

为了实现闭包,我们需要把函数内部的作用域强行保留在内存中。这就是导致了这部分的内存无法回收。大量的闭包会导致内存消耗太多。

所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。

1.4 作用域链

作用域有上下级关系,上下级关系的确定就看函数是在哪个作用域下创建的。如上,fn作用域下创建了bar函数,那么“fn作用域”就是“bar作用域”的上级。

 作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。

 变量取值:到创建 这个变量 的函数的作用域中取值

在作用域链中,只能子调用父,不能父调用子。

一般情况下,变量取值到 创建 这个变量 的函数的作用域中取值。

  但是如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。

name="lwy";
function t(){
    var name="tlwy";
    function s(){
        var name="slwy";
        console.log(name);
    }
    function ss(){
        console.log(name);
    }
    s();
    ss();
}
t();

2、js中的真假判断

不是只有boolean类型才能进行逻辑运算。在js中变量可以转换为布尔值。

通常用来判断变量是否为空

    var a = 0;
    var b = 6;
    console.log(a&&b);
    console.log(b&&a);

在js中以下都是假变量:

  • false

  • 0 数字类型 注意

  • 空字符串

  • null

  • undefined

  • NaN (not a number)

3、js函数中的隐藏参数

arguments是函数体中的一个隐藏的对象。

  • 用来取参数。

  • 传入的实参都能在函数体里通过arguments类数组获取到

  • 当方法的参数个数不确定的时候,可以用它来获取参数

    function test01(num1,num2){
        // arguments可以拿到所有的实参
        console.log(arguments);
    }
    test01(1,2,3,4,5);

4、立即执行函数

什么是立即执行函数? IIFE : Immediate-invoke function expressioon

4.1 特点

立即执行函数,会在函数声明后立即执行,执行后会销毁。

4.2 写法

// 常用方式1
(function(str){
    console.log('a'+str)
}('abc'));
​
// 常用方式2
(function(str){
    console.log('a'+str)
})('abc');
​
// 常用方式3
var foo = function(){
    console.log('方式3')
}();

4.3 错误写法

    function () {
        console.log('错误写法1')
    }();
    
    function foo() {
        console.log('错误写法2')
    }();

4.4 与闭包的常用结合

let single = (function () {
    let name = "小明";
    let age = 20;
    return {
        getName: function () {
            return name;
        },
        getAge: function () {
            return age;
        }
    }
})();
console.log(single.getName()); //小明
console.log(single.getAge()); //20

5、变量的提升

通常JS引擎会在正式执行之前先进行一次预编译,在这个过程中,首先将变量声明及函数声明提升至当前作用域的顶端,然后进行接下来的处理。

    function foo() {
        a=10; // a变量没有声明直接赋值,会产生变量的提升,提升到全局作用域
        console.log(a); // 输出10
        a=20;
    }
    foo();
    console.log(a) // 输出20
    function foo() {
        a=10; // a变量没有声明直接赋值,会产生变量的提升,但是由于本作用域中出现了声明代码,所以变量提升到本作用域顶端
        console.log(a); // 输出10
        var a=20;
    }
    foo();
    console.log(a) // 报错

let 会阻止变量的提升

    function foo() {
        a=10; // 报错。 没有变量的提升
        console.log(a);
        let a=20; // 使用let声明变量,最阻止变量的提升
    }
    foo();
    console.log(a)

下面是一道经典面试题,大家猜猜运行结果

console.log(v1); // undefined
var v1 = 100;
function foo() {
    console.log(v1); // undefined
    var v1 = 200;
    console.log(v1);// 200
}
foo();
console.log(v1);// 100

当在不同的作用域中都声明了同名变量。那js引擎会优先调用本作用域的变量。

6、函数的提升

js在预编译阶段,会把函数的声明,提前到代码的前端。

函数的声明和函数的调用是两回事。函数的声明会提升,但是函数的调用是顺序执行的。

函数的作用域,只有在函数执行时才会产生,运行完毕后销毁。

函数的声明有两种:

  1. 函数式声明。会有函数的提升

  2. 字面量声明。不会有函数的提升

    console.log(foo);
    function foo() { // 函数的声明提升
        console.log(1);
    }
    foo();
    console.log(foo);
    var foo = function () { // 函数声明不提升
        console.log(1);
    }
    foo();

7、递归

递归就是在方法内部自己调用自己。

递归中一定要有退出判断,否则就是死循环。

    function foo(i) {
        if (i == 4) {
            return;
        }
        console.log("fb:" + i);
        foo(i + 1);
        console.log("fe:" + i);
    }
    foo(1);

8、原型

prototype。每个方法都有一个对象prototype,这个对象就是原型。

一般工作的时候不常用,开发框架和插件的时候使用。但是面试问的多。

原型的作用:

  • 给构造出来的对象设置公共的属性和方法

  • 建立了构造函数和实例化出来对象的联系

多个原型之间是有类似继承关系的,构成了原型链。原型链的尽头是null,属于object对象。

9、数组

数据就是放置多个数据的集合对象。js中数组的长度和存储的类型是不固定的。

9.1 创建数组

var list1 = new Array(4); // 创建一个长度为4的数组,里面有4个空值
var list2 = ['1', 2, 3, true]; // 创建数组并赋值。

9.2 基本方法

    var list = [1, 2, 3]; // 创建数组并赋值。
    console.log(list.indexOf(1));// 返回1这个元素在数组中第一次出现的下标,如果没有返回-1
    list[1] = 22; // 修改指定下标的值
    list.push(4,5);// 向数组末尾追加元素,元素数量不定。返回新的长度
    list.unshift(-1,0) // 向数组头部添加元素,元素数量不定。返回新的长度
​
    list.pop(); // 删除最后一位元素,返回被删除的元素
    list.shift(); // 删除第一位元素,返回被删除的元素
​
    list.splice(1); // 从指定下标开始删除,删到最后。返回删除的元素数组
    list.splice(1,2); // 删除从下标1开始,长度为2的元素。返回删除的元素数组
    list.splice(1,1,33,44); // 删除从下标1开始,长度为1的元素,并在删除的位置上添加两个元素,添加的元素个数不定
    
    var str = list.join(''); // 把数组按照指定的间隔符,拼成一个字符串
​
    var list3 = [11,22,33];
    var newlist = list.concat(list3); // 合并数组,注意合并数组返回的是新的数组对象,对原对象不做修改
    // console.log(list2)
    // console.log(str);
    console.log(list);

10、判断数据类型

  • typeof:此方法直接返回原型链上的最后一个对象。

  • instanceof: 此操作符会查找原型链上是否存在这个构造函数。存在返回true。

11、事件

11.1 绑定事件

绑定事件有三种方式:

  • 在html中添加事件属性。html和js代码耦合在一起,不利于维护。不推荐。

<body>
    <ul id="list" οnclick="func()">
        <li>1</li>
        <li>2</li>
        <li>3</li>
    </ul>
</body>
<script>
    function func(){
        console.log('触发了')
    }
</script>
  • 在js中调用事件属性。会产生覆盖,只能写一次。本质上和上面一样。

<body>
    <ul id="list">
        <li>1</li>
        <li>2</li>
        <li>3</li>
    </ul>
</body>
<script>
    var listDom = document.getElementById('list');
    listDom.onclick = function(){
        console.log('触发了1')
    };
    listDom.onclick = function(){
        console.log('触发了2')
    };
</script>
  • 在js中添加事件监听器。不会覆盖,可以绑定多个。

<body>
    <ul id="list">
        <li>1</li>
        <li>2</li>
        <li>3</li>
    </ul>
</body>
<script>
    var listDom = document.getElementById('list');
    listDom.onclick = function(){
        console.log('触发了1')
    };
    listDom.onclick = function(){
        console.log('触发了2')
    };
    listDom.addEventListener('click',function(){
        console.log('触发了3');
    });
    listDom.addEventListener('click',function(){
        console.log('触发了4');
    });
​
    var func5 = function(){
        console.log('触发了5');
    }
    listDom.addEventListener('click',func5); // 添加事件
    listDom.removeEventListener('click',func5); // 移除指定方法
</script>

11.2 事件传播机制

事件有两种传播机制:

  • 冒泡 。先触发子元素事件,再触发父元素事件

  • 捕获。 先触发父元素事件,再触发子元素事件

<body>
    <ul id="parent">
        <li id="chlid1">1</li>
        <li>2</li>
        <li>3</li>
    </ul>
</body>
<script>
    var parentDom = document.getElementById('parent');
    parentDom.addEventListener('click',function(){
        console.log('parent');
    },true) // 第三个参数控制事件传播机制,默认false冒泡,true捕获
    var child1Dom = document.getElementById('chlid1');
    child1Dom.addEventListener('click',function(){
        console.log('chlid1');
    })
</script>

11.3 阻止冒泡

    var parentDom = document.getElementById('parent');
    parentDom.addEventListener('click',function(){
        console.log('parent');
    })
    var child1Dom = document.getElementById('chlid1');
    child1Dom.addEventListener('click',function(event){
        console.log('chlid1');
        event.stopPropagation(); // event为事件对象,调用stopPropagation()阻止冒泡
    })

12、cookie

原生的js操作cookie比较麻烦,我们自己可以封装一些方法来使用。

    function setCookie(cname, cvalue) {
        var d = new Date();
        d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
        var expires = "expires=" + d.toGMTString();
        document.cookie = cname + "=" + cvalue + "; "+expires;
    }
​
    function getCookie(cname) {
        var name = cname + "=";
        var ca = document.cookie.split(';'); // 将字符串串以;分割数组
        for (var i = 0; i < ca.length; i++) {
            var c = ca[i].trim(); // 把多余空格和回⻋车删掉
            if (c.indexOf(name) == 0) {
                return c.substring(name.length, c.length);
            }
        }
        return "";
    }

13、es6关于对象的新特性

13.1 扩展运算符

{
    let person = {id:'1',name:'张三',sex:1};
    let personitem = {name:'王五',pwd:'abc'};
    // 复制对象,和复制数组一样
    let newPerson = {...person};
    console.log(newPerson);
    
    
    
    // 复制对象,并修改其中的某几个属性
    let newPerson1 = {...person, name:'李四',sex:0,love:'吃'}
    console.log(newPerson1);
    
    
    
    // 合并对象,谁在后谁优先
    let newPerson2 = {...person,...personitem};
    console.log(newPerson2)
}

13.2 对象初始化

{
    // 属性初始化简写
    let objes5 = {
        name:name,
        sayHello:function(){
            console.log(this.name)
        }
    }
    objes5.sayHello();
    let objes6 = {
        name,
        sayHello(){
            console.log(this.name)
        }
    }
}
{
    // 可计算的属性名
    let key = 'name';
    let objes5 = {};
    objes5[key] = '张三';
    console.log(objes5);
​
    let objes6 = {
        [key]:'李四'
    }
    console.log(objes6);
}

13.3 新增方法

{
    // Object.is() 判断两个对象是否相等,类似于===。但也有不同
    let result = Object.is(NaN,NaN);
    console.log(result);
    console.log(NaN===NaN);
​
    let person1 = {id:'1',name:'张三',sex:1};
    let person2 = {id:'1',name:'张三',sex:1};
    let person3 = person1;
    console.log(Object.is(person1,person2));
    console.log(Object.is(person1,person3));
}
{
    // Object.assign() 复制对象,注意复制简单属性,类属性还是引用
    let sourcePerson = {id:'1',name:'张三',sex:1,info:{height:180}};
    let targetPerson = {}
    Object.assign(targetPerson,sourcePerson);
    targetPerson.info.height=160;
    targetPerson.name='李四';
    console.log(sourcePerson,targetPerson)
}
{
    // Object.keys(); Object.values(); Object.entries();
    let json = {id:'1',name:'张三',sex:1};
    let obj = {};
    for (const key of Object.keys(json)) {
        obj[key] = json[key]
    }
    console.log(obj);
}

14、Map

js中的对象本身就是一个Hash结构的,但是他的key只能是string类型。

Map数据结构他的key就可以是任意类型。

{
    let map1 = new Map();
    // 添加方法
    map1.set([1,2,3],'abc');
    console.log(map1);
​
    // 声明并赋值
    let map2 = new Map([['name','张三'],['sex','男']]);
    console.log(map2);
    console.log(map2.size); // size属性
    // 遍历方法 map2.keys(); map2.values(); map2.entries();
    for (const key of map2.keys()) {
        console.log(key);
    }
    // 是否包涵key
    console.log(map2.has('name'));
    // 根据key删除
    console.log(map2.delete('name'));
    map2.clear(); // 清空
}

15、for in 和for of的区别

1、for...in 循环:只能获得对象的键名,不能获得键值

for...of 循环:允许遍历获得键值

var arr = ['red', 'green', 'blue']
 
for(let item in arr) {
  console.log('for in item', item)
}
/*
  for in item 0
  for in item 1
  for in item 2
*/
 
for(let item of arr) {
  console.log('for of item', item)
}
/*
  for of item red
  for of item green
  for of item blue
*/

2、对于普通对象,没有部署原生的 iterator(迭代器) 接口,直接使用 for...of 会报错

var obj = {
   'name': 'Jim Green',
   'age': 12
 }
 
 for(let key of obj) {
   console.log('for of obj', key)
 }
 // Uncaught TypeError: obj is not iterable

可以使用 for...in 循环遍历键名

for(let key in obj) {
   console.log('for in key', key)
 }
 /*
   for in key name
   for in key age
 */

也可以使用 Object.keys(obj) 方法将对象的键名生成一个数组,然后遍历这个数组

for(let key of Object.keys(obj)) {
   console.log('key', key)
 }
 /*
   key name
   key age
 */

3、for...in 循环不仅遍历数字键名,还会遍历手动添加的其它键,甚至包括原型链上的键。for...of 则不会这样

let arr = [1, 2, 3]
arr.set = 'world'  // 手动添加的键
Array.prototype.name = 'hello'  // 原型链上的键
 
for(let item in arr) {
  console.log('item', item)
}
 
/*
  item 0
  item 1
  item 2
  item set
  item name
*/
 
for(let value of arr) {
  console.log('value', value)
}
 
/*
  value 1
  value 2
  value 3
*/

4、forEach 循环无法中途跳出,break 命令或 return 命令都不能奏效

let arr = [1, 2, 3, 5, 9]
arr.forEach(item => {
  if(item % 2 === 0) {
    return
  }
  console.log('item', item)
})
/*
  item 1
  item 3
  item 5
  item 9
*/

for...of 循环可以与break、continue 和 return 配合使用,跳出循环

for(let item of arr) {
   if(item % 2 === 0) {
     break
   }
   console.log('item', item)
 }
 // item 1

总之,for...in 循环主要是为了遍历对象而生,不适用于遍历数组

for...of 循环可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象

16、什么是event loop

  • 主线程运行的时候会生成堆(heap)和栈(stack);

  • js 从上到下解析方法,将其中的同步任务按照执行顺序排列到执行栈中;

  • 当程序调用外部的 API 时(比如 ajax、setTimeout 等),会将此类异步任务挂起,继续执行执行栈中的任务。等异步任务返回结果后,再按照顺序排列到事件队列中;

  • 主线程先将执行栈中的同步任务清空,然后检查事件队列中是否有任务,如果有,就将第一个事件对应的回调推到执行栈中执行,若在执行过程中遇到异步任务,则继续将这个异步任务排列到事件队列中。

  • 主线程每次将执行栈清空后,就去事件队列中检查是否有任务,如果有,就每次取出一个推到执行栈中执行,这个循环往复的过程被称为“Event Loop 事件循环”

 结束语:


首先,恭喜大家已经阅读完整个JS(重点难点),一般而言,不管书籍也好,能够完整跟下来的就已经很不容易了。所以尽量帮助初学者减少初级的困难,其实一旦掌握了之后,会发现它其实是非常容易。但大道至简,知易行难,需要大家之后不断练习,在此基础上加强知识的认知深度。虽然我尽量以通俗易通的形式,将内容体现出来,但水平毕竟有限,望大家海涵。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值