[关闭]
@Secretmm 2023-02-06T02:00:39.000000Z 字数 11449 阅读 887

扎实的基础-js

p6必备


原型/原型链

原型

简述: js实现继承实例化的手段

proto vs prototype

prototype:对象生成器的一个属性,显式原型,默认情况下(可以修改)使用这个生成器生成的实例对象的_proto_会指向这里的prototype
_proto_:对象的一个属性,隐式原型,指向生成该对象的生成器的prototype

继承

  1. 继承的概念:继承是类和类(方法/生成器之间)之间的关系。有类(生成器)A和B,a是A的实例,那么a可以访问声明在A上的变量和方法,如果a也可以访问声明在B以及B的父类上的方法和变量,那么A就继承自B。
  2. js对象变量和方法的访问逻辑:如果在对象a上访问某个属性,先在a本身上找,有即访问;若没有则再去a._proto_A.prototype上找,有即访问;若没有则再去a._proto_._proto_A.prototype._proto_上找,有即访问;以此类推。
  3. 根据以上两点,A继承自B,即a可以通过A.prototypeA.prototype._proto_访问声明在B及B的父类上的方法,那么只需要将B的实例b赋值给A.prototypeA.prototype即是b,可以访问声明在B上的属性,A.prototype._proto_即是b._proto_B.prototype,可以访问B的原型属性。这就是最简单直接的原型链继承,缺点是所有A的实例共享声明在父类B上的属性,因为它们的原型是同一个b实例。
    eg:
  1. function B () {
  2. this.name = '666';
  3. }
  4. var b = new B();
  5. function A () {};
  6. A.prototype = b;
  7. a1 = new A();
  8. a2 = new A();
  9. a3 = new A();
  10. a4 = new A();
  11. a1.name === a2.name === a3.name === a4.name === b.name === '666';//true
  12. b.name = '333';
  13. a1.name === a2.name === a3.name === a4.name === '333';//true
  14. //a2实例上新增属性name,访问a2.name时,原型链上的属性name被屏蔽
  15. a2.name = '555';
  16. a1.name === a3.name === a4.name === '333';//true
  17. a2.name === '555';//true

其他继承方式

  1. function A() {
  2. B.call(this); //this指的是实例对象a;
  3. }
  4. //new 把上下文从默认改成了实例对象本身
  5. a = new A();
  1. function A() {
  2. var obj = Object.create(new B());
  3. return obj;
  4. }
  5. a = new A();
  6. //这个A的实例会共享一个b实例的属性
  7. funtion A() {
  8. }
  9. A.prototype = new B();
  10. a = new A();

原型和class的对应:
1.构造函数function 对应class中的构造函数constructor
2.function的原型prototype上的属性方法 对应class中的成员方法;
3.function的原型prototype上的属性变量在class中没有语法对应;【jsclass只需要声明方法,可以声明成员变量,但是成员变量不会到原型prototype上】

  1. class A {
  2. //声明的变量只会在实例的属性中,不会出现在原型上
  3. //这里不声明也可以,直接在constructor中使用就行
  4. name = '666';
  5. age = 12;
  6. constructor() {
  7. this.name = 'aa';
  8. }
  9. //方法会到实例的原型prototype上
  10. walk() {
  11. }
  12. }
  13. a = new A();
  14. //a.name = 'aa'
  15. //a.age = 12

实现new关键字

new: 把new后面的函数作为构造函数,生成一个实例对象;
实现new的四个步骤
1.创建一个新的空对象;
2.将新对象的_proto_指向构造函数的prototype
3.以新对象为上下文执行执行构造函数;【可得到构造函数内部声明的属性】
4.返回新对象

  1. 实现 newFoo(Foo, ...args) 功能等同于 new Foo(...args);
  2. newFoo(Foo, ...args) {
  3. let obj = {}; // 1.创建一个空对象
  4. obj.__proto__ = Foo.prototype; //2.将新对象的`_proto_`指向构造函数的`prototype`;
  5. Foo.call(obj, ...args); // 3.以新对象为上下文执行执行构造函数
  6. return obj; //4.返回新对象
  7. }
  8. //这个就暂时不懂
  9. newFoo(Foo, ...args) {
  10. let obj = Object.create(Foo.prototype); // 1, 2
  11. let obj2 = Foo.call(obj, ...args); // 3
  12. if(obj2 instanceof Object){
  13. return obj2;
  14. }
  15. return obj;
  16. }

