刷题 | 牛客 - js入门15题(更ing)5/15知识点解答
知识点总结:
a.contains(b)---> a 节点中是否包含 b
a.parentNode()-----> 求a节点的父节点
Object.assign(target, ...sources)-----> 浅拷贝:只拷贝对象的第一层属性,内部嵌套对象依然是引用类型。常用Object.assign(tar,sour):将所有源对象的可枚举属性复制到目标对象
防御性编程
链式赋值
fn(...arr)-----> 扩展运算符,注:arr为参数/集合(数组)
fn.apply(this,arr)-------->apply:修改 this的指向,一参是函数体内的this指向,二参是收集一个集合对象(数组和类数组)
fn.call(this, ...arr)--------->call:修改 this指向,一参 函数体内的this指向,二参是往后是依次传入的参数
JS41 dom 节点查找
描述
查找两个节点的最近的一个共同父节点,可以包括节点自身
输入描述:
oNode1 和 oNode2 在同一文档中,且不会为相同的节点
解题思路:
递归求解,一个节点不动,另一个节点不断向上查找,直到找到一个父节点包含另一个节点为止。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
/* 填写样式 */
</style>
</head>
<body>
<!-- 填写标签 -->
<script type="text/javascript">
// 填写JavaScript
function commonParentNode(oNode1, oNode2) {
// 方法一:不断向上查找,直到找到一个父节点包含oNode2
/* for(;;oNode1 = oNode1.parentNode){
if(oNode1.contains(oNode2)){
return oNode1;
}
}*/
// 方法二:递归求解。一个节点不动,另一个节点不断向上查找,直到找到一个父节点能够包含另一个节点为止
if(oNode1.contains(oNode2)){
return oNode1;
}else{
return commonParentNode(oNode1.parentNode,oNode2);
}
}
</script>
</body>
</html>
JS44 根据包名,在指定空间中创建对象
描述
根据包名,在指定空间中创建对象
输入描述:
namespace({a: {test: 1, b: 2}}, 'a.b.c.d')
输出描述:
{a: {test: 1, b: {c: {d: {}}}}}
解题与分析
法一:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
/* 填写样式 */
</style>
</head>
<body>
<!-- 填写标签 -->
<script type="text/javascript">
function namespace(oNamespace, sPackage) {
const arr = sPackage.split('.');
let res = oNamespace
// 如果不是对象,则使它成为对象
for(let i=0;i<arr.length;i++){
if(typeof res[arr[i]] != 'object'){
res[arr[i]] = {};
}
res = res[arr[i]]; // 不可省,在循环中将其赋给res
}
return res
// return oNamespace 二者都可,因为复制的是对象的引用
}
</script>
</body>
法二,浅拷贝方法:
// 法二:浅拷贝
let o = oNamespace;
let res = sPackage.split('.').forEach(item => {
o[item] = o[item] ? Object.assign({}, o[item]) : {} ;
o = o[item];
});
// return res;
return o;
知识点:
🔑 一、Object.assign()
细讲
Object.assign()
是 ES6 引入的方法,作用是将所有源对象的可枚举属性复制到目标对象,并返回目标对象。
语法:Object.assign(target, ...sources)
参数 | 作用 |
---|---|
target | 目标对象(会被修改并返回) |
sources | 一个或多个源对象 |
2. 浅拷贝特性
浅拷贝只拷贝对象的第一层属性,内部嵌套对象依然是引用类型。
示例:
let obj1 = { a: 1, b: { c: 2 } };
let obj2 = Object.assign({}, obj1);
obj2.a = 100; obj2.b.c = 200;
console.log(obj1.a); // 1 ✅(基本类型被拷贝)
console.log(obj1.b.c); // 200 ❌(引用类型被共享)
👉 说明:
a
是基本类型,直接拷贝值 ✅b.c
是引用类型,拷贝的只是地址引用 🔥
3. 为什么用 Object.assign({}, tmpWrap[item])
?
假设代码里直接写:tmpWrap[item] = tmpWrap[item] || {};
如果 tmpWrap[item]
是对象:
- 直接赋值会共用同一个对象的引用,修改
tmpWrap[item]
也会影响原始对象 - 而
Object.assign()
会创建一个新的对象,确保不会共享引用。
🚨 防御性编程核心原因
tmpWrap[item] = Object.assign({}, tmpWrap[item]);
这个写法的优势:
写法 | 是否安全 | 原因 |
---|---|---|
`tmpWrap[item] = tmpWrap[item] | {};` | |
tmpWrap[item] = {}; | ❌ | 会覆盖已有数据 |
Object.assign({}, tmpWrap[item]) | ✅ | 创建新对象,数据隔离 |
🔥 二、额外思考的安全原因
代码:
tmpWrap[item] = tmpWrap[item] ? Object.assign({}, tmpWrap[item]) : {};
这个写法为什么更安全?
✅ 安全点在于:
?
这个三元运算符确保只有在对象已经存在的情况下才会执行浅拷贝- 如果对象不存在,直接创建
{}
,避免多余的浅拷贝
性能对比
写法 | 性能 | 安全性 | 说明 |
---|---|---|---|
Object.assign({}, tmpWrap[item]) | 较低 | ✅ | 每次都会浅拷贝 |
tmpWrap[item] ? Object.assign({}, tmpWrap[item]) : {} | 较高 | ✅ | 只有存在对象时才拷贝 |
★ 三、三个等号解释
代码:
tmpWrap = tmpWrap[item] = Object.assign({}, tmpWrap[item]);
等号的执行顺序是从右往左,相当于:
Object.assign({}, tmpWrap[item])
先执行,返回一个新对象tmpWrap[item] = 新对象
- 将新对象赋值给当前层级的属性
tmpWrap = 新对象
- 把
tmpWrap
移动到下一级,方便下一次迭代
- 把
🚀 执行顺序演示
假设:let obj = {}; namespace(obj, 'a.b.c');
执行过程:
步骤 | tmpWrap | tmpWrap[item] | 说明 |
---|---|---|---|
初始 | {} | - | 初始对象 |
a | {} | {} | 创建 a 属性 |
b | {} | {} | 创建 a.b 属性 |
c | {} | {} | 创建 a.b.c 属性 |
🔑 终极总结
知识点 | 说明 |
---|---|
浅拷贝 | 避免共享引用 |
Object.assign() | 数据隔离 + 防御性编程 |
三个等号执行顺序 | 代码链式赋值的重要特性 |
额外思考写法 | 性能 + 安全性更高 |
💡 最佳代码写法
tmpWrap[item] = tmpWrap[item] ? Object.assign({}, tmpWrap[item]) : {}; tmpWrap = tmpWrap[item];
👉 这样写:
- 只有在需要时才进行浅拷贝
- 不会污染已有对象
- 代码可读性更好
本题考察的核心点:
- 对 浅拷贝/深拷贝 的理解
- 对象引用类型的陷阱
- 链式赋值的执行顺序
- 防御性编程思想
JS54 函数传参
描述
将数组 arr 中的元素作为调用函数 fn 的参数
示例1
输入:
function (greeting, name, punctuation) {return greeting + ', ' + name + (punctuation || '!');}, ['Hello', 'Ellie', '!']
复制输出:
Hello, Ellie!
解题:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
/* 填写样式 */
</style>
</head>
<body>
<!-- 填写标签 -->
<script type="text/javascript">
// 填写JavaScript
function argsAsArray(fn, arr) {
// 法一:扩展运算符,防止fn不存在
// return fn && fn(...arr);
// 法二:apply 修改this的指向,一参是函数体内的this指向,二参是收集一个集合对象(数组和类数组)
// return fn.apply(this,arr);
// 法三:call 修改this指向,一参 函数体内的this指向,二参是往后是依次传入的参数
return fn.call(this, ...arr);
}
</script>
</body>
</html>