【前端基础知识复习】

2023-11-19

原型链

  每一个构造函数都会有一个原型对象,可以使用prototype来找到,通过构造函数进行实例化对象的时候,这个实例化对象就会有一个__proto__的属性在里面,这个属性指向的就是原型对象。
  原型对象不能直接找到实例化对象,但是可以通过constructor找到构造函数,每一个原型对象都有一个constructor指向关联的构造函数。
  原型链:当读取一个实例化对象的属性时,如果他自身没有这个属性,就会向他的上一级原型对象上找,如果他的构造函数的原型对象也没有,就继续往构造函数的构造函数的原型属性去找,一级一级找到最顶层为止。最顶层就是Object.prototype,它指向的时null。
  整个过程有点像继承,但是js不会复制对象的属性,所以不应该叫做继承。
原型与原型链示意图

继承

原型链继承

用法:改变子函数的prototype指向父函数。
优点:通过子函数实例化出来的对象,如果修改了数据,这些数据会通过原型链被其他实例化对象共享。
缺点:不能向父级Parent传参。

function Parent(){
	this.names = ['a','b'];
}
function Child(){
	
}
Child.prototype = new Parent();//使用原型链继承
var child1 = new Child();
child1.names.push('c')
console.log(child1.names);//['a','b','c']
var child2 = new Child();
console.log(child2.names);//['a','b','c']

经典继承(借用构造函数)

用法,在子函数中使用call改变this指向父函数
优点:数据不会被共享,可以向Parent传参
缺点:方法都定义在构造函数中,每一次实例化都会创建一遍方法,复用差,浪费性能

//不传参的例子
function Parent(){
	this.names = ['a','b'];
}
function Child(){
	Parent.call(this);//改变this指向
}
var child1 = new Child();//使用构造函数创建实例化对象
var child2 = new Child();
child1.names.push('c')
console.log(child1.names)//['a','b','c']
console.log(child2.names)//['a','b']

//传参的做法
function Parent(name){
	this.name='a';
}
function Child(name){
	Parent.call(this,name);//改变this指向,并传参
}
var child1 = new Child('a');
var child2 = new Child('b');
console.log(child1)//a
console.log(child2)//b

组合继承(1和2结合)(常用)

优点:结合了两种方式的优点,可传参,复用性高
缺点:调用两次父类的构造函数(消耗内存)

function Parent(name){
	this.name = name 
	this.num = [1,2,3]
}
function Child(name){
	Parent.call(this,name)
}
Child.prototypr = new Parent();//调用一次Parent构造函数
Child.prototype.contructor = Child;
var child1 = new Child('nike')//又调用一次Parent构造函数
var child2 = new Child('like')
child1.num.push(4)
console.log(child1.name)//nike
console.log(child1.num)//[1,2,3,4]
console.log(child2.name)//like
console.log(child1.num)//[1,2,3]

原型式继承(ES5 Object.create)

将传入的对象作为创建对象的原型,这也是ES5中的Object.create的模拟实现

function creatrObj(o){
	function F(){}
	F.prototype = o;
	return new F();
}

缺点:1.与原型链继承一样,会共享数据。
2.无法实现复用。(新实例属性都是后面添加的)

例子如下:

function create(o){
	function F(){}
	F.prototype = o;
	return new F();
}
var Parent ={
	name = 'nike';
	num = [1,2,3];
}
var child1 = create(Parent);
var child2 = create(Parent);
child1.name = 'like'
child1.num.push(4)
console.log(child1.name);//like
console.log(child1.num);//[1,2,3,4]
console.log(child2.name);//nike
console.log(child2.num);//[1,2,3,4]
//child1和child2的name值不同是因为,child1.name = 'like'这句代码会给child1添加一个name属性,而child2用的是Parent上的属性

寄生式继承

实现:其实就是在原型式继承的基础上再套一个函数

function create(o){
	function F(){}
	F.prototype = o;
	return new F();
}
function createObj(o){//把原型式继承再用一个function包起来
	var clone = new create(o);
	clone.name = 'nike';
	return clone;
}
var Parent = {
	name = 'like';
	num = [1,2,3];
}
var child = new createObj(Parent);
console.log(child.name);//nike
console.log(child.num);//[1,2,3]

寄生组合继承(最理想)

其实就是通过使用第三方函数,让这个第三方函数的实例对象指向父类的实例对象,再把子类的实例对象指向第三方的实例上。
优点:只调用了一次 Parent 构造函数,原型链保持不变

function Parent(name){
	this.name = name;
	this.num = [1,2,3];
}
function Child(name,age){
	Parent.call(this,name);
	this.age = age;
}