数据类型

String, Number, Boolean, Object,Null, Undefined, SymbolBigInt

https://juejin.cn/post/7000754813801775111

Symbol

代表独一无二的值

使用Symbol来作为对象属性名(key)

BigInt

表示任意大小的整数

如何判断数据类型

关于NaN

NaN是一个number类型的特殊值;
全局方法isNaN()只能判断一个变量在转换为数字的时候是否会转为NaN,不能判断变量本身是否为NaN
es6中有Number.isNaN()可以判断变量是否为NaN
NaN不等于任何值,包括其本身,==也不等!;
可通过上一特点来判断一个变量是否为NaNvalue !== value,为true即为NaN

闭包

B函数可以访问A函数内部的变量,B函数就是闭包;

有意义的闭包:
B函数在A函数内部,B函数使用了A函数声明的变量,A函数返回B函数;
这样在外部调用A函数的时候,由于A函数执行完之后返回了B函数,B函数中使用了A函数的变量,所以A函数中的变量一直不能被回收,会造成内存占用过高

问题: 没有访问该变量就不是闭包了吗? 对!不是!

作用域/作用域链

https://juejin.im/post/5c8290455188257e5d0ec64f

作用域:是变量与函数的可访问范围【全局作用域、函数作用域、块级作用域(let const)】

VOVariable Object(变量对象):函数创建阶段(创建函数)
AOActivetion Object(活动对象):函数执行阶段(函数内部)

当代码在一个环境中执行时,会创建变量对象的一个作用域链来保证对执行环境有权访问的变量和函数的有序访问。作用域第一个对象始终是当前执行代码所在环境的变量对象(VO)。

作用域链: AO => AO => AO => (无数个AO) => VO(全局)

  1. function Person(){
  2. this.age = 0;
  3. setInterval(() => {
  4. this.age++; // |this| 正确地指向 p 实例
  5. }, 1000);
  6. }
  7. var p = new Person();

作用域与执行上下文

我们知道JavaScript属于解释型语言,JavaScript的执行分为:解释和执行两个阶段;
解释阶段:

词法分析
语法分析
作用域规则确定

执行阶段:

创建执行上下文
执行函数代码
垃圾回收

JavaScript解释阶段便会确定作用域规则,因此作用域在函数定义时就已经确定了,而不是在函数调用时确定,但是执行上下文是函数执行之前创建的。执行上下文最明显的就是this的指向是执行时确定的。而作用域访问的变量是编写代码的结构确定的。

作用域和执行上下文之间最大的区别是:

执行上下文在运行时确定,随时可能改变;作用域在定义时就确定,并且不会改变。
一个作用域下可能包含若干个上下文环境。有可能从来没有过上下文环境(函数从来就没有被调用过);有可能有过,现在函数被调用完毕后,上下文环境被销毁了;有可能同时存在一个或多个(闭包)。同一个作用域下,不同的调用会产生不同的执行上下文环境,继而产生不同的变量的值。

this指向 【指向运行时调用它的上下文】


  1. 根据当前的执行上下文确定this指向;
  2. this所在的函数运行时由哪个对象调用,this就会指向谁; 【可以理解为this就是函数运行时所在的对象】

    2.1 如果是该函数是一个构造函数,this指针指向实例化出来的对象;
    2.2 在严格模式下的函数调用下,this指向undefined;一般指向window;
    2.3 如果是该函数是一个对象的方法,则它的this指针指向这个对象;

this指向相关问题

  1. const o = {
  2. f1() {
  3. console.log(this);
  4. },
  5. f2: () => {
  6. console.log(this);
  7. }
  8. }
  9. const {f1, f2} = o;
  10. f1();
  11. f2();

