基础知识
基础
1.栈
- 栈是一种数据存放方式,特点是先进后出(吃了吐),后进先出 push和pop操作
- 栈也是存放数据的一种内存区域,查找快,存储比较小的,还有一种是堆
class Stack {
private items: number[] = [];
push(item: number) {
return this.items.push(item);
}
pop(): number {
return this.items.pop()
}
}
let stack = new Stack();
stack.push(1);
stack.push(2);
stack.push(3);
console.log(stack.pop());
console.log(stack.pop());
console.log(stack.pop());
2.队列
- 队列是一种操作受限制的线性表,先进先出(吃了拉)
class Queue {
private items: number[] = [];
enqueue(ele: number) {
this.items.push(ele);
}
dequeue() {
return this.items.shift();
}
print() {
console.log(this.items.toString());
}
};
let newQueue = new Queue();
newQueue.enqueue(1);
newQueue.enqueue(2);
newQueue.enqueue(3);
newQueue.print();
newQueue.dequeue();
newQueue.print();
newQueue.dequeue();
newQueue.print();
3.执行上下文
function one() {
var a = 1;
function two() {
var b = 2;
function three() {
var c = 3;
console.log("🍎", a,b,c);
}
three()
}
two()
}
one() // 1,2,3
// 全局上下文 scopeChain: 作用域链
var globalExecuteContext = {
VO: {
one: () => { var a = 1}
},
scopeChain: [globalExecuteContextVO]
}
// 执行one,创建one执行上下文
var oneExecuteContext = {
VO: {
a: 1,
two: () => {var b = 2}
},
// 入栈
scopeChain: [oneExecuteContextVO, globalExecuteContext.VO]
}
// 执行two,创建two执行上下文
var twoExecuteContext = {
VO: {
b: 2,
three: () => {var c = 3}
},
// 入栈
scopeChain: [twoExecuteContext, oneExecuteContextVO, globalExecuteContext.VO]
}
// 执行three,创建three执行上下文
var threeExecuteContext = {
VO: {
c: 3
},
// 入栈
scopeChain: [threeExecuteContext, twoExecuteContext, oneExecuteContextVO, globalExecuteContext.VO]
}
// 作用域链的查找过程
function getValue(varName) {
for (let i = 0; i < threeExecuteContext.scopeChain.length; i++) {
if (varName in threeExecuteContext.scopeChain[i]) {
return threeExecuteContext.scopeChain[i][varName];
}
}
}
//console.log(a, b, c);
console.log(getValue('a'), getValue('b'), getValue('c'));
// 作用域链是由当前执行环境与上层执行环境的一系列变量对象组成的,它保证了当前执行环境对符合访问权限的变量和函数的有序访问
// 闭包的概念
// 闭包有两部分组成,1,当前执行上下文one, 2.在该执行上下文中创建的函数,也就是two
// 当two执行的时候,引用了one中的变量就产生了闭包
// 闭包的本质是在函数的外部保持内部变量的引用,从而阻止垃圾回收
function one() {
var a = 1;
function two() {
console.log(a);
}
return two;
}
var a = 2;
var two = one();
two();
// 打印a,two中没有,往上查找父作用域链,a = 1
// function重复会覆盖,var重复不会覆盖
// 1. 编译阶段,先扫描function,再var, 2.执行阶段
// 1.创建全局上下文对象
var globalExecuteContextVO = {
one: `()=>{var a = 1;}`,
a: undefined,
two: undefined
}
var globalExecuteContext = {
VO: globalExecuteContextVO,
scopeChain: [globalExecuteContextVO]
}
//2.开始执行
globalExecuteContextVO.a = 2;
//3.开始执行one, two的作用域链是再创建oneExecuteContextVO的时候确定,不是在执行的时候确定的
// this是在执行时候创建的,不是编译阶段
var oneExecuteContextVO = {
a: undefined,
two: `()=>{console.log(a)}`
}
var oneExecuteContext = {
VO: oneExecuteContextVO,
// 当oneExecuteContextVO处于执行栈的顶端,此时oneEC就会成为oneAO(Activation Object),激活状态,此时AO中会多出this属性 oneVO.this = window
scopeChain: [oneExecuteContextVO, globalExecuteContextVO]
}
oneExecuteContextVO.a = 1;
//4.给two赋值
globalExecuteContextVO.two = oneExecuteContextVO.two;
//5.执行two
var twoExecuteContextVO = {}
var twoExecuteContext = {
VO: twoExecuteContextVO,
//scopeChain是在创建此函数据的时候就决定了,跟在哪里执行无关
scopeChain: [twoExecuteContextVO, oneExecuteContextVO, globalExecuteContextVO]
}
4.let const
// let const
var a = 1;
function fn() {
console.log(a);
if(false) {
var a = 2;
}
}
fn() // undefined
// 由于编译阶段,变量提升,因为首先扫描var a = undefined
// let 声明的变量window访问不到,js做了VO 和 GO的 分离,只存在VO上, Local就是AO,哪个执行栈顶,VO就是AO
// 编译阶段先扫描function,声明并且赋值,再var,声明不赋值,赋值再执行阶段
5. this
/**
* this
* this出现的原因,当前执行这个逻辑的调用者是谁
* 如何确定this,当前函数的执行主体是谁
*/
// 1.如果对象来调用,this就是调用的对象
let jeffywin = {
name: 'jeffywin',
eat() {
console.log("🍎", '吃');
}
}
jeffywin.eat();
// 2. 如果没人来调用
// 严格模式 undefined, 非严格模式 window
// 3. 如果是再事件绑定的时候, this就是绑定的元素
/**
* call
* apply
* bind
*/
function fn1() {console.log(1)}
function fn2() {console.log(2)}
fn1.call.call(fn2) // 2
function getName(age, home) {
console.log("🍎", this.name, age, home);
}
let obj = {name: 'jeffywin'}
// obj.getName = getName;
// obj.getName();
// delete obj.getName;
// getName.call(obj);
// 原理
!(function(prototype) {
function getContext(context) {
context = context || window;
let type = typeof context;
// if(['number', 'boolean', 'string'].includes(type)) {
// context = new context.constructor(context);
// }
context = Object(context);
return context;
}
function call2(context, ...args) {
context = getContext(context);
// this 是getName函数
let symbol = Symbol('fn');
// 想让this指向context
// fn1执行的时候,xxx.fn1() .前面是谁,this就指向谁
context[symbol] = this; // context.symbol = this;
// 因为context.symbol = this,所以context.fn()执行相当于this执行,this指向context,也就是传进来的值
context[symbol](...args);
delete context[symbol];
}
// apply 参数是数组
function apply2(context, args) {
context = getContext(context);
let symbol = Symbol('fn');
context[symbol] = this;
context[symbol](...args);
delete context[symbol];
}
function bind2(context, ...outArgs) {
// this => getName
return (...args) => this.call(context, ...outArgs, ...args);
}
prototype.apply2 = apply2;
prototype.call2 = call2;
prototype.bind2 = bind2;
})(Function.prototype)
// getName.call2(obj);
getName.apply2(obj, [20, 'hangzhou']);
let bindFun = getName.bind2(obj, 10);
bindFun('hangzhou')
6. 原型链
为什么要有原型链:实现属性和方法的共享
6.1 instanceof
第一个参数是对象,第二个参数一般是函数 instanceof 原理:看看对象的__proto__是否等于函数的prototype
class Person {}
const p1 = new Person()
console.log(p1 instanceof PerSon)
p1.__proto__ === Person.prototype true
p1 instanceof Function => false
7.继承
class Father {
static staticFatherName = "jeffywin";
static staticFatherGetName = () => {
console.log("🍎", Father.staticFatherName);
}
constructor(public name) {
this.name = name;
}
getName() {
console.log(this.name);
}
}
class Child extends Father {
static staticChildName = "son";
static staticChildGetName = () => {
console.log("🍌", Child.staticChildGetName);
}
constructor(public name, public age) {
super(name)
this.age = age;
}
getAge() {
console.log(this.age);
}
}
const child = new Child('jack', 10);
child.getName();
child.getAge(); // 原型上的方法
Child.staticFatherGetName(); // 类上的属性
Child.staticChildGetName(); // 类上的方法
7.1 ES5实现继承
// 继承ES5 实现
var father = (function() {
function Father(name) {
this.name = name;
}
// 方法放到原型上
Father.prototype.getName = function() {
console.log(this.name);
}
Father.staticFatherName = "jeffywin";
Father.staticFatherGetName = function() {
console.log("🍎", Father.staticFatherName);
}
return Father;
})();
var childs = (function(_super) {
_extends(Child, _super);
function Child(name, age) {
// this 指向子类的实例
// 调用父类的构造函数,初始化父类私有属性
_super.call(this, name)
this.age = age;
}
// 方法放到原型上
Child.prototype.getAge = function() {
console.log(this.age);
}
Child.staticChildName = "son";
Child.staticFatherGetName = function() {
console.log("🍎", Child.staticChildName);
}
return Child;
})(Father);
function _extends(Child, Father) {
Child.__proto__ = Father; // 继承静态属性
function Temp() {
this.constructor = Child;
}
// __ => Temp b => Father (__.prototype = b.prototype, new __())
Temp.prototype = Father.prototype;
Child.prototype = new Temp();
}
8.面试题
优先级 xx.xx > new(带参数列表) > new(无参数)
8.1 编写parse函数,实现访问对象里属性的值
let obj = { a: 1, b: { c: 2 }, d: [1, 2, 3], e: [{ f: [4, 5, 6] }] };
let r1 = parse(obj, 'a');// = 1;
let r2 = parse(obj, 'b.c');// = 2;
let r3 = parse(obj, 'd[2]');// = 3;
let r4 = parse(obj, 'e[0].f[0]');// = 4;
function parse(obj, str) {
return new Function('obj', 'return obj.' + str.replace(/\.(\d+)/g, '\[$1\]'))(obj);
}
function parse(obj, str) {
str = str.replace(/\[(\d+)\]/g, '.$1');
arr = str.split('.');
arr.forEach(function (item) {
obj = obj[item];
})
return obj;
}
console.log(r1, r2, r3, r4);
8.2 flat
实现flat拍平
let arr = [ [1],
[2, 3],
[4, 5, 6, [7, 8, [9, 10, [11]]]],
12
];
let arr = [[1], [2, 3], [4, 5, 6, [7, 8, [9, 10, [11]]]], 12];
// const flat = (arr) => {
// return arr
// .toString()
// .split(",")
// .map((item) => Number(item));
// };
const flat = (arr) => {
return JSON.stringify(arr)
.replace(/\[|\]/g, "")
.split(",")
.map((item) => Number(item));
};
console.log(flat(arr));
while(arr.some((item) => Array.isArray(item))) {
// concat 每次能展开一层
arr = [].concat(...arr)
}
8.3 不可扩展, 密封,冻结 =》 浅层
// 不可扩展,但是老的属性可以删除,也可以更改
let obj = {name: jeffywin}
console.log(isExtensible(obj))
Object.preventExtensions(obj)
// 密封 seal 不可扩展,老的属性不可以删除,也可以更改
console.log(isSealed(obj))
Object.seal(obj)
// 冻结 不可扩展,老的属性不可以删除,也不可以更改
Object.freeze(obj)
// 简单版本 深度冻结
const obj1 = { name: { name: "jeffywin" }, arr: [1, 2] };
function DepFreeze(obj1) {
const newObj = {};
for (let key in obj1) {
const val = obj1[key];
newObj[key] = Object.freeze(val);
}
}
DepFreeze(obj1);
console.log(obj1.arr.push(3));
8.4
[]+{} = "[object,object]" => Object.prototype.toString.call({})
{}+[]=0 => {}相当于代码块执行,[].toString() = "", + "" = 0;
8.5 curry
const add = (function(total){
let newArr = [];
function _add(...args) {
newArr = [...newArr, ...args];
if (newArr.length >= total) {
let ret = newArr.reduce((pre, cur) => pre + cur, 0);
newArr.length = 0;
return ret;
} else {
return _add;
}
}
return _add;
})(5)
function add(...args) {
let _add = add.bind(null, ...args);
_add.toString = function() {
args.reduce((pre, cur) => pre + cur, 0);
}
return _add;
}
alert(add(1,2,3,4,5)); // alter的时候会自动调用toString 方法
function curry(fn, ...args) {
// args.length = 0 < fn.length = 5 函数的lenth就是参数的个数,第一次 args 是空
// return args.length < fn.length ? (...innerArgs) => curry(fn, ...args, ...innerArgs) : fn(...args);
return args.length < fn.length ? (...innerArgs) => {
return curry(fn, ...args, ...innerArgs)
} : fn(...args);
}
function addFn(a, b, c, d, e) {
return a + b + c + d + e;
}
let add = curry(addFn);
// let add = (...innerArgs) => curry(fn, ...args, ...innerArgs)
// console.log(add(1,2,3,4,5));
console.log(add(1)(2)(3)(4)(5));
// console.log(add(1,2)(3,4,5));
8.6 拷贝
- 浅拷贝 基本数据类型
const obj = {name: 'jeffywin', age: '20'}
const obj2 = JSON.parse(JSON.stringify(obj))
- 深拷贝
let obj = {
name: 'jeffywin',
age: '18',
home: { address: '杭州'},
hobbies: ['美女', '汽车']
};
// obj.obj = obj; // 会出现死循环
function clone (source, map = new Map()) {
if (typeof source === 'object') {
if(map.get(source)) {
return map.get(source);
}
let target = Array.isArray(source) ? [] : {}
map.set(source, target);
for (const key in source) {
target[key] = clone(source[key], map) // 对象或者数组深拷贝
}
return target;
}
return source; // 基本类型直接拷贝
}
模块化
- 早期模块化
(function (global) {
function add(a, b) {
return a + b;
}
global.addModule = { add };
})(window);
(function (global) {
function minus(a, b) {
return a - b;
}
global.minusModule = { minus };
})(window);
(function (global, addModule, minusModule) {
global.mathModule = { add: addModule.add, minus: minusModule.minus };
})(window, addModule, minusModule);
console.log(mathModule.add(2, 2));
console.log(mathModule.minus(2, 2));
- AMD
// 原理
let moduleFactory = {};
function define(name, factory) {
moduleFactory[name] = factory;
}
function require(dependencies, callback) {
callback(...dependencies.map(dependency => moduleFactory[dependency]()));
}
// modules\addModule.js
define(function () {
function add(a, b) {
return a + b;
}
return {
add
}
});
// modules\minusModule.js
define(function () {
function minus(a, b) {
return a - b;
}
return {
minus
}
})
require.config({
baseUrl: 'modules'
});
require(['addModule', 'minusModule'], (addModule, minusModule) => {
console.log(addModule.add(1, 2), minusModule.minus(3, 4));
});
- CMD 通用模块定义规范(Common Module Defination ) seajs
// 原理
function require(name) {
if (modules[name]) {
return modules[name];
}
let factory = factories[name];
let exports = {};
factory(require, exports);
modules[name] = exports;
return exports;
}
function define(name, factory) {
factories[name] = factory;
}
function use(name) {
require(name);
}
和AMD类似,可以通过require自由引入module
define(function (require, exports) {
var addModule = require('./modules/addModule')
let result1 = addModule.add(1, 2);
console.log(result1);
var minusModule = require('./modules/minusModule')
let result2 = minusModule.minus(1, 2);
console.log(result2);
})
define(function (require, exports) {
exports.add = function (a, b) {
return a + b;
}
})
define(function (require, exports) {
exports.minus = function (a, b) {
return a - b;
}
})
- COMMONJS
module.exports = {
a: 1
};
const a = require('./a');
- ES6
import
export