首页 > 其他分享 >js面试

js面试

时间:2022-09-22 10:34:10浏览次数:53  
标签:function 函数 对象 作用域 js 面试 Promise prototype

js基础

new源码

  1. 将新创建出来的函数的Prototype 改成传入进来的函数
    function createObject(o){
        function Fn(){}
        Fn.prototype = o
        return new Fn()
    }
    
  2. 改变创建出来对象的this,使用apply或者call方法

数组方法

​ 影响原数组:

1. 尾减加 pop 和 push
2. 首减加 shift 和 unshift
3. 插入splice 
4. 排序sort

5. 反转数组reverse

不影响原数组:

​ 1 .连接 concat

​ 2 .截取 slice

​ 3 .索引查找 indexOf lastIndexOf

4 .迭代方法 every some filter map forEach

  1. reduce 和 reduceRight
  2. 数组转字符 => toString() join()

解释DOM和BOM

​ DOM是文档对象 , 这个对象定义了处理网页内容的方法和接口

​ BOM是浏览器对象, 这个对象定义了与浏览器交互的接口, 核心是

​ window, window是js访问浏览器的接口也是全局对象,

​ 网页中定义的方法和属性都属于全局对象 , dom的核心对象document也是window的子对象

类数组

一个拥有 length 属性和索引属性的对象可以被叫做类数组对象,和数组的区别就是不能调用数组方法, 常见的有arugments属性

转换成数组的方法有

(原理就是在arrayLiike方法中调用Array.prototype.slice方法)

​ Array.prototype.slice.call(arrayLike);

​ Array.prototype.splice.call(arrayLike, 0);

​ Array.prototype.concat.apply([], arrayLike);

​ Array.from(arrayLike);

AJAX

JavaScript 的 异步通信, 从服务器获取数据, 更新部分网页不刷新整个网页

​ Ajax源码:

  	1. 创建一个 XMLHttpRequest 对象
            	2. 使用 open 方法创建一个 HTTP 请求
         	3. 通过responseType  设置返回类型
                 	4. 通过setRequestHeader 设置请求头信息
                	5. xhr.send(null);        发送请求
const SERVER_URL = "/server";
let xhr = new XMLHttpRequest();
// 创建 Http 请求
xhr.open("GET", url, true);
// 设置状态监听函数
xhr.onreadystatechange = function() {
  if (this.readyState !== 4) return;
  // 当请求成功时
  if (this.status === 200) {
    handle(this.response);
  } else {
    console.error(this.statusText);
  }
};
// 设置请求失败时的监听函数
xhr.onerror = function() {
  console.error(this.statusText);
};
// 设置请求头信息
xhr.responseType = "json";
xhr.setRequestHeader("Accept", "application/json");
// 发送 Http 请求
xhr.send(null);

js变量提升

​ 变量提升的表现是,在函数声明前调用函数不保存(codeway的图)

​ 解析阶段,和执行阶段有助于性能提升

常见DOM操作

DOM 节点的获取

getElementById 按照 id 查询
getElementsByTagName 按照标签名查询
getElementsByClassName 按照类名查询
querySelectorAll 按照 css 选择器查询

DOM 节点的创建

// 首先获取父节点
var container = document.getElementById('container')
// 创建新节点
var targetSpan = document.createElement('span')
// 设置 span 节点的内容
targetSpan.innerHTML = 'hello world'
// 把新创建的元素塞进父节点里去
container.appendChild(targetSpan)

DOM 节点的删除

// 获取目标元素的父元素
var container = document.getElementById('container')
// 获取目标元素
var targetNode = document.getElementById('title')
// 删除目标元素
container.removeChild(targetNode)

修改DOM

// 获取父元素
var container = document.getElementById('container')   
// 获取两个需要被交换的元素
var title = document.getElementById('title')
var content = document.getElementById('content')
// 交换两个元素,把 content 置于 title 前面
container.insertBefore(content, title)