箭头函数

箭头函数不会创建自己的this,它只会从自己的作用域链上一层继承this;

bind call apply

  1. function a () {
  2. };
  3. let obj = {
  4. };
  5. a.call(obj);//以obj为上下文执行a,a内部即可任意揉捏obj

1.第一个参数都是this的指向;
2.bind创建一个新函数不调用,callapply不创建新函数直接调用;
3.bindcall接收参数列表,apply接收参数数组;

bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。bind()的实现,相当于使用函数在内部包了一个 call / apply ,第二次 bind() 相当于再包住第一次 bind() ,故第二次以后的bind 是无法生效的。

apply() 方法调用一个具有给定this值的函数,以及作为一个数组(或类似数组对象)提供的参数。

call() 方法使用一个指定的 this值和单独给出的一个或多个参数来调用一个函数。

实现

参考链接:https://github.com/mqyqingfeng/Blog/issues/11


  1. 实现this绑定

    模拟的步骤可以分为:
    1.将函数设为对象的属性;
    2.执行该函数;
    3.删除该函数;

  2. 给定参数执行函数
    Arguments 对象中取值,取出第二个到最后一个参数,然后放到一个数组里,用eval拼接【eval() 函数会将传入的字符串当做 JavaScript 代码进行执行。】

call

  1. Function.prototype.call2 = function (obj) {
  2. var obj = obj || window;
  3. obj.fn = this;
  4. //obj.fn 中 this的理解
  5. // 使用时 a.call(obj);
  6. //a 是一个 function,所以在call方法里面的this指的就是a本身,即调用call方法的函数本身
  7. // 把 a 设置为 obj的属性,所以 就 obj.fn = a;即,obj.fn = this;
  8. //咱这里就是call方法里面
  9. var args = [];
  10. for(var i = 1, len = arguments.length; i < len; i++) {
  11. args.push('arguments[' + i + ']');
  12. }
  13. var result = eval('obj.fn(' + args +')');// obj.fn(...args)
  14. delete obj.fn
  15. return result;
  16. }

apply:

  1. Function.prototype.apply = function (obj, arr) {
  2. var obj = Object(obj) || window;
  3. obj.fn = this;
  4. var result;
  5. if (!arr) {
  6. result = obj.fn();
  7. }
  8. else {
  9. var args = [];
  10. for (var i = 0, len = arr.length; i < len; i++) {
  11. args.push('arr[' + i + ']');
  12. }
  13. result = eval('obj.fn(' + args + ')')
  14. }
  15. delete obj.fn
  16. return result;
  17. }

堆和栈

https://juejin.im/post/5b1deac06fb9a01e643e2a95#heading-19

浏览器事件循环(event loop)

https://zhuanlan.zhihu.com/p/45111890
https://www.tangshuang.net/7617.html

macro-task(宏任务):setTimeoutsetIntervalsetImmediateI/OUI交互事件
micro-task(微任务):Promise中的then,catchprocess.nextTickMutaionObserver

描述

js代码的执行过程中,除了依靠函数执行栈来确定函数的执行顺序以外,还需要任务队列来确定其他异步代码的执行顺序,整个执行过程,就是事件循环过程。任务队列又分为宏任务队列和微任务队列;能产生宏任务的来源有setTimeoutsetInterval,UI render, I/O),产生微任务的来源有promiseresolve/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:

  1. let a = object.assign({}, b, c);

2.变量解构
eg:

  1. const b = {
  2. name: 'zhou',
  3. sex: {box: 'bbb'}
  4. }
  5. const a = {...b}

实现深拷贝

1.JSON.parse(JSON.stringify())

局限:
不能用于存在循环引用的对象;
会丢失值为函数的属性;
多个属性指向同一个引用地址时,新对象会复制多份,生成多个引用地址;

