obj.proto == Object.prototype // true
从这一点来看,是不是更好理解一点。
为了防止无休止的循环下去,所以 Object.prototype.__proto__
是指向 null
的,null
是万物的终点。
Object.prototype.proto == null // true
既然 null
是万物的终点,那使用 Object.create(null)
创建的对象是没有 __proto__
属性的,也没有 prototype
属性。
10. 原型继承
所有的 JS 对象(JS 函数是 prototype)都有一个 __proto__
属性,指向它的原型对象。当试图访问一个对象的属性时,如果没有在该对象上找到,它还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。
11. 继承
寄生组合式继承
function SuperType(name) {
this.name = name
this.colors = [‘red’]
}
SuperType.prototype.sayName = function() {
console.log(this.name)
}
// 继承实例属性
function SubType(name, age) {
SuperType.call(this, name)
this.age = age
}
function inheritPrototype(subType, superType) {
let prototype = Object.create(superType.prototype)
prototype.constructor = subType
subType.prototype = prototype
}
// 继承原型方法
inheritPrototype(SubType, SuperType)
// 定义自己的原型方法
SubType.prototype.sayAge = function() {
console.log(this.age)
}
12. 闭包
闭包是指有权访问另一个函数作用域中的变量的函数。
function sayHi(name) {
return () => {
console.log(Hi! ${name}
)
}
}
const test = sayHi(‘xiaoming’)
test() // Hi! xiaoming
虽然sayHi函数已经执行完毕,但是其活动对象也不会被销毁,因为test函数仍然引用着sayHi函数中的变量name,这就是闭包。
但也因为闭包引用着另一个函数的变量,导致另一个函数即使不使用了也无法销毁,所以闭包使用过多,会占用较多的内存,这也是一个副作用。
利用闭包实现私有属性
const test = (function () {
let value = 0
return {
getVal() { return value },
setVal(val) { value = val }
}
})()
上面的代码实现了一个私有属性 value
,它只能用过 getVal()
来取值,通过 setVal(val)
来设置值。
13. 内存回收
在 JS 中,有两种内存回收算法。第一种是引用计数垃圾收集,第二种是标记-清除算法(从2012年起,所有现代浏览器都使用了标记-清除垃圾回收算法)。
引用计数垃圾收集
如果一个对象没有被其他对象引用,那它将被垃圾回收机制回收。
let o = { a: 1 }
一个对象被创建,并被 o 引用。
o = null
刚才被 o 引用的对象现在是零引用,将会被回收。
循环引用
引用计数垃圾收集有一个缺点,就是循环引用会造成对象无法被回收。
function f(){
var o = {};
var o2 = {};
o.a = o2; // o 引用 o2
o2.a = o; // o2 引用 o
return “azerty”;
}
f();
在 f() 执行后,函数的局部变量已经没用了,一般来说,这些局部变量都会被回收。但上述例子中,o 和 o2 形成了循环引用,导致无法被回收。
标记-清除算法
这个算法假定设置一个叫做根(root)的对象(在Javascript里,根是全局对象)。垃圾回收器将定期从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象……从根开始,垃圾回收器将找到所有可以获得的对象和收集所有不能获得的对象。
对于刚才的例子来说,在 f() 执行后,由于 o 和 o2 从全局对象出发无法获取到,所以它们将会被回收。
高效使用内存
在 JS 中能形成作用域的有函数、全局作用域、with,在 es6 还有块作用域。局部变量随着函数作用域销毁而被释放,全局作用域需要进程退出才能释放或者使用 delete 和赋空值 null
undefined
。
在 V8 中用 delete 删除对象可能会干扰 V8 的优化,所以最好通过赋值方式解除引用。
参考资料:
14. 有一个函数,参数是一个函数,返回值也是一个函数,返回的函数功能和入参的函数相似,但这个函数只能执行3次,再次执行无效,如何实现
这个题目是考察闭包的使用
function sayHi() {
console.log(‘hi’)
}
function threeTimes(fn) {
let times = 0
return () => {
if (times++ < 3) {
fn()
}
}
}
const newFn = threeTimes(sayHi)
newFn()
newFn()
newFn()
newFn()
newFn() // 后面两次执行都无任何反应
通过闭包变量 times
来控制函数的执行
15. 实现add函数,让add(a)(b)和add(a,b)两种调用结果相同
实现1
function add(a, b) {
if (b === undefined) {
return function(x) {
return a + x
}
}
return a + b
}
实现2——柯里化
function curry(fn, …args1) {
// length 是函数对象的一个属性值,指该函数有多少个必须要传入的参数,即形参的个数。
if (fn.length == args1.length) {
return fn(…args1)
}
return function(…args2) {
return curry(fn, …args1, …args2)
}
}
function add(a, b) {
return a + b
}
console.log(curry(add, 1)(2)) // 3
console.log(curry(add, 1, 2)) // 3
16. 使用Ajax的优缺点分别是什么
优点
-
交互性更好。来自服务器的新内容可以动态更改,无需重新加载整个页面。
-
减少与服务器的连接,因为脚本和样式只需要被请求一次。
-
状态可以维护在一个页面上。JavaScript 变量和 DOM 状态将得到保持,因为主容器页面未被重新加载。
-
基本上包括大部分 SPA 的优点。
缺点
-
动态网页很难收藏。
-
如果 JavaScript 已在浏览器中被禁用,则不起作用。
-
有些网络爬虫不执行 JavaScript,也不会看到 JavaScript 加载的内容。
-
基本上包括大部分 SPA 的缺点。
参考资料:
17. Ajax和Fetch区别
-
ajax是使用XMLHttpRequest对象发起的,但是用起来很麻烦,所以ES6新规范就有了fetch,fetch发一个请求不用像ajax那样写一大堆代码。
-
使用fetch无法取消一个请求,这是因为fetch基于Promise,而Promise无法做到这一点。
-
在默认情况下,fetch不会接受或者发送cookies
-
fetch没有办法原生监测请求的进度,而XMLHttpRequest可以
-
fetch只对网络请求报错,对400,500都当做成功的请求,需要封装去处理
-
fetch由于是ES6规范,兼容性上比不上XMLHttpRequest
18. 变量提升
var会使变量提升,这意味着变量可以在声明之前使用。let和const不会使变量提升,提前使用会报错。
变量提升(hoisting)是用于解释代码中变量声明行为的术语。使用var关键字声明或初始化的变量,会将声明语句“提升”到当前作用域的顶部。 但是,只有声明才会触发提升,赋值语句(如果有的话)将保持原样。
19. 使用let、var和const创建变量有什么区别
用 var 声明的变量的作用域是它当前的执行上下文,它可以是嵌套的函数,也可以是声明在任何函数外的变量。let 和 const 是块级作用域,意味着它们只能在最近的一组花括号(function、if-else 代码块或 for 循环中)中访问。
var 声明的全局变量和函数都会成为 window 对象的属性和方法。使用 let 和 const 的顶级声明不会定义在全局上下文中,但在作用域链解析上效果是一样的。
function foo() {
// 所有变量在函数中都可访问
var bar = ‘bar’;
let baz = ‘baz’;
const qux = ‘qux’;
console.log(bar); // bar
console.log(baz); // baz
console.log(qux); // qux
}
console.log(bar); // ReferenceError: bar is not defined
console.log(baz); // ReferenceError: baz is not defined
console.log(qux); // ReferenceError: qux is not defined
if (true) {
var bar = ‘bar’;
let baz = ‘baz’;
const qux = ‘qux’;
}
// 用 var 声明的变量在函数作用域上都可访问
console.log(bar); // bar
// let 和 const 定义的变量在它们被定义的语句块之外不可访问
console.log(baz); // ReferenceError: baz is not defined
console.log(qux); // ReferenceError: qux is not defined
var会使变量提升,这意味着变量可以在声明之前使用。let和const不会使变量提升,提前使用会报错。
console.log(foo); // undefined
var foo = ‘foo’;
console.log(baz); // ReferenceError: can’t access lexical declaration ‘baz’ before initialization
let baz = ‘baz’;
console.log(bar); // ReferenceError: can’t access lexical declaration ‘bar’ before initialization
const bar = ‘bar’;
用var重复声明不会报错,但let和const会。
var foo = ‘foo’;
var foo = ‘bar’;
console.log(foo); // “bar”
let baz = ‘baz’;
let baz = ‘qux’; // Uncaught SyntaxError: Identifier ‘baz’ has already been declared
let和const的区别在于:let允许多次赋值,而const只允许一次。
// 这样不会报错。
let foo = ‘foo’;
foo = ‘bar’;
// 这样会报错。
const baz = ‘baz’;
baz = ‘qux’;
20. 对象浅拷贝和深拷贝有什么区别
在 JS
中,除了基本数据类型,还存在对象、数组这种引用类型。 基本数据类型,拷贝是直接拷贝变量的值,而引用类型拷贝的其实是变量的地址。
let o1 = {a: 1}
let o2 = o1
在这种情况下,如果改变 o1
或 o2
其中一个值的话,另一个也会变,因为它们都指向同一个地址。
o2.a = 3
console.log(o1.a) // 3
而浅拷贝和深拷贝就是在这个基础之上做的区分,如果在拷贝这个对象的时候,只对基本数据类型进行了拷贝,而对引用数据类型只是进行了引用的传递,而没有重新创建一个新的对象,则认为是浅拷贝。反之,在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量,则认为是深拷贝。
21. 怎么实现对象深拷贝
这种方法有缺陷,详情请看关于JSON.parse(JSON.stringify(obj))实现深拷贝应该注意的坑
let o1 = {a:{
b:1
}
}
let o2 = JSON.parse(JSON.stringify(o1))
基础版
function deepCopy(target) {
if (typeof target == ‘object’) {
const result = Array.isArray(target)? [] : {}
for (const key in target) {
if (typeof target[key] == ‘object’) {
result[key] = deepCopy(target[key])
} else {
result[key] = target[key]
}
}
return result
} else if (typeof target == ‘function’) {
return eval(‘(’ + test.toString() + ‘)’)
} else {
return target
}
}
完整版
const mapTag = ‘[object Map]’
const setTag = ‘[object Set]’
const arrayTag = ‘[object Array]’
const objectTag = ‘[object Object]’
const symbolTag = ‘[object Symbol]’
function deepCopy(origin, map = new WeakMap()) {
if (!origin || !isObject(origin)) return origin
if (typeof origin == ‘function’) {
return eval(‘(’ + origin.toString() + ‘)’)
}
const objType = getObjType(origin)
const result = createObj(origin, objType)
// 防止循环引用,不会遍历已经在 map 中的对象,因为在上一层正在遍历
if (map.get(origin)) {
return map.get(origin)
}
map.set(origin, result)
// set
if (objType == setTag) {
for (const value of origin) {
result.add(deepCopy(value, map))
}
return result
}
// map
if (objType == mapTag) {
for (const [key, value] of origin) {
result.set(key, deepCopy(value, map))
}
return result
}
// 对象或数组
if (objType == objectTag || objType == arrayTag) {
for (const key in origin) {
result[key] = deepCopy(origin[key], map)
}
return result
}
return result
}
function getObjType(obj) {
return Object.prototype.toString.call(obj)
}
function createObj(obj, type) {
if (type == objectTag) return {}
if (type == arrayTag) return []
if (type == symbolTag) return Object(Symbol.prototype.valueOf.call(obj))
return new obj.constructor(obj)
}
function isObject(origin) {
return typeof origin == ‘object’ || typeof origin == ‘function’
}
22. 数组去重
ES5
function unique(arry) {
const temp = []
arry.forEach(function(item) {
if (temp.indexOf(item) == -1) {
temp.push(item)
}
})
return temp
}
ES6
function unique(arry) {
return Array.from(new Set(arry))
}
23. 数据类型
-
Undefined
-
Null
-
Boolean
-
Number
-
String
-
Object
-
symbol(ES6新增)
24. 内置函数(原生函数)
-
String
-
Number
-
Boolean
-
Object
-
Function
-
Array
-
Date
-
RegExp
-
Error
-
Symbol
原始值 “I am a string” 并不是一个对象,它只是一个字面量,并且是一个不可变的值。
如果要在这个字面量上执行一些操作,比如获取长度、访问其中某个字符等,那需要将其转换为 String 对象。
幸好,在必要时语言会自动把字符串字面量转换成一个 String 对象,也就是说你并不需要显式创建一个对象。
25. 如何判断数组与对象
Array.isArray([]) // true
Array.isArray({}) // false
typeof [] // “object”
typeof {} // “object”
Object.prototype == [].proto // false
Object.prototype == {}.proto // true
Array.prototype == [].proto // true
Array.prototype == {}.proto // false
26. 自动分号
有时 JavaScript 会自动为代码行补上缺失的分号,即自动分号插入(Automatic SemicolonInsertion,ASI)。
因为如果缺失了必要的 ; ,代码将无法运行,语言的容错性也会降低。ASI 能让我们忽略那些不必要的 ;
。
请注意,ASI 只在换行符处起作用,而不会在代码行的中间插入分号。
如果 JavaScript 解析器发现代码行可能因为缺失分号而导致错误,那么它就会自动补上分号。并且,只有在代码行末尾与换行符之间除了空格和注释之外没有别的内容时,它才会这样做。
27. 浮点数精度
28. cookie、localStorage、sessionStorage区别
| 特性 | cookie | localStorage | sessionStorage |
| — | — | — | — |
| 由谁初始化 | 客户端或服务器,服务器可以使用Set-Cookie
请求头。 | 客户端 | 客户端 |
| 数据的生命周期 | 一般由服务器生成,可设置失效时间,如果在浏览器生成,默认是关闭浏览器之后失效 | 永久保存,可清除 | 仅在当前会话有效,关闭页面后清除 |
| 存放数据大小 | 4KB | 5MB | 5MB |
| 与服务器通信 | 每次都会携带在HTTP头中,如果使用cookie保存过多数据会带来性能问题 | 仅在客户端保存 | 仅在客户端保存 |
| 用途 | 一般由服务器生成,用于标识用户身份 | 用于浏览器缓存数据 | 用于浏览器缓存数据 |
| 访问权限 | 任意窗口 | 任意窗口 | 当前页面窗口 |
29. 自执行函数?用于什么场景?好处?
自执行函数:
-
声明一个匿名函数
-
马上调用这个匿名函数。
作用:创建一个独立的作用域。
好处
-
防止变量弥散到全局,以免各种js库冲突。
-
隔离作用域避免污染,或者截断作用域链,避免闭包造成引用变量无法释放。
-
利用立即执行特性,返回需要的业务函数或对象,避免每次通过条件判断来处理。
场景
一般用于框架、插件等场景
30. 多个页面之间如何进行通信
有如下几个方式:
-
cookie
-
web worker
-
localeStorage和sessionStorage
31. css动画和js动画的差异
-
代码复杂度,js 动画代码相对复杂一些
-
动画运行时,对动画的控制程度上,js 能够让动画,暂停,取消,终止,css动画不能添加事件
-
动画性能看,js 动画多了一个js 解析的过程,性能不如 css 动画好
32. new一个对象经历了什么
function Test(){}
const test = new Test()
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
最后:
总结来说,面试成功=基础知识+项目经验+表达技巧+运气。我们无法控制运气,但是我们可以在别的地方花更多时间,每个环节都提前做好准备。
面试一方面是为了找到工作,升职加薪,另一方面也是对于自我能力的考察。能够面试成功不仅仅是来自面试前的临时抱佛脚,更重要的是在平时学习和工作中不断积累和坚持,把每个知识点、每一次项目开发、每次遇到的难点知识,做好积累,实践和总结。
-
cookie
-
web worker
-
localeStorage和sessionStorage
31. css动画和js动画的差异
-
代码复杂度,js 动画代码相对复杂一些
-
动画运行时,对动画的控制程度上,js 能够让动画,暂停,取消,终止,css动画不能添加事件
-
动画性能看,js 动画多了一个js 解析的过程,性能不如 css 动画好
32. new一个对象经历了什么
function Test(){}
const test = new Test()
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-AYqUCN5D-1712079682302)]
[外链图片转存中…(img-IALp48Cu-1712079682303)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
[外链图片转存中…(img-1xRJYVM7-1712079682303)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
最后:
总结来说,面试成功=基础知识+项目经验+表达技巧+运气。我们无法控制运气,但是我们可以在别的地方花更多时间,每个环节都提前做好准备。
面试一方面是为了找到工作,升职加薪,另一方面也是对于自我能力的考察。能够面试成功不仅仅是来自面试前的临时抱佛脚,更重要的是在平时学习和工作中不断积累和坚持,把每个知识点、每一次项目开发、每次遇到的难点知识,做好积累,实践和总结。