JavaScript脚本延迟加载的方式有哪些?

  • defer 属性:给 js 脚本添加 defer 属性,这个属性会让脚本的加载与文档的解析同步解析,然后在文档解析完成后再执行这个脚本文件,这样的话就能使页面的渲染不被阻塞。多个设置了 defer 属性的脚本按规范来说最后是顺序执行的,但是在一些浏览器中可能不是这样。
  • async 属性:给 js 脚本添加 async 属性,这个属性会使脚本异步加载,不会阻塞页面的解析过程,但是当脚本加载完成后立即执行 js 脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个 async 属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序依次执行。
  • 使用 setTimeout 延迟方法:设置一个定时器来延迟加载js脚本文件
  • 让 JS 最后加载:将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最后来加载执行。
  • 动态创建 DOM 方式:动态创建 DOM 标签的方式,可以对文档的加载事件进行监听,当文档加载完成后再动态的创建 script 标签来引入 js 脚本。

JavaScript 类数组对象的定义?

一个拥有 length 属性和若干索引属性的对象就可以被称为类数组对象

Array.prototype.slice.call(arrayLike);
Array.prototype.splice.call(arrayLike, 0);
Array.prototype.concat.apply([], arrayLike);

Array.from(arrayLike);

如何判断一个对象是否属于某个类?

  • 第一种方式,使用 instanceof 运算符来判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。
  • 第二种方式,通过对象的 constructor 属性来判断,对象的 constructor 属性指向该对象的构造函数,但是这种方式不是很安全,因为 constructor 属性可以被改写。
  • 第三种方式,如果需要判断的是某个内置的引用类型的话,可以使用 Object.prototype.toString() 方法来打印对象的[[Class]] 属性来进行判断。

for...in和for...of的区别

for…of 是ES6新增的遍历方式,允许遍历一个含有iterator接口的数据结构(数组、对象等)并且返回各项的值,和ES3中的for…in的区别如下

  • for… in 会遍历对象的整个原型链,性能非常差不推荐使用,

  • for…of 只遍历当前对象不会遍历原型链

总结:for...in 循环主要是为了遍历对象而生

​ for...of 循环可以用来遍历数组、类数组对象,符串、Set、Map 、Generator 对象。

数组的遍历方法有哪些

方法 是否改变原数组 特点
forEach() 数组方法,不改变原数组,没有返回值
map() 数组方法,不改变原数组,有返回值,可链式调用
filter() 数组方法,过滤数组,返回包含符合条件的元素的数组,可链式调用
for...of for...of遍历具有Iterator迭代器的对象的属性,返回的是数组的元素、对象的属性值,不能遍历普通的obj对象,将异步循环变成同步循环
every() 和 some() 数组方法,some()只要有一个是true,便返回true;而every()只要有一个是false,便返回false.
find() 和 findIndex() 数组方法,find()返回的是第一个符合条件的值;findIndex()返回的是第一个返回条件的值的索引值
reduce() 和 reduceRight() 数组方法,reduce()对数组正序操作;reduceRight()对数组逆序操作

数据类型

js数据类型:

八大数据类型: Undefinded Null Boolean Number String Object、Symbol、BigInt

数据可以分为原始数据类型和引用数据类型

​ 原始数据类型(栈) : Undefinded Null Boolean Number String

​ 引用数据类型(堆): Object 对象、数组和函数

存储位置不同

​ 栈空间(原始数据类型): 空间小 ,大小固定 , 频繁使用

​ 堆空间(指针数据类型): 空间大 , 大小不固定, 引用数据类型在栈中存储了指针,

​ 解释器会查栈中的地址,取到地址后得到数据

数据类型检测方式

​ typeof ,instanceof, construector, Object.prototype.toString.call()

判断数组的方式

​ Object.prototype.toString.call()

​ 原型链: obj.proto === Array.prototype;

  ES6:  	 Array.isArray()做判断

  instanceof

