《你不知道的JavaScript上卷》知识点整理与读书笔记_百度你不知道的js书知识

5.1 总结

我喜欢开篇,直接就总结完。

闭包产生的2种情况

  • 当函数作为另一个函数的参数
  • 函数作为返回值返回
function foo() {
    var a = 2;
    function bar() {
        console.log(a);
    }
    return bar;
}
var baz = foo();
baz();//2 朋友,这就是闭包效果

5.2 循环和闭包

要说明闭包,for循环是一个常见的例子

for ( i = 1; i <= 5; i++) {
    setTimeout(function timer(){
        console.log(i);
    }, i\*1000);
}

这段代码再运行时候会每秒一次的频率输出5次6.

延迟函数的回调会在全部循环迭代结束的之后时候进行调用(请查询宏任务、微任务相关知识点),而不是每次迭代时候调用。所以最后调用i,但是i是公共的,并且值为最后一个循环决定的6。所以结果是5次6

那怎么给每个迭代的版本获取一个实时的i,满足哪怕是最后循环迭代完再调用定时函数,但是每个定时函数都是调用自己版本的,而不是调用最后的公用6呢?

那就是每次循环迭代时候,我们给每一个迭代都绑定一个i。如下所示,我们使用let让每一个i都再内部迭代进行绑定。

for(let i = 1; i <= 5; i++){
    setTimeout(function timer(){
        console.log(i);
    },i\*1000);
}

5.3 模块

在js中的模块也是和闭包息息相关。

模块:

  • 必须要有外部的封闭函数,该函数必须要被调用一次
  • 封闭的函数至少要返回一个内部函数
  • 使用立即执行函数配合有奇效
var foo = (function(){
    var something = "cool";
    var another = [1,2,3];
    function doSomething() {
        console.log(something);
    }
    function doAnother() {
        console.log(another.join("!"));
    }
    return {
        doSomething,
        doAnother
    }
})();
foo.doSomething();//cool
foo.doAnother();//1!2!3

第二部分 this和对象原型

第1章 关于this
1.1为什么要使用this

首先记住,this提供了一种更优雅的方式隐式的传递一个对象引用,从而使API设计更加的简洁,并且更加易于复用

1.2对this的一些误解
  • 误解一、指向自身
    按照this这个单词的语意,我们总是会把他认为是指向自身,事实上有些时候确实如此,但是并不是总指向自身。分析下面的模式。
function foo(num) {
    //记录count被调用的次数
    this.count++;
}
foo.count = 0;
var i;
for(i=0;i<10;i++){
    if(i>5){
        foo(i);
    }
}
//foo:6
//foo:7
//foo:8
//foo:9
console.log(foo.count);//0

是不是没想到,哈哈哈。我读到这里时候也是没想到,好像就是从来没有仔细想过这件事一样。首先这里解释一下,这里的this.count会在全局创建一个变量,值为NaN。至于为什么后面第2章在解释,这里只是为了说明this真的不是任何时候指向自己。下面是单独打印的结果。

(function foo(num) {
    //记录count被调用的次数
    this.count++;
    console.log(this);//window,也就是全局
    console.log(this.count);//NaN
}())

  • 误解二、指向他的作用域
    首先这里this有时候指向作用域,有时候又不是,但是明确的一点就是任何时候this都是不会指向他的词法作用域。因为词法作用域是属于引擎的,无法通过js代码进行访问。

首先明确一下前面的案例中foo()中的this是指向window,这里我们在前面的基础上改一改

function foo() {
    var a = 2;
    this.bar();
}
function bar() {
    console.log(this.a);
}
foo();//undefined

明明this是window,也就是全局,然后bar也在全局,this.bar()没问题,但是因为bar中有this,按照词法作用域,bar在foo中,this.a右查询,就会查询bar的作用域中的a,但是没有就向上面找,上面foo中有a,所以调用。可是事实呢,结果是找不到,说明this.a进行右查询之后压根没有往上,或者往上后查找失败。(学了后面知道,是没有往上,因为bar中的this不是指向bar作用域,而是全局,所以最顶层没有,也不往上,直接undefined。至于是为什么bar中的this以及foo中的this均为window,请看后面的章节)

第2章 this全面解析
2.1调用位置

调用位置就是函数在代码中被调用的位置,不是声明的位置。

function foo() {
    function bar() {
   }
   bar();//在foo中声明,所以bar的调用位置在foo中
}
foo();//在全局中声明,所以foo内的调用位置在window上