var F = function(){}
F.prototype = Parent.prototype
Child.prototype = new F();

var child1 = new Child('nike','18');
console.log(child1);

上述代码经过封装后就会变成:

function create(o){
	function F(){}
	F.prototype = o;
	return new F();
}
function prototype(child,parent){
	var prototype = create(parent.prototype);
	prototype.constructor = child;
	child.prototype = prototype;
}
//调用时
prototype(Child,Parent)

作用域与作用域链

在js中没有块级作用域,es6才新增了块级作用域,但是在以下情况会形成块级作用域 {} if{} for{},这些作用域也是局部作用域。每定义一个函数都会生成一个局部作用域,在没有函数包围的最外层有一个全局作用域window。

优先级:局部作用域>全局作用域

在查找对象时,优先在局部作用域中查找,在当前函数作用域中找不到时,向上级函数作用域查找,直到最终找到window全局对象为止,这个链条就被成为作用域链。

在查找的过程中有些地方需要注意:js中有变量提升的概念,还要注意匿名函数也会有作用域,举个例子说明:

	var i = "外部全局变量";
      function a() {
        console.log(i);//函数作用域中找不到,向上查找,打印全局变量
      }
      function b() {
        console.log(i);//虽然写在下面,但是变量提升,相当于在函数第一句写了var i
        //所以不会获取全局变量
        var i = "变量提升";
      }
      function c() {
        var i = "局部变量";
        console.log(i);//有局部变量,优先在自己函数作用域找到并输出
      }
      a();
      b();
      c();

闭包

官方的定义是:指那些可以访问自由变量的函数
换成最简单的话来说,闭包其实就是函数嵌套函数。
优点:可以获取函数作用域中的变量,并且使它不会被垃圾回收机制回收
缺点:因为变量不会被回收,造成内存浪费
实现:

function a(){
	var x=1;
	function b(){
		x++
		return x
	}
	return b;
}
a();

立即执行函数

顾名思义,不需要手动调用就会自动执行的函数,写法很简单,就是两个小括号()();或者(()),他可以是具名函数也可以是匿名函数

(function(){
	alert('自执行函数')
})();

(function(){
alert('自执行函数')}())

typeof和instanceof

typeof是用来判断变量的数据类型的,它能够帮助我们判断一下几种数据类型:number,string,boolean,object,function,undefine,symbol,它的返回值是字符串,告诉我们是什么类型的,但是针对null,array这种它是判断不出来的,都会返回object。

instanceof主要是用来判断变量是否属于一个对象的实例,返回的是布尔值

这里补充一下,还有一个判断类型的 方法,Object.prototype.toString.call(变量),这个方法可以返回[object 变量类型]

顺带提一下,数据类型有两种:

基础数据类型:

number、string、boolean、null、undefined。

引用数据类型:

object(Array、function、json、date)不属于基础数据类型的变量,类型都是object。

bind实现

bind是用来绑定上下文的,强制的把函数的执行环境绑定到目标作用域中,其作用于call、apply相似,与之不同的是,不会立即执行,且会返回函数

arguments是一个类数组对象,在所有的函数中,除了箭头函数,都存在这个对象,可以通过它获取到函数的参数。当然他也可以被转化为真正的数组:提供四种方法参考:

1.	var args = Array.prototype.slice.call(arguments)
2.	var args  = [].splice.call(arguments)
3.	var args  = Array.from(arguments)
4.	var args  =[... arguments]

bind的实现原理:

Function.prototype.bind = function(oThis){
    if(typeof this !== 'function'){//检测对象类型不是函数抛出错误
      throw new TypeError('被绑定的对象需要是函数')
    }
    var self = this//存储当前this
    var args = [].slice.call(arguments, 1)//取出函数的参数,从第二位开始取
    var func = function(){}//定义一个函数
    fBound = function(){ 
    var bindArgs = [].slice.call(arguments)
    // instanceof用来检测某个实例对象的原型链上是否存在这个构造函数的prototype属性,
    //this instanceof func === true时,说明返回的fBound被当做new的构造函数调用,
    //此时this=fBound(){},否则this=window, 如果是的话使用新创建的this代替硬绑定的this
      return self.apply(this instanceof func ? this : oThis, args.concat(bindArgs))
    }
    //维护原型关系
    if(this.prototype){
      func.prototype = this.prototype
    }
    //使fBound.prototype是func的实例,返回的fBound若作为new的构造函数,新对象的__proto__就是func的实例
    fBound.prototype = new func()
    return fBound
}

call和apply