2.自己实现
思路: Map标记已经复制过的值(value)

  1. function deepClone(theObj) {
  2. const exists = new Map();
  3. function clone(obj) {
  4. if (typeof obj !== 'object') {
  5. return obj;
  6. }
  7. let newObj;
  8. if (obj instanceof Array) {
  9. newObj = [];
  10. } else {
  11. newObj = {};
  12. }
  13. exists.set(obj, newObj);
  14. for (let key in obj) {
  15. const existValue = exists.get(obj[key]);
  16. newObj[key] = existValue ? existValue : clone(obj[key]);
  17. }
  18. return newObj;
  19. }
  20. return clone(theObj);
  21. }
  22. const example = {
  23. name: 'mm',
  24. sex: '女'
  25. };
  26. deepClone(example)

防抖节流

举个例子:

鼠标一直滚动滚动条,一直在触发滚动事件,在滚动的时候要做的事情为函数A
防抖:直到滚动停下的n秒后,执行函数A
节流:滚动期间,每n秒执行一次函数A

函数防抖

事件在n秒后执行回调,在n秒内频繁触发该事件,则回调执行的时间是最后一次触发+n秒。重点是有个回调

专业说法:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时

实现

  1. //function() {
  2. //clearTimeout;
  3. //setTimeout;
  4. //}
  5. //模拟一段ajax请求
  6. function ajax(content) {
  7. console.log('ajax request ' + content)
  8. }
  9. function debounce(fun, delay) {
  10. return function (args) {
  11. let that = this
  12. let _args = args
  13. clearTimeout(fun.id)
  14. fun.id = setTimeout(function () {
  15. fun.call(that, _args)
  16. }, delay)
  17. }
  18. }
  19. let inputb = document.getElementById('debounce')
  20. let debounceAjax = debounce(ajax, 500)
  21. inputb.addEventListener('keyup', function (e) {
  22. debounceAjax(e.target.value)
  23. })

函数节流(频率降低)

频繁触发该事件,每个单位时间内,都只会执行一次,【两个单位时间就是一定会执行两次】

专业说法:如果这个单位时间内触发多次函数,只有一次生效。

实现:
事件触发,执行函数,setTimeOut(1000)期间,事件触发都不管,结束之后,循环以上步骤;

  1. function throttle(fun, delay) {
  2. let last, deferTimer
  3. return function (args) {
  4. let that = this
  5. let _args = arguments
  6. let now = +new Date();
  7. if (last && (now < last + delay)) {
  8. clearTimeout(deferTimer)
  9. deferTimer = setTimeout(function () {
  10. last = now
  11. fun.apply(that, _args)
  12. }, delay)
  13. } else {
  14. last = now
  15. fun.apply(that,_args)
  16. }
  17. }
  18. }
  19. let throttleAjax = throttle(ajax, 1000)
  20. let inputc = document.getElementById('throttle')
  21. inputc.addEventListener('keyup', function(e) {
  22. throttleAjax(e.target.value)
  23. })

var let const

let const: 块级作用域,在同一个作用域内不能重复声明;
var: 【变量提升:会被提升到函数顶部提前声明,不会报错,即可以先使用再声明】【函数作用域】

  1. a = 6;
  2. let a;
  3. //会报错
  4. a = 6;
  5. var a;
  6. // 不会报错

delete操作符

vue.delete有什么区别

promise

用来处理异步过程的一种解决方案

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.all

类方法,多个 Promise 任务同时执行。
如果全部成功执行,则以数组的方式返回所有 Promise 任务的执行结果。 如果有一个 Promise 任务 rejected,则只返回 rejected 任务的结果。

事件绑定

事件监听

事件委托(代理)

委托,统一交给父级处理【利用事件冒泡】

JS通过CSSStyleSheet对象访问CSS样式

获取方式

1、document.styleSheets可获取文档样式表集合,一个CSSStyleSheet对象的类数组集合;
2、获取linkstyle元素后,通过element.sheet获取元素包含的CSSStyleSheet对象

访问样式内容

