js前端基础进阶
- 栈数据结构(先进后出,后进先出)
- 堆数据结构(树状结构,可以无序)
- 队列(先进先出的数据结构)
- 基础数据类型与变量对象(Undefined、Null、Boolean、Number、String、Symbol)
- 引用数据类型与堆内存(object包含:function、Array、Date)

- 分配你所需要的内存
- 使用分配到的内存(读、写)
- 不需要时将其释放、归还
- 其实很简单,就是找出那些不再继续使用的值,然后释放其占用的内存。垃圾收集器会每隔固定的时间段就执行一次释放操作。
- 在JavaScript中,最常用的是通过标记清除的算法来找到哪些对象是不再继续使用的,因此a = null其实仅仅只是做了一个释放引用的操作,让 a 原本对应的值失去引用,脱离执行环境,这个值会在下一次垃圾收集器执行操作时被找到并释放。而在适当的时候解除引用,是为页面获得更好性能的一个重要方式。
- 在局部作用域中,当函数执行完毕,局部变量也就没有存在的必要了,因此垃圾收集器很容易做出判断并回收。但是全局变量什么时候需要自动释放内存空间则很难判断,因此在我们的开发中,需要尽量避免使用全局变量。
- JavaScript代码运行起来会首先进入该环境
- 函数环境:当函数被调用执行时,会进入当前函数中执行代码
- eval(不建议使用,可忽略)
- 单线程
- 同步执行,只有栈顶的上下文处于执行中,其他上下文需要等待
- 全局上下文只有唯一的一个,它在浏览器关闭时出栈
- 函数的执行上下文的个数没有限制
- 每次某个函数被调用,就会有个新的执行上下文为其创建,即使是调用的自身函数,也是如此
当调用一个函数时(激活),一个新的执行上下文就会被创建。而一个执行上下文的生命周期可以分为两个阶段。
- 建立arguments对象。检查当前上下文中的参数,建立该对象下的属性与属性值。
- 检查当前上下文的函数声明,也就是使用function关键字声明的函数。在变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用。如果函数名的属性已经存在,那么该属性将会被新的引用所覆盖。
- 检查当前上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为undefined。如果该变量名的属性已经存在,为了防止同名的函数被修改为undefined,则会直接跳过,原属性值不会被修改。

- 基础数据类型与引用数据类型
- 内存空间
- 垃圾回收机制
- 执行上下文
- 变量对象与活动对象
- 在JavaScript中,我们可以将作用域定义为一套规则,这套规则用来管理引擎如何在当前作用域以及嵌套的子作用域中根据标识符(变量名或者函数名)进行变量查找。
- 在JavaScript中,只有全局作用域与函数作用域(因为eval我们平时开发中几乎不会用到它,这里不讨论)。
- 作用域与执行上下文是完全不同的两个概念。我知道很多人会混淆他们,但是一定要仔细区分。
- JavaScript代码的整个执行过程,分为两个阶段,代码编译阶段与代码执行阶段。

