面试题:JavaScript + ES5+
js
1. this指向
参考文献:https://juejin.cn/post/7547552301117997110
参考练习:https://blog.csdn.net/Dailoge/article/details/82910404
- 看函数的调用方式,而不是他的定义时候
分类
- 构造函数==> new时候创建的对象
- 对象的方法内部 ==》调用方法的对象(谁调用指向谁)
- 事件处理函数 ==》绑定的事件
- 箭头函数 ==》没有自己的this, 同步父集的this
- 其他函数(全局的/局部的) ==》匿名的就是window
- 定时器函数 ==》window
- 立即执行函数==》window
- 正常的全局命名函数==》window (无论是否嵌套在函数内部)
点击查看代码
// 1.
function CreateObj(){
console.log(this); // user
this.name="zhou"
}
let user = new CreateObj();
// 2.
const person = {
name: "小明",
sayName: function() {
console.log("我叫" + this.name);
}
};
1. person.sayName(); // 输出:我叫小明 this指向person
2. const say = person.sayName;
say(); // 输出:我叫undefined this指向全局window
// 3. <a></a>元素标签
<a href="#" id="link" onClick="console.log(this)">click me</a>
// 4. 可用于同步上一级的this的指向;
var name='window';
var obj = {
name:'obj',
nameprintf:()=>{
console.log(this.name)
}
}
obj.nameprintf();//window,因为此时父级是obj,obj的上下文是window
// 5.
console.log(this) //window / undefined
// 5.1
window.setTimeout(function () {
console.log(this)
}, 1000) // 结果: window对象
// 5.2
(function pointer() {
console.log(this)
})() // 结果: window对象 (所处的环境,如果是对象内就指向对象)
// 5.3
function pointer() {
console.log(this)
}
pointer() // 结果: window对象
改变this指向
- call/apply/bind/箭头
2. call apply bind 使用及区别
-
参数上限个数不同,传递参数的形式也不同;
-
返回给新的函数,共用一个函数体;
// call //正常提供参数 var obj = { value: "vortesnail", }; function fn(x,y) { log(x+y) console.log(this.value); } fn.call(obj,4,5);// 改变this指向以及执行fn; // apply 使用数组方式提供参数 var Person = { name: "zhangsan", age: 19 } function aa(x, y) { console.log(x + "," + y); console.log(this); console.log(this.name); } aa.apply(Person, [4, 5]);//Person // bind 创建一个新的函数需要手动调用。 var Person = { name: "zhangsan", age: 19 } function aa(x, y) { console.log(x + "," + y); console.log(this); console.log(this.name); } aa.bind(Person, 4, 5); //只是更改了this指向,没有输出 aa.bind(Person, 4, 5)(); //this指向Person--4,5 Person{}对象 zhangsan
3. 箭头函数
- 没有prototype属性(原型对象);
- 没有this指向,指向父集的作用域。
- 没有arguments参数;不能使用new关键字做构造函数;
点击查看代码
// 1. 普通函数;
const ary = [1,2,3,4];
const normalEvt = function(A,B) {
console.log(A);
console.log(arguments[0]); // 第一个参数
console.log(arguments[1]); // 第二个参数
console.log(arguments[2]); // 第三个参数
};
normalEvt(ary, true, "参数3") // 打印出来的arguments是类数组对象;
// 2. 箭头函数;
// 没有自己的类数组对象,会拿到自己父亲的类数组对象;
4. 原型(对象)/隐式原型
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log(`Hello, my name is ${this.name}`);
};
const alice = new Person('Alice');
alice.sayHello(); // 输出 'Hello, my name is Alice'
显示原型
-
定义:每个函数都有的prototype属性显示原型属性;
-
特点:
- 显示原型属性指向空的Object对象;
- 该属性的值指向的对象、也称之为原型(原型对象);
- 存放属性和方法。
隐式原型
-
每一个实例对象p1都有个__ proto __称之为隐式原型属性;
- 对象的隐式原型的值为其对应的构造函数的显示原型的值(都存放的地址值)
生成时间:
-
prototype生成时间:
- Person函数创建时生成的
- 创建Person时:this.prototype ={}
-
__proto__生成时间:
- new 实例对象时生成的
- new 实例时:this.__ proto __ ==Fn.prototype
- 创建prototype时指向的原型对象的值为地址值 ==创建proto
- 尽量操作显示原型上的属性或方法/而不要手动修改隐式原型上的方法属性;
-
图解
- Fn/fn/Object ==> 函数/new的实例/js内置函数;

5. 原型链(隐式原型链)
定义:
- 访问一个对象(实例对象)的属性时先在自身上寻找找到返回;否则再沿着__ proto__,去隐式原型对象上寻找,找到则返回,否则undefined;尽头是Object原型对象Object.prototype.proto(这里的Object是Object函数对象);沿着隐式原型
作用:
-
查找一个对象的属性(不是变量)和方法的;
-
查变量去作用域链(作用域内,逐级向外侧回溯);
-