call和apply都可以改变this指向,都要求只有函数可以调用该方法

区别:
传参方式不同
call的第一个参数是对象,其他参数将会被作为函数的参数传进去
apply只接收两个参数,第一个参数是对象,第二个参数是数组,数组内元素将会被作为参数一个个传进去
第一个参可以为null,此时这个对象指向window

funa.call(obj,1,2,3)//this指向的是obj,传入的参数是1,2,3
funa.call(obj,[1,2,3])//this指向的是obj,传入的参数是[1,2,3],undefine

funa.apply(obj,[1,2,3])//this指向的是obj,传入的参数是1,2,3

柯里化

把使用多个参数的一个函数,转化为多个使用一个参数的函数

// 实现一个add方法,使计算结果能够满足如下预期:
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;

function add() {
    // 第一次执行时,定义一个数组专门用来存储所有的参数
    var args = Array.prototype.slice.call(arguments);

    // 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
    var adder = function() {
        args.push(...arguments);
        return adder;
    };

    // 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
    adder.toString = function () {
    //reduce:数组中的元素依次执行回调函数
        return args.reduce(function (a, b) {
            return a + b;
        });
    }
    return adder;
}

add(1)(2)(3)                // 6
add(1, 2, 3)(4)             // 10
add(1)(2)(3)(4)(5)          // 15
add(2, 6)(1)                // 9

垃圾回收机制

在V8引擎中,将内存分为了新生代和老生代两部分。

一般情况下,新生代的存活时间比较短,老生代的存活时间比较长且数量多。

新生代内存中使用GC算法,老生代使用的标记清除算法和标记压缩算法。

新生代将内存分为两块空间,FROM和TO,这两块空间一定是有一块被用的,有一块是空闲的。

新分配的对象会先进入FROM空间,当From空间满了以后,新生代GC算法启动,就会向TO空间进行复制,在复制过程中会对对象进行检测,删除失活对象,当所有对象都被转移到TO空间时,再把两个空间互换,至此一次垃圾回收完成

老生代就比较多内容了,有很多空间,有两种情况会进入到老生代空间,第一种就是已经经历了一次GC算法的对象,另一种是TO空间的对象占比大小超过25%。

在老生代空间中出现以下情况会启动标记清除算法:
1.某一块空间没有分块
2.空间中对象超过一定限制
3.空间不能保证新生代对象进入老生代空间时

标记清除算法:遍历一遍堆中的对象,标记出活的对象,然后再将没有标记的对象清除,这个过程会非常耗时间。

标记压缩算法:清除对象后可能造成堆内存产生碎片,当碎片产生足够多时,触发压缩算法,此时将会把活的对象统一向一端移动,然后清理掉不需要的内存空间

浮点数精度

在计算时,十进制的小数会被转换为二进制进行计算,此时浮点数用二进制表示是无穷的。而js的64位双精度浮点数的小数部分最多可以有53位,此时计算0.1+0.2后,由于位数截断,再转换为十进制时就比0.3大一点,由此产生误差

new操作符

new操作其实做了四个步骤
1.创建一个空的对象;
2.设置空对象的__proto__指向构造函数的prototype,用来继承构造函数上的原型公有属性和方法;
3.调用构造函数,将构造函数中的this改变为空对象的this,继承构造函数的属性;
4.返回一个对象。
用代码实现一遍如下:

function myNew(fun){
	return function(){
		//创建一个空对象,并把__proto__指向构造函数的prototype
		let obj = {
			__proto__ : fun.prototype
		}
		//改变构造函数的this指向空对象的this
		fun.call(obj,...arguments)
		//返回新的对象
		return obj
	}
}
function Person(name,age){
	this.name = name;
	this.age = age; 
}
let child = myNew(Person)('nike',20)
console.log(child)

事件循环机制

JS是单线程的:
由于Js存在操作Dom元素的情况,如果他是多线程的话,有可能在一个线程中删除了某个Dom元素,在另一个线程中又会对这个Dom元素进行操作,这个时候就会出现报错,所以JS在设计时就被设计为单线程的。
JS是非阻塞的:
既然是单线程他就应该是阻塞的,但是在JS中为了使程序运算效果更快,引入了Event Loop,实现了非阻塞性。

加入了EventLoop之后,js的事件队列中就有主线程,微任务,宏任务,这三个队列。
由于js是在script标签中编写的,而script又被定义为宏任务,所以
主线程就是当前执行的线程,从页面的第一个script开始,建立主线程,一直往下走
宏任务:
script
setInterval
setTimeout
PostMasge
I/O

微任务:
new Promise.then()
MotationObserver

