目录
一、浏览器工作原理和V8引擎
1、浏览器内核和js引擎的关系
* WebCore:负责HTML解析、布局、渲染等等相关的工作
* JavaScriptCore:解析、执行JavaScript代码
2、V8引擎的原理
* JavaScript源代码
- 词法分析:解析源代码中每个单词的类型、值等信息
- 语法分析:根据单词的类型信息可做语法分析,生成抽象语法树
* AST抽象语法树(格式固定的树结构对象。babel原理:ts -> ast -> js)
* MachineCode优化的机器码(多次执行的字节码函数会被标记为hot函数,hot函数会优化为固定的一组机器指令,
无需每次执行都将字节码函数转成机器指令,从而提高执行效率。hot函数优化生成的机器指令,会由于类型等原因
造成机器指令执行不正确,机器指令则会反向生成字节码函数,再由字节码函数转成机器指令执行,类型确定是ts执
行效率高于js的原因。)
* bytecode字节码
二、JavaScript的执行过程
1、全局代码执行和作用域提升
* 执行上下文栈(ECStack)
全局执行上下文(GEC)
变量对象(VO) == 全局对象(GO)
函数执行上下文(FEC)
变量对象(VO) == 激活对象(AO)
作用域链 == AO + 父级作用域 + GO
* 声明提升:还未进行变量赋值和函数执行的过程。声明变量:默认是undefined。声明函数:会在堆内存开辟空间
以存放函数代码块,再将内存地址赋给声明的函数。
* 延迟解析:将全局作用域下执行的函数进行预解析,非全局作用域下执行的函数(嵌套函数),在函数被调用时才会
全量解析,提高网页的运行效率。
* JavaScript 函数作用域: 作用域在函数内修改。
* var a = b = 10 等价 var a = 10; b = 10(未用var声明的变量,会成为GO的属性)。
* var a, b = 10 等价 var a; var b = 10
三、JS的内存管理和闭包
1、常见的GC算法
* 引用计数
* 标记清除
2、闭包
/**
* 高阶函数:当function的参数或者返回值也为函数时,则称这个function为高阶函数
* 闭包:函数+可以访问的自由变量
* 基本数据类型所占内存空间:小于2**32的整数占4字节32位,小数占8字节64位
*/
function foo() {
var name = "foo"
// 内层函数(bar代码块)未使用到的自由变量,js引擎会做优化将其销毁
var age = 18
function bar() {
console.log(name)
}
return bar
}
// 内层函数(bar代码块)被fn引用着,GC不处理则不释放内层函数(bar代码块)所占的内存空间
var fn = foo()
fn()
// 释放内层函数(bar代码块)所占的内存空间
fn = null
// 释放外层函数(foo代码块)所占的内存空间
foo = null
四、关于javascript中的this
1、this在全局作用域下
* 浏览器:this指向window
* Node环境:this指向空对象({})。Node环境会将.js文件作为一个module,放入一个函数中,
并使用.call({})方式执行这个函数。
2、this的四种绑定规则
* 默认绑定(fun()):独立函数调用this指向window
* 隐式绑定(object.fun()):object对象会被js引擎绑定到fun函数中的this里面
* 显示绑定:
- apply:fun.apply(object,[itemX]);
- call:fun.call(object,itemX);
- bind:var funBind = fun.bind(object); funBind();
* new绑定:function Person(name) { this.name = name; }
this指向函数作为构造器new出来的对象
* 优先级:new绑定 > 显示绑定(bind > call/apply) > 隐式绑定 > 默认绑定
3、this的其他补充(非箭头函数)
* setTimeout(fun):函数中this指向window
* div.onclick=fun:函数中this指向div
div.addEventListener("click",fun):函数中this指向div。
* 数组forEach/map/filter/find:有参数2则函数中的this指向参数2,没有则this指向window
* 忽略显示绑定:apply/call/bind参数传null/undefined相当于独立函数调用this指向window
* 间接函数引用:(obj1.fun=obj2.fun)() 是独立函数调用this指向window
* (object.fun)() 等价 object.fun() 是隐式绑定this指向object
4、箭头函数
* ()=>({}):只有一行代码并且返回一个对象的简写方式
* 箭头函数的this获取:箭头函数不绑定this,this从上层作用域获取。call/apply/bind绑定无效
5、call函数的实现
// 参数1:绑定的对象;参数2:剩余参数方式接收参数,args为数组
Function.prototype.mycall = function (bind, ...args) {
// 当xyz.mycall()则this指向xyz
var fun = this
// bind传入基本数据类型则要转成包装类型对象,传入null/undefined则为window对象
var rebind = (bind !== null && bind !== undefined) ? Object(bind) : window
rebind.fun = fun
// 展开运算符方式传入参数
var res = rebind.fun(...args)
delete rebind.fun
return res
}
function xyz(a, b) {
console.log(this)
return a + b
}
console.log(xyz.mycall({}, 1, 2))
6、类数组对象arguments
function fun(...args) {
// 知识点一、类数组对象:有length属性,可通过数组下标获取元素
console.log(arguments.length)
console.log(arguments[0])
// 指向函数体本身,严格模式("use strict";)下会报错
// console.log(arguments.callee)
// 知识点二、类数组对象转数组
// 方式1:
var arr1 = []
for (var i = 0; i < arguments.length; i++) {
arr1.push(arguments[i])
}
console.log(arr1)
// 方式2:
var arr2 = Array.prototype.slice.call(arguments)
console.log(arr2)
// 方式3:
var arr3 = [].slice.call(arguments)
console.log(arr3)
// 方式4:
var arr4 = Array.from(arguments)
console.log(arr4)
// 方式5:
var arr5 = [...arguments]
console.log(arr5)
// 知识点三、箭头函数不绑定arguments,从上层作用域获取,全局作用域没有arguments
var xyz = () => {
console.log(arguments)
}
xyz()
}
fun(1, 2, 3)
五、函数式编程
1、纯函数
* 相同的输入一定产生相同的输出
* 在执行的过程中不会产生任何的副作用
2、柯里化
* 柯里化的好处:对上层函数逻辑的复用
* 简化写法:var fun = x => y => z => x + y + z
3、通用柯里化函数的实现
function autoCurrying(fun) {
return function curried(...args1) {
// 函数的length属性表示可传参数的长度
if (args1.length >= fun.length) {
return fun.apply(this, args1)
} else {
return function (...args2) {
return curried.apply(this, args1.concat(args2))
}
}
}
}
function fun(time, level, message) {
console.log(`[${time.getHours()}:${time.getMinutes()}][${level}][${message}]`)
}
var curryingFun = autoCurrying(fun)
curryingFun(new Date())("DEBUG")("这是一段中文")
curryingFun(new Date(), "DEBUG")("这是一段中文")
curryingFun(new Date(), "DEBUG", "这是一段中文")
4、组合函数
* 组合函数:依次执行的多个函数组合在一起
5、通用组合函数的实现
function composeFun(...funs) {
var length = funs.length;
for (var i = 0; i < length; i++) {
if (typeof funs[i] !== "function") {
throw new TypeError("Expected arguments are functions")
}
}
return function (...args) {
var index = 0
var result = length ? funs[index].apply(this, args) : args
while (++index < length) {
result = funs[index].call(this, result)
}
return result
}
}
function fun1(a, b) {
return a + b
}
function fun2(c) {
return c * 2
}
var fun = composeFun(fun1, fun2)
console.log(fun(1, 2))
六、with-eval-strict
1、with
var obj = {message: "obj"}
function fun() {
var message = "fun"
/**
* with语句:可以形成自己的作用域
* 作用域链:传入的对象 -> 父级作用域 -> GO
* 严格模式下会报错,不推荐使用
*/
with (obj) {
console.log(message)
}
}
fun()
2、eval
* eval可以将传入的字符串当做javascript代码来运行
* 不推荐使用:代码可读性差;字符串易被篡改,有被攻击的风险;js引擎无法优化
3、严格模式
* 开启严格模式:
- 作用整个文件:文件第一行加"use strict";
- 作用某个函数:函数第一行加"use strict";
* 严格模式常见的限制
- 禁止意外创建全局变量
- 不允许函数有相同的参数名称
- 静默错误
- 不允许使用原先的八进制格式 0123
- with语句不允许使用
- eval函数不会向上引用变量了
- 在严格模式下,自执行函数(默认绑定)会指向undefined
- setTimeout(fun,delay)的this,fun.apply(this = window)
七、js面向对象(字面量)
1、创建对象的方式
* 通过new Object()创建:var obj = new Object();
* 字面量形式:var obj = {}
2、对象属性的操作
var obj = {
name: "黄婷婷",
age: 18
}
// 1、获取属性的值
console.log(obj.name)
// 2、设置属性的值
obj.name = "孟美岐"
// 3、删除属性
// delete obj.age
console.log(obj)
// 4、遍历属性
for (const objKey in obj) {
console.log(objKey)
}
Object.keys(obj).forEach(objKey => {
console.log(objKey)
})
3、defineProperty方法(数据属性描述符)
var obj = {
name: "黄婷婷"
}
/**
* 未使用属性描述符定义的属性("name"属性),也具备对应的特性:
* value: 赋值的value
* writable: true
* configurable: true
* enumerable: true
*/
Object.defineProperty(obj, "address", {
// 默认值undefined
value: "无锡市",
// 默认值false。该特性表示"address"属性不可赋值(写入值)
writable: false,
// 默认值false。该特性表示"address"属性不可删除(严格模式下会报错),也不可重新定义属性描述符
configurable: false,
// 默认值false。该特性表示"address"属性不可枚举
enumerable: false
})
obj.address = "上海市"
delete obj.address
console.log(Object.keys(obj))
console.log(obj)
4、defineProperty方法(存取属性描述符)
var obj = {
// 下划线开头的属性常用来表示私有的属性
_address: ""
_age: 18,
// configurable默认true,enumerable默认true
get age() {
return this._age
},
set age(value) {
this._age = value
}
}
// get/set不可与数据属性描述符的value/writable同时使用
/**
* 作用一、隐藏某一个私有属性,不希望直接被外界使用和赋值
* 作用二、如果我们希望截获某一个属性它访问和设置值的过程时,也会使用存取属性描述符
*/
Object.defineProperty(obj, "address", {
configurable: true,
enumerable: true,
get: function () {
return this._address
},
set: function (value) {
this._address = value
}
})
console.log(obj)
5、定义多个属性描述符
var obj = {
_age: 18
}
Object.defineProperties(obj, {
name: {
value: "黄婷婷",
writable: true,
configurable: true,
enumerable: true
},
age: {
configurable: true,
enumerable: true,
get: function () {
return this._age
},
set: function (value) {
this._age = value
}
}
})
6、补充说明Object的方法
var obj = {
name: "黄婷婷",
age: 18
}
// 1、获取对象某一个属性的属性描述符
console.log(Object.getOwnPropertyDescriptor(obj, "name"))
// 2、获取对象所有属性的属性描述符
console.log(Object.getOwnPropertyDescriptors(obj))
// 3、禁止对象添加新的属性
Object.preventExtensions(obj)
obj.address = "无锡市"
console.log(obj)
// 4、禁止对象配置/删除里面的属性
Object.seal(obj)
delete obj.name
console.log(obj)
// 5、让属性不可修改(writable:false)
Object.freeze(obj)
obj.name = "孟美岐"
console.log(obj)
八、js面向对象(构造函数)
1、创建对象方案(工厂模式)
// 缺点:获取不到对象最真实的类型
function createPerson(name, age) {
var person = {}
person.name = name
person.age = age
return person
}
var person = createPerson("黄婷婷", 18)
2、认识构造函数
function Person() {
}
// 通过new关键字去调用一个函数,那么这个函数就是一个构造函数了
var person = new Person(); // 无需传参时小括号可省略
3、创建对象方案(构造函数)
// 规范:构造函数的首字母一般是大写
function Person(name, age) {
this.name = name
this.age = age
}
/**
* 如果一个函数被使用new操作符调用了,那么它会执行如下操作:
* - 1、在内存中创建一个新的对象(空对象)
* var 空对象 = {}
* - 2、构造函数内部的this,会指向创建出来的新对象
* this = 空对象
* - 3、这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性
* this.__proto__ = Person.prototype
* - 4、执行函数的内部代码(函数体代码)
* Person()
* - 5、如果构造函数没有返回非空对象,则返回创建出来的新对象
*/
var person = new Person("黄婷婷", 18);
// 获取对象类型(构造函数名)
console.log(person.__proto__.constructor.name)
4、构造函数缺点
function Person() {
this.eating = function () {
}
}
// 缺点:每次new对象eating方法都会创建一个函数,比较浪费内存空间
var person = new Person();
5、对象的原型(隐式原型)的理解
var obj = {}
/**
* 每个对象中都有一个[[prototype]],这个[[prototype]]可以称之为对象的原型(隐式原型)
* 1、获取原型对象:
* - obj.__proto__
* - Object.getPrototypeOf(obj)
* 2、原型的作用:当我们从一个对象中获取某一个属性时,它会触发[[get]]操作。1、在当前对象中去查
* 找对应的属性,如果找到就直接使用;2、如果没有找到,那么会沿着它的原型去查找[[prototype]]
*/
console.log(obj)
6、函数的原型(显示原型)的理解
/**
* 函数也是一个对象(new Function())
* 函数作为对象来说,它也是有[[prototype]]隐式原型的
* console.log(Person.__proto__)
*/
function Person() {
}
var p = new Person()
// 函数它因为是一个函数,所以它还会多出来一个显示原型属性:prototype
console.log(p.__proto__ === Person.prototype)
7、构造函数、原型对象、实例对象
原型对象.constructor -> 构造函数
构造函数.prototype -> 原型对象
实例对象.__proto__ -> 原型对象
new 构造函数() -> 实例对象
8、自定义构造函数的原型对象
function Person() {
}
// 1、Person.prototype重新赋值
Person.prototype = {
name: "黄婷婷"
}
// 2、Person.prototype的constructor属性指向Person构造函数(不可枚举)
Object.defineProperty(Person.prototype, "constructor", {
value: Person,
writable: true,
configurable: true,
enumerable: false
})
console.log(Person.prototype)
9、创建对象方案(构造函数+原型对象)
function Person(name) {
// 1、属性定义在构造函数中
this.name = name
}
// 2、方法定义在原型对象上
Person.prototype.sleep = function () {
console.log(this.name + " 睡觉")
}
var person = new Person("孟美岐");
person.sleep()
九、js面向对象(三大特性)
1、原型链直接继承的弊端
function Person(name, friends) {
this.name = name
this.friends = friends
}
var person = new Person("林志玲", []);
function Student(age) {
this.age = age
}
Student.prototype = person
// 弊端三:在前面实现类的过程中都没有传递参数
var student = new Student(18);
// 弊端一:打印student对象,继承的属性是看不到的
console.log(student)
// 弊端二:创建两个Student类型对象
// 直接修改对象上的属性,是给本对象添加了一个新属性
student.name = "佟丽娅"
// 获取引用,修改引用中的值,会相互影响
student.friends.push("鞠婧祎")
2、借用构造函数继承的弊端
function Person(name, friends) {
this.name = name
this.friends = friends
}
var person = new Person("", []);
function Student(age, name, friends) {
Person.call(this, name, friends)
this.age = age
}
Student.prototype = person
var student = new Student(18, "林志玲", []);
// 弊端一、Person函数至少被调用了两次
// 弊端二、student的原型对象上会多出一些属性,但是这些属性是没有存在的必要
3、父类原型对象赋值给子类原型对象继承的弊端
function Person(name, friends) {
this.name = name
this.friends = friends
}
function Student(age, name, friends) {
Person.call(this, name, friends)
this.age = age
}
// 弊端:操作子类原型对象,会影响父类原型对象
Student.prototype = Person.prototype
4、原型式继承(对象继承对象)
var obj = {
name: "孟美岐"
}
// 方式一
// var info = createObject1(obj);
function createObject1(o) {
var newObj = {}
// 相当于newObj.__proto__ = o(可以这么理解,不能这么做)
Object.setPrototypeOf(newObj, o)
return newObj
}
// 方式二
// var info = createObject2(obj);
function createObject2(o) {
function Fn() {
}
Fn.prototype = o
var newObj = new Fn()
return newObj
}
// 方式三
var info = Object.create(obj);
5、寄生式继承
var personObj = {
running: function () {
console.log("running")
}
}
function createStudent(name) {
var stu = Object.create(personObj)
stu.name = name
stu.studying = function () {
console.log("studying~")
}
return stu
}
var student = createStudent("黄婷婷");
6、寄生组合式继承
// 相当于Object.create()
function createObject(o) {
function Fn() {
}
Fn.prototype = o
return new Fn()
}
// 2、用于继承方法(配合原型三角图理解,不用考虑父类实例)
function inheritPrototype(SubType, SuperType) {
SubType.prototype = createObject(SuperType.prototype)
Object.defineProperty(SubType.prototype, "constructor", {
value: SubType,
writable: true,
configurable: true,
enumerable: false
})
}
function Person() {
}
function Student() {
// 1、用于继承属性
Person.call(this)
}
inheritPrototype(Student, Person)
7、补充说明Object的方法
var obj = {
name: "孟美岐",
age: 18
}
var info = Object.create(obj, {
address: {
value: "北京市",
enumerable: true
}
})
// 1、hasOwnProperty:对象是否有某一个属于自己的属性(不是在原型上的属性)
console.log(info.hasOwnProperty("address"))
console.log(info.hasOwnProperty("name"))
// 2、in/for in 操作符:判断某个属性是否在某个对象或者对象的原型上
console.log("address" in info)
console.log("name" in info)
for (var key in info) {
console.log(key)
}
// 3、instanceof:用于检测构造函数的prototype,是否出现在某个实例对象的原型链上
console.log(obj instanceof Object)
// 4、isPrototypeOf:用于检测某个对象,是否出现在某个实例对象的原型链上
console.log(obj.isPrototypeOf(info))
8、Function、Object
* function fun() {} 等价 var fun = new Function("")
* 所有构造函数的__proto__都指向Function的原型对象
Function.__proto__ -> Function.prototype
Object.__proto__ -> Function.prototype
* 除了Object原型对象,所有(排除继承)原型对象的__proto__都指向Object的原型对象
Function.prototype.__proto__ -> Object.prototype
Object.prototype.__proto__ -> null