共计 5138 个字符,预计需要花费 13 分钟才能阅读完成。
JS 是编译型语言
编译发生在代码执行前几微秒, 简单来说就是 js 在执行前要进行编译,编译过程发生在代码执行前几微妙,甚至更短。
编译的步骤
词法分析以 var a = 2 为例,词法分析会将其分成三个有意义的代码块即词法单元。
语法分析将词法单元组合生成代表了程序语法的结构的树,即抽象语法书(AST)。
代码生成将 AST 生成可执行的代码。即将 AST 转化成一组机器指令。
LHS RHS
如果查找的目的是对变量进行赋值,那么就会使用 LHS 查询; 如果目的是获取变量的值,就会使用 RHS 查询。
词法作用域
决定于你在写代码时的块级作用域
优化
依赖于词法的静态分析
eval with 会创建新的作用域
在词法分析阶段,无法知道 eval with 会对作用域做怎样的修改,此时引擎不再对作用域进行任何优化
函数作用域
函数声明 函数表达式
区分函数声明和表达式最简单的方法是看 function 关键字出现在声明中的位置(不仅仅是一行代码,而是整个声明中的位置)。如果 function 是声明中的第一个词,那么就是一个函数声明,否则就是一个函数表达式。
let
隐式的生成块级作用域
不存在变量提升
提升
原因
变量(包括函数在内)的所有声明都会优先执行,只有声明本身会提升,而赋值或其他运行逻辑会留在原位置
过程
这意味着无论作用域中的声明出现在什么地方,都将在代码本身被执行前首先进行处理。可以将这个过程形象地想象成所有的声明 (变量和函数) 都会被“移动”到各自作用域的最顶端,这个过程被称为提升。声明本身会被提升,而包括函数表达式的赋值在内的赋值操作并不会提升。
闭包
定义
当函数能够记住或访问所在的词法作用域,及时是被作用域外调用,就产生了闭包
模块
现代模块机制
未来的模块机制
关于 this
绑定时间点
是在函数运行时绑定的,而非定义时。它的上下文取决于函数调用时的各种条件,和在哪里定义的没有关系,只取决于函数的调用方式。
绑定过程
当函数被调用时,会创建一个执行上下文,在这个上下文里包含了函数在哪里没调用(调用栈),调用函数的方法,参数等。this 作为执行上下文的一个属性,可以在函数执行的过程中用到。
绑定类型
默认绑定即绑定到全局,严格模式下回绑定到 undefined。
function foo() {
console.log(this.a);
}
var a = 2;
(function(){
“use strict”;
foo(); // 2
})()
隐式绑定即绑定到最顶层(或最近调用对象)上
function fun() {
console.log(this.a)
}
var obj2 = {
a: 3,
fun: fun,
}
var obj1 = {
a: 2,
obj2: obj2,
}
obj1.obj2.fun() // 3
显式绑定即用 call 或 apply 手动进行绑定
bind 方法实现
new 绑定(构造函数)
不存在其实在 js 中不存在构造函数,我们所说的构造函数其实就是普通的函数,它只是用 new 被“构造调用”而已。
new 发生了什么?
创建 (或者说构造) 一个全新的对象。
这个新对象会被执行 [[原型]] 连接。
这个新对象会绑定到函数调用的 this。
如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。
箭头函数 =>
对象
内置对象
基本类型在需要的时候(比如说获取长度),会被引擎默认转成内置对象,从而进行方法的调用。基础类型并不是继承自内置对象
var strPrimitive = “I am a string”;
typeof strPrimitive; // “string”
strPrimitive instanceof String; // false
var strObject = new String(“I am a string”);
typeof strObject; // “object”
strObject instanceof String; // true
Object.prototype.toString.call(strObject); // [object String]
null
typeof null === Object;
原理是这样的,不同的对象在底层都表示为二进制,在 JavaScript 中二进制前三位都为 0 的话会被判断为 object 类型,null 的二进制表示是全 0,自然前三位也是 0,所以执行 typeof 时会返回“object”
拷贝
浅拷贝 Object.assign({}, obj)
深拷贝 JSON.stringify
属性描述符
getOwnPropertyDescriptor(myObj, ‘a’)
defineProperty
Object.defineProperty(myObj, ‘a’, {
value: 2,
writable: true,
configurable: true,
enumerable: true
})
Getter、Setter
var obj = {
get a() {
return this._a_
},
set a(val) {
this._a_ = val * 5
}
}
obj.a = 10
console.log(obj.a) // 50
var obj2 = {}
Object.defineProperty(obj2, ‘a’, {
get: function() {
return this._a_
},
set: function(val) {
this._a_ = val * 2
}
})
obj2.a = 15
console.log(obj2.a) // 30
存在性
in’a’ in obj1 会检查 obj 及其原型链上是否有 ’a’
hasOwnProperty 不会检查原型链,如果需要可以 Object.prototype.hasOwnProperty.call(myObj, ‘a’)
原型(prototype)
constructor
返回实例对象 O 的构造函数(的引用)。任何一个 prototype 对象都有一个 constructor 属性,指向它的构造函数,每一个实例也有一个 constructor 属性,默认调用 prototype 对象的 constructor 属性例如
function Test() {
this.name = ‘test’
}
var test = new Test()
console.log(test.constructor === Test) // true
类 constructor
构造函数 constructor 是用于创建和初始化类中创建的一个对象的一种特殊方法.
class Polygon {
constructor() {
this.name = “Polygon”;
}
}
class Square extends Polygon {
constructor() {
super();
}
}
class Rectangle {}
Object.setPrototypeOf(Square.prototype, Rectangle.prototype);
console.log(Object.getPrototypeOf(Square.prototype) === Polygon.prototype); //false
console.log(Object.getPrototypeOf(Square.prototype) === Rectangle.prototype); //true
let newInstance = new Square();
console.log(newInstance.name); //Polygon
proto
实例对象__proto__指向生成改对象的构造函数的原型例如
|function Test() {
this.name = ‘test’
}
Test.prototype = {
color: ‘red’
}
var test = new Test()
console.log(test.__proto__ === Test.prototype) // true
console.log(test.__proto__)
Object.create
var foo = {
something: function() {
console.log(“Tell me something good…”);
}
};
var bar = Object.create(foo);
bar.something(); // Tell me something good…
Object.create(..) 会创建一个新对象 (bar) 并把它关联到我们指定的对象(foo)
这样 我们就可以充分发挥 [[Prototype]] 机制的威力 (委托) 并且避免不必要的麻烦(比如使 用 new 的构造函数调用会生成 .prototype 和 .constructor 引用)。
继承
原型继承
缺点实例的属性都会指向同一个引用实现
function Parent() {
this.names = [1, 2, 3]
}
function Child() {
}
Child.prototype = new Parent()
var child1 = new Child()
var child2 = new Child()
child1.names.push(4)
console.log(child1.names) // [1,2, 3,4]
console.log(child2.names) // [1,2, 3,4]
借用构造函数
实现
function Parent() {
this.names = [1, 2, 3]
this.getName = function () {
console.log(this.name)
}
}
function Child() {
Parent.call(this)
}
var child1 = new Child()
var child2 = new Child()
child1.names.push(4)
console.log(child1.names)
console.log(child2.names)
缺点每个子实例都会实例化方法一次,内存爆炸
组合继承(最常用)
实现
function Parent() {
this.names = [1, 2, 3]
}
Parent.prototype.getName = function () {
console.log(this.names)
}
function Child() {
Parent.call(this)
}
Child.prototype = new Parent()
var child1 = new Child()
var child2 = new Child()
child1.names.push(4)
child1.getName()
child2.getName()
缺点
子类实例上有一份父类的属性,二者重复造成内存浪费
父类的构造函数被调用了两次
寄生式组合继承
实现
function Parent() {
this.names = [1, 2, 3]
}
Parent.prototype.getName = function () {
console.log(this.names)
}
function Child() {
Parent.call(this)
}
Child.prototype = Object.create(Parent.prototype)
var child1 = new Child()
var child2 = new Child()
child1.names.push(4)
child1.getName()
child2.getName()
优点属性不会再原型链上重复
行为委托
js 中的继承其实就是在对象间建立关联关系,而行为委托就是建立这种关联关系的纽带。
(“ 原型 ”)面向对象风格
function Foo(who) {
this.me = who;
}
Foo.prototype.identify = function () {
return “I am” + this.me;
};
function Bar(who) {
Foo.call(this,who);
}
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.speak = function () {
alert(“Hello,” + this.identify() + ‘.’);
};
var b1 = new Bar(“b1”);
var b2 = new Bar(“b2”);
b1.speak();
b2.speak();
对象关联风格
Foo = {
init:function (who) {
this.me = who;
},
identify:function () {
return “I am” + this.name
}
};
Bar = Object.create(Foo);
Bar.speak = function () {
alert(“hello,” + this.identify() + ‘.’);
};
var b3 = Object.create(Bar);
b3.init(“b3”);
var b4 = Object.create(Bar);
b4.init(“b4”);
b1.speak();
b2.speak();