当主线程向下执行时,如果遇到微任务,就把这个任务放到微任务队列,遇到宏任务就放在宏任务队列,然后继续向下执行,直到主线程执行完毕,优先向微任务队列中提取任务加到主线程,如果微任务队列中没有任务,则在宏任务中提取任务到主线程。换句话说,每一次主线程执行完毕要去提取队列时,都先提取一遍微任务,由于这些都是队列,所以遵循的原则是先进先出。

看一个例子:

console.log('start')

setTimeout(function() {
  console.log('setTimeout')
}, 0)

Promise.resolve().then(function() {
  console.log('promise1')
}).then(function() {
  console.log('promise2')
})

console.log('end')
//输出顺序:start、end、promise1、promise2、setTimeout

分析上面的例子,首先全部代码压入执行栈。
打印start;
遇到setTimeout,放入宏任务队列;
遇到Promise,放入微任务队列;
打印end;
主线程走到底了,去微任务拿到Promise事件;
打印promise1;
遇到第二个promise.then,放到微任务队列;
主线程又走到底了,去微任务拿到Promise事件;
打印promise2;
主线程又走到底了,去微任务拿不到事件,去宏任务拿到setTimeout事件;
打印setTimeout;

再看一个相对复杂的例子:

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');
}, 0)
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

promise原理

promise是用来异步计算的,主要作用就是把异步操作队列化,按照期望的顺序去执行,返回预期结果;
使用时可以在对象之间传递和操作promise。

同步:类似队列,先进先出,不允许同时执行其他操作,需要等上一个操作执行完毕才能走下一个事件。
异步:可以同时运行多个事件,此时返回数据结果与顺序无关。

promise可以解决的问题:回调地狱,比如上一个请求的输出,将会作为下一个请求的输入,不可倒序计算。

//回调地狱演示
setTimeout(() => {
    console.log('1');
    setTimeout(() => {
        console.log('2');
        setTimeout(() => {
            console.log('3');
            setTimeout(() => {
                console.log('4');
            }, 1000);
        }, 1000);
    }, 1000);
}, 1000);

promise有两个重要关键词:resolve:把状态从pending转为fulfilled;reject:把状态从pending转为rejected;
状态只有pending–>fulfilled或者pending–>rejected,且状态一经改变就无法再次变化。

promise有三个状态:pending、fulfilled、rejected;待定(初始状态)、实现(操作成功)、否决(操作失败)
promise在调用时,有两种方式:
1、每次都执行promise.then((data)=>{}),这种情况下,每一次then都会使用resolve传过来的值,这就是promise具有状态缓存的特性。
2、链式调用,promise.then().then().then(),这种情况下,如果上一个then有return值,那当次then接收到 的就是return来的参数,如果没有return,接收到的就是undefined。

//创建一个promise
let promise = new Promise((resolve,reject)=>{
	setTimeout(()=>{
		resolve('success')
	},1000)
})
//显式调用
promise.then((data)=>{console.log('resolve:'+data)});//resolve:success
promise.then((data)=>{console.log('resolve:'+data)});//resolve:success
promise.then((data)=>{console.log('resolve:'+data)});//resolve:success
//链式调用
promise.then((data)=>{console.log('resolve:'+data)})//注意此处then里面是一个函数,才会正确执行,由于没有return,下一次的then,将会得到resolve(undefined)
.then(5)//then里面是其他数据类型,值穿透,此句代码在本次实例中无效
.then((data)=>{console.log('resolve:'+data)})//接收上一次的resolve,由于没有return,所以接收到undefined————resolve:undefined
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【前端基础知识复习】 的相关文章