2.2绑定规则
  • (1)默认绑定
    this绑定在window上。这个时候满足:查看函数调用位置时候,函数是光秃秃的直接调用,比如前面2.1中的bar,尽管是在foo中调用,可以打印看看bar中this是不是指向window
  • (2)隐式绑定
    this绑定到某个对象上。这个时候满足,这个对象的一个属性是一个函数,并且调用这个函数属性时候,是通过对象.函数名()或者对象函数名调用。否者会出现隐式丢失。
var a = 3;//我是全局的3
function foo(){
    console.log(this.a);
}
var obj = {
    a:2,//我是对象的2
    foo:foo
};

obj.foo();//2,this绑定的是obj
obj["foo"]();//2,this绑定的是obj

var bar = obj.foo;//注意,这里可不是调用,后面没有(),这里只是拿出来
bar();//结果是3,明明bar就是foo函数,
//但是注意,这里并不是我们说的调用方法的两种之一
//所以呀,绑定丢失。而这里,
//bar的调用位置是光秃秃的直接调用,
//所以这里this绑定到window上咯 

  • (3)显式绑定
    this绑定到某个对象上,但是其变种不会产生隐式丢失。这种方法很粗暴,直接使用call(),或者apply()。这两个函数,直接修改this的绑定对象。
function foo(){
    console.log(this.a);
}
var obj = {
    a:2,
    foo:foo
};
foo.call(obj);//2。也就是把foo函数的this,绑定到obj上

可是在进行如下调用时候,也会丢失

var a = 3;//我是全局的3
function foo(){
    console.log(this.a);
}
var obj = {
    a:2,
    foo:foo
};
foo.call(obj);

//下面是调用
var bar = obj.foo;
bar()//是3,哈哈哈

显示绑定的变种的核心思想都是外面包裹一个函数,每次调用这个包裹函数就好,哪怕是在调用位置光秃秃调用,都不会绑定到全局的window,因为调用包裹函数,每次自动调用一次apply或者call。下面是变种的写法。

//变种一,直接包裹一个函数
var a = 3;//我是全局的3
var obj = {
    a:2,//我是对象的2
    foo:foo
};
function foo(){
    console.log(this.a);
}
function bar() {
    return foo.call(obj);
}

bar();//2 所以每次这样就好,哈哈哈

  • (4)new绑定
    首先JavaScript中new和其他语言中的new是完全不同。
    JS中的new:使用new来调用构造函数,但JS中构造函数不属于某个类,也不会实例化某个类。(关于更多的解释,在第6章)

new的机制为:
创建一个全新的对象
这个新对象执行[原型]链接
这个新对象绑定到函数调用的this中
如果函数没有返回其他对象,new自动返回这个新对象

function foo(a){
    this.a =a ;
}
var bar = new foo(2);
console.log(bar.a);//2

2.3优先级

不举例了,这里直接总结结论。

  • new最大
  • 显示
  • 隐式
  • 默认
2.4箭头函数

箭头函数是ES6中新东西,不遵循上面的4条绑定规则。而是根据外层作用域决定的。

var a = 3;
var obj = {
    a:2
};
function foo(){
   ((a)=>{
        //this继承foo
        console.log(this.a);
    })()
}
foo()//3 这里光秃秃调用,foo中this是window,所以回调中是window的a

var a = 3;
var obj = {
    a:2
};
function foo(){
   ((a)=>{
        //this继承foo
        console.log(this.a);
    })()
}
foo.call(obj)//2 这里显示调用,foo中this是obj,所以回调中是obj的a

第3章 对象
3.1对象定义的语法

有两种方式,一种是通过声明形式,一种是通过构造形式

//声明
var obj = {
    key:value
}

//构造
var obj = new Object();

3.2类型

对象有6种基本类型

  • string
  • number
  • boolean
  • null
  • undefined
  • object
    当然这些基本类型本身不是对象,只是说对象是根据基本类型划分有这么几个类型。不明白的可以继续看下去。

内置对象
JS中有许多特殊的对象子类型,被成为内置对象。下面的一些内置对象的名字和基本类型相似,但是不是同一个东西。下面的是实实在在的一个对象,不像前面的基本类型只是判别标准

  • String
  • Number
  • Boolean
  • Object
  • Function
  • Array
  • Date
  • RegExp
  • Error
    这些内置的对象好比java语言中的包装类,但是你先明白JavaScript中的类都是函数,是通过函数表达类的表象(这是后话,后面的第四章会有详细的类的讲解)
3.3对象中的内容

首先要强调一点,我们说的内容是,似乎暗示说这些值在对象内部,但是其实不是。如果是学过c语言或者c++的指针,或者明白说java中的引用。理解这一点是很容易的。在js中的对象的内容,对象中对的属性并不保存某些值,而是通过引用的方式,存放的是一些真正值的地址。

