Hi,我是前端人,今日与君共勉!
要说 js 的深浅拷贝,就不得不提 js 的两大数据类型:基本数据类型和引用类型。基本数据类型的变量名和值都存储在栈中,对于引用类型的变量名存储在栈中,而值存储在堆中。由于存储方式不同,所以导致了他们复制的时候方式不同。
浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精准拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另外一个对象。
深拷贝是将一个对象从内存中完整的拷贝一份出来,从内存堆中存放一个新的对象。这是两个对象,所以修改其中一个,另外一个不会受影响。
深浅拷贝主要针对的是引用类型,简单数据类型不受影响。
相关笔试题
var person ={
name:"前端人",
hobby:['学习','敲代码','潜水']
}
functioncopy(source){
var newObj = newObject()
for(var i insource){
if(source.hasOwnProperty(i)){
newObj[i] =source[i]
}
}
returnnewObj
}
var p1 =copy(person);
p1.name = "Web Person"console.log(person.name)
console.log(p1.name)
p1.hobby = ["内卷"]
console.info(person.hobby)
console.info(p1.hobby)
/*运行结果:
前端人
Web Person
["学习", "敲代码", "潜水"]
["内卷"]
*/
js 数据类型一共有 8 种,分为两大类:基本类型和引用类型。
它们的数据类型分别为:
基本类型:string、number、boolean、null、undefined、symbol、bigint
引用类型:object
相关面试题
//注意:其他类型与数值进行相加时,其他类型的转为 number 类型
console.log( true+1 ) //2
console.log( undefined +1 ) //NaN
console.log( null ) //object
console.log( undefined ) //undefined
共有 6 种方式,分别为:
它们的区别介绍:
1、async:为 <script>标签定义了 async 属性。async 和 html 解析是同步的,不是顺次执行 js 脚本,谁先加载完成先执行谁。
<script async type="text/javascript" src="demo1.js" ></script>
<script async type="text/javascript" src="demo2.js" ></script>
2、defer 会等到 html 解析完成之后再执行 js 代码,如果有多个脚本时,会按照顺序依次执行脚本。
<script defer type="text/javascript" src="demo1.js" ></script>
3、js 最后加载
把 js 外部引入的文件放置在页面的底部,让 js 最后加载,从而加快页面加载速度。
4、利用 setTimeout
5、动态创建 DOM 的方式
var element = document.createElement("script");
element.src = "box.js";
document.body.appendChild(element);
这种方式通过操作动态加载 js 文件,不触发的时候不加载,减少页面文件大小,加快加载速度。
6、使用 jQuery 的 getScript 方法
$.getScript( "box.js",function(){//回调函数,成功获取文件后执行的函数
console.log("脚本加载完成")
});
相关面试题:
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script type="text/javascript" src="box.js"></script>
</head>
<body>
<div id="box"></div>
</body>
</html>
//box.js 代码如下
console.log( document.getElementById('box') ) //null
box.js 想正常获取元素 box ,并进行一系列操作应该如何延迟加载 js 文件呢?
作用域通俗地讲,就是指一个变量的作用范围。分为全局作用域和函数作用域。
全局作用域
函数作用域(局部)
函数在被调用的时候会先进行预编译:
全局作用域预编译:
函数作用域预编译:
相关面试题:
<script type="text/javascript">
functionfn(a,c){
console.log(a)
var a = 12console.log(a)
console.log(c)
functiona(){ }
if(false){
var d = 34}
console.log(d)
console.log(b)
var b = function(){}
console.log(b)
functionc(){}
console.log(c)
}
fn(1,2)
</script>
//运行结果:/*function a(){}
12
function c(){}
undefined
undefined
function (){}
function c(){}
*/
null 和 undefined 两个都表示无的值。
作者设计 js 的时候,借鉴的 java 语言先设计的 null 。null 使用的时候会被隐式转化成 0,不容易发现错误。
console.log( number(null) ) //0
undefined 是为了填补 null 的坑。所以后来又新增了 undefined 。
console.log( number(undefined) ) //NaN
实现 new 操作符的方法:
functioncreate( fn,...args ){
var obj={}
Object.setPrototypeOf( obj,fn.prototype )
var resault =fn.apply(obj,args)
return (resault instanceof Object) ?result : obj
}
7.1、什么是闭包?
闭包就是函数嵌套函数,通过函数内的函数访问变量的规则,实现外部访问函数内的变量。
7.2、闭包的特点:
实例3:闭包解决问题
var liArr = document.getElementsByTagName('li')
for(var i=0;i<liArr.length;i++){
(function(i){
liArr[i].onclick = function(){
console.log('点击元素',liArr[i])
}
})(i)
}
7.3、闭包优点:
防抖和节流就是闭包的经典应用。
7.4、闭包缺点:
8.1、什么是防抖函数?
当持续触发事件,一定时间内没有再触发事件,事件处理函数才会执行一次,如果在设定的时间到来之前又触发了事件,就会重新计时。
防抖函数常见的实际应用:使用 echart 的时候,浏览器 resize 时,需要重新绘制图表大小,还有典型的输入框搜索应用。
8.2、节流函数是什么?
当持续触发事件的时候,保证一段时间内只调用一次事件处理函数,一段时间内,只允许做一件事情。
防抖和节流主要是用来限制触发频率较高的事件,再不影响效果的前提条件下,降低事件触发频率,减小浏览器或服务器的压力,提升用户体验效果。
方法1: new set()
return Array.from(newSet(arr))
//或
return [...new Set(arr)]
方法2:使用两次循环
for(var i=0,len=arr.length;i<len;i++){
for(var j=i+1,len=arr.length;j<len;j++){
if( arr[i]===arr[j] ){
arr.splice(i,1)
j--;
len--}
}
}
return arr
方法3:indexOf 实现
let arr1 =[]
for(var i=0;i<arr.length;i++){
if( arr1.indexOf(arr[i]) === -1){
arr1.push(arr[i])
}
}
return arr1
方法4:includes 实现
let arr1 =[]
for(var i=0;i<arr.length;i++){
if( !arr1.includes(arr[i]) ){
arr1.push(arr[i])
}
}
return arr1
方法5:filter 实现
array.indexOf(item,start) start 表示开始检索的位置。
return arr.filter(( item, index )=>{
return arr.indexOf( item, 0 ) ==index
})
三者都是改变函数执行的上下文,即改变 this 指向。
它们之间的区别为:
使用场景:
1、需要改变某个函数的this指向时
2、当参数较少时可以使用call,参数较多可以使用apply以数组的方式传递
3、当需要重复调用时,可以使用bind新定义一个方法
方法1:isArray
var arr = [1,2,3]
console.log(Array.isArray(arr))
方法2:instanceof
var arr = [1,2,3]
console.log( arr instanceofArray )
console.log( arr instanceof Object )
该方法不够严谨。
方法3:prototype
console.log( Object.prototype.toString.call(arr).indexOf('Array')>-1 )
方法4:isPrototypeOf
console.log( Array.prototype.isPrototypeOf( arr ) )
方法5:constructor
console.log(arr.constructor.toString().indexOf('Array')>-1 )
slice 是用来截取字符串的,返回一个新数组,但不会影响原数组。
使用语法:
arr.slice( start , end )
截取 arr 数组,从 start 开始到 end 结束,第二个参数是可选参数,没有时从 start 开始截取到结尾。
如果 start 参数是负数时,就会从 arr.lengtn + start 开始截取到结束。
var arr = ['a','b','c','d','e']
console.log( arr.slice(-3) ) //["c", "d", "e"]
console.log(arr) //["a", "b", "c", "d", "e"]
splice 是一个更强大的方法,可以添加、删除、替换数组元素,返回的是被删除元素,它的操作会改变原数组。
使用语法:
splice( start, n, new )
从 start 开始,删除 n 个元素,然后把 new 添加到 start 元素之后。第三个参数为可选参数
var arr = ['a','b','c','d','e']
var ar = arr.splice( 1, 1 ,'f','g')
console.log('ar',ar) //["b"]
console.log('arr',arr) //["a", "f", "g", "c", "d", "e"]
== 比较的是值,=== 除了比较值,还比较类型。
console.log( [1,2]=='1,2' ) //true
console.log( [1,2] === '1,2' ) //false
valueOf 方法返回 Math 对象的原始值,通常由 javascript 在后台自动调用,并不显示的出现在代码中。
console.log([1,2].valueOf()) //[1,2]
console.log('1,2'.valueOf()) //[1,2]//所以
console.log( [1,2]=='1,2' ) //true
不管是字符串和数字比较,还是布尔值和数字比较,都会使用 valueOf 隐式转换。
总结:== 需要使用 valueOf() 进行隐式转换,所以性能差。 === 会避开一些不必要的麻烦。
大厂笔试题:
var name = 'window name'
var p1 ={
name:'p1 name',
showName:function(){
console.info(this.name)
}
}
var fn =p1.showName
fn()
p1.showName()
var p2 ={
name:'p2 name',
showName:function(fun){
fun()
}
}
p2.showName(p1.showName)
p2.showName =p1.showName
p2.showName()
/*运行结果:
window name
p1 name
window name
p2 name
*/
这是一道关于 this 指向的面试题,接下来我们就说说 this 是如何指向的?
this 对象是运行时基于函数的执行环境绑定的:
第 1 种:原型链继承
functionParent(){
this.name = "前端人"}
Parent.prototype.showName = function(){
console.log(this.name)
}
functionChild(){}
//原型链继承
Child.prototype = newParent()
var p = newChild()
console.dir(p.name) //前端人
特点:
第 2 种:借用构造函数
functionAnimal (name) {
this.name = name || 'Animal';
this.sleep = function(){
console.log(this.name + '正在睡觉!');
}
}
Animal.prototype.eat = function(food) {
console.log(this.name + '正在吃:' +food);
};
functionCat(name){
Animal.call(this);
this.name = name || 'Tom';
}
//Test Code
var cat = newCat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); //false
console.log(cat instanceof Cat); //true
特点:
还有组合式继承、ES6 的继承 和 寄生组合继承等等。每种继承方式都有各自的特点和缺点。
JavaScript 语言是一门弱类型语言,存在许多类型错误,因此 ES6 引入了严格模式概念。
如果不加 ‘use strict’ 常规模式下就是属于非严格模式。
严格模式
在 js 文件顶部添加 ‘use strict’ 就属于严格模式,严格模式也可以指定在函数内部。
<script>
'use strict'
//或者函数内部
(function(){
'use strict'})()
</script>
严格模式,是为 js 定义来了一种不同的解析与执行模型,在严格模式下,ECMAScipt 3 中一些不解和不确定的行为将得到处理,而且会对不安全的操作会抛出异常。‘use strict’ 会告诉浏览器引擎可以切换到严格模式执行。
严格模式与非严格模式区别
| <br>严格模式<br> | <br>非严格模式<br> |
| <br>变量必须声明才能赋值<br> | <br>变量不进行声明,可直接赋值<br> |
| <br>不能使用 delete 字符删除变量或对象<br> | <br>可以使用 delete 删除<br> |
| <br>函数参数变量名不允许重复<br> | <br>变量名重复,获取最后最后那个值<br> |
| <br>普通函数内的 this 为 undefined<br> | <br>普通函数内的 this 为 window<br> |
| <br>不允许使用八进制<br> | <br>允许任意进制<br> |
| <br>eval 和 arguments 当做关键字,不能被赋值和用作变量名<br> | <br>可以使用 eval 、arguments 作为变量名<br> |
| <br>call、apply 传入 null undefined 保持原样不被转为window<br> | <br>默认转为 window 对象<br> |
| <br>限制对调用栈的检测能力,访问 arguments.callee 会抛出异常<br> | <br>arguments.callee 运行正常<br> |
console.log( '2'>10 ) //false
console.log( '2'>'10' ) //true
console.log( 'abc'>'b' ) //false
console.log( 'abc'>'aab' ) //true
console.log( undefined == null ) //true
console.log( NaN == NaN )//false
console.log( [] == 0 ) //true
console.log( ![] == 0 ) //true
console.log( [] == [] ) //false
console.log( {} == {} ) //false
console.log( {} == !{} ) //false
阿里面试题1:
<script type="text/javascript">
var p =new Promise(resolve=>{
console.log(4)
resolve(5)
})
functionf1(){
console.log(1)
}
functionf2(){
setTimeout(()=>{
console.log(2)
},0)
f1()
console.log(3)
p.then(res=>{
console.log(res)
})
}
f2()
</script>
//运行结果 4 1 3 5 2//如果已经了解事件运行机制,就可以跳过该问题了
事件循环机制,event-loop 。包含三部分:调用栈、消息队列、微任务队列。
事件循环开始的时候,会从全局一行一行的执行代码,遇到函数调用的时候,就会压入调用栈中,当函数执行完成之后,弹出调用栈。
//如:代码会一行一行执行,函数全部调用完成之后清空调用栈
functionf1(){
console.log(1)
}
functionf2(){
f1()
console.log(2)
}
f2()
//执行结果 1 2
如果遇到 fetch、setInterval、setTimeout 异步操作时,函数调用压入调用栈时,异步执行内容会被加入消息队列中,消息队列中的内容会等到调用栈清空之后才会执行。
//如:
functionf1(){
console.log(1)
}
functionf2(){
setTimeout(()=>{
console.log(2)
},0)
f1()
console.log(3)
}
f2()
//执行结果 :1 3 2
遇到 promise、async、await 异步操作时,执行内容会被加入微任务队列中,会在调用栈清空之后立即执行。
调用栈加入的微任务队列会立即执行。
如
let p =new Promise(resolve=>{
console.log('立即执行')
resolve(1) //在 then 调用中执行
})
微任务队列中内容优先执行,所以比消息队列中的内容执行得早。
了解这些知识后,再试一下最前面的那道面试题,应该就没什么问题了。
这个问题就留给读到最后,能够坚持学习的人,问问我们自己有什么是我们擅长的?在哪块领域是我们占据竞争优势的?
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://www.cnblogs.com/web-learn/p/15878089.html
内容来源于网络,如有侵权,请联系作者删除!