一、 js相关知识点:
1、原型链
function Person(){
}
// 原型上属性
Person.prototype.name='wl';
// 实例化
let person1 = new Person();
let person2 = new Person();
console.log('person1',person1.name);
console.log('person2',person2.name);
// 构造函数 === 原型
console.log('实例原型constructor指向 构造函数',Person===Person.prototype.constructor);
//
console.log('实例化指向实例原型',person1.__proto__ === Person.prototype);
console.log('通过es5方式获得对象的原型',Object.getPrototypeOf(person1)===Person.prototype)
let obj = new Object();
obj.name = 'John';
console.log('obj',obj);
console.log(Person.prototype.__proto__===Object.prototype);
console.log(Object.prototype.__proto__===null);
2、继承的6种实现方式 及相关优缺点
//方法1.原型链继承
function Parent () {
this.name = 'kevin';
}
function Child () {
}
Child.prototype = new Parent();
var child = new Child();
console.log('child',child.name) // kevin
此方式存在问题:引用类型的属性被所有实例共享
function Parent () {
this.names = ['kevin', 'daisy'];
}
function Child () {
}
Child.prototype = new Parent();
var child1 = new Child();
child1.names.push('yayu');
console.log(child1.names); // ["kevin", "daisy", "yayu"]
var child2 = new Child();
console.log(child2.names); // ["kevin", "daisy", "yayu"]
上述原型链方式实现的继承 存在的问题:在创建 Child 的实例时,不能向Parent传参
//方式2.构造函数的继承
function Person(){
this.name=['lala','vivi'];
}
function Child(){
Person.call(this);
}
Child.prototype = new Person();
var child1 = new Child();
child1.name.push("bb")
console.log('child1',child1.name);
var child2 = new Child();
console.log('child2',child2.name);
优点:
- 避免了引用类型的属性被所有实例共享
- 可以在child中向parent中传参
举个例子:方法都在构造函数中定义,每次创建实例,都会创建一遍方法
function Parent (name) {
this.name = name;
}
function Child (name) {
Parent.call(this, name);
}
var child1 = new Child('kevin');
console.log(child1.name); // kevin
var child2 = new Child('daisy');
console.log(child2.name); // daisy
// 方式3.组合式继承
function Parent (name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function () {
console.log('getName',this.name);
}
function Child (name,age) {
Parent.call(this, name);
this.age = age;
}
Child.prototype = new Person();
Child.prototype.constructor=Child;
var child1 = new Child('kevin','11');
child1.colors.push("yellow");
console.log(child1.name,child1.age,child1.colors); // kevin
var child2 = new Child('daisy','20');
console.log(child2.name); // daisy
console.log(child2.age); // 20
console.log(child2.colors); // ["red", "blue", "green"]
优点:融合原型链继承和构造函数继承方式的优点,是javascript最常用的继承
//方式4:原型式继承
function createObj(o) {
function F(){}
F.prototype = o;
return new F();
}
var person = {
name: 'kevin',
friends: ['daisy', 'kelly']
}
var person3 = createObj(person);
var person4 = createObj(person);
person3.name = 'person1';
console.log("person3",person3.name); // kevin
person4.friends.push('taylor');
console.log("person4",person4.friends); // ["daisy", "kelly", "taylor"]
注意:修改`person1.name`的值,`person2.name`的值并未发生改变,并不是因为`person1`和
`person2`有独立的 name 值,而是因为`person1.name = 'person1'`,给`person1`添加了
name 值,并非修改了原型上的 name 值。
就是es5中的Object.create 的模拟实现,将传入的对象作为创建的对象的原型的实现。 缺点:引用类型的属性值始终会共享相应的值,这一点和原型链继承一样
方式5、寄生式继承:
function createObj (o) {
var clone = Object.create(o);
clone.sayName = function () {
console.log('hi');
}
return clone;
}
缺点:跟借用构造函数模式一样,每次创建对象都会创建一遍方法
//方式6、寄生组合式继承
function Parent (name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child (name, age) {
Parent.call(this, name);
this.age = age;
}
Child.prototype = new Parent();
var child1 = new Child('kevin', '18');
console.log(child1)
3、作用域
作用域:程序源代码中定义变量的区域。
作用域规定了如何查找变量,也就是确定了当前执行代码对变量的访问权限。
js采用的词法作用域,也就是静态作用域
-
静态作用域:也是词法作用域,函数的作用域在函数定义的时候就决定了 -
动态作用域:函数的作用域是在函数调用的时候才决定的
4.作用域链
作用域链:当查找变量的时候,会从当前上下文的变量对象中查找,如果没有找到,就会从父级执行上下文变量对象中查找,一直找到全局上下文变量对象,也就是全局对象。这样由多个执行上下文的变量构成链表 叫做作用域链。
5.闭包
闭包的定义
你不知道的js中有这样一句话:当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时就产生了闭包
MDN 对闭包的理解
闭包:那些能够访问自由变量的函数。
自由变量:是指在函数中使用的,既不是函数参数,也不是函数局部变量的变量。
闭包 = 函数+函数能够访问的自由变量
ECMAScript中,闭包指的是
理论角度: 所有的函数。因为他们都在创建的时候就将上层上下文的数据保存起来了,哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域
实践角度:
- 即时创建他的上下文已经销毁,它仍然然存在
- 在代码中引用自由变量
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
var foo = checkscope();
foo();
6.数据类型?
- boolean
- null
- undefined
- number
- string
- symbol
- bigint
引用数据类型: 对象Object(包含普通对象-Object,数组对象-Array,正则对象-RegExp,日期对象-Date,数学函数-Math,函数对象-Function)
基本数据类型用栈存储,引用数据类型用堆存储,注意:闭包是堆 存储
7.判断数据类型的几种实现方式?
- typeof 注意:其中typeof返回的类型都是字符串形式,需注意!!!!!
alert(typeof "helloworld") ------------------>"string"
alert(typeof 123) ------------------>"number"
alert(typeof [1,2,3]) ------------------>"object"
alert(typeof new Function()) ------------------>"function"
alert(typeof new Date()) ------------------>"object"
alert(typeof new RegExp()) ------------------>"object"
alert(typeof Symbol()) ------------------>"symbol"
alert(typeof true) ------------------>"true"
alert(typeof null) ------------------>"object"
alert(typeof undefined) ------------------>"undefined"
alert(typeof 'undefined') ------------------>"string"
- Object.prototype.toString
注意:适用于所有类型的判断检测,注意区分大小写. toString方法,在Object原型上返回数据格式,
console.log(Object.prototype.toString.call("123")) -------->[object String]
console.log(Object.prototype.toString.call(123)) -------->[object Number]
console.log(Object.prototype.toString.call(true)) -------->[object Boolean]
console.log(Object.prototype.toString.call([1, 2, 3])) -------->[object Array]
console.log(Object.prototype.toString.call(null)) -------->[object Null]
console.log(Object.prototype.toString.call(undefined)) -------->[object Undefined]
console.log(Object.prototype.toString.call({name: 'Hello'})) -------->[object Object]
console.log(Object.prototype.toString.call(function () {})) -------->[object Function]
console.log(Object.prototype.toString.call(new Date())) -------->[object Date]
console.log(Object.prototype.toString.call(/\d/)) -------->[object RegExp]
console.log(Object.prototype.toString.call(Symbol())) -------->[object Symbol]
- instanceof 注意: instanceof 后面一定要是对象类型,并且大小写不能错,该方法适合一些条件选择或分支
[1,2,3] instanceof Array -------->true
new Date() instanceof Date -------->true
new Function() instanceof Function -------->true
new Function() instanceof function -------->false
null instanceof Object -------->false
- constructor 注意:constructor 判断方法跟instanceof相似,但是constructor检测Object与instanceof不一样,constructor还可以处理基本数据类型的检测,不仅仅是对象类型
注意:
1.null和undefined没有constructor;
2.判断数字时使用(),比如 (123).constructor,如果写成123.constructor会报错
3.constructor在类继承时会出错,因为Object被覆盖掉了,检测结果就不对了
//注意当出现继承的时候,使用constructor会出现问题
function A() {};
function B() {};
A.prototype = new B(); //A继承自B
console.log(A.constructor === B) -------->false
var C = new A();
//现在开始判断C是否跟A的构造器一样
console.log(C.constructor === B) -------->true
console.log(C.constructor === A) -------->false
//解决这种情况,通常是手动调整对象的constructor指向
C.constructor = A; //将自己的类赋值给对象的constructor属性
console.log(C.constructor === A); -------->true
console.log(C.constructor === B); -------->false
8.call apply bind的实现?
call 和apply
// 手写实现一个apply
Function.prototype.myApply = function(context, args) {
if (!context || context === null) {
context = window;
}
let fn = Symbol();
context[fn] = this;
let res = context[fn](...args);
delete context[fn];
return res;
};
//手写实现一个call
Function.prototype.myCall = function(context, ...args) {
// context为undefined或null时,则this默认指向全局window
if (!context || context === null) { context = window; }
// 利用Symbol创建一个唯一的key值,防止新增加的属性与obj中的属性名重复
let fn = Symbol();
// this指向调用call的函数
context[fn] = this;
// 隐式绑定this,如执行obj.foo(), foo内的this指向obj
let res = context[fn](...args);
// 执行完以后,删除新增加的属性
delete context[fn]; return res;
let obj1 ={
name:'xiaohua',
fn(){
console.log("obj1",this.name);
}
}
let obj2 ={
name:'xiaoming',
fn(...args){
console.log("obj2",this.name);
console.log("args",args);
return Array.from(args).reduce((total, item) => total + item, 0);
}
}
obj1.fn();
let res = obj2.fn.myApply(obj1,[1,2,3,4,5]);
let res2 = obj2.fn.myCall(obj1,1,2,3,4,5);
console.log("res",res2);
bind
// bind要考虑返回的函数,作为构造函数被调用的情况
Function.prototype.myBind = function(context, ...args)
{
if (!context || context === null){
context = window;
}
let fn = this;
let f = Symbol();
const result = function(...args1) {
if (this instanceof fn) {
// result如果作为构造函数被调用,this指向的是new出来的对象
// this instanceof fn,判断new出来的对象是否为fn的实例
this[f] = fn;
this[f](...args1, ...args);
delete this[f];
} else {
// bind返回的函数作为普通函数被调用时
context[f] = fn;
context[f](...args1, ...args);
delete context[f];
}
};
// 如果绑定的是构造函数 那么需要继承构造函数原型属性和方法
// 实现继承的方式: 使用Object.create
// result.prototype = Object.create(fn.prototype);
return result;
};
let obj1 ={
name:'xiaohua',
fn(){
console.log("obj1",this.name);
}
}
let obj2 ={
name:'xiaoming',
fn(...args){
console.log("obj2",this.name);
console.log("args",args);
console.log("Array.from(args).reduce((total, item) => total + item, 0)",Array.from(args).reduce((total, item) => total + item, 0))
}
}
obj1.fn();
obj2.fn.myBind(obj1,1,2,3,4,5)();
9.call bind apply的基本用法和区别?
call 和apply的共同点:能够改变函数执行时的上下文,将一个对象的方法教给另一个对象来执行,并且是立即执行。
Function.call(obj,[param1[,param2[,…[,paramN]]]])
- 调用 call 的对象,必须是个函数 Function。
- call 的第一个参数,是一个对象。 Function 的调用者,将会指向这个对象。如果不传,则默认为全局对象 window。
- 第二个参数开始,可以接收任意个参数。每个参数会映射到相应位置的 Function 的参数上。但是如果将所有的参数作为数组传入,它们会作为一个整体映射到 Function 对应的第一个参数上,之后参数都为空。
function func (a,b,c) {}
func.call(obj, 1,2,3)
// func 接收到的参数实际上是 1,2,3
func.call(obj, [1,2,3])
// func 接收到的参数实际上是 [1,2,3],undefined,undefined
apply 的写法
Function.apply(obj[,argArray])
- 它的调用者必须是函数 Function,并且只接收两个参数,第一个参数的规则与 call 一致。
- 第二个参数,必须是数组或者类数组,它们会被转换成类数组,传入 Function 中,并且会被映射到 Function 对应的参数上。这也是 call 和 apply 之间,很重要的一个区别。
func.apply(obj, [1,2,3])
// func 接收到的参数实际上是 1,2,3
func.apply(obj, {
0: 1,
1: 2,
2: 3,
length: 3
})
// func 接收到的参数实际上是 1,2,3
bind的语法?
Function.bind(thisArg[, arg1[, arg2[, ...]]])
bind 方法 与 apply 和 call 比较类似,也能改变函数体内的 this 指向。不同的是,bind 方法的返回值是函数,并且需要稍后调用,才会执行。而 apply 和 call 则是立即调用
function add (a, b) {
return a + b;
}
function sub (a, b) {
return a - b;
}
add.bind(sub, 5, 3); // 这时,并不会返回 8
add.bind(sub, 5, 3)(); // 调用后,返回 8
bind 也能改变对象的执行上下文,它与 call 和 apply 不同的是,返回值是一个函数,并且需要稍后再调用一下,才会执行
10.函数柯里化
var length = fn.length;
args = args || [];
return function() {
var _args = args.slice(0),
arg, i;
for (i = 0; i < arguments.length; i++) {
arg = arguments[i];
_args.push(arg);
}
if (_args.length < length) {
return curry.call(this, fn, _args);
}
else {
return fn.apply(this, _args);
}
}
}
var fn = curry(function(a, b, c) {
console.log([a, b, c]);
});
fn("a", "b", "c") // ["a", "b", "c"]
fn("a", "b")("c") // ["a", "b", "c"]
fn("a")("b")("c") // ["a", "b", "c"]
fn("a")("b", "c") // ["a", "b", "c"]
11.浏览器v8引擎的垃圾回收机制
1.为什么需要垃圾回收
在V8引擎逐行执行JavaScript代码的过程中,当遇到函数的情况时,会为其创建一个函数执行上下文(Context)环境并添加到调用堆栈的栈顶,函数的作用域(handleScope)中包含了该函数中声明的所有变量,当该函数执行完毕后,对应的执行上下文从栈顶弹出,函数的作用域会随之销毁,其包含的所有变量也会统一释放并被自动回收。试想如果在这个作用域被销毁的过程中,其中的变量不被回收,即持久占用内存,那么必然会导致内存暴增,从而引发内存泄漏导致程序的性能直线下降甚至崩溃,因此内存在使用完毕之后理当归还给操作系统以保证内存的重复利用。
2.如何避免内存泄露?
- 尽可能少的创建全局变量
- 手动清除定时器
- 少用闭包
- 清除dom的引用
- 弱引用
WeakMap和WeakSet
12.浮点数精度
0.1+0.2=== 0.3?
13.new 的模拟实现?
14.事件循环机制?
浏览器事件循环机制?
概念: s引擎遇到一个异步事件后并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。当一个异步事件返回结果后,js会将这个事件加入与当前执行栈不同的另一个队列,我们称之为事件队列。被放入事件队列不会立刻执行其回调,而是等待当前执行栈中的所有任务都执行完毕, 主线程处于闲置状态时,主线程会去查找事件队列是否有任务。如果有,那么主线程会从中取出排在第一位的事件,并把这个事件对应的回调放入执行栈中,然后执行其中的同步代码...,如此反复,这样就形成了一个无限的循环。这就是这个过程被称为“事件循环(Event Loop)”的原因。
- 宏任务:
setInterval()setTimeout()- 微任务:
new Promise()new MutaionObserver()
当当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行。
node的事件循环?
15.promise原理?
16.generator原理?
17.原生实现一个input 输入框 同时下发展示输入内容?
<!DOCTYPE html>
<html>
<body>
<input type="text" id="user" name="username" class="uusr" onchange="get_input_value()">
<p id="demo"></p>
<script>
function get_input_value(){
var name = document.getElementById("user").value;
document.getElementById("demo").innerHTML = name;
}
</script>
</body>
</html>
18、受控组件和非受控组件?
受控组件:即通过setState的形式控制输入的值及更新,
非受控组件:即通过dom的形式更新值,要获取其值可以通过ref的形式去获取。
19数组去重复的几种方式?
1、[...new Set(arr)]
const arr = [1, 2, 3, 2, 3];
[...new Set(arr)]; // [1, 2, 3]
2、Array.from(new Set(arr))
const arr = [1, 2, 3, 2, 3];
Array.from(new Set(arr)); // [1, 2, 3]
3.利用indexOf去重
function unique(arr) {
if(!Array.isArray(arr)) {
console.log('type error!')
return
}
var array = [];
for(var i = 0; i < arr.length; i++) {
if(array.indexOf(arr[i]) === -1) {
array.push(arr[i])
}
}
return array;
}
4.利用includes
function unique(arr) {
if(!Array.isArray(arr)) {
console.log('type error!')
return
}
var array =[];
for(var i = 0; i < arr.length; i++) {
if( !array.includes( arr[i]) ) {//includes 检测数组是否有某个值
array.push(arr[i]);
}
}
return array
}
5.利用filter
function unique(arr) {
return arr.filter(function(item, index, arr) {
//当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素
return arr.indexOf(item, 0) === index;
});
}
var arr = [1,1,'true','true']
6.双层for循环
20.遍历对象的有几种方式?
1.for...in
let obj = {"0":"a","1":"b","2":"c"};
for (let i in obj){
console.log(i,obj[i]);
}
2.Object.keys()遍历 Object.values()遍历 Object.entries()
let obj = {"0":"a","1":"b","2":"c"};
Object.keys(obj).forEach((i)=>{
console.log(i,obj[i]);
})
const obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.entries(obj)); // [ ['0', 'a'], ['1', 'b'], ['2', 'c']
3.Object.getOwnPropertyNames(obj) 返回一个数组,包含对象自身的所有属性 不含Symbol属性,但是包括不可枚举属性
var obj = {'0':'a','1':'b','2':'c'};
Object.getOwnPropertyNames(obj).forEach(function(key){
console.log(key,obj[key]);
});
4.Reflect.ownKeys(obj) 返回一个数组,包含对象自身的所有属性,不管属性名是Symbol或字符串,也不管是否可枚举
var obj = {'0':'a','1':'b','2':'c'};
Reflect.ownKeys(obj).forEach(function(key){
console.log(key,obj[key]);
});