书中的3.3.2中,再次阐述,一个函数与一个对象的关系。我通读完后,觉得其实就是任然说明和前面this隐式绑定一个对象的道理一样。一个函数无论如何也不要理解为属于一个对象。我们应该理解为这个对象拥有这个函数,或者说这个对象目前是这个函数的落脚点。毕竟,在对象中,如果某个属性是函数,那么这个属性保存的值其实是这个函数的引用而已。

特别注意数组也是js中的子对象Array。然后尽管是数组按照组织下标的方式存储数据,但是你也可以为数组添加key:value的形式的内容。还记得吗,数组不是有一个length属性吗,这个就是最好的例子。

var array = ["foo","22"];
array.bar = "bar";
console.log(array.bar)//bar

现在关于引用,探讨对对象的内容的拷贝。因为引用的存在,所以出现了深浅拷贝。浅拷贝是拷贝引用,深拷贝而是彻底的进行复制一份数据。而在修改,数据时候,又牵扯到属性描述符以及setter与getter。下面的这两篇博客中部分内容对此进行了总结。

对象

getter与setter

3.4对象中的遍历

for…in循环可以用来遍历对象的可枚举属性列表(包括[prototype]链),但是如何遍历属性的值???对于数组来说:

var array = [1,2,3];
for (let i = 0; i < array.length; i++) {
    console.log(array[i]);
}

这可不是遍历数组的值,这是遍历数组的下标,而不是值!!!
通过这个说明了通过for…in遍历值是不行。但是对于数组而言有一些方法可以进行值的遍历,(针对数组的遍历的方法,由于不是这一节的重点,不总结进来)

但是对与其他的对象呢?ES6中就专门增加了for…of结构遍历值(当然数组也可使用这个哈)

var array = [1,2,3];
for(var v of array){
    console.log(v);
}
//1
//2
//3

好神奇,成功了。可是这种for…of的核心是怎么工作的呢?

for…of会首先向被迭代对象请求一个迭代器对象,然后通过调用迭代器对象的next()方法来遍历所有的返回值。

某种数据结构中必须要有@@iterator,才可以给for…of一个迭代器对象

数组中有内置的@@iterator,所以可以给for…of一个迭代器对象

var array = [1,2,3];
var it = array[Symbol.iterator]();

it.next();//{value: 1, done: false}
it.next();//{value: 2, done: false}
it.next();//{value: 3, done: false}
it.next();//{value: undefined, done: true}

  • 我们使用Symbol.iterator获取@@iterator内部属性,关于点击这里Symbol,请记住引用类似的类似iterator特殊属性时候,要使用符号名array[Symbol.iterator],而不是单独一个array[iterator]
  • array[Symbol.iterator]也就是@@iterator本身不是迭代器,而是一个返回迭代器的函数,所以上面的的代码中,通过后面()调用,返回一个迭代器,然后赋值给it

所以最后,让我们按照上面的2条,我们自己为一个遍历的对象实现一个迭代器

var obj = {
    a:2,
    b:3
}
//下面代码的意义在于给obj定义一个Symbol.iterator属性,这个属性是一个函数,用来返回迭代器
Object.defineProperty(obj,Symbol.iterator,{
    enumerable:false,
    writable:false,
    configurable:true,
    //看到了吗,这个属性值是一个函数
    value:function () {
        var o = this;//也就是这个对象本身,想一想为什么this是指向这个对象本身,我保证前面的this里面讲的知识,绝对可以分析出来这里的this指向哪里
        var idx = 0;
        var ks = Object.keys(o);//返回这个对象的键值列表
        return{
            next:function () {
                return{
                    value:o[ks[idx++]],
                    done:(idx>ks.length)
                }
            }
        }
    }
})

//使用for...of遍历
for(var v of obj){
    console.log(v);
}


  • 这一章需要有一定的类的基础,不然读起来真的云里雾里。如果没有请先查阅其他资料补习。
4.1类理论
  • 类/继承描述了一种代码的组织结构形式。
  • 多态其实就是说父类的通用行为可以被子类用更加特殊的行为重写。(甚至相对多态允许我们从重写行为中引用基础行为)

面向对象编程强调的是数据和操作数据的行为本质上是相互关联的,因此好的设计就是把数据以及它相关的相关行为打包(封装)起来,有时候这种情况被叫做数据结构。

  • 类只是一种模式,而不是必须的!当然java,c#这些语言,没得选,只能使用类模式。
  • JavaScript中并不是必须采用的类模式。现在JavaScript中的“类”,也只是近似类。记住一点,js的“类”和他们都不一样,这也是加双引号的原因。
