@Secretmm
2023-02-06T02:00:39.000000Z
字数 11449
阅读 887
p6必备
简述: js
实现继承实例化的手段
prototype
:对象生成器的一个属性,显式原型,默认情况下(可以修改)使用这个生成器生成的实例对象的_proto_
会指向这里的prototype
。
_proto_
:对象的一个属性,隐式原型,指向生成该对象的生成器的prototype
。
a._proto_
即A.prototype
上找,有即访问;若没有则再去a._proto_._proto_
即A.prototype._proto_
上找,有即访问;以此类推。A.prototype
、A.prototype._proto_
访问声明在B及B的父类上的方法,那么只需要将B的实例b赋值给A.prototype
,A.prototype
即是b,可以访问声明在B上的属性,A.prototype._proto_
即是b._proto_
即B.prototype
,可以访问B的原型属性。这就是最简单直接的原型链继承,缺点是所有A的实例共享声明在父类B上的属性,因为它们的原型是同一个b实例。
function B () {
this.name = '666';
}
var b = new B();
function A () {};
A.prototype = b;
a1 = new A();
a2 = new A();
a3 = new A();
a4 = new A();
a1.name === a2.name === a3.name === a4.name === b.name === '666';//true
b.name = '333';
a1.name === a2.name === a3.name === a4.name === '333';//true
//a2实例上新增属性name,访问a2.name时,原型链上的属性name被屏蔽
a2.name = '555';
a1.name === a3.name === a4.name === '333';//true
a2.name === '555';//true
借用构造函数
:在A
方法的开头调用B.call(this)
,这样在执行A
的时候就会先执行一遍B
,把B
中声明的实例属性也初始化到A
上,A
的所有实例都能访问到B的实例属性。缺点是不继承原型属性【A.protype._proto_
不指向 B.prototype
】。
function A() {
B.call(this); //this指的是实例对象a;
}
//new 把上下文从默认改成了实例对象本身
a = new A();
Object.create
:Object.create(obj)
方法返回一个继承了obj
属性和原型的新实例对象a
【a._proto_
= obj
】; extends
:es6语法
function A() {
var obj = Object.create(new B());
return obj;
}
a = new A();
//这个A的实例会共享一个b实例的属性
funtion A() {
}
A.prototype = new B();
a = new A();
class
原型和class
的对应:
1.构造函数function
对应class
中的构造函数constructor
;
2.function
的原型prototype
上的属性方法 对应class
中的成员方法;
3.function
的原型prototype
上的属性变量在class
中没有语法对应;【js
的class
只需要声明方法,可以声明成员变量,但是成员变量不会到原型prototype
上】
class A {
//声明的变量只会在实例的属性中,不会出现在原型上
//这里不声明也可以,直接在constructor中使用就行
name = '666';
age = 12;
constructor() {
this.name = 'aa';
}
//方法会到实例的原型prototype上
walk() {
}
}
a = new A();
//a.name = 'aa'
//a.age = 12
new
关键字new
: 把new
后面的函数作为构造函数,生成一个实例对象;
实现new
的四个步骤
1.创建一个新的空对象;
2.将新对象的_proto_
指向构造函数的prototype
;
3.以新对象为上下文执行执行构造函数;【可得到构造函数内部声明的属性】
4.返回新对象
实现 newFoo(Foo, ...args) 功能等同于 new Foo(...args);
newFoo(Foo, ...args) {
let obj = {}; // 1.创建一个空对象
obj.__proto__ = Foo.prototype; //2.将新对象的`_proto_`指向构造函数的`prototype`;
Foo.call(obj, ...args); // 3.以新对象为上下文执行执行构造函数
return obj; //4.返回新对象
}
//这个就暂时不懂
newFoo(Foo, ...args) {
let obj = Object.create(Foo.prototype); // 1, 2
let obj2 = Foo.call(obj, ...args); // 3
if(obj2 instanceof Object){
return obj2;
}
return obj;
}
String
, Number
, Boolean
, Object
,Null
, Undefined
, Symbol
,BigInt
代表独一无二的值
使用
Symbol
来作为对象属性名(key
)
表示任意大小的整数
typeof
:区分不了引用类型【函数例外】instanceOf
:区分不了父子类型,
(a instanceof B) true
a是A的实例,因为A继承自B
一个变量的_proto_
和 一个类型的prototype
比较;
NaN
是一个number
类型的特殊值;
全局方法isNaN()
只能判断一个变量在转换为数字的时候是否会转为NaN
,不能判断变量本身是否为NaN
;
es6
中有Number.isNaN()
可以判断变量是否为NaN
;
NaN
不等于任何值,包括其本身,==
也不等!;
可通过上一特点来判断一个变量是否为NaN
,value !== value
,为true
即为NaN
。
B函数可以访问A函数内部的变量,B函数就是闭包;
有意义的闭包:
B函数在A函数内部,B函数使用了A函数声明的变量,A函数返回B函数;
这样在外部调用A函数的时候,由于A函数执行完之后返回了B函数,B函数中使用了A函数的变量,所以A函数中的变量一直不能被回收,会造成内存占用过高
问题: 没有访问该变量就不是闭包了吗? 对!不是!
作用域:是变量与函数的可访问范围【全局作用域、函数作用域、块级作用域(let const)】
VO
:Variable Object
(变量对象):函数创建阶段(创建函数)
AO
:Activetion Object
(活动对象):函数执行阶段(函数内部)
当代码在一个环境中执行时,会创建变量对象的一个作用域链来保证对执行环境有权访问的变量和函数的有序访问。作用域第一个对象始终是当前执行代码所在环境的变量对象(VO)。
作用域链: AO => AO => AO => (无数个AO) => VO(全局)
function Person(){
this.age = 0;
setInterval(() => {
this.age++; // |this| 正确地指向 p 实例
}, 1000);
}
var p = new Person();
我们知道JavaScript属于解释型语言,JavaScript的执行分为:解释和执行两个阶段;
解释阶段:
词法分析
语法分析
作用域规则确定
执行阶段:
创建执行上下文
执行函数代码
垃圾回收
JavaScript
解释阶段便会确定作用域规则,因此作用域在函数定义时就已经确定了,而不是在函数调用时确定,但是执行上下文是函数执行之前创建的。执行上下文最明显的就是this
的指向是执行时确定的。而作用域访问的变量是编写代码的结构确定的。
作用域和执行上下文之间最大的区别是:
执行上下文在运行时确定,随时可能改变;作用域在定义时就确定,并且不会改变。
一个作用域下可能包含若干个上下文环境。有可能从来没有过上下文环境(函数从来就没有被调用过);有可能有过,现在函数被调用完毕后,上下文环境被销毁了;有可能同时存在一个或多个(闭包)。同一个作用域下,不同的调用会产生不同的执行上下文环境,继而产生不同的变量的值。
this
指向;this
所在的函数运行时由哪个对象调用,this
就会指向谁; 【可以理解为this
就是函数运行时所在的对象】 2.1 如果是该函数是一个构造函数,this
指针指向实例化出来的对象;
2.2 在严格模式下的函数调用下,this
指向undefined
;一般指向window
;
2.3 如果是该函数是一个对象的方法,则它的this
指针指向这个对象;
const o = {
f1() {
console.log(this);
},
f2: () => {
console.log(this);
}
}
const {f1, f2} = o;
f1();
f2();
箭头函数不会创建自己的this
,它只会从自己的作用域链的上一层继承this
;
function a () {
};
let obj = {
};
a.call(obj);//以obj为上下文执行a,a内部即可任意揉捏obj
1.第一个参数都是
this
的指向;
2.bind
创建一个新函数不调用,call
,apply
不创建新函数直接调用;
3.bind
,call
接收参数列表,apply接收参数数组;
bind()
方法创建一个新的函数,在 bind()
被调用时,这个新函数的 this
被指定为 bind()
的第一个参数,而其余参数将作为新函数的参数,供调用时使用。bind()
的实现,相当于使用函数在内部包了一个 call / apply
,第二次 bind()
相当于再包住第一次 bind()
,故第二次以后的bind
是无法生效的。
apply()
方法调用一个具有给定this
值的函数,以及作为一个数组(或类似数组对象)提供的参数。
call()
方法使用一个指定的 this
值和单独给出的一个或多个参数来调用一个函数。
模拟的步骤可以分为:
1.将函数设为对象的属性;
2.执行该函数;
3.删除该函数;
Arguments
对象中取值,取出第二个到最后一个参数,然后放到一个数组里,用eval
拼接【eval()
函数会将传入的字符串当做 JavaScript
代码进行执行。】call
Function.prototype.call2 = function (obj) {
var obj = obj || window;
obj.fn = this;
//obj.fn 中 this的理解
// 使用时 a.call(obj);
//a 是一个 function,所以在call方法里面的this指的就是a本身,即调用call方法的函数本身
// 把 a 设置为 obj的属性,所以 就 obj.fn = a;即,obj.fn = this;
//咱这里就是call方法里面
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}
var result = eval('obj.fn(' + args +')');// obj.fn(...args)
delete obj.fn
return result;
}
apply
:
Function.prototype.apply = function (obj, arr) {
var obj = Object(obj) || window;
obj.fn = this;
var result;
if (!arr) {
result = obj.fn();
}
else {
var args = [];
for (var i = 0, len = arr.length; i < len; i++) {
args.push('arr[' + i + ']');
}
result = eval('obj.fn(' + args + ')')
}
delete obj.fn
return result;
}
DOM
,ajax
,setTimeout
,会依次进入到队列中,当栈中的代码执行完毕后,再将队列中的事件放到栈中依次执行https://zhuanlan.zhihu.com/p/45111890
https://www.tangshuang.net/7617.html
macro-task
(宏任务):setTimeout
、setInterval
、setImmediate
、I/O
、UI交互事件
micro-task
(微任务):Promise中的then,catch
、process.nextTick
、MutaionObserver
js代码的执行过程中,除了依靠函数执行栈来确定函数的执行顺序以外,还需要任务队列来确定其他异步代码的执行顺序,整个执行过程,就是事件循环过程。任务队列又分为宏任务队列和微任务队列;能产生宏任务的来源有setTimeout
、setInterval
,UI render
, I/O)
,产生微任务的来源有promise
的resolve/reject
,process.nextTick
,async/await
,MutationObserver
;当js主线程执行到类似settimeout
的代码时,会生成一个宏任务并放到宏任务队列的末尾,当js主线程执行到类似promise.then
的代码是,会生成一个微任务并放到微任务队列的末尾,
当一个宏任务执行完之后,浏览器会从微任务队列中按顺序取出微任务进屋js主线程执行,执行过程中遇到异步代码时按照以上处理,直到微任务队列清空,才会开始执行下一个宏任务,一直如此循环;
https://github.com/i-want-offer/FE-Essay/blob/master/JS/%E4%BA%8B%E4%BB%B6%E5%BE%AA%E7%8E%AF.md
答题大纲
1. 先说基本知识点,宏任务、微任务有哪些
2. 说事件循环机制过程,边说边画图出来
3. 说async/await
执行顺序注意,可以把chrome
的优化,做法其实是违法了规范的,V8 团队的PR这些自信点说出来,显得你很好学,理解得很详细,很透彻。
4. 把node
的事件循环也说一下,重复1、2、3点,node中的第3点要说的是node11前后的事件循环变动点。
首先,数据类型分基本类型和引用类型。
基本类型:数据直接存储在栈里面;
引用类型【即对象】:数据存储在堆内存里面,栈里面存储该引用类型的引用地址【地址指向数据】
其次:【深拷贝,浅拷贝只针对引用类型,即对象】
赋值:直接赋值栈中的数据和地址;当把一个对象赋值给一个新变量时,只是把引用地址赋值给了新变量,新对象和旧对象拥有相同的引用地址,共享一块内存数据,实际上是同一个对象【即:修改一个对象的属性,另一个对象的属性也会被修改】
浅拷贝:会创建一个新对象【即,拥有一个新的引用地址】,新对象对旧对象的每一个属性值都有一个拷贝。咋拷贝【即,上面的赋值定义】:如果属性值是基本类型,则拷贝该基本类型的值,如果属性值是引用类型,则拷贝该引用类型的地址;
深拷贝: 会创建一个新对象【即,拥有一个新的引用地址】,新对象对旧对象的每一个属性值都有一个拷贝。咋拷贝:如果属性值是基本类型,则拷贝该基本类型的值,如果属性值是引用类型(对象),则继续深拷贝;
1.Object.assign(target, ...sources)
将所有可枚举属性的值从一个或多个源对象复制到目标对象。返回目标对象。
当对象只有一层的时候,是深拷贝
eg:
let a = object.assign({}, b, c);
2.变量解构
eg:
const b = {
name: 'zhou',
sex: {box: 'bbb'}
}
const a = {...b}
1.JSON.parse(JSON.stringify())
局限:
不能用于存在循环引用的对象;
会丢失值为函数的属性;
多个属性指向同一个引用地址时,新对象会复制多份,生成多个引用地址;
2.自己实现
思路: Map标记已经复制过的值(value)
function deepClone(theObj) {
const exists = new Map();
function clone(obj) {
if (typeof obj !== 'object') {
return obj;
}
let newObj;
if (obj instanceof Array) {
newObj = [];
} else {
newObj = {};
}
exists.set(obj, newObj);
for (let key in obj) {
const existValue = exists.get(obj[key]);
newObj[key] = existValue ? existValue : clone(obj[key]);
}
return newObj;
}
return clone(theObj);
}
const example = {
name: 'mm',
sex: '女'
};
deepClone(example)
举个例子:
鼠标一直滚动滚动条,一直在触发滚动事件,在滚动的时候要做的事情为函数
A
;
防抖:直到滚动停下的n
秒后,执行函数A
节流:滚动期间,每n
秒执行一次函数A
事件在n
秒后执行回调,在n
秒内频繁触发该事件,则回调执行的时间是最后一次触发+n
秒。重点是有个回调
专业说法:在事件被触发
n
秒后再执行回调,如果在这n
秒内又被触发,则重新计时。
实现
//function() {
//clearTimeout;
//setTimeout;
//}
//模拟一段ajax请求
function ajax(content) {
console.log('ajax request ' + content)
}
function debounce(fun, delay) {
return function (args) {
let that = this
let _args = args
clearTimeout(fun.id)
fun.id = setTimeout(function () {
fun.call(that, _args)
}, delay)
}
}
let inputb = document.getElementById('debounce')
let debounceAjax = debounce(ajax, 500)
inputb.addEventListener('keyup', function (e) {
debounceAjax(e.target.value)
})
频繁触发该事件,每个单位时间内,都只会执行一次,【两个单位时间就是一定会执行两次】
专业说法:如果这个单位时间内触发多次函数,只有一次生效。
实现:
事件触发,执行函数,setTimeOut(1000)
期间,事件触发都不管,结束之后,循环以上步骤;
function throttle(fun, delay) {
let last, deferTimer
return function (args) {
let that = this
let _args = arguments
let now = +new Date();
if (last && (now < last + delay)) {
clearTimeout(deferTimer)
deferTimer = setTimeout(function () {
last = now
fun.apply(that, _args)
}, delay)
} else {
last = now
fun.apply(that,_args)
}
}
}
let throttleAjax = throttle(ajax, 1000)
let inputc = document.getElementById('throttle')
inputc.addEventListener('keyup', function(e) {
throttleAjax(e.target.value)
})
let
const
: 块级作用域,在同一个作用域内不能重复声明;
var
: 【变量提升:会被提升到函数顶部提前声明,不会报错,即可以先使用再声明】【函数作用域】
a = 6;
let a;
//会报错
a = 6;
var a;
// 不会报错
和
vue.delete
有什么区别
用来处理异步过程的一种解决方案
1、了解 Promise 吗?
2、Promise 解决的痛点是什么?
3、Promise 解决的痛点还有其他方法可以解决吗?如果有,请列举。
4、Promise 如何使用?
5、Promise 常用的方法有哪些?它们的作用是什么?
6、Promise 在事件循环中的执行过程是怎样的?
7、Promise 的业界实现都有哪些?
8、能不能手写一个 Promise 的 polyfill。
链接:https://juejin.im/post/5b31a4b7f265da595725f322
https://juejin.im/post/5e58c618e51d4526ed66b5cf
类方法,多个 Promise
任务同时执行。
如果全部成功执行,则以数组的方式返回所有 Promise
任务的执行结果。 如果有一个 Promise
任务 rejected
,则只返回 rejected
任务的结果。
委托,统一交给父级处理【利用事件冒泡】
1、document.styleSheets
可获取文档样式表集合,一个CSSStyleSheet
对象的类数组集合;
2、获取link
或style
元素后,通过element.sheet
获取元素包含的CSSStyleSheet
对象
CSSStyleSheet
对象有cssRules
属性,是CSS
规则的集合,其中每一条规则上,有选择器名selectorText
,样式内容对象style
,样式内容文本cssText
等属性,可以用于访问具体的样式:
<style type="text/css">
.demo {
background-color: blue;
width: 100px;
height: 200px;
}
</style>
<script>
var sheet = document.styleSheets[0];
var rules = sheet.cssRules || sheet.rules;
var rule = rules[0];
console.log(rule.selectorText); //.demo
console.log(rule.style.backgroundColor); //blue
console.log(rule.style.width); //100px
console.log(rule.style.height); //200px
//.demo { background-color: blue; width: 100px; height: 200px; }
console.log(rule.cssText);
//background-color: blue; width: 100px; height: 200px;
console.log(rule.style.cssText);
</script>
js
具有自动垃圾回收机制
标记清除
引用计数
将一个多层嵌套数组转为一层
https://www.cnblogs.com/guolao/p/10155127.html
//[1, 2, 3, 4, [5, 6, 7, [8, 1], 2]] => [1, 2, 3, 4, 5, 6, 7, 8, 1, 2]
1.reduce
方法
//[1, 3].concat([6], 7,[6, 8, 9]) => [1, 3, 6, 7, 6, 8, 9]
function getArr(arr) {
return arr.reduce((pre, cur) => {
return pre.concat( Array.isArray(cur) ? getArr(cur) : cur);
},[]);
}
2.扩展运算符[...]
function getArr(arr) {
while(arr.some(item => Array.isArray(arr))) {
arr = [].concat(...arr);
}
return arr;
}
1.Set
function getArr(arr) {
return Array.from(new Set(arr));
}
function getArr(arr) {
return [...new Set(arr)];
}
2.双层for
循环:外层循环元素,内层循环时比较值arr[i] === arr[j]
。值相同时,则删去这个值。
3.
//indexOf, includes, some,find...
function getArr(arr) {
let list = [];
for (i = 0; i < arr.length; i++) {
list.indexOf(arr[i]) === -1 ? list.push(arr[i]) : '';
}
}
function getArr(arr) {
return arr.reduce((pre, cur) => pre.includes(cur) ? pre : pre.push(cur), []);
}
function getArr(arr) {
return arr.filter((item, index) => arr.indexOf(item) === index);
}
CommonJS
模块是运行时加载,ES6 Module
是编译时输出接口;
CommonJS
加载的是整个模块,将所有的接口全部加载进来,ES6 Module
可以单独加载其中的某个接口;
CommonJS
输出是值的拷贝,ES6 Module
输出的是值的引用,被输出模块的内部的改变会影响引用的改变;
CommonJS
this
指向当前模块,ES6 Module
this
指向undefined;
<ul>
<li></li>
//在这里插入
<li></li>
</ul>
事件冒泡:事件会从最内层的元素开始发生,一直向上传播,直到document对象。
事件捕获:与事件冒泡相反,事件会从最外层开始发生,直到最具体的元素。
w3c采用折中的方式,制定了统一的标准——先捕获再冒泡。
addEventListener的第三个参数就是为冒泡和捕获准备的:element.addEventListener(event, function, useCapture)
。
JS中事件冒泡与捕获
匿名的符号变量,可以用来创建匿名的对象属性。该数据类型通常被用作一个对象属性的键值——当你想让它是私有的时候。
var myPrivateMethod = Symbol();
this[myPrivateMethod] = function() {...};