​ Array.prototype.isPrototypeOf

null和undefined

​ 1. 它们都是基本数据类型, null表示空对象, undefined是未定义

​ 2. typeof null=onject

​ 3. null ==object ---true 历史遗留问题

 	 000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object 。 

​ 4. null ====object ---false

instanceof源码

	instanceof 运算符用于判断**构造函数的 prototype 属性**是否出现在对象的原型链中的任何位置。	

​ 1.proto=通过Object.getPrototype 获取对象原型

​ 2. prototype=获取构造函数的prototyper对象

​ 3.判断构造函数是否出现在指定对象的函数上

​ 4.没有就继续调用Object.getPrototype 方法

function myInstanceof(left, right) {
  // 获取对象的原型
  let proto = Object.getPrototypeOf(left)
  // 获取构造函数的 prototype 对象
  let prototype = right.prototype; 
 
  // 判断构造函数的 prototype 对象是否在对象的原型链上
  while (true) {
    if (!proto) return false;
    if (proto === prototype) return true;
    // 如果没有找到,就继续从其原型上找,Object.getPrototypeOf方法用来获取指定对象的原型
    proto = Object.getPrototypeOf(proto);
  }
}
		//使用
		console.log(auto instanceof Car);

0.1+0.2 ! == 0.3

toFixed(num) 方法 例:(n1 + n2).toFixed(2)

​ 计算机是通过二进制的方式存储数据的,所以计算机计算0.1+0.2的时候,实际上是计算的两个数的二进制的和。

== 操作符转换规则

​ 1.判断类型是否相同

​ 2.相同就比较大小,不同就转换数据类型

​ 3.null==undefined 结果为 true

​ 4.string和number 转number

​ 5.boolean 和 number 转number 1 0

  1. object 和 基本数据类型 obj转原始数据类型

Object.js() === ==

​ ==类型不一致会转换数据类型

​ ===类型不一致直接返回fasle

​ Object.is(a,b) 和 ===一样, 但是-0和+0不等, 两个NaN相等 (''aaaaa'' - 1 )

typeof null 的结果是什么,为什么?

typeof null 的结果是Object。

isNaN 和 Number.isNaN 函数的区别?

  • 函数 isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true ,会影响 NaN 的判断。
  • 函数 Number.isNaN 会首先判断传入参数是否为数字,如果是数字再继续判断是否为 NaN ,不会进行数据类型的转换,这种方法对于 NaN 的判断更为准确。

其他值到字符串的转换规则?

  • Null 和 Undefined 类型 ,null 转换为 "null",undefined 转换为 "undefined",
  • Boolean 类型,true 转换为 "true",false 转换为 "false"。
  • Number 类型的值直接转换,不过那些极小和极大的数字会使用指数形式。
  • Symbol 类型的值直接转换,但是只允许显式强制类型转换,使用隐式强制类型转换会产生错误。
  • 对普通对象来说,除非自行定义 toString() 方法,否则会调用 toString()(Object.prototype.toString())来返回内部属性 [[Class]] 的值,如"[object Object]"。如果对象有自己的 toString() 方法,字符串化时就会调用该方法并使用其返回值。

其他值到数值的转换规则?

  • Undefined 类型的值转换为 NaN。
  • Null 类型的值转换为 0。
  • Boolean 类型的值,true 转换为 1,false 转换为 0。
  • String 类型的值转换如同使用 Number() 函数进行转换,如果包含非数字值则转换为 NaN,空字符串为 0。
  • Symbol 类型的值不能转换为数字,会报错。
  • 对象(包括数组)会首先被转换为相应的基本类型值,如果返回的是非数字的基本类型值,则再遵循以上规则将其强制转换为数字。

其他值到布尔类型的值的转换规则?

以下这些是假值:

• undefined

• null

• false

• +0、-0 和 NaN

• ""