CSSStyleSheet对象有cssRules属性,是CSS规则的集合,其中每一条规则上,有选择器名selectorText,样式内容对象style,样式内容文本cssText等属性,可以用于访问具体的样式:

  1. <style type="text/css">
  2. .demo {
  3. background-color: blue;
  4. width: 100px;
  5. height: 200px;
  6. }
  7. </style>
  8. <script>
  9. var sheet = document.styleSheets[0];
  10. var rules = sheet.cssRules || sheet.rules;
  11. var rule = rules[0];
  12. console.log(rule.selectorText); //.demo
  13. console.log(rule.style.backgroundColor); //blue
  14. console.log(rule.style.width); //100px
  15. console.log(rule.style.height); //200px
  16. //.demo { background-color: blue; width: 100px; height: 200px; }
  17. console.log(rule.cssText);
  18. //background-color: blue; width: 100px; height: 200px;
  19. console.log(rule.style.cssText);
  20. </script>

垃圾回收与内存泄漏

js具有自动垃圾回收机制

垃圾回收机制

标记清除
引用计数

数组扁平化

将一个多层嵌套数组转为一层
https://www.cnblogs.com/guolao/p/10155127.html

  1. //[1, 2, 3, 4, [5, 6, 7, [8, 1], 2]] => [1, 2, 3, 4, 5, 6, 7, 8, 1, 2]

1.reduce方法

  1. //[1, 3].concat([6], 7,[6, 8, 9]) => [1, 3, 6, 7, 6, 8, 9]
  2. function getArr(arr) {
  3. return arr.reduce((pre, cur) => {
  4. return pre.concat( Array.isArray(cur) ? getArr(cur) : cur);
  5. },[]);
  6. }

2.扩展运算符[...]

  1. function getArr(arr) {
  2. while(arr.some(item => Array.isArray(arr))) {
  3. arr = [].concat(...arr);
  4. }
  5. return arr;
  6. }

数组去重

1.Set

  1. function getArr(arr) {
  2. return Array.from(new Set(arr));
  3. }
  4. function getArr(arr) {
  5. return [...new Set(arr)];
  6. }

2.双层for循环:外层循环元素,内层循环时比较值arr[i] === arr[j]。值相同时,则删去这个值。
3.

  1. //indexOf, includes, some,find...
  2. function getArr(arr) {
  3. let list = [];
  4. for (i = 0; i < arr.length; i++) {
  5. list.indexOf(arr[i]) === -1 ? list.push(arr[i]) : '';
  6. }
  7. }
  8. function getArr(arr) {
  9. return arr.reduce((pre, cur) => pre.includes(cur) ? pre : pre.push(cur), []);
  10. }
  11. function getArr(arr) {
  12. return arr.filter((item, index) => arr.indexOf(item) === index);
  13. }

函数柯里化

ES6 模块与 CommonJS 模块的差异

CommonJS模块是运行时加载,ES6 Module是编译时输出接口;
CommonJS加载的是整个模块,将所有的接口全部加载进来,ES6 Module可以单独加载其中的某个接口;
CommonJS输出是值的拷贝,ES6 Module输出的是值的引用,被输出模块的内部的改变会影响引用的改变;
CommonJS this指向当前模块,ES6 Module this指向undefined;

ajax上传一张图片

如何在上传图片之前进行本地预览

如何插入一个兄弟节点

  1. <ul>
  2. <li></li>
  3. //在这里插入
  4. <li></li>
  5. </ul>

事件冒泡和捕获

事件冒泡:事件会从最内层的元素开始发生,一直向上传播,直到document对象。
事件捕获:与事件冒泡相反,事件会从最外层开始发生,直到最具体的元素。
w3c采用折中的方式,制定了统一的标准——先捕获再冒泡。
addEventListener的第三个参数就是为冒泡和捕获准备的:element.addEventListener(event, function, useCapture)
JS中事件冒泡与捕获

symbol类型的作用

匿名的符号变量,可以用来创建匿名的对象属性。该数据类型通常被用作一个对象属性的键值——当你想让它是私有的时候。

  1. var myPrivateMethod = Symbol();
  2. this[myPrivateMethod] = function() {...};

Symbol数据类型

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注