4.2类的机制

书中表达的观点是,类和实例对象之间的关系看作之间关系而不是间接关系更好。因为所谓的类的关系,都是复制而已。具体怎么复制,以及细节,看下面。

构造函数就是复制的关键点。他的目的就是完成复制的关键。术语叫做初始化对象。

构造函数的特点

  • 使用new来调用构造函数
  • 函数名与类名相同
  • new的过程,请看前面讲解this时候笔记
4.3类的继承

下面的书中的例子,是我觉得这个章节中最最最精彩的部分
下面的伪代码例子说明了2点:

  • 1、在js中的继承就是复制!!!
  • 2、js中的多态!!!
  • 3、js中的相对多态!!!
//下面几个类均不含构造方法

class Vehicle{ //交通工具类
    engines = 1 //交通工具的属性

    ignition(){//交通工具的方法
        output("Turning on my engine , Vehicle!")
    }

    drive(){//交通工具的方法
        ignition();
        output("Steering and moving forward")
    }
}

class Car inherits Vehicle{ //一个继承交通工具类的汽车类
    wheels = 4//汽车的属性

    drive(){//这里就是子类重写父类的方法
        inherited:drive();//这里就是子类引用父类基础行为,这就是相对多态
        output("Steering and moving forward")//这里就是子类更加特殊的行为,这就是多态
    }
}

class SpeedBoat inherits Vehicle{ //一个继承交通工具类的快艇类
    engines = 2//快艇的属性

    ignition(){//快艇的方法 这里就是子类重写父类的方法
        output("Turning on my engine ,SpeedBoat!")//这里就是子类更加特殊的行为,这就是多态
    }

    drive(){//快艇的方法 子类重写父类的方法
        inherited:drive();//这里就是子类引用父类基础行为,这就是相对多态
        output("SpeedBoat through the water with ease")//这里就是子类更加特殊的行为,这就是多态
    }
}

好啦,上面的2、3点都写在注释中了,可是“在js中的继承就是复制!!!”,我们还没有得到解释。

仔细看一个有趣的点:

  • (1)SpeedBoat中有一个drive方法对吧
  • (2)这个是引用的方法对吧
  • (3)所以SpeedBoat中的drive方法来自父类,自然里面的代码是这样
 drive(){//交通工具的方法
    ignition();
    output("Steering and moving forward")
}

  • (4)这个时候drive里面继续调用ignition(),确实没毛病
  • (5)回顾一下前3点:这个SpeedBoat中的drive来自Vehicle,这个drive调用ignition
  • (5)问题来了,SpeedBoat 与 Vehicle均有ignition,调用那个的呢?
  • (6)答案是SpeedBoat
  • (7)这说明了什么?

在js中的继承就是复制!!! 调用的是SpeedBoat中的,而不是Vehicle,说明了他们两个类没有所谓的关系,SpeedBoat就是Vehicle的一个复刻品,只不过后面这个复刻品有了一些自己的新东西(多态)。如果不是复制,为啥从Vehicle继承的drive,不去找Vehicle中调用ignition,而是去SpeedBoat中?直接从SpeedBoat使用ignition,说明SpeedBoat已经复制了Vehicle的一切,就使用自己,而不是去父类中找。

抱歉,鉴于我的表达能力,上面的话说的有些绕,不明白请看原书第一版的p131-p133

4.4混入

上面解释了js中的继承就是复制,这里混入就是讲解几种种不同复制方式

  • 显示混入
  • 隐式混入
    这里就不总结前两个了,问题比较多,感觉不怎么使用到。这里提前补充一个使用原型进行混入,看不懂可以跳过这段
function Animal(name) {
    this.name = name
}
Animal.prototype.showName = function () {
    console.log("我的名字是" + this.name)
}

function Dog(name,color) {
    //强制吧animal中的this,绑定当new出来的对象,
    //不知道为啥this指向new出来的对象的同学
    //请看前面this部分,以及new的几个过程
    Animal.call(this,name)//只能继承属性
    this.color = color
}
Dog.prototype = new Animal()
Dog.prototype.constructor = Dog

第5章 原型

性能优化

1.webpack打包文件体积过大?(最终打包为一个js文件)

2.如何优化webpack构建的性能

3.移动端的性能优化

4.Vue的SPA 如何优化加载速度

5.移动端300ms延迟

6.页面的重构

所有的知识点都有详细的解答,我整理成了280页PDF《前端校招面试真题精编解析》。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值