|| 和 && 操作符的返回值?

|| 和 && 首先会对第一个操作数执行条件判断,如果其不是布尔值就先强制转换为布尔类型,然后再执行条件判断。

  • 对于 || 来说,如果条件判断结果为 true 就返回第一个操作数的值,如果为 false 就返回第二个操作数的值。
  • && 则相反,如果条件判断结果为 true 就返回第二个操作数的值,如果为 false 就返回第一个操作数的值。

|| 和 && 返回它们其中一个操作数的值,而非条件判断的结果

JavaScript 中如何进行隐式类型转换?

首先要介绍ToPrimitive方法,这是 JavaScript 中每个值隐含的自带的方法,用来将值 (无论是基本类型值还是对象)转换为基本类型值。如果值为基本类型,则直接返回值本身;如果值为对象,其看起来大概是这样:

/**
* @obj 需要转换的对象
* @type 期望的结果类型
*/
ToPrimitive(obj,type)
console.log('1'+1);
console.log('1'-1);

String() a.toString() JSON.stringify()

String()是js的全局函数可以把null、undefined转换为字符,但是没办法转换为进制字符串,其他的都和toString()一样

toString()是object原型的一个方法,它与String的区别就是不能转换null、undefined会报错

JSON.stringify() 对象序列化转换为字符串的时候也调用了toString的方法,这里值得注意的是并非严格意义上的强制类型转换用法与toString基本一致.

Date()

Number() a. parseInt() parseFloat()

ES6

let const var

块级作用域: let const 有 var没有

变量提升: var 有 let const没有

给全局添加属性: var 有 let const没有

重复声明 : var有并且后面的会覆盖前面的, const 和 let 不能重复声明

暂时性死区 : 函数外面使用var定义a ,函数内在let const 定义 a 在定义前使用,显示变量 不可用,语法上称作暂时性死区

初始值 : var 和let不用给初试值, cosnt要给

指针指向 : let可以重新赋值 const不行 因为const指针不可变

箭头函数和普通函数的区别

更加简洁: 没有参数一个空括号 ()=>{},

​ 一个参数不用括号 a=>{}

​ 函数体只有一句话 a=>log( a )

​ 函数体不需要返回值,viod调用方法 () => viod xxx()

箭头函数没有自己的this

​ 箭头函数不会创建自己的this, 会用自己上层作用域的this。

​ 箭头函数中this的指向在定义时已经确定了,之后不会改变。

​ 显示调用, 隐式调用(call apply bind) , new 不能改变

不能当做构造函数:

​ 因为new 关键字要改变创建出来新对象的this

没有arguments

​ 调用的是外层函数的arguments

​ 箭头函数没有prototype

​ 箭头函数不能用作Generator函数,不能使用yeild关键字

const对象的属性可以修改吗

常量不可以修改,指针不可变

对象内的值可以改变.

如果new一个箭头函数的会怎么样

箭头函数是ES6中的提出来的,它没有prototype,也没有自己的this指向,更不可以使用arguments参数,所以不能New一个箭头函数。

new操作符的实现步骤如下:

  1. 创建一个对象
  2. 将构造函数的作用域赋给新对象(也就是将对象的__proto__属性指向构造函数的prototype属性)
  3. 指向构造函数中的代码,构造函数中的this指向该对象(也就是为这个对象添加属性和方法)
  4. 返回新的对象

所以,上面的第二、三步,箭头函数都是没有办法执行的。

箭头函数的this指向哪⾥?

箭头函数不同于传统JavaScript中的函数,箭头函数并没有属于⾃⼰的this,它所谓的this是捕获其所在上下⽂的 this 值,作为⾃⼰的 this 值,并且由于没有属于⾃⼰的this,所以是不会被new调⽤的,这个所谓的this也不会被改变。

对 rest 参数的理解( ... )

它还可以把一个分离的参数序列整合成一个数组