- 作用域链,是由当前环境与上层环境的一系列变量对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。
- 作用域链是由一系列变量对象组成,我们可以在这个单向通道中,查询变量对象中的标识符,这样就可以访问到上一层作用域中的变量了。
- 闭包是一种特殊的对象。它由两部分组成,执行上下文(代号A),以及在该执行上下文中创建的函数(代号B)。
- 当B执行时,如果访问了A中变量对象中的值,那么闭包就会产生。
- 在大多数理解中,包括许多著名的书籍,文章里都以函数B的名字代指这里生成的闭包。而在chrome中,则以执行上下文A的函数名代指闭包。
- 通过闭包,我们可以在其他的执行上下文中,访问到函数的内部变量。
- 柯里化(在函数式编程中,利用闭包能够实现很多炫酷的功能,柯里化便是其中很重要的一种)
- 模块(模块是闭包最强大的一个应用场景)
- 是在函数被调用的时候确定的。也就是执行上下文被创建时确定的。
- 在函数执行过程中,this一旦被确定,就不可更改了。
- 全局对象中的this(关于全局对象的this,它是一个比较特殊的存在。全局环境中的this,指向它本身。)
- 函数中的this(在一个函数上下文中,this由调用者提供,由调用函数的方式来决定。如果调用者函数,被某一个对象所拥有,那么该函数在调用时,内部的this指向该对象。如果函数独立调用,那么该函数内部的this,则指向undefined。但是在非严格模式中,当this指向undefined时,它会被自动指向全局对象。)
- 使用call,apply显示指定this(JavaScript内部提供了一种机制,让我们可以自行手动设置this的指向。它们就是call与apply。所有的函数都具有这两个方法。它们除了参数略有不同,其功能完全一样。它们的第一个参数都为this将要指向的对象。)
- 构造函数与原型方法上的this(在封装对象的时候,我们几乎都会用到this,但是,只有少数人搞明白了在这个过程中的this指向,就算我们理解了原型,也不一定理解了this。所以这一部分,我认为将会为这篇文章最重要最核心的部分。理解了这里,将会对你学习JS面向对象产生巨大的帮助。)
- 创建一个新的对象;
- 将构造函数的this指向这个新对象;
- 指向构造函数的代码,为这个对象添加属性,方法等;
- 返回新对象。
- 我们需要牢记两点:第一,__proto__和constructor属性是对象所独有的;第二,prototype属性是函数所独有的,因为函数也是一种对象,所以函数也拥有__proto__和constructor属性。
- 属性__proto__的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__属性所指向的那个对象(父对象)里找,一直找,直到__proto__属性的终点null,再往上找就相当于在null上取值,会报错。通过__proto__属性将对象连接起来的这条链路即我们所谓的原型链。
- 属性prototype的作用就是让该函数所实例化的对象们都可以找到公用的属性和方法,即f1.proto === Foo.prototype。
- 属性constructor的含义就是指向该对象的构造函数,所有函数(此时看成对象了)最终的构造函数都指向Function。
- 在chrome的开发者工具中,通过断点调试,我们能够非常方便的一步一步的观察JavaScript的执行过程,直观感知函数调用栈,作用域链,变量对象,闭包,this等关键信息的变化。因此,断点调试对于快速定位代码错误,快速了解代码的执行过程有着非常重要的作用,这也是我们前端开发者必不可少的一个高级技能。
在最右侧上方,有一排图标。我们可以通过使用他们来控制函数的执行顺序。从左到右他们依次是:
- resume/pause script execution(恢复/暂停脚本执行)
- step over next function call(跨过,实际表现是不遇到函数时,执行下一步。遇到函数时,不进入函数直接执行下一步)
- step into next function call(跨入,实际表现是不遇到函数时,执行下一步。遇到到函数时,进入函数执行上下文)
- step out of current function(跳出当前函数)
- deactivate breakpoints(停用断点)
- don‘t pause on exceptions(不暂停异常捕) 其他:
- call Stack(函数调用栈)
- Scope(作用域链)
- Local(当前的局部变量对象)
- Closure(当前作用域链中的闭包)
在显示代码行数的地方点击,即可设置一个断点。断点设置有以下几个特点:
- 在单独的变量声明(如果没有赋值),函数声明的那一行,无法设置断点。
- 设置断点后刷新页面,JavaScript代码会执行到断点位置处暂停执行,然后我们就可以使用上边介绍过的几个操作开始调试了。
- 当你设置多个断点时,chrome工具会自动判断从最早执行的那个断点开始执行,因此我一般都是设置一个断点就行了。
- 在函数内部创建新的函数
- 新的函数在执行时,访问了函数的变量对象 (我们也可以这样定义闭包:闭包是指这样的作用域(foo),它包含有一个函数(fn1),这个函数(fn1)可以调用被这个作用域所封闭的变量(a)、函数、或者闭包等内容。通常我们通过闭包所对应的函数来获得对闭包的访问。)
- 关于函数在实际开发中的应用,大体可以总结为函数声明、函数表达式、匿名函数、自执行函数。
- 当值作为函数的参数传递进入函数内部时,也有同样的差异。我们知道,函数的参数在进入函数后,实际是被保存在了函数的变量对象中,因此,这个时候相当于发生了一次复制。
- 当我们想要使用一个函数时,通常情况下其实就是想要将一些功能,逻辑等封装起来。
- 柯里化是指这样一个函数(假设叫做createCurry),他接收函数A作为参数,运行后能够返回一个新的函数。并且这个新的函数能够处理函数A的剩余参数。
// 简单实现,参数只能从右到左传递
function createCurry(func, args) {
var arity = func.length;
var args = args || [];
return function() {
var _args = [].slice.call(arguments);
[].push.apply(_args, args);
// 如果参数个数小于最初的func.length,则递归调用,继续收集参数
if (_args.length < arity) {
return createCurry.call(this, func, _args);
}
// 参数收集完毕,则执行func
return func.apply(this, _args);
}
}- 接收单一参数,将更多的参数通过回调函数来搞定?
- 返回一个新函数,用于处理所有的想要传入的参数;
- 需要利用call/apply与arguments对象收集参数;
- 返回的这个函数正是用来处理收集起来的参数。
- 与普通函数相比,构造函数并没有任何特别的地方,首字母大写只是我们约定的小规定,用于区分普通函数;
- new关键字让构造函数具有了与普通函数不同的许多特点,而new的过程中,执行了如下过程:
- 声明一个中间对象;
- 将该中间对象的原型指向构造函数的原型;
- 将构造函数的this,指向该中间对象;
- 返回该中间对象,即返回实例对象。
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.getName = function() {
return this.name
}
Person.prototype.getAge = function() {
return this.age;
}
function Student(name, age, grade) {
// 构造函数继承
Person.call(this, name, age);
this.grade = grade;
}
// 原型继承
Student.prototype = Object.create(Person.prototype, {
// 不要忘了重新指定构造函数
constructor: {
value: Student
},
getGrade: {
value: function() {
return this.grade
}
}
})
var s1 = new Student('ming', 22, 5);
console.log(s1.getName()); // ming
console.log(s1.getAge()); // 22
console.log(s1.getGrade()); // 5在ECMAScript5中,对每个属性都添加了几个属性类型,来描述这些属性的特点。他们分别是
- configurable: 表示该属性是否能被delete删除。当其值为false时,其他的特性也不能被改变。默认值为true
- enumerable: 是否能枚举。也就是是否能被for-in遍历。默认值为true
- writable: 是否能修改值。默认为true
- value: 该属性的具体值是多少。默认为undefined
- get: 当我们通过person.name访问name的值时,get将被调用。该方法可以自定义返回的具体值时多少。get默认值为undefined
- set: 当我们通过person.name = 'Jake'设置name的值时,set方法将被调用。该方法可以自定义设置值的具体方式。set默认值为undefined
注:需要注意的是,不能同时设置value、writable 与 get、set的值