-

6. 作用域链:
- 每个执行环境都有与之关联的变量,保证执行环境变量函数有序性,向下访问变量不被允许;作用域则是变量函数的访问范围
7. (构造函数)new做了什么
let this ={}; //自动创建this对象;创建空对像,this变量引用
this.__proto__ = Fn.prototype;//继承原型
return this// 返回this
8. 深/浅拷贝
参考文献:https://zhuanlan.zhihu.com/p/654901885
实现/验证:
-
let obj ={ uname:'zhou', child:{age:18} } // 浅拷贝: let obj2.uname=obj.uname; let obj2.child=obj.child; obj=obj2 //false; // 深拷贝: let obj3={ uname:obj.uname, child:obj.child } obj.child == obj2.child // true; obj.child == obj3.child // true; -
浅拷贝:
-
定义:
- 创建一个新的对象或数据结构,拷贝原始对象的引用地址值。
-
特点:
- 内存引用:浅拷贝复制对象的引用。
- 对象变化:更改其中一个对象,会影响到另一个对象;
- 嵌套对象/数据的复制:复制对象的第一曾结构,不会递归复制嵌套的对象数据/属性值;
-
实现:
- 自己封装函数遍历,
- 展开运算符,
- assign,
- lodash(第三方js库)
-
使用场景:
- 复制简单的数据结构/传递引用/缓存数据(不需要重新使用副本/避免频繁网络请求/计算重量较大的数据)
let obj1 = { name: '张三', age: 18, child: { name: '张三丰' } } // 1- 自己封装函数遍历 function clone(obj) { // 遍历 obj 里面的属性名 和 属性值-- 添加到 一个新的对象中 let resObj = {} for (let key in obj) { //key 属性名 // value 属性值 let value = obj[key] // 添加到 一个新的对象中 // 如果value 是一个对象--》child ,直接把地址给了 resObj resObj[key] = value; } return resObj } // 验证: let obj2 = clone(obj1); console.log(obj2); console.log(obj1 === obj2);//false console.log(obj1.child === obj2.child);//t // 2- 展开运算符(多) // ...obj1 // 验证: let obj3 = { ...obj1 }; console.log(obj1 === obj3);//false console.log(obj1.child === obj3.child);//true // 3- Object.assign()方法; const originalObj = {name: "john", age: 30}; const copyObj = Object.assign({},originalObj); // 4- Array.slice() / Array.concat();
3- Object.assgin(obj1,obj2)
Object.assgin({},obj2)
把后面对象的成员(属性和方法)都添加到 前一个对象中
返回的是第一个对象
let obj4 = Object.assign({}, obj1);
console.log(obj4);
console.log(obj4 === obj1);//false
console.log(obj4.child === obj1.child);//t
4- lodash --第三方 js工具库(封装了很多函数)
// https://www.lodashjs.com/docs/lodash.clone#_clonevalue
// https://www.bootcdn.cn/lodash.js/ 去这个免费cdn网站找 lodash的js文件
// 引入到页面中
- 深拷贝:
-
定义:
- 创建新的对象或数据结构,内部包含完全独立于原视对象的副本,新老对象具有完全不同的地址值;
-
特点:
- 内存的引用: 完全独立。
- 对象变化: 任意新老对象的更改不会影响原对象。
- 嵌套对象/数据的复制: 通过递归复制嵌套的对象或数据,每个对象有独立的副本。
-
实现:
- 自己封装递归
- JSON.parse(JSON.stringify());
- 一层深拷贝,适用于没有循环引用的简单对象/数组
- 无法处理包含函数,RegExp,Date 特殊类型
- 三方库(lodash/cloneDeep)
- 大型复杂对象会带来性能顿号;
// 手写递归1;
function deepCopy(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
let result;
if (Array.isArray(obj)) {
result = [];
for (let i = 0; i < obj.length; i++) {
result[i] = deepCopy(obj[i]);
}
} else {
result = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = deepCopy(obj[key]);
}
}
}
return result;
}
const originalObj = { name: 'John', age: 30 };
const deepCopyObj = deepCopy(originalObj);
// 手写递归2:
function deepClone(obj, visited = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
// 处理循环引用
if (visited.has(obj)) return visited.get(obj);
let clone;
if (obj instanceof Date) {
clone = new Date(obj);
} else if (obj instanceof RegExp) {
clone = new RegExp(obj);
} else if (Array.isArray(obj)) {
clone = obj.map(item => deepClone(item, visited));
} else {
clone = {};
visited.set(obj, clone);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key], visited);
}
}
}
visited.set(obj, clone);
return clone;
}
9. 数据类型
区别
- 存储到堆与栈中不同:
- 基础数据类型存储再栈中;
- 复杂数据类型数据存入堆,堆的地址值存入栈;
常见的类型:
| 数据类型 | 类型 |
|---|---|
| 简单数据类型 | 数值number,字符string,布尔Boolean,空null,undegined,Symbol,bigint |
| 复杂数据类型 | 对象,(Array,object),Set,Map |
- undefined 和 null的区别;
- 人话:undefined定义未符值就是undefined;null定义符值就是null是一个空对象;
- 符值空对象的本质就是置空地址值释放内存;
10. 数组方法 15+8
| 方法类型 | 方法 | 返回值 |
|---|---|---|
| 增删 | push(),pop(),unshift(),shift() | 修改后数组长度 |
| 截取/插入 | splice(开始索引,个数,(插入元素)) | 返回新的数组 |
| 排序 | reverse(),sort(function(a,b){return a-b}) | 返回排序后数组/改变原数组/从小到大 |
| 合并 | concat() | 合并后的新数组 |
| 转字符串 | join('') | 某个符号连接的字符 |
| 复制 | slice(开始索引号(>=),结束索引(<)) | 复制后的新数组/不改变原数组 |
| 索引查询 | indexOf('查询字符','第几次出现'),lastIndexOf | 返回被查寻得字符第几次(最后几次)出现得位置索引号,不存在的返回-1 |
| 数组包含 | includes('查询字符') | true/false |
| 遍历 | for(),for in,for of | |
| 新增 | forEach | 无返回值 |
| 映射 | map | 操作后的值 |
| 过滤 | filter | 满足条间的新数组 |
| 判断所有boolean | every | 判断是否满足条件,其中一个不满足都会返回false |
| 判断boolean | some | 至少一个满足条件返回true |
| 查找值 | find | 返回满足条件的第一个值 |
| 查找索引 | findIndex | 返回满足条件的第一个索引 |
| 累计 | ary.reduce(((prev,item)=>return prev + item),1000) | 回调累计计算 |
- reduce 详解
let ary =[3,4,5,6]
let sum=ary.reduce(function(start,b){
console.log(start,b);
return start+b
},1000)
console.log(ary,sum);
11. 字符串方法 15
| 类型 | 方法名 | 功能 |
|---|---|---|
| 拼接 | concat() | 合并字符串 |
| index查unicode | charCodeAt(x) | 返回字符串中x位置处字符的unicode值 |
| str查index | charAt(x) | 返回字符串中x字符的位置,下标从0开始 |
| u转str | fromCharcode() | 将一组Unicode码转换为字符 |
| 匹配 | indexOf(substr, [start]) | 返回指定的字符串值首次出现的位置。没有匹配返回 -1。 |
| 反匹配 | lastIndexOf(substr, [start]) | 返回指定文本最后一次出现的索引, 未找到,则返回-1。 |
| 替换all | replaceAll(oldStr,newStr) | 替换查询到的所有匹配字符串,没有则返回null。 |
| 替换 | replace(oldStr,newStr) | 替换查询到到的一个匹配字符串; |
| 字符转数组 | split(delimiter, [limit]) | 按照某个分割符间隔为数组 |
| 截取 | substr(start, [length]) | start 截取length个字符。没指定 length,则返回到结尾。 |
| 复制 | substring(【start,stop)) | 两个指定下标之间的字符. |
| 转小写 | toLowerCase() | 把字符串转换为小写。 |
| 转大写 | toUpperCase() | 把字符串转换为大写。 |
| 包含 | includes() | 检查字符串是否包含指定的字符串或字符。 |
| 去空 | trim() | 从一个字符串的两端删除空白字符。 |
12. 闭包
定义:
- 闭包:闭包就是函数和周围状态的组合;JS中内层函数(闭包)可以访问外层函数的变量,内部私有变量不受外界干扰,起到保护和保存的作用,我们把这个特性称作闭包。
产生步骤:
- 函数内部返回一个新函数
- 外部函数定义一个局部变量
- return 的函数内使用此局部变量
特点/功能:
- 局部变量常驻内存,像全局变量
- 避免使用全局变量,防止全局污染
- 私有化变量,隔离作用域,保护私有变量
- 操作其他函数内部,回调;
- 外部函数访问内部变量,访问变量
缺点:
- 内层函数引用外层函数变量,占用内存后,不释放内存导致内存泄漏;
使用场景:
-
防抖节流。
-
函数外部调用函数内部 (内部函数被引用是,会保留其作用域链)
function box(){ **let a=1; return function(){ a++ log a }** } let fn = box() //fn是box的内部匿名函数 fn() // fn可以使用内部局部变量的a,a=2,3,4...每调用一次 值都不同; // 如果外层函数直接写a=1 ,a++,不return则每一次的值都相同为2; // 此时全局调用fn(),使用外层的变量a也就是**函数外部调用函数内部。**


浙公网安备 33010602011771号