栈
栈
者,存储货物或供旅客住宿的地方,可引申为仓库
数据结构中的栈
- 栈是一组数据的存放方式,特点是先进后出,后进先出
方法名 | 操作 |
---|---|
push() | 添加新元素到栈顶 |
pop() | 移除栈顶的元素,同时返回被移除的元素 |
class Stack {
private items: number[] = [];
// 添加元素到栈顶,也就是栈的末尾
push(element: number) {
this.items.push(element);
}
// 栈的后进先出原则,从栈顶出栈
pop(): number {
return this.items.pop();
}
}
let stack = new Stack();
stack.push(1);
stack.push(2);
stack.push(3);
console.log(stack.pop());
代码的运行方式
- 表示函数的一层层调用
function one() {
function two() {
function three() {
debugger;
}
three();
}
two();
}
one();
内存区域
- 栈也是是存放数据的一种内存区域
- 程序运行的时候,需要内存空间存放数据。一般来说,系统会划分出两种不同的内存空间:一种叫做stack(栈),另一种叫做heap(堆)
- stack是有结构的,每个区块按照一定次序存放,可以明确知道每个区块的大小,存储空间比较小
- heap是没有结构的,数据可以任意存放。因此,stack的寻址速度要快于heap
- 只要是局部的、占用空间确定的数据,一般都存放在stack里面,否则就放在heap里面,所有的对象都存放在heap,可以任意存放,存放空间没有限制,但是操作效率比较低
function task() {
var a = 1;
var b = 2;
var c = {
name: 'hs',
age: 10
}
}
task();
队列
- 队列是一种操作受限制的线性表
- 特殊之处在于它只允许在表的前端进行删除操作,而在表的后端进行插入操作
- 进行插入操作的端称为队尾,进行删除操作的端称为队头
- 因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出线性表
class Queue {
private items: number[] = [];
// 添加元素到栈顶,也就是栈的末尾
enqueue(element: number) {
this.items.push(element);
}
dequeue() {
return this.items.shift();
}
}
let queue = new Queue();
queue.enqueue(1);
queue.enqueue(2);
queue.enqueue(3);
console.log(queue.dequeue());//1
执行上下文
如何存储
- 当函数运行时,会创建一个执行环境,这个执行环境就叫执行上下文(Execution Context)
- 执行上下文中会创建一个对象叫作变量对象(Value Object),基础数据类型都保存在变量对象中
- 引用数据类型的值保存在堆里,我们通过操作对象的引用地址来操作对象
function task(){
var a = 1;
var b = {
name:'hs'
}
var c = [1,2,3];
}
let ExecuteContext = {
VO:{
a:1,
b:'XO1',
c:'XA1'
}
};
如何复制
基本数据
- 基本数据类型复制的是值本身
var a = 1;
var b = a;
b = 2;
console.log(a);
var ExecuteContext = {
VO: { a: 1 }
};
ExecuteContext.VO.b = ExecuteContext.VO.a;
ExecuteContext.VO.b = 2;
console.log(ExecuteContext.VO.a);
引用数据
- 引用数据类型复制的是引用地址指针
var m = { a: 1, b: 2 };
var n = m;
n.a = 10;
console.log(m.a);
var ExecuteContext = {
VO: { m: { a: 1, b: 2 } }
};
ExecuteContext.VO.b = ExecuteContext.VO.a;
ExecuteContext.VO.a = 10;
console.log(ExecuteContext.VO.a);
多个执行上下文栈
执行上下文分类
- JS代码在执行的时候会进入一个执行上下文,可以理解为当前代码的运行环境
- 在JS中运行环境主要分为全局执行上下文环境和函数环执行上下文环境
- 全局执行上下文只有一个,在客户端中一般由浏览器创建,也就是我们熟知的window对象,我们能通过this直接访问到它
- window对象还是var声明的全局变量的载体。我们通过var创建的全局对象,都可以通过window直接访问
多个执行上下文
- 在JS执行过程会产出多个执行上下文,JS引擎会有栈来管理这些执行上下文
- 执行上下文栈(下文简称执行栈)也叫调用栈,执行栈用于存储代码执行期间创建的所有上下文,具有LIFO(Last In First Out后进先出,也就是先进后出)的特性
- 栈底永远是全局上下文,栈顶为当前正在执行的上下文
- 当开启一个函数执行时会生成一个新的执行上下文并放入调用栈,执行完毕后会自动出栈
function one() {
var a = 1;
debugger;
function two() {
var b = 1;
debugger;
function three() {
var c = 1;
debugger;
}
three();
debugger;
}
two();
debugger;
}
one();
var globalExecuteContext = {
VO: { setTimeout: 'setTimeout' }
}
var executeContextStack = [globalExecuteContext];
var oneExecuteContext = {
VO: { a: 1 }
}
executeContextStack.push(oneExecuteContext);
var twoExecuteContext = {
VO: { b: 2 }
}
executeContextStack.push(twoExecuteContext);
var threeExecuteContext = {
VO: { c: 3 }
}
executeContextStack.push(threeExecuteContext);
console.log(executeContextStack);
executeContextStack.pop();
executeContextStack.pop();
executeContextStack.pop();
执行上下文生命周期
生命周期有两个阶段
- 一个新的执行上下文的生命周期有两个阶段
- 创建阶段
- 创建变量对象
- 确定作用域链
- 确定
this
指向
- 执行阶段
- 变量赋值
- 函数赋值
- 代码执行
- 创建阶段
变量对象
- 变量对象会保存变量声明(var)、函数参数(arguments)、函数定义(function)
- 变量对象会首先获得函数的参数变量和值
- 获取所有用
function
进行的函数声明,函数名为变量对象的属性名,值为函数对象,如果属性已经存在,值会用新值覆盖 - 再依次所有的var关键字进行的变量声明,每找到一个变量声明,就会在变量对象上建一个属性,值为
undefined
,如果变量名已经存在,则会跳过,并不会修改原属性值,let
声明的变量并不会在此阶段进行处理
- 函数声明优先级更高,同名的函数会覆盖函数和变量,但同名
var
变量并不会覆盖函数.执行阶段重新赋值可以改变原有的值
基本类型
console.log(a);
var a = 1;
var a = undefined;//变量提升
console.log(a);
a = 1;
变量提升
- 正常编写
var a = 1;
function fn(m) { console.log('fn'); }
function fn(m) { console.log('new_fn'); }
function a() { console.log('fn_a'); }
console.log(a);
fn(1);
var fn = 'var_fn';
console.log(fn);
//1
//new_fn
//var_fn
- 真正执行
// 创建阶段
function fn(m) { console.log('fn'); }
function fn(m) { console.log('new_fn'); }
function a() { console.log('fn_a'); }
var a = undefined;
var fn = undefined;
//执行阶段
a = 1;
console.log(a);
fn();
fn = 'var_fn';
console.log(fn);
- 上下文
// 创建阶段
var globalEC = {
VO: {
...arguments,
a: () => { console.log('fn_a'); },
fn: () => { console.log('new_fn'); }
}
}
var ECStack = [globalEC];
//执行阶段
globalEC.VO.a = 1;
console.log(globalEC.VO.a);
globalEC.VO.fn();
globalEC.VO.fn = 'var_fn';
console.log(globalEC.VO.fn);
激活对象
- 在函数的调用栈中,如果当前执行上下文处于函数调用栈的顶端,则意味着当前上下文处于激活状态,此时变量对象称为活动对象(AO,Activation Object) VO=>AO
- 活动变量包含变量对象所有的属性,并有包含
this
指针
function one(m) {
function two() {
console.log('two');
}
}
one(1);
//执行阶段 VO=>AO
let VO = AO = {
m:1,
two: () => { console.log('two'); },
}
let oneEC={
VO,
this: window,
scopeChain:[VO,globalVO]
}
全局上下文的变量对象
- 在浏览器里,全局对象为
window
- 全局上下文的变量对象为
window
,而且这个变量对象不能激活变成活动对象 - 只在窗口打开,全局上下文会一直存在,所有的上下文都可以直接访问全局上下文变量对象上的属性
- 只有全局上下文的变量对象允许通过VO的属性名称来间接访问,在函数上下文中是不能直接访问VO对象的
- 未进入执行阶段前,变量对象中的属性都不能访问!但是进入到执行阶段之后,变量对象转变成了活动对象,里面的属性都能被访问了,对于函数上下文来讲,活动对象与变量对象其实都是同一个对象,只是处于执行上下文的不同生命周期
作用域
作用域
- 在JS中,作用域是用来规定变量访问范围的规则
function one() {
var a = 1;
}
console.log(a);
作用域链
作用域链
-
作用域链是由当前执行环境与上层执行环境的一系列变量对象组成的,它保证了当前执行环境对符合访问权限的变量和函数的有序访问
function one() { var a = 1; function two() { var b = 2; function three() { var c = 3; console.log(a, b, c); } three(); } two(); } one();
// 1.创建全局上下文 var globalExecuteContextVO = { one: `()=>{var a = 1;}` } var globalExecuteContext = { VO: globalExecuteContextVO, scopeChain: [globalExecuteContextVO] } var executeContextStack = [globalExecuteContext]; //2.执行one,创建one执行上下文 var oneExecuteContextVO = { a: 1, two: `()=>{var b = 2 ;}` } var oneExecuteContext = { VO: oneExecuteContextVO, scopeChain: [oneExecuteContextVO, globalExecuteContext.VO] } //2.执行two,创建two执行上下文 var twoExecuteContextVO = { b: 2, three: `()=>{var c = 3 ;}` } var twoExecuteContext = { VO: twoExecuteContextVO, scopeChain: [twoExecuteContextVO, oneExecuteContext.VO, globalExecuteContext.VO] } //3.执行three,创建three执行上下文 var threeExecuteContextVO = { c: 3 } var threeExecuteContext = { VO: threeExecuteContextVO, scopeChain: [threeExecuteContextVO, twoExecuteContext.VO, oneExecuteContext.VO, 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'), );
scopeChain
scopeChain
其实是在创建函数的时候确定的
function one() {
var a = 1;
function two() {
console.log(a);
}
return two;
}
var a = 2;
var two = one();
two();
// 1.创建全局上下文
var globalExecuteContextVO = { one: `()=>{var a = 1;}`, a: undefined, two: undefined }
var globalExecuteContext = {
VO: globalExecuteContextVO,
scopeChain: [globalExecuteContextVO]
}
//2.开始执行
globalExecuteContextVO.a = 2;
//3.开始执行one
var oneExecuteContextVO = { a: undefined, two: `()=>{console.log(a)}` }
var oneExecuteContext = {
VO: oneExecuteContextVO,
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]
}
闭包
- 闭包有两部分组成,一个是当前的执行上下文A,一个是在该执行上下文中创建的函数B
- 当B执行的时候引用了当前执行上下文A中的变量就会产出闭包
- 当一个值失去引用的时候就会会标记,被垃圾收集回收机回收并释放空间
- 闭包的本质就是在函数外部保持内部变量的引用,从而阻止垃圾回收
- 调用栈的并不会影响作用域链,函数调用栈是在执行时才确定,而作用域规则是在代码编译阶段就已经确定了
- MDN定义:闭包是指这样的作用域
foo
,它包含了一个函数fn
,这个函数fn1
可以调用被这个作用域所封闭的变量a
、函数等内容
闭包
Call Stack
为当前的函数调用栈Scope
为当前正在被执行函数的作用域链Local
为当前的活动对象
function one() {
var a = 1;
var b = 2;
function two() {
var c = 3;
debugger;
console.log(a,c);
}
return two;
}
let two = one();
two();
function one() {
var a = 1;
var b = 2;
function two() {
debugger;
console.log(a);
}
two();
}
one();
闭包优化
- 中间没用到的变量闭包会被忽略
function one() {
var a = 1;
function two() {
var b = 2;
function three() {
var c = 3;
debugger;
console.log(a, b, c);
}
three();
}
two();
}
one();
function one() {
var a = 1;
function two() {
var b = 2;
function three() {
var c = 3;
debugger;
console.log(a, c);
}
three();
}
two();
}
one();
arguments
function log(a, b) {
debugger;
console.log(a, b);
}
log(1, 2);
var和let
- JS中作用域有:全局作用域、函数作用域。没有块作用域的概念。ECMAScript 6(简称ES6)中新增了块级作用域
- 块作用域由
{ }
包括,if
语句和for
语句里面的{ }
也属于块作用域
ES5问题
全局变量
-
在if或者for循环中声明的变量会变成全局变量
for(var i=0;i<=5;i++){ console.log("hello"); } console.log(i); //5
内层变量可能会覆盖外层变量
var a = 1;
function fn() {
console.log(a);
if (false) {
var a = 2;
}
}
fn(); //undefined
let
- 允许块级作用域任意嵌套
- 外层作用域无法读取内层作用域的变量
- 内层作用域可以定义外层作用域的同名变量
- 函数本身的作用域在其所在的块级作用域之内
'use strict'
function fn() {
console.log("out");
}
(function () {
if (false) {
function fn() {
console.log("in");
}
}
fn();
}());
var&let&const
- var定义的变量没有块的概念,可以跨块访问,不能跨函数访问,有变量提升,可重复声明
- let定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问,无变量提升,不可以重复声明
- let 声明的变量只在块级作用域内有效,不存在变量提升,而是绑定在暂时性死区
- 或者说let变量提升了,但是在let声明变量前不能使用该变量,这特性叫暂时性死区(temporal dead zone)
- 如果有重复变量
let
会在编译阶段报错
暂时性死区
// 不存在变量提升
'use strict';
function func(){
console.log(i);
let i;
};
func(); // 报错
全局变量
- ES5声明变量只有两种方式:var和function
- ES6有let、const、import、class再加上ES5的var、function共有六种声明变量的方式
- 浏览器环境中顶层对象是window,Node中是global对象
- ES5中 顶层对象的属性等价于全局变量
- ES6中var、function声明的全局变量,依然是顶层对象的属性;let、const、class声明的全局变量不属于顶层对象的属性
this
- 当前函数的this是在被调用的时候才能确定的
- 如果当前的执行上下文处于调用栈的栈顶,这个时候变量对象变成了活动对象,THIS指针才能确定
全局对象
- 全局对象this指向本身
var a=1;//声明绑定变量对象,但在全局环境中,变量对象就是全局对象
this.b=2;//this绑定全局对象
c=3;//赋值操作 隐式绑定
用点调用
- 在一个函数上下文中,this由函数的调用者提供,由调用函数的方式来决定指向
- 如果是函数执行,如果前面有点,那么点前面是谁
this
就是谁
let obj = {
getName(){
console.log(this);
}
};
obj.getName();
直接调用
如果没有,this就是window(严格模式下是undefined),自执行函数中的this一般都是window
Strict_mode
let obj = {
getName(){
console.log(this);
}
};
let getName = obj.getName;
getName();
绑定事件
- 给元素绑定事件的时候,绑定的方法中的this一般是元素本身
container.addEventListener('click',function(){
console.log(this);
});
箭头函数
- 箭头函数没有自己的this
- 也没有prototype
- 也没有arguments
- 无法创建箭头函数的实例
let fn = () => {
console.log(this);
console.log(arguments);//Uncaught ReferenceError: arguments is not defined
}
console.log(fn.prototype);//undefined
fn();
new fn();//VM4416:8 Uncaught TypeError: fn is not a constructor
构造函数
- 构造函数中的THIS是当前类的实例
function fn(){
}
let obj = new fn();
call/apply/bind
- call/apply/bind可以改变函数中this的指向
- 第一个参数是改变this指向(非严格模式下,传递null/undefined指向也是window)
- call参数是依次传递,apply是以数组的方式传递
!function (proto) {
function getContext(context) {
context = context || window;
var type = typeof context;
if (['number', 'string', 'boolean', 'null'].includes(type)) {
context = new context.constructor(context);
}
return context;
}
function call(context, ...args) {
context = getContext(context);
context._fn = this;
let result = context._fn(...args);
delete context._fn;
return result;
}
function apply(context, args) {
context = getContext(context);
context._fn = this;
let result = context._fn(...args);
delete context._fn;
return result;
}
function bind(context, ...bindArgs) {
return (...args) => this.call(context, ...bindArgs, ...args);
}
proto.call = call;
proto.apply = apply;
proto.bind = bind;
}(Function.prototype)
绑定
- 默认绑定
- 隐式绑定
- 显式绑定
- new绑定
- new > 显示 > 隐式 > 默认
隐式 > 默认
function one() {
console.log(this)
}
var obj = {
name: "obj",
one
}
obj.one()
显示 > 隐式
function one() {
console.log(this)
}
var obj = {
name: "obj",
one: one.bind("hello")
}
obj.one()
new > 显示
function one() {
console.log(this)
}
var helloOne = one.bind("hello")
var obj = new helloOne();
console.log(obj);
面向对象
- 对象为无序属性的集合,其属性可以包含基本值、对象和函数
- Inheritance_and_the_prototype_chain
对象和基本数据类型的本质区别
- 基本数据类型是光棍,或者说一个值
- 二对象类型是若干属性的集合
- 一切引用类型都是对象
- 函数和数组也是对象
function和其他对象的本质区别
- function本质上来说是可以生产别的对象,它是一个对象的工厂,所有的函数对象,包含函数对象本身都是函数产生的
- 函数可以用来批量的生产对象
- 我们可以把对象的属性分成两种,有些属性是私有的,有些属性是共有的
- 把批量创建出来的对象(构造函数实例)共有的属性放在构造函数的原型
prototype
上
原型链
- jsinstanceof
- 原型链是为了实现属性和方法的共享
- Function是的核心作用是用来批量创建对象
一切皆对象
- 对象就是一些属性的集合
- 方法也是一种属性
- 一切(引用类型)都是对象,对象是属性的集合
- 函数和数组也是对象
- 为什么
typeof function='function'
typeof
- 检测数据类型
typeof
返回的都是字符串 - 基本数据类型 number string boolean undefined symbol
- 引用类型 null {} [] /&$/ Date => object
console.log(typeof a); // undefined
console.log(typeof 1); // number
console.log(typeof 'hs'); // string
console.log(typeof true); // boolean
console.log(typeof Symbol('a')); // symbol
console.log(typeof function () { }); //function
console.log(typeof [1, 2, 3]); //object
console.log(typeof { name: 'hs' }); //object
console.log(typeof null); //object
console.log(typeof new Number(1)); //object
函数
- 对象是通过函数创建的
- 批量生产对象的函数
Object
- 实现私有和公有属性的封装
let obj = new Object();
obj.name='hs';
obj.age = 10;
隐式原型
proto
-
Function.prototype == Function.__proto__
-
Object.prototyp.__proto__ = null
-
函数的祖宗是Function
-
Object的祖宗是null
自定义函数的prototype
自定义函数
- 自定义函数Foo.proto指向Function.prototype
- Function的prototype和proto都指向
Function.prototype
let add = new Function('a','b','return a+b');
console.log(add(1,2));
instanceof
- nstanceof运算符的第一个参数是一个对象,第二个参数一般是一个函数
- instanceof的判断规则是: 沿着对象的
__proto__
这条链来向上查找找,如果能找到函数的prototype则返回true,否则 返回false
批量创建对象
- 通过
new
来调用一个函数,这个函数就成为了构造函数,构造函数里可以对例对象的私有属性赋值 - 每个函数会有一个
prototype
属性,此原型对象上存放所有实例的公有方法 - 若new的构造函数自己返回引用值,则以自己返回的为主,否则 返回创建的实例
- create
function Person(name) {
this.name = name;
}
Person.prototype.getName = function () {
console.log(this.name);
}
let person = new Person('hs');
person.getName();
Object.create = function (proto) {
function F() {}
F.prototype = proto;
return new F();
};
function _new(clazz, ...args) {
let _this = Object.create(clazz.prototype);
let result = clazz.call(_this, ...args);
if ((result != null && typeof result === 'object') || typeof result === 'function') {
return result;
}
return _this;
}
继承
lass Father {
static staticFatherName = "FatherName"
static staticGetFatherName = function () {
console.log(Father.staticFatherName);
}
constructor(public name) {
this.name = name;
}
getName() {
console.log(this.name);
}
}
class Child extends Father {
static staticChildName = "ChildName"
static staticGetChildName = function () {
console.log(Child.staticChildName);
}
constructor(public name, public age) {
super(name);
this.age = age;
}
getAge() {
console.log(this.age);
}
}
let child = new Child('hs', 10);
child.getName();
child.getAge();
Child.staticGetFatherName();
Child.staticGetChildName();
var _extends = (function () {
var extendStatics = function (Child, Father) {
return Object.setPrototypeOf(Child, Father);
}
return function (Child, Father) {
extendStatics(Child, Father);
function Temp() {
this.constructor = Child;
}
Temp.prototype = Father.prototype;
Child.prototype = new Temp();
};
})();
var Father = (function () {
function Father(name) {
this.name = name;
}
Father.prototype.getName = function () {
console.log(this.name);
};
Father.staticFatherName = "FatherName";
Father.staticGetFatherName = function () {
console.log(Father.staticFatherName);
};
return Father;
}());
//_super父类构造函数
var Child = (function (_super) {
_extends(Child, _super);
function Child(name, age) {
_super.call(this, name);//继承父类的实例私有属性
this.age = age;
return this;
}
Child.prototype.getAge = function () {
console.log(this.age);
};
Child.staticChildName = "ChildName";
Child.staticGetChildName = function () {
console.log(Child.staticChildName);
};
return Child;
}(Father));
let child = new Child('hs', 10);
console.log(child);
child.getName();
child.getAge();
Child.staticGetFatherName();
Child.staticGetChildName();
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var Father = /** @class */ (function () {
function Father(name) {
this.name = name;
this.name = name;
}
Father.prototype.getName = function () {
console.log(this.name);
};
Father.staticFatherName = "FatherName";
Father.staticGetFatherName = function () {
console.log(Father.staticFatherName);
};
return Father;
}());
var Child = /** @class */ (function (_super) {
__extends(Child, _super);
function Child(name, age) {
var _this = _super.call(this, name) || this;
_this.name = name;
_this.age = age;
_this.age = age;
return _this;
}
Child.prototype.getAge = function () {
console.log(this.age);
};
Child.staticChildName = "ChildName";
Child.staticGetChildName = function () {
console.log(Child.staticChildName);
};
return Child;
}(Father));
var child = new Child('hs', 10);
child.getName();
child.getAge();
Child.staticGetFatherName();
Child.staticGetChildName();
原型链面试题
-
Operator_Precedence
function Foo() { getName = function () { console.log(1); } return this; } Foo.getName = function () { console.log(2); } Foo.prototype.getName = function () { console.log(3); } var getName = function () { console.log(4); } function getName() { console.log(5); } Foo.getName(); getName(); Foo().getName(); getName();//1 new Foo.getName(); new Foo().getName(); new new Foo().getName();
异步面试题
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(function () {
console.log('setTimeout')
})
async1()
new Promise(function (resolve) {
console.log('promise1')
resolve()
}).then(function () {
console.log('promise2')
})
console.log('script end')
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
ES6
- 词法环境VS静态作用域
- 变量环境(variableEnvironment)和词法环境(Lexical Environment)
- 闭包
call 和 apply 的区别是什么,哪个性能更好一些?
- 都可以让函数执行并且改变函数里的this
- call参数是以此传入
- apply函数是以数组的形式传入
手写call
call 性能更高,更快
Function.prototype.call = function (obj, ...arg) {
const context = obj;
// Symbol保证唯一性
const fn = Symbol();
context[fn] = this;
const ret = context[fn](...arg);
delete context[fn];
return ret;
}
手写apply
Function.prototype.apply = function(obj, arg) {
const context = obj;
const fn = Symbol();
context[fn] = this;
const ret = context[fn](...arg);
delete context[fn]
return ret;
}
XHR具体底层原理
- https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest
编写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);
数组扁平化flat方法的多种实现?
let arr = [
[1],
[2, 3],
[4, 5, 6, [7, 8, [9, 10, [11]]]],
12
];
flat
// let flattedArr = arr.flat(Infinity);
// console.log(flattedArr);
//toString
console.log(arr.toString().split(',').map(item => Number(item)));
//stringify
console.log(JSON.stringify(arr).replace(/\[|\]/g, '').split(',').map(item => Number(item)));
//while
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
console.log(arr);
//prototype
Array.prototype.flat = function () {
let result = [];
let _this = this;
function _flat(arr) {
for (let i = 0; i < arr.length; i++) {
let item = arr[i];
if (Array.isArray(item)) {
_flat(item);
} else {
result.push(item);
}
}
}
_flat(_this);
return result;
}
console.log(arr.flat());
实现一个不可变对象
- 无论是不可扩展,密封,还是冻结,都是浅层控制的,即只控制对象本身属性的增删改。如果对象属性是一个引用类型,比如数组 subArr 或对象 subObj等,虽然subArr、subObj 的不可被删改,但subArr、subObj 的属性仍然可增删改
- 由于每个对象都有一个属性
__proto__
,该属性的值是该对象的原型对象,也是引用类型,由于冻结是浅层的所以原型对象并不会被连着冻结,仍然可以通过给对象的原型对象加属性达到给当前对象新增属性的效果。所以如果想进一步冻结还需要把原型对象也冻结上
不可扩展
Object.preventExtensions()
可以使一个对象不可再添加新的属性,参数为目标对象,返回修改后的对象
var obj = Object.preventExtensions({
name: 'hs'
});
var obj = { name: 'hs' };
console.log(Object.isExtensible(obj));// true
Object.preventExtensions(obj);
console.log(Object.isExtensible(obj)); // false
//Object.defineProperty(obj, 'age', {value: 10});
//TypeError: Cannot define property age, object is not extensible
obj.age = 10;
console.log(obj.age); // undefined
密封
Object.seal()
可以使一个对象无法添加新属性的同时,也无法删除旧属性。参数是目标对象,返回修改后的对象- 其本质是通过修改属性的
configurable
为false
来实现的 configurable
为false
时,其他配置不可改变,writable
只能true
变false
,且属性无法被删除。而由于只要writable
或configurable
其中之一为true
,则value
可改,所以密封之后的对象还是可以改属性值的Object.isSealed()
可以检测一个对象是否密封,即是否可以增删属性。参数是目标对象,返回布尔值,true 代表被密封不可增删属性,false 代表没被密封可增删属性
var obj = new Object();
Object.isExtensible(obj); // true
Object.isSealed(obj); // false
Object.seal(obj);
Object.isExtensible(obj); // false,注意 seal 后对象的 isExtensible() 也随之改变
Object.isSealed(obj); // true
var obj = { name: 'hs' };
console.log(Object.getOwnPropertyDescriptor(obj, 'name'));
/**
{
value: 'hs',
writable: true,
enumerable: true,
configurable: true
}
*/
Object.seal(obj);
console.log(Object.getOwnPropertyDescriptor(obj, 'name')); // seal 后 configurable 变为 false
/**
{
value: 'hs',
writable: true,
enumerable: true,
configurable: false
}
*/
冻结
Object.freeze()
可以使对象一个对象不能再添加新属性,也不可以删除旧属性,且不能修改属性的值。参数是目标对象,返回修改后的对象。Object.isFrozen()
可以检测一个对象是否冻结,即是否可以增删改。参数是目标对象,返回布尔值,true 表示已经冻结不可再增删改,false 反之
ar obj = new Object();
Object.isExtensible(obj); // true
Object.isSealed(obj); // false
Object.isFrozen(obj); // false
Object.freeze(obj);
Object.isExtensible(obj); // false,注意 freeze 后对象的 isExtensible() 也随之改变
Object.isSealed(obj); // true,注意 freeze 后对象的 isSealed() 也随之改变
Object.isFrozen(obj); // true
var obj = Object.freeze({ name: 'hs' });
// 直接定义新的属性会报错
Object.defineProperty(obj, 'name', {
value: 'hs'
});
obj.name = 'hs';
obj.name; // undefined
delete obj.name; // 删除失败,返回 false
obj.name = 'jiagou';
obj.name; // 仍然是 "hs"
给定一组url,利用js的异步实现并发请求,并按顺序输出结
function printOrder(urlArr) {
Promise.all(urlArr.map(url => new Promise(function (resolve, reject) {
let xhr = new XMLHttpRequest;
xhr.open('GET', url, true);
xhr.responseType = 'json';
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
resolve(xhr.response);
}
}
xhr.send();
}))).then(result => {
console.log(result);
});
}
printOrder(['/1.json?ts=' + Date.now(), '/2.json?ts=' + Date.now()]);
function printOrder(urlArr, callback) {
let result = {};
function sendRequest(url, index) {
let xhr = new XMLHttpRequest;
xhr.open('GET', url, true);
xhr.responseType = 'json';
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
result[index] = xhr.response;
if (Object.keys(result).length == urlArr.length) {
result.length = Object.keys(result).length;
callback(null, Array.from(result));
}
}
}
xhr.send();
}
urlArr.forEach(function (url, index) {
sendRequest(url, index);
});
}
printOrder(['/1.json?ts=' + Date.now(), '/2.json?ts=' + Date.now()], (err, result) => {
console.log(result);
});
Reflect Proxy
- Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来
代理
某些操作,可以译为“
代理器` - Proxy
let target = {
name: 'hs',
age: 10
}
let handler = {
get: function (target, key) {
return target[key];
},
set: function (target, key, value) {
target[key] = value;
}
}
let proxy = new Proxy(target, handler)
console.log(proxy.name);
proxy.age = 25;
console.log(proxy.age);
- 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上
- Reflect
let target = {
name: 'hs',
age: 10
}
//Object.defineProperty();
Reflect.defineProperty(target, 'home', {
value: '北京'
})
console.log(target.home);
// 'home' in target
console.log(Reflect.has(target, 'home'));
写一个函数,可以控制最大并发
let Semaphore = require('semaphore');
let semaphore = new Semaphore(2);
console.time('cost');
semaphore.take(function () {
setTimeout(() => {
console.log(1);
semaphore.leave();
}, 1000);
});
semaphore.take(function () {
setTimeout(() => {
console.log(1);
semaphore.leave();
}, 2000);
});
semaphore.take(function () {
console.log(3);
semaphore.leave();
console.timeEnd('cost');
});
class Semaphore {
constructor(available) {
this.available = available;
this.waiters = [];
this._continue = this._continue.bind(this);
}
take(callback) {
if (this.available > 0) {
this.available--;
callback();
} else {
this.waiters.push(callback);
}
}
leave() {
this.available++;
if (this.waiters.length > 0) {
process.nextTick(this._continue);
}
}
_continue() {
if (this.available > 0) {
if (this.waiters.length > 0) {
this.available--;
const callback = this.waiters.pop();
callback();
}
}
}
}
js模块化(commonjs/AMD/CMD/ES6)
- https://blog.csdn.net/weixin_44100002/article/details/134944673
promise、async await、Generator的区别
- https://blog.51cto.com/u_15967457/6081719
柯理化
函数柯里化就是把接受多个参数的函数变换成接受一个单一参数的函数,并且返回接受余下参数返回结果
bind
~function (prototype) {
function bind(context = global, ...outerArgs) {
return (...innerArgs) => {
return this.call(context, ...outerArgs, ...innerArgs);
}
}
prototype.bind = bind;
}(Function.prototype);
function sum(...args) {
return this.prefix + args.reduce((acc, curr) => acc + curr, 0);
}
let obj = { prefix: '$' };
let bindSum = sum.bind(obj, 1, 2, 3);
console.log(bindSum(4, 5));
~(function () {
Object.create = function (proto) {
function F() { }
F.prototype = proto;
return new F();
};
Function.prototype.bind = function (oThis, ...outerArgs) {
var thatFunc = this,
fBound = function (...innerArgs) {
return thatFunc.apply(
this instanceof thatFunc ? this : oThis, [...outerArgs, ...innerArgs])
};
fBound.prototype = Object.create(thatFunc.prototype);
return fBound;
}
})();
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return this.x + ',' + this.y;
};
var emptyObj = {};
var YAxisPoint = Point.bind(null, 1/*x*/);
var axisPoint = new YAxisPoint(2);
console.log(axisPoint.toString()); // '1,2'
console.log(axisPoint instanceof Point); // true
console.log(axisPoint instanceof YAxisPoint); // true
19.2 add
console.log(add(1, 2, 3, 4, 5));//15
console.log(add(1)(2, 3)(4, 5));//15
console.log(add(1)(2)(3)(4)(5));//15
const add = (function (length) {
let allArgs = [];
function _add(...args) {
allArgs = [...allArgs, ...args];
if (allArgs.length >= length) {
let sum = allArgs.reduce((acc, curr) => acc + curr, 0);
allArgs.length = 0;
return sum;
} else {
return _add;
}
}
return _add;
})(5);
alert(add(1, 2, 3, 4, 5));//15
alert(add(1)(2, 3)(4));//15
alert(add(1)(2)(3));//15
function add(...args) {
var _add = add.bind(null, ...args);
_add.toString = function () {
return args.reduce((sum, item) => sum + item, 0);
}
return _add;
}
- 函数柯里化就是把接受多个参数的函数变换成接受一个单一参数的函数,并且返回接受余下参数返回结果的技术
function curry(fn, ...args) {
return args.length < fn.length ? (...extraArgs) => curry(fn, ...args, ...extraArgs) : fn(...args)
}
function addFn(a, b, c, d, e) {
return a + b + c + d + e;
}
let add = curry(addFn);
console.log(add(1, 2, 3, 4, 5));//15
console.log(add(1)(2, 3)(4, 5));//15
console.log(add(1)(2)(3)(4)(5));//15
拷贝
JSON.parse
- 无法支持所有类型,比如函数
let obj = { name: 'hs', age: 10 };
console.log(JSON.parse(JSON.stringify(obj)));
浅拷贝
function clone(source) {
let target = {};
for (const key in source) {
target[key] = source[key];
}
return target;
};
深拷贝
- 支持对象和数组
let obj = {
name: 'hs',
age: 10,
home: { name: '北京' },
hobbies: ['抽烟', '喝酒', '烫头']
};
function clone(source) {
if (typeof source === 'object') {
let target = Array.isArray(source) ? [] : {};
for (const key in source) {
target[key] = clone(source[key]);
}
return target;
}
return source;
};
let cloned = clone(obj);
console.log(Array.isArray(cloned.hobbies));
循环引用
let obj = {
name: 'hs',
age: 10,
home: { name: '北京' },
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;
};
let cloned = clone(obj);
console.log(cloned.obj);
while
let obj = {
name: 'hs',
age: 10,
home: { name: '北京' },
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);
let keys = Object.keys(source);
let length = keys.length;
let index = 0;
while (index < length) {
target[keys[index]] = clone(source[keys[index]], map);
index++;
}
return target;
}
return source;
};
function getType(source) {
return Object.prototype.toString.call(source);
}
精确类型
判断类型方式
- typeof
- 返回结果都是字符串
- 字符串中包含了对应的数据类型 number string boolean undefined symbol
- typeof null == ‘object’
- typeof {} [] /&$/ Date== ‘object’
- instanceof
- Object.prototype.toString.call
判断类型
et obj = {
married: true,
age: 10,
name: 'hs',
girlfriend: null,
boyfriend: undefined,
flag: Symbol('man'),
home: { name: '北京' },
set: new Set(),
map: new Map(),
getName: function () { },
hobbies: ['抽烟', '喝酒', '烫头'],
error: new Error('error'),
pattern: /^regexp$/ig,
math: Math,
json: JSON,
document: document,
window: window
};
obj.set.add(1);
obj.map.set('name', 'value');
obj.obj = obj;
let OBJECT_TYPES = [{}, [], new Map(), new Set(), new Error(), new Date(), /^$/].map(item => getType(item));
const MAP_TYPE = getType(new Map());
const SET_TYPE = getType(new Set());
const CONSTRUCT_TYPE = [new Error(), new Date()].map(item => getType(item));
const SYMBOL_TYPE = getType(Symbol('1'));
const REGEXP_TYPE = getType(/^$/);
function clone(source, map = new Map()) {
let type = getType(source);
if (!OBJECT_TYPES.includes(type)) {//基本数据类型
return source;
}
if (map.get(source)) {
return map.get(source);
}
if (CONSTRUCT_TYPE.includes(type)) {
return new source.constructor(source);
}
let target = new source.constructor();
map.set(source, target);
if (SYMBOL_TYPE === type) {
return Object(Symbol.prototype.valueOf.call(source));
}
if (REGEXP_TYPE === type) {
const flags = /\w*$/;
const target = new source.constructor(source.source, flags.exec(source));
target.lastIndex = source.lastIndex;
return target;
}
if (SET_TYPE === type) {
source.forEach(value => {
target.add(clone(value, map));
});
return target;
}
if (MAP_TYPE === type) {
source.forEach((value, key) => {
target.set(key, clone(value, map));
});
return target;
}
let keys = Object.keys(source);
let length = keys.length;
let index = 0;
while (index < length) {
target[keys[index]] = clone(source[keys[index]], map);
index++;
}
return target;
};
function getType(source) {
return Object.prototype.toString.call(source);
}
let cloned = clone(obj);
console.log(cloned);
console.log(obj.home === cloned.home);
console.log(obj.set === cloned.set);
console.log(obj.map === cloned.map);
/*
[object Boolean]
[object Number]
[object String]
[object Null]
[object Undefined]
[object Symbol]
[object Object]
[object Function]
[object Array]
[object Error]
[object RegExp]
[object Math]
[object JSON]
[object HTMLDocument]
[object Window]"
*/
参考资料
- https://zhuanlan.zhihu.com/p/697139311