前端JS面试
2021-01-17 08:11
标签:必须 自己的 mozilla 函数调用 foo int 输出 解决方法 有关 原始值类型:Number、String、Boolean、Null、Undefined 引用值类型:Object、Array、Function、Date、RegExp 区别:原始值存储在栈中,引用值把引用变量存储在栈中,而实际的对象存储在堆中,每一个引用变量都有一个指针指向其堆中的实际对象 原始变量赋值给另一个原始变量时,只是把栈中的内容复制给另一个原始变量,此时这两个原始变量互不影响 引用变量赋值给另一个引用变量时,各自的变量名存储在栈中,而实际对象的值指向堆中同一个地址,当变量a通过方法改变值时,实际上只改变堆中的内容,但地址不变,因此b的值也会改变;但是当变量a通过非方法改变值时,系统会为a重新创建一个堆区,a的指针指向新的堆地址,而b的指针仍然指向旧的堆地址 1.typeof typeof 对于原始数据类型除了 null 以外都能判断出来,但是对于引用数据类型,判断的结果都是 object 2.instanceof a instanceof b 判断 b 的原型对象是否在 a 的原型链上 原理如下: instanceof 主要用于判断引用数据类型 instanceof 也可以用来判断原始数据类型(null 和 undefined 除外) 为什么返回 false 呢?因为 instanceof 是 通过 new Number() 生成的实例就是 object 3.Object.prototype.toString.call() 利用 Object 原型对象上的 toString 方法可以精确的判断各种数据类型 4.constructor 通过实例对象原型上的 constructor 属性来判断数据类型(null 和 undefined 除外) 类数组: 常见的类数组有: 类数组转换为数组: Array.prototype.slice.call 扩展运算符 Array.from isArray():判断是否为数组 toString():将数组转换为以逗号分隔的字符串 join():返回按照指定字符分隔的字符串 concat():用于连接两个或多个数组,该方法不会改变原数组,而仅仅会返回被连接数组的一个副本 slice(start, end):截取数组中的元素,该方法不会改变原数组,而是返回一个子数组 start 和 end均为空时,截取数组所有元素 end 为空时,从 start 开始截取到数组结尾 end 不为空时,从 start 开始截取到 end 的前一位 start 和 end 为负数时,从数组末尾开始计算 reverse():翻转数组 sort():自定义排序 splice():向数组中添加/删除元素,该方法会改变原数组 添加元素 删除元素 替换元素 push():向数组末尾添加一个或多个元素,并返回新的长度 unshift():向数组开头添加一个或多个元素,并返回新的长度 pop():删除数组末尾的元素 shift():删除数组开头的元素 entries():返回数组的可迭代对象 every():检测数组的每个元素是否都符合条件,不会对空数组进行检测,不会改变原数组 fill():用一个固定值来填充数组 filter():检测数组元素,并返回符合条件的所有元素的数组,不会对空数组进行检测,不会改变原数组 find():返回数组中符合条件的第一个元素,空数组不会执行,不会改变原数组 findIndex():返回数组中符合条件的第一个元素的索引,空数组不会执行,不会改变原数组,如果不存在,则返回 -1 forEach():遍历数组 from():从一个类数组或可迭代对象创建一个新的浅拷贝的数组 flat():按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回 扁平化嵌套数组 扁平化并移除数组空项 includes():用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false indexOf():返回数组中给定元素的第一个索引,如果不存在,则返回-1 reduce():对数组中的每个元素执行一个提供的函数(升序执行),并将结果汇总为单个返回值 map():创建一个新数组,其结果是该数组中的每个元素调用提供的函数后的返回值 考点: 通常情况下, 期望输出 parseInt 经常被带着一个参数使用, 但是这里接受两个。第一个参数是一个表达式,第二个是callback function的基, 第三个参数被parseInt忽视了, 但第二个参数会被使用 上述的迭代步骤为: 解决方案: 参考 MDN Array bind():该方法会创建一个函数的实例,其 this 值会被绑定到传给 bind() 函数的值 语法: 使用场景为函数不需要立即调用,但又想改变函数内部的 this 指向(比如定时器内部的 this) bind() 主要是为了改变函数内部的 this 指向 apply():apply() 方法接收两个参数,一个是在其中运行函数的作用域,另一个是参数数组(参数数组可以是数组实例,也可以是 arguments 对象) 语法: 使用场景主要与数组有关 1.Math.max 实现得到数组的最大项 2.Array.prototype.push 实现合并两个数组 call():call() 方法与 apply() 方法类似,接收参数的方式有些不同,第一个参数为在其中运行函数的作用域,其余参数都直接传递给函数,即传递给函数的参数必须逐个列举出来 语法: 使用场景是可以实现继承 实现原理如下: this:谁调用它,this 就指向谁 1.普通函数:this 指向 window,严格模式下(‘use strict‘)会抛出错误 undefined 2.对象函数:this 指向该函数所属对象 3.构造函数:如果构造函数没有返回对象,则 this 指向创建的对象实例;如果构造函数有返回对象,则 this 指向返回的对象 4.绑定事件函数:this 指向事件的调用者 5.定时器函数:this 指向 window 6.立即执行函数:this 指向 window 7.箭头函数:不绑定 this,this 指向函数定义位置的上下文 8.显式绑定:函数通过 call()、apply()、bind()方法绑定,this 指向方法中传入的对象 如果这些方法中传入的第一个参数是 undefined 或 null,严格模式下 this 指向传入的值 undefined 或 null;非严格模式下 this 指向 window 9.隐式绑定:函数的调用时在某个对象上触发的,即调用位置存在上下文对象(相当于对象函数中的 this 指向)典型的隐式绑定为 xxx.fn() 变量提升:将变量的声明提升到它所在作用域的顶端去执行,将赋值放在代码所在的位置(注意只有 var 才存在变量提升) 上述代码的实际执行顺序如下: 而如果先进行赋值: 声明提升到顶端,所以输出1 函数提升:函数提升是整个代码块提升到它所在作用域的顶端执行 执行顺序相当于: 函数提升存在函数优先原则: 作用域:变量在某个范围内生效,目的是为了提高程序的安全性,减少命名冲突,分为全局作用域和局部作用域 作用域链:一般情况下,变量的取值是到创建该变量的函数作用域下查找,但是如果在当前作用域下没有查找到,就会向上一级作用域查找,直到全局作用域,这样一个查找过程形成的链称为作用域链。作用域链相当于内部函数访问外部函数的变量,采取的是链式查找的方式来决定取哪个值。 执行上下文:当代码运行时,会产生一个对应的执行环境,在这个环境中,所有变量会被提升,有的直接赋值,有的为默认值 undefined,代码从上往下开始执行,就叫做执行上下文,JavaScript 运行任何代码都是在执行上下文中运行 执行上下文分类: 执行上下文栈:也叫调用栈,执行上下文栈用于存储代码运行期间创建的所有执行上下文,JS 代码首次运行,先创建一个全局执行上下文并压入栈中,之后每次函数调用,都会创建一个函数执行上下文并压入栈中,当函数调用完成后,这个函数执行上下文以及其中的数据都会被销毁,然后重新进入全局执行上下文 执行上下文的生命周期: 执行上下文特点: 高阶函数 高阶函数时对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值;JavaScript 的回调函数是以实参形式传入其他函数中,也属于高阶函数 变量作用域 闭包 闭包指有权访问另一个函数作用域中的变量的函数,闭包允许函数访问局部作用域之外的数据,即使外部函数已经退出,外部函数中的变量仍然可以被内部函数访问到,闭包的主要作用:延伸了变量的作用范围 闭包实现的三个条件: 上述函数执行的时候,f 得到的是闭包对象的引用,fn 函数执行完毕退出,但是 fn 函数中的活动对象由于闭包的存在并没有被销毁,执行 f 函数仍然可以访问到 a 变量,而执行 f(2)后 a 变量的值为4,因为闭包的引用,f 并没有消除 闭包的核心内容:有些情况下(函数调用返回一个函数),函数调用完成之后,其执行上下文环境不会被销毁,所以使用闭包会增加内存开销,在 IE 中可能导致内存泄露,解决方法:在退出函数之前,将不使用的局部变量全部清除(变量赋值为null) 原型:在 JavaScript 中,每一个函数都有一个 prototype 对象属性,指向另一个对象(原型对象),prototype 的所有属性和方法都会被构造函数的实例所继承。所以,我们可以把那些公共不变的方法,直接定义在 prototype 对象属性上 (一般情况下,公共属性定义在构造函数里,公共方法定义在原型对象上) 原型链:JavaScript 成员查找机制是按照原型链来查找的(就近原则) prototype(显式原型属性):只有函数对象才具有 prototype 属性,这个属性指向一个对象,这个对象包含所有实例共享的属性和方法,这个对象也有一个属性 constructor,指回原构造函数 父类: 1.原型继承:子类构造函数的原型等于父类构造函数的实例 优点:实例可以继承构造函数的属性,父类构造函数的属性,父类构造函数原型的属性 缺点: 实例无法向父类构造函数传参 所有实例都会共享父类的引用类型属性 2.借用构造函数:利用 call() 方法将父类构造函数引入子类构造函数 优点: 缺点: 3.组合继承:将原型继承和借用构造函数继承组合(常用) 优点: 缺点: 4.原型式继承:将一个函数的原型指向父类实例,然后返回这个函数的对象 缺点: 5.寄生式继承:给原型式继承再嵌套一层,实现传参 缺点:所有实例都会共享父类的引用类型属性 6.寄生组合式继承:寄生+组合实现继承 优点: 区分浅拷贝与深拷贝:假设 B 复制了 A,如果修改 B,A 也发生变化,就是浅拷贝;如果 A 没有发生变化,就是深拷贝 实现浅拷贝 1.for...in 循环赋值(只能拷贝第一层) 2.Object.assign 3.直接用 =赋值 实现深拷贝 1.递归拷贝所有层级属性 2.通过 JSON 对象来实现深拷贝 缺点: 3.通过jQuery的extend方法实现深拷贝 4.lodash函数库实现深拷贝 函数防抖和节流是为了解决用户在某一时间内频繁提交请求,给服务器造成压力的情况 函数防抖:在一定时间内,连续触发同一事件,只执行一次(只在最后一次执行或第一次执行) 定时器实现 : 时间戳实现: 函数节流:在单位时间内,连续触发同一事件,只执行一次 定时器实现: 时间戳实现: 1.查找节点 2.生成节点 3.事件操作 4.节点操作 前端JS面试 标签:必须 自己的 mozilla 函数调用 foo int 输出 解决方法 有关 原文地址:https://www.cnblogs.com/codeDD/p/13369719.htmlJavaScript
1、原始值和引用值类型和区别
let a = 1;
let b = a;
a = 2;
console.log(b); // 1
let a = [1,2,3,4];
let b = a;
a.push(5);
console.log(b); // [1,2,3,4,5]
a = [6];
console.log(b); // [1,2,3,4,5]
2、判断数据类型
console.log(typeof 1); // number
console.log(typeof ‘1‘); // string
console.log(typeof true); // boolean
console.log(typeof undefined); // undefined
console.log(typeof null); // object
console.log(typeof function () {}); // function
console.log(typeof []); // object
console.log(typeof {}); // object
function instanceOf(a, b) {
let bp = b.prototype;
let ap = a.__proto__;
while(true){
if(ap === bp){
return true;
}else if(ap === null){
return false;
}
ap = ap.__proto__;
}
}
console.log([] instanceof Array); // true
console.log([] instanceof Object); // true
console.log(function(){} instanceof Function); // true
console.log({} instanceof Object); // true
console.log(function(){} instanceof Object); // true
let a = 1;
console.log(a instanceof Number); // false
Object instanceof constructor
,如果不是 Object 都返回 falseconsole.log(new Number(1) instanceof Number); // true
console.log(typeof new Number(1)); // Object
let test = Object.prototype.toString;
console.log(test.call(1)); // [object Number]
console.log(test.call(‘1‘)); // [object String]
console.log(test.call(true)); // [object Boolean]
console.log(test.call(null)); // [object Null]
console.log(test.call(undefined)); // [object Undefined]
console.log(test.call([])); // [object Array]
console.log(test.call({})); // [object Object]
console.log(1.constructor === Number); // true
console.log(true.constructor === Boolean); // true
console.log("1".constructor === String); // true
console.log([].constructor === Array); // true
console.log(function(){}.constructor === Function); // true
console.log({}.constructor === Object); // true
3、类数组与数组的区别与转换
arguments
const divs = document.querySelectorAll(‘div‘);
const newDivs = Array.prototype.slice.call(divs);
const divs = document.querySelectorAll(‘div‘);
const newDivs = [...divs];
const divs = document.querySelectorAll(‘div‘);
const newDivs = Array.from(divs);
4、数组的常见API
const arr = [1, 2, 3];
console.log(Array.isArray(arr)); // true
const arr = [1, 2, 3];
console.log(arr.toString()); // 1,2,3
const arr = [1, 2, 3];
console.log(arr.join(‘-‘)); //1-2-3
const arr = [1, 2, 3];
const arr1 = [4, 5, 6];
console.log(arr.concat(arr1)); // [1,2,3,4,5,6]
const arr = [1, 2, 3];
console.log(arr.slice()); // [1,2,3]
const arr = [1, 2, 3];
console.log(arr.slice(1)); // [2,3]
const arr = [1, 2, 3];
console.log(arr.slice(1, 2)); // [2]
const arr = [1, 2, 3];
console.log(arr.slice(-3, -1)); // [1,2]
const arr = [1, 2, 3];
console.log(arr.reverse()); // [3,2,1]
const arr = [1, 2, 3];
console.log(arr.sort(function (a, b) {
//return a - b; 从小到大排序
return b - a; // 从大到小排序
}));
参数
描述
index
必需。整数,规定添加/删除项目的位置,使用负数可从数组结尾处规定位置。
howmany
必需。要删除的项目数量。如果设置为 0,则不会删除项目。
item1, ..., itemX
可选。向数组添加的新项目。
const arr = [1, 2, 3];
arr.splice(3,0,4,5);
console.log(arr); // [1,2,3,4,5]
const arr = [1, 2, 3];
arr.splice(2,1);
console.log(arr); // [1,2]
const arr = [1, 2, 3];
arr.splice(2,1,4);
console.log(arr); // [1,2,4]
const arr = [1, 2, 3];
console.log(arr.push(4)); // 4
console.log(arr); // [1,2,3,4]
const arr = [1, 2, 3];
console.log(arr.unshift(0)); // 4
console.log(arr); // [0,1,2,3]
const arr = [1, 2, 3];
arr.pop();
console.log(arr); // [1,2]
const arr = [1, 2, 3];
arr.shift();
console.log(arr); // [2,3]
const arr = [‘张三‘, ‘赵四‘, ‘王五‘];
let iterator = arr.entries();
for(let v of iterator){
console.log(v);
}
/*
[0, "张三"]
[1, "赵四"]
[2, "王五"]
*/
const arr = [1, 2, 3];
const result = arr.every(item => item > 0 );
console.log(result); // true
const arr = [1, 2, 3];
arr.fill(6);
console.log(arr); // [6,6,6]
const arr = [1, 2, 3];
console.log(arr.filter(item => item>1)); // [2,3]
const arr = [1, 2, 3];
console.log(arr.find(item => item>1)); // 2
const arr = [1, 2, 3];
console.log(arr.findIndex(item => item > 1)); // 1
console.log(arr.findIndex(item => item > 4)); // -1
const arr = [1, 2, 3];
arr.forEach(item => {
console.log(item);
})
console.log(Array.from(‘foo‘));
// ["f", "o", "o"]
console.log(Array.from([1, 2, 3], x => x + x));
// [2,4,6]
const arr1 = [1, 2, [3, 4]];
arr1.flat();
// [1, 2, 3, 4]
const arr2 = [1, 2, [3, 4, [5, 6]]];
arr2.flat();
// [1, 2, 3, 4, [5, 6]]
const arr3 = [1, 2, [3, 4, [5, 6]]];
arr3.flat(2);
// [1, 2, 3, 4, 5, 6]
//使用 Infinity,可展开任意深度的嵌套数组
const arr4 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]];
arr4.flat(Infinity);
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
const arr4 = [1, 2, , 4, 5];
arr4.flat();
// [1, 2, 4, 5]
const arr = [1,2,3];
console.log(arr.includes(1)); // true
const arr = [1,2,3,1];
console.log(arr.indexOf(1)); // 0
console.log(arr.indexOf(1,2)); // 3
console.log(arr.indexOf(4)); // -1
const arr = [1,2,3,4,5,6,7,8,9,10];
// 累加必须提供初始值
console.log(arr.reduce((acc, cur) => acc + cur, 0)); // 55
const arr = [1,2,3];
console.log(arr.map(item => item*2));
// [2,4,6]
map
方法中的 callback
函数只需要接受一个参数,就是正在被遍历的数组元素本身。但这并不意味着 map
只给 callback
传了一个参数。例如:const arr = [‘1‘,‘2‘,‘3‘];
console.log(arr.map(parseInt));
[1,2,3]
,然而实际结果是 [1,NaN,NaN]
Array.prototype.map
传递3个参数:
参数
描述
string
必需。要被解析的字符串。
radix
可选。表示要解析的数字的基数。该值介于 2 ~ 36 之间。如果省略该参数或其值为 0,则数字将以 10 为基础来解析。如果它以 “0x” 或 “0X” 开头,将以 16 为基数。如果该参数小于 2 或者大于 36,则 parseInt() 将返回 NaN。
// parseInt(string, radix) -> map(parseInt(value, index))
/* 1 */ parseInt(‘1‘,0); // 1
/* 2 */ parseInt(‘2‘,1); // NaN
/* 3 */ parseInt(‘3‘,2); // NaN 二进制只有0和1
function returnInt(element) {
return parseInt(element, 10);
}
[‘1‘, ‘2‘, ‘3‘].map(returnInt); // [1, 2, 3]
// 指定基数即进制为10
// 只给parseInt传入当前元素值
[‘1‘, ‘2‘, ‘3‘].map( item => parseInt(item) );
[‘1‘, ‘2‘, ‘3‘].map(Number); // [1, 2, 3]
5、bind、call、apply的区别
var fn = Function.bind(obj, [param1[,param2][,...paramN]])
const btn = document.querySelector(‘button‘);
btn.onclick = function () {
this.disabled = true;
setTimeout(function () {
this.disabled =false;
}.bind(this), 2000);
}
Function.apply(obj, args)
// args将作为参数传递给Function
const arr = [1,2,3];
console.log(Math.max.apply(Math, arr)); // 也可以使用null,但严格模式下还是要使用Math
// 3
const arr1 = [1,2,3];
const arr2 = [4,5,6];
Array.prototype.push.apply(arr1, arr2);
console.log(arr1);
// [1,2,3,4,5,6]
Function.call(obj, [param1[,param2][,...paramN]])
// param参数列表会直接传递给Function
function Person(name, age) {
this.name = name;
this.age = age;
}
function Student(name, age, id) {
Person.call(this, name, age);
this.id = id;
}
let s = new Student(‘张三‘, 20, ‘007‘);
console.log(s);
// Student?{name: "张三", age: 20, id: "007"}
6、new 的原理
function Person(name) {
this.name = name;
}
function newObject(parent, ...args) {
let child = {};
child.__proto__ = parent.prototype;
parent.apply(child, args);
return child;
}
const p = newObject(Person, ‘张三‘);
console.log(p);// 张三
Person.prototype.sayName = function () {
console.log(‘我叫‘+this.name);
};
p.sayName(); // 我叫张三
7、如何正确判断 this 的指向
// ‘use strict‘;
var name = ‘张三‘;
function fn() {
console.log(this.name);
}
fn();
var obj = {
sayHello(){
console.log(this);
}
}
obj.sayHello();
// {sayHello: f}
function Person(name, age) {
this.name = name;
this.age = age;
}
var p = new Person(‘张三‘, 20);
console.log(p);
// Person?{name: "张三", age: 20}
function Person(name, age) {
this.name = name;
this.age = age;
let obj = {
name: ‘赵四‘,
age: 18
};
return obj;
}
var p = new Person(‘张三‘, 20);
console.log(p);
// {name: ‘赵四‘, age: 18}
var btn = document.querySelector(‘button‘);
btn.onclick = function () {
console.log(this);
}
//
setTimeout(function () {
console.log(this);
},1000);
(function() {
console.log(this);
})();
btn.onclick = function () {
setTimeout(() => {
console.log(this);
//
},1000)
}
// 通过箭头函数可以改变定时器函数的this指向
function fn() {
console.log(this);
}
var person = {
name: ‘张三‘
};
fn.call(person);
fn.apply(person);
fn.bind(person)();
// {name: ‘张三‘}
function fn() {
console.log(this);
}
fn.call(null);// window
‘use strict‘;
function fn() {
console.log(this);
}
fn.call(null); // null
function fn() {
console.log(this.name);
}
var person = {
name: ‘张三‘,
fn
};
person.fn(); // 张三
8、变量提升与函数提升
console.log(a);
var a = 1; // undefined
var a;
console.log(a);
a = 1;
a = 1;
var a;
console.log(a); // 1
console.log(‘1-‘+v1);
var v1 = 100;
function foo() {
console.log(‘2-‘+v1);
var v1 = 200;
console.log(‘3-‘+v1);
}
foo();
console.log(‘4-‘+v1);
// 1-undefined
// 2-undefined
// 200
// 100
console.log(fn);
function fn () {
console.log(1);
}
/*
? fn () {
console.log(1);
}
*/
function fn () {
console.log(1);
}
console.log(fn);
foo(); //1
var foo;
function foo () {
console.log(1);
}
foo = function () {
console.log(2);
}
9、作用域与作用域链、执行上下文
//第一种情况,当函数作为参数
var x = 10;
function show(callback) {
var x = 5;
callback && callback();
}
function fun() {
console.log(x);// 10 函数fun的上级作用域是全局作用域
}
show(fun);
//第二种情况,当函数作为返回值输出
var x = 10;
function show() {
var x = 5;
return function() {
console.log(x);// 5 函数的上级作用域是show函数
}
}
var res = show();
res();
var a = 10 ; // 1.进入全局上下文环境
var fun;
var bar = function (x) {
var b = 10;
fun(x + b); // 3.进入fun上下文环境
}
fun = function (y) {
var c = 20;
console.log(y + c);
}
bar(5); // 2.进入bar上下文环境
10、闭包及其作用
// 1.将函数作为参数(回调函数)
function fn(callback) {
callback && callback();
}
fn(function () {
console.log(‘hello‘);
})
// 2.将函数作为返回值
function fun() {
return function () {
console.log(‘world‘);
}
}
fun()();
function fn() {
var a = 1;
return function (b) {
a = a + b;
console.log(a);
}
}
var f = fn();
f(1); // 2
f(2); // 4
11、原型和原型链
__proto__
)指向的构造函数的原型对象(prototype)__proto__
,直到找到,找不到则为 null12、prototype 与
__proto__
的关系与区别__proto__
(隐式原型属性):所有对象都具有该属性,指向构造该对象的构造函数的原型13、继承的实现方式及比较
function Parent(name) {
this.name = name;
this.arr = [1,2,3];
}
Parent.prototype.showName = function () {
console.log(this.name);
}
function Child() {}
Child.prototype = new Parent();
var c = new Child();
console.log(c);
var c = new Child();
var c1 = new Child();
c.age = 20;
c.arr.push(4);
console.log(c1.arr); // [1,2,3,4]
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
var c = new Child(‘张三‘, 20);
console.log(c);
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
Child.prototype = new Parent();
var c = new Child(‘张三‘, 20);
console.log(c);
c.showName();
function Child(obj) {
function F() {}
F.prototype = obj;
return new F();
}
var p = new Parent(‘张三‘);
var c = new Child(p);
console.log(c);
function Child(obj) {
function F() {}
F.prototype = obj;
return new F();
}
var p = new Parent(‘张三‘);
// 以上是原型式继承
function ChildObject(obj, age) {
var c = Child(obj);
c.age = age;
return c;
}
var c2 = new ChildObject(p, 20);
console.log(c2);
function Child(obj) {
function F() {}
F.prototype = obj;
return new F();
}
var c = new Child(Parent.prototype);
function ChildObject(name, age) {
Parent.call(this, name);
this.age = age;
}
ChildObject.prototype = c;
c.constructor = ChildObject;
var co = new ChildObject(‘赵四‘, 20);
console.log(co);
深拷贝与浅拷贝
function simpleCopy(obj1) {
let obj2 = Array.isArray(obj1) ? [] : {};
for(let k in obj1){
obj2[k] = obj1[k];
}
return obj2;
}
let obj1 = {
a: 1,
b: 2,
c: {
d: 3
}
};
let obj2 = simpleCopy(obj1);
obj2.a = 3;
obj2.c.d = 4;
console.log(obj1.a); // 1
console.log(obj1.c.d); // 4
let obj2 = Object.assign(obj1);
obj2.a = 3;
obj2.c.d = 4;
console.log(obj1.a); // 3
console.log(obj1.c.d); // 4
let obj2 = obj1;
obj2.a = 3;
obj2.c.d = 4;
console.log(obj1.a); // 3
console.log(obj1.c.d); // 4
let obj1 = {
a: 1,
b: 2,
c: {
d: [1,2,3]
},
f: function () {
console.log(‘f‘);
}
};
function deepCopy(obj1) {
let obj2 = Array.isArray(obj1) ? [] : {};
for(let k in obj1){
if(typeof obj1[k] === "object"){
obj2[k] = deepCopy(obj1[k])
}else{
obj2[k] = obj1[k];
}
}
return obj2;
}
let obj2 = deepCopy(obj1);
console.log(obj2);
obj2.c.d.push(4);
console.log(obj1.c.d); // [1,2,3]
function deepCopy(obj1) {
let obj = JSON.stringify(obj1);
return JSON.parse(obj);
}
let obj2 = deepCopy(obj1);
var array = [1,2,3,4];
var newArray = $.extend(true,[],array); // true为深拷贝,false为浅拷贝
let result = _.cloneDeep(test)
14、函数防抖和节流
// 只在最后一次执行
function debounce(fn, delay) {
let timer = null;
return () => {
if(timer){
//第一次触发时不会执行,后续触发时会清除定时器
clearTimeout(timer);
}
timer = setTimeout(() => {
fn.apply(this, arguments);
}, delay)
}
}
// 只在第一次执行
function debounce(fn, delay) {
let timer = null;
return () => {
if(timer){
clearTimeout(timer);
}
let callNow = !timer; // true
// 后续如果没有触发,则将timer初始化为null
timer = setTimeout(() => {
timer = null;
}, delay);
if(callNow){
fn.apply(this, arguments);
}
}
}
function debounce(fn, delay) {
let pre = 0;
return () => {
let now = new Date();
if(now - pre > delay){
fn.apply(this, arguments);
}
pre = now;
}
}
function throttle(fn, delay) {
let flag = true;
return () => {
if(!flag) return; // flag为false时,直接返回
flag = false;
setTimeout(() => {
fn.apply(this, arguments);
// 执行完fn函数再将flag重置为true
flag = true;
}, delay)
}
}
// 或者
function throttle(fn, delay) {
let timer = null;
return () => {
if(!timer){ // timer为null时才设置定时器
timer = setTimeout(() => {
//执行完fn函数后将timer重置为null
fn.apply(this, arguments);
timer = null;
},delay)
}
}
}
function throttle(fn, delay) {
let pre = 0;
return () => {
let now = new Date();
if(now - pre > delay){
fn.apply(this, arguments);
pre = now;
}
}
}
15、DOM常见的操作方式
document.querySelector(selectors)
//接受一个CSS选择器为参数,返回第一个匹配该选择器的元素节点
document.querySelectorAll(selectors)
//接受一个CSS选择器为参数,返回所有匹配该选择器的元素节点
document.getElementById(id)
//返回匹配指定id属性的元素节点
document.createElement(tagName)
// 用来生成HTML元素节点
document.createTextNode(text)
// 用来生成文本节点
document.createAttribute(name)
// 生成一个新的属性对象节点,并返回它
document.addEventListener(type,listener,capture) // 注册事件
document.removeEventListener(type,listener,capture) // 注销事件
Node.appendChild(node)
// 向节点添加最后一个子节点
Node.hasChildNodes()
// 返回布尔值,表示当前节点是否有子节点
Node.cloneNode(true);
// 默认为false(克隆节点), true(克隆节点及其属性,以及后代)
Node.insertBefore(newNode,oldNode)
// 在指定子节点之前插入新的子节点
Node.removeChild(node)
// 删除节点,在要删除节点的父节点上操作
Node.replaceChild(newChild,oldChild)
// 替换节点