随机推荐

  • webpack代码混淆

    作者 桑榆 QQ 934440653 有问题 评论留言 或qq联系 安装 npm install save dev webpack obfuscator 属性 compact true 压缩 无换行 controlFlowFlattenin
  • 网络安全体系方法论

    安全牛整合多位资深安全顾问的一线咨询经验 首次公开发布 网络安全体系方法论 旨在给企业或机构提供一个最佳实践的参考 以帮助企业真正提升对网络安全工作的认识 并在安全建设和运营中不断成长 本架构方法论参考了NIST Cybersecurity
  • 什么是流程图

    什么是流程图 流程图是对过程 算法 流程的一种图像表示 在技术设计 交流及商业简报等领域有广泛的应用 通常用一些图框来表示各种类型的操作 在框内写出各个步骤 然后用带箭头的线把它们连接起来 以表示执行的先后顺序 用图形表示算法 直观形象 易
  • How does double arrow (=>) operator work in Perl?

    原文链接 https stackoverflow com questions 4093895 how does double arrow operator work in perl The use of the gt operator ca
  • Ant Design Pro从零到一(认识AntD)

    废话 在我们第一次接触AntD的时候 会遇到两个东西 一个是Ant Design 另一个是Ant Design Pro 他们的官网分别是 Ant Design 一套企业级 UI 设计语言和 React 组件库 Ant Design Pro
  • 商品期货策略-ATR通道突破策略

    实现平台 BigQuant 人工智能量化投资平台 可在文末前往原文一键克隆代码进行进一步研究 导语 商品期货交易上线啦 听闻这个消息的小编当然坐不住了 决定立刻商品期货走一波 本文选择实现的是经典的ATR通道突破策略 也被称为波动性突破策略
  • C++——详解类模板与友元函数

    纵有疾风起 人生不言弃 本文篇幅较长 如有错误请不吝赐教 感谢支持 文章目录 类模板与友元函数 1 非模板友元函数 2 约束模板友元函数 3 非约束模板友元函数 类模板与友元函数 模板类的友元函数有三类 1 非模板友元函数 友元函数不是模板
  • 【2023秋招面经】深信服 前端 一面(1h)

    自我介绍 项目比较有难度的地方 你刚刚说Vue3按功能来组合 不是按Option API组合 你简单写一下伪代码 实现一个计时器 进入这个模块计时器开始计时 离开页面则销毁 在中间过程中 获取计时的时间 function useTime 实
  • JavaScript动态生成表格

    源代码
  • 修改 TeamViewer ID 的方法:

    TeamViewer 使用频繁后会被判定为商业用途 不可用 此软件的账号和设备mac地址绑定 修改TeamViewer ID后可以重新开始使用 下述方法可以成功修改TeamViewer ID 关闭TeamViewer 开始 gt 运行 录入
  • element-table新增 以input框形式输入

    工作遇到的问题记录下 代码如下 选了其中一行做例子
  • 好用的在线画图工具processon

    ProcessOn是一款基于SaaS的前沿 高效线上作图工具 它将Visio Xmind等专业作图工具搬到了 云端 注册链接 https www processon com i 564bf42ce4b0a080c6d1b18f 保存 分享都
  • go字符串详解

    文章目录 摘要 1 byte和rune类型 2 字符串 string 3 练习 反转字符串 摘要 go字符串结构体包含 指向底层存储数组的指针 字符串长度 字符串按utf 8将字符编码成二进制数 然后存储在byte数组中 因为utf 8编码
  • gin jwt-go 生成token及jwt中间件验证

    一 定义jwt中间件 同时将 生成token 验证token token中间件handlerfunc都写在中间件文件中 基于 github com dgrijalva jwt go 包 gin框架 obj middleware jwt go
  • 回声消除(AEC)原理、算法及实战——AEC背景介绍

    回声就是声音信号经过一系列反射之后 又听到了自己讲话的声音 这就是回声 一些回声是必要的 比如剧院里的音乐回声以及延迟时间较短的房间回声 而大多数回声会造成负面影响 比如在有线或者无线通信时重复听到自己讲话的声音 回想那些年我们开黑打游戏时
  • EL表达式向select中赋值

    在使用el表达式的时候 有时需要向select下拉菜单中赋值 可以使用三目运算法进行赋值
  • 3. ClickHouse数据类型和表结构

    3 1 数据类型 整数类型 整数类型有Int8 Int16 Int32 Int64 分别表示8位 16位 32位和64位有符号整数 适用场景 存储整数值 如年龄 数量等 浮点类型 浮点类型有Float32和Float64 分别表示32位和6
  • Jenkins自动化部署项目

    Jenkins史上最详细的使用教程 jenkins官网 jenkins简单部署Vue项目 jenkins部署springboot项目 jenkins详细部署说明 后续小编会写上自己的使用心得 jenkins官网 https www jenk
  • 计算机用于更新无法卸载补丁,出现windows 系统补丁无法卸载该怎么解决?简单几步即可解决...

    电脑已经成为我们日常生活中的必备品 长期使用电脑肯定会碰到win10系统kb4034674无法卸载提示 没有成功卸载全部更新的问题 很多用户之前从未遇到win10系统kb4034674无法卸载提示 没有成功卸载全部更新这样的问题 其实win
  • 【前端基础知识复习】

    js基础知识复习 原型链 继承 原型链继承 经典继承 借用构造函数 组合继承 1和2结合 常用 原型式继承 ES5 Object create 寄生式继承 寄生组合继承 最理想 作用域与作用域链 闭包 立即执行函数 typeof和insta