ES6中模板语法与字符串处理``${}

html``

边量${}

存在性判定:includes, startsWith, endsWith

原型

原型和原型链

​ js使用构造函数创建对象, 当中有一个prototype属性指向这个对象的原型,

​ 浏览器实现__proto__属性也能访问到这个函数,一般不推荐使用,

​ ES5实现了Object.getPrototypeOf() 方法可以访问

​ 当访问一个对象属性的时候,内部不存在这个属性就去原型上找, 原型上没有就去, 原型上的原型查, 最终找到源头Object.prototype

​ js对象是通过引用传递,每个新对象没有自己的原型对象 (new 和 var 创建对象),修改原型, 相关对象也会继承这个改变 function定义才有副本

原型修改、重写

修改__proto__指向 , 修改constructor指向,

原型链的指向

异步编程

Promise理解

​ Promise是异步编程的一种解决方案, 为了解决回调地狱的问题.

​ Promise的三个状态 Pending Resolved Rejected

当把一件事情交给promise时,它的状态就是Pending,任务完成了状态就变成了Resolved、没有完成失败了就变成了Rejected。

实现方法

​ promise调用reslove()法会跳转到对应的then方法中,

​ 原因是在Promise内部定义的reslove()方法会 调用onFulfilled或者onRejected方法,

​ 这个方法就是then中的方法, promise内部定义的 reslove或reject 方法会将onRejected

​ 添加到微任务队列, 先实现then的方法, 在将then方法赋值给primise对象

**Promise的用法 **

new Promise()来创建promise对象

const promise = new HYpromise((resolve,reject) =>{
    // resolve(1111)
    reject(2222)
})

Promise.resolve(11).then 会直接进入resolve状态

catch()指向reject函数

async/await理解

​ async /await 像生成器和Promise结合的效果 ,为了解决Promise.then的链式调用

​ function* 定义一个生成器, yield关键字是一种 for of 的语法糖.

​ 写一个递归函数处理.then的函数

​ 通过function.next()可以调用,并且可以给yield返回值. 返回的值会赋给上次调用的方法

​ async await有类似的效果,所以await也可以接受返回值,await会return一个promise

​ await后面跟的值就是then中的代码

async/await对Primise的优势

​ 代码更容易读,解决链式调用的问题

​ 更好进行调试

​ 错误处理更友好

实现方式

回调函数, Promise, async

setTimeout、Promise、Async/Await 的区别

(1)setTimeout

(2)Promise

Promise本身是同步的立即执行函数, 当在executor中执行resolve或者reject的时候, 此时是异步操作, 会先执行then/catch等,当主栈完成后,才会去调用resolve/reject中存放的方法执行,打印p的时候,是打印的返回结果,一个Promise实例。

(3)async/await

async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再执行函数体内后面的语句。可以理解为,是让出了线程,跳出了 async 函数体。

Promise解决了什么问题

在工作中经常会碰到这样一个需求,比如我使用ajax发一个A请求后,成功后拿到数据,需要把数据传给B请求;那么需要如下编写代码

Promise.all和Promise.race的区别的使用场景

Promise.all

Promise.all可以将多个Promise实例包装成一个新的Promise实例,

成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值

Promise.race

顾名思义,Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。当要做一件事,超过多长时间就不做了,可以用这个方法来

await 到底在等啥

​ await 在等待什么呢?一般来说,都认为 await 是在等待一个 async 函数完成。不过按语法说明,await 等待的是一个表达式,这个表达式的计算结果是 Promise 对象或者其它值(换句话说,就是没有特殊限定)。

因为 async 函数返回一个 Promise 对象,所以 await 可以用于等待一个 async 函数的返回值——这也可以说是 await 在等 async 函数,但要清楚,它等的实际是一个返回值。注意到 await 不仅仅用于等 Promise 对象,它可以等任意表达式的结果,所以,await 后面实际是可以接普通函数调用或者直接量的。所以下面这个示例完全可以正确运行:

function getSomething() {
    return "something";
}
async function testAsync() {
    return Promise.resolve("hello async");
}
async function test() {
    const v1 = await getSomething();
    const v2 = await testAsync();
    console.log(v1, v2);
}
test();

async/await的优势

单一的 Promise 链并不能发现 async/await 的优势,但是,如果需要处理由多个 Promise 组成的 then 链的时候,优势就能体现出来了(很有意思,Promise 通过 then 链来解决多层回调的问题,现在又用 async/await

执行上下文/作用域链/闭包

闭包

​ 当前作用域访问上层作用域的变量,导致内存无法回收这个块区域的内存,闭包会有内存泄露

,在内存中的表现这个变量被别的作用域指向,导致内存无法被回收

解决循环中var定义变量的问题

​ 1. 使用闭包

​ setTimeout是异步函数,for循环是同步的, 当 i 结束后才调用 setTimeout函数

​ 使用立即执行函数包裹setTimeout , setTimeout中的timer就会形成闭包

i 传入函数内部,这个时候值就被固定在了参数 j 上面不会改变

​ 2. 使用let关键字

作用域和作用域链

全局作用域和函数作用域

​ (1)全局作用域

​ 所有window对象的属性拥有全局作用域, 全局作用域变量会污染全局命名空间

​ (2)函数作用域

​ 函数作用域声明在函数内部的变量,一般只有固定的代码片段可以访问到

块级作用域

ES6中新增的let和const指令可以声明块级作用域

作用域链:

​ 在当前作用域中查找所需变量,但是该作用域没有这个变量,那这个变量就是自由变量。如果在自己作用域找不到该变量就去父级作用域查找,直到访问到window对象就被终止,

执行上下文(图)

js引擎内部有一个执行上下文栈(ECS) 函数执行上下文(FEC)

​ 当开始执行代码的时候会在ECS创建全局执行上下文(GEC),

​ GEC有一个VO对应GO +父级作用域, GO存在堆空间, GO会进行预解析,也就是作用域提升,

​ 解析变量为undefined,解析函数会在堆空间开辟空间并将指针值传递过去 , 其中包括代码块 和父级作用域

​ 然后在GEC中开始执行代码,执行变量赋值, 执行函数就会创建一个函数执行上下文(FEC)

​ FEC有一个VO对应AO和父级作用域链 ,AO在堆空间,AO会进行预解析,变量赋值为undeined

​ 函数就会接着创建下一个函数执行上下文,使用完FEC AO都会被移除

1662973810518

this/call/apply/bind

this 的理解

this 有四种调用模式

​ 默认调用

​ 显示调用

​ 隐式调用

​ 构造调用

call/apply/bind源码

给Function原型上添加上一个方法

判断传入的对象是不是空的,

通过上下文对象掉用这个函数

Function.prototype.myCall = function(context) {
  // 判断调用对象
  if (typeof this !== "function") {
    console.error("type error");
  }
  // 获取参数
  let args = [...arguments].slice(1),
    result = null;
  // 判断 context 是否传入,如果未传入则设置为 window
  context = context || window;
  // 将调用函数设为对象的方法
  context.fn = this;
  // 调用函数
  result = context.fn(...args);
  // 将属性删除
  delete context.fn;
  return result;
};


面相对象

对象的定义
**1.直接定义** 

	var student = new Object(); 

​ student.name="Lucy";

2.初始化定义

​ var student = {

​ name:"Tim",

​ age:12 }

3.构造函数式

​ function Student(name){

​ this.name=name;

​ this.eatting=function(){ console.log(this.name+"正在吃东西"); }; }

4.原型式

​ function Student(){ }

​ Student.prototype.name="Kitty";

​ Student.prototype.eatting=function(){ console.log(this.name+"正在吃东西"); };

5.混合式

​ function Student(name){

​ this.name=name; }

​ Student.prototype.eatting=function(){ console.log(this.name+"正在吃东西"); };

对象创建方式

​ 字面量定义: var a={}

​ new

​ new Object ( Object.name=aaa )

对象继承方式

(1) 原型链的方式来实现继承

弊端: 1. 在包含有引用类型的数据时,会被所有的实例对象所共享

​ 2. 引用数据类型会去prototype添加属性, 基本数据类型会在当前实例上添加属性

				 3. 无法传递参数给父类,new Student 只能传递给自己的构造函数

1663040887787

        var stu1=new Student()
        var stu2=new Student()
        stu1.friends.push("kobe")
        console.log(stu1.friends);
        console.log(stu2.friends);
        结果是stu1 和 stu2都会打印出数据

(2)借用构造函数的方式

弊端: 1. Person会被调用两次

​ 2.原型对象增加不需要的属性

1663049444946
function Person(name,sno,friends){
    //给stu加上属性
    this.name=name
    this.sno=sno
    this.friends =friends
}

function Student(name,sno,friends){
    person.call(this,name,sno,friends)
    this.sno=111
}

(4)第四种方式是原型式继承

​ 弊端:缺点和原型链相同

function createObject(o){
    function Fn(){}
    Fn.prototype = o
    // newObj.__proto__=o 不要通过这种方式给隐式原型父子
    var newObj = new Fn()
    return newObj
}

function createObject(o){
    var newObj = {}
    //给newObject设置原型
    Object.setPrototypeOf(newObj,o)
    return newObj
}
var info2=Object.create(obj)

(5)第五种方式是寄生式继承

function createStudent(name){
    var stu = Object.create(PersonObj)
    stu.name=name
    stu.studying = function(){
        console.log("studying~");
    }
}

(6)第六种方式是寄生式组合继承

function createObject(o){
    function Fn(){}
    Fn.prototype = o
    return new Fn()
}
function inheritPrototype(SubType,SuperType){
SubType.prototype=createObject(SuperType.prototype)
Object.defineProperty(SubType.prototype,"constructor",{
    enumerable: false,
    configurable: true,
    writable: true,
    value: SubType
})
}

垃圾回收和内存泄漏

回收机制

​ JS具有自动垃圾回收机制,会定期对那些不再使用的变量、对象进行释放,

​ 原理就是找到不再使用的变量,然后释放掉其占用的内存。

​ 局部变量会在堆或栈中存储它们的值当函数执行结束后,这些局部变量不再被使用,它们所占有的空间就会被 释放。当局部变量被外部函数使用时,其中一种情况就是闭包,在函数执行结束后,函数外部的变量依然指向 函数内部的局部变量,此时局部变量依然在被使用,所以不会回收。

垃圾回收的方式:

标记清除

​ 当变量进入执行环境时,就标记这个变量“进入环境”

​ 当变量离开环境时,就会被标记为“离开环境”

​ 被标记为“离开环境”的变量会被内存释放。

引用计数

​ 引用计数就是跟踪记录每个值被引用的次数。

​ 当这个引用次数变为0时,会被回收

​ 这种方法会引起循环引用的问题:例如: obj1obj2通过属性进行相互引用,

内存泄露

闭包不合理的使用闭包,从而导致某些变量一直被留在内存当中。

面试题

js

本地存储方式有哪些

对比项目 cookie localstorage sessionStorage
数据存储时间 可设置失效时间 永久 仅当前会话
容量 <=4kb <=5mb <=5mb

css

响应式布局有哪些

响应式布局方法一:媒体查询

响应式布局方法二:百分比%

响应式布局方法三:vw/vh

响应式布局方法四:rem

  1. 节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效

  2. 防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时

    es模块化 导入导出

标签:function,函数,对象,作用域,js,面试,Promise,prototype
From: https://www.cnblogs.com/uoue/p/16718292.html

相关文章