本文已参与「新人创作礼」活动,一起开启掘金创作之路。
bind、call、apply
this指向问题
bind call apply都可以改变this的指向问题
区别
bind和(call、apply)的区别
bind 返回一个改变了this指向的函数(永久改变)
call、apply直接改变this指向,并执行函数(这一次改变)
call 和 apply的区别
call 传参 :可以传入多个参数 第一个为this的指向,其余为函数所需的参数 apply 传参:只能传入两个参数 第一个为this的指向,第二个为数组,(数组中为函数所需的参数)
手写
/手写call
Function.prototype.myCall=function(context=window,...args){
//处理args,转成数组
args = args ||[];
//
const key = Symbol();
//绑定this 指向调用call的函数
context[key] = this;
const result = context[key](...args);
//接触绑定
delete context[key];
return result
}
// obj.say.myCall(tom,18,'nan');
//手写apply
Function.prototype.myApply = function(context=window,args){
args = args ||[];
//
const key = Symbol();
//绑定this 指向调用call的函数
context[key] = this;
const result = context[key](...args);
//接触绑定
delete context[key];
return result
}
// obj.say.myApply(tom,[19,'男'])
//手写bind
Function.prototype.myBind = function(){
const fn = this;
//第一个参数为改变的指向
const context = arguments[0];
// 获取剩余参数 类数组转数组对象
const args = [].slice.call(arguments,1);
return function(){
// 获取参数
const returnArgs = [].slice.call(arguments,1);
fn.apply(context,returnArgs.concat(args))
}
}
数组去重
//indexOf 和filter
function unique(arr){
if(!Array.isArray(arr)){
return console.error('is not a array');
}
let res = arr.filter((item,index)=>{
//indexOf返回数组中第一个该元素的下标
return arr.indexOf(item) == index;
})
return res;
}
console.log(unique(array));
//indexOf和循环
function unique(arr){
if(!Array.isArray(arr)){
return console.error('is not a array');
}
let res = [];
for(let i =0;i<arr.length;i++){
//使用用indexOf
// if(res.indexOf(arr[i])===-1){
// res.push(arr[i])
// }
// 使用includes
if(!res.includes(arr[i])){
res.push(arr[i])
}
}
return res;
}
console.log(unique(array));
function unique(arr){
if(!Array.isArray(arr)){
return console.log('not a array');
}
let res = [arr[0]];
for(let i = 0;i<arr.length;i++){
let flag = true
for(let j = 0; j<res.length;j++){
if(arr[i]===res[j]){
flag = false;
break
}
}
if(flag){
res.push(arr[i])
}
}
return res
}
console.log(unique(array));
// 相邻元素去重
function unique(arr){
if(!Array.isArray(arr)){
return console.log('not a array');
}
let res = [];
//排序
arr = arr.sort();
for(let i = 0 ;i<arr.length;i++){
if(arr[i]!==arr[i+1]){
res.push(arr[i])
}
}
return res
}
console.log(unique(array));
//利用对象属性去重
function unique(arr){
if(!Array.isArray(arr)){
return console.log('is not a array');
}
let res = [];
let obj = {};
for(let i = 0;i<arr.length;i++){
if(!obj[arr[i]]){
res.push(arr[i]);
obj[arr[i]] = 1;
}
else{
obj[arr[i]]++;
}
}
return res;
}
console.log(unique(array));
//ES6 set 去重
function unique(arr){
if(!Array.isArray(arr)){
return console.log('not a array');
}
//set 和解构赋值
// return [...new Set(arr)]
//set 和Array.from
return Array.from(new Set(arr))
}
function unique(arr){
if(!Array.isArray(arr)){
return console.log('not a array');
}
let obj = {}
let res = arr.filter((item,index)=>{
if(obj.hasOwnProperty(typeof item +item)){
return false
}
else{
obj[typeof item +item] = true;
return true
}
})
return res;
}
console.log(unique(array));
原型和原型链
所有的函数对象都有一个属性prototype,指向一个对象(原型对象),由该函数创建的实例的原型对象,原型对象的构造器指向函数
function Person(){
this.name = 'jack';
}
let p = new Person();
实例对象的隐式原型对象(proto)指向构造函数的显示原型对象(prototype)
p.__proto__===Person.prototype
构造函数的原型对象的构造器(constructor)指向构造函数本身
Person.prototype.constructor === Person
所有对象都有__protp__属性
函数对象本质是由 new Function()创建,Function是所有函数对象的构造函数
Person.__proto__===Function.prototype
原型对象也是一个对象,也有__proto__,原型对象是一个普通对象,其构造函数时Object()
Person.prototype.__proto__ === Object.prototype
Object()是个对象,其构造函数是Function()
Object.__proto__===Function.prototype
Function()也是一个对象,有__proto__属性
Function.__proto__ === Function.prototype
Function的原型对象的隐式原型
Function.prototype.__proto__===Object.prototype
Object.protptype.__proto__===null
作用域和作用域链
静态作用域和动态作用域
静态作用域也叫词法作用域
作用域在声明的时候确定,而非在执行的时候确定
作用域本质上是一个对象AO。在作用域内,即是否为该作用域对象的属性
var str = 'hello'
function say(){
console.log(str);
}
function test(){
var str = 'world'
say();
}
test();//'hello'
动态作用域会打印world ,词法作用域打印hello
作用域链
当访问某个变量时,会现在当前AO中查找该属性,如果没有该属性,就向上去父级AO中查找属性,以此类推直到查找到根节点
执行上下文
执行上下文是评估和执行 JavaScript 代码的环境的抽象概念。每当 Javascript 代码在运行的时候,它都是在执行上下文中运行
三种上下执行上下文
全局上下文
函数上下文
eval函数上下文
程序执行时会创建一个全局执行上下文,并且将其压入执行栈的底部(执行栈:先进后出的调用栈)当函数执行时会创建函数上下文并压入栈的顶部,当前函数执行完成就弹出。
执行上下文分两个阶段:创建阶段和执行阶段
创建阶段
this的绑定 创建词法环境(变量作用域)
一个函数可以访问在它的调用上下文中定义的变量,这个就是**词法作用域(Lexical scope)** 。
创建变量环境
执行阶段
闭包
当程序调用一个函数时,会发生什么?
- JavaScript创建一个新的执行上下文,我们叫作本地执行上下文。
- 这个本地执行上下文将有它自己的一组变量,这些变量将是这个执行上下文的本地变量。
- 新的执行上下文被推到到执行堆栈中。可以将执行堆栈看作是一种保存程序在其执行中的位置的容器。 函数什么时候结束?当它遇到一个
return语句或一个结束括号}。 当一个函数结束时,会发生以下情况:- 这个本地执行上下文从执行堆栈中弹出。
- 函数将返回值返回调用上下文。调用上下文是调用这个本地的执行上下文,它可以是全局执行上下文,也可以是另外一个本地的执行上下文。这取决于调用执行上下文来处理此时的返回值,返回的值可以是一个对象、一个数组、一个函数、一个布尔值等等,如果函数没有
return语句,则返回undefined。- 这个本地执行上下文被销毁,销毁是很重要,这个本地执行上下文中声明的所有变量都将被删除,不在有变量,这个就是为什么 称为本地执行上下文中自有的变量。
闭包:我们终于找到了,丢失的那块。 它是这样工作的,无论何时声明新函数并将其赋值给变量,都要存储函数定义和闭包。闭包包含在函数创建时作用域中的所有变量,它类似于背包。函数定义附带一个小背包,它的包中存储了函数定义创建时作用域中的所有变量。 任何函数具有闭包,甚至是在全局范围内创建的函数?答案是肯定的。在全局作用域中创建的函数创建闭包,但是由于这些函数是在全局作用域中创建的,所以它们可以访问全局作用域中的所有变量,闭包的概念并不重要。
//每一个函数都会有一个闭包。闭包会保存该函数读取的上层作用域的变量。
//let fn = function() {} fn保存了function 的函数声明和该函数的闭包
function outter(){
let num =10;
function inner(){
console.log(++num);
}
return inner;
}
let test1 = outter();
test1 ();//11
test1 ();//12
let test2 = outter();
test2();//11
判断类型的方法
typeof
//基础类型
typeof 1 // "number"
typeof 'a' // "string"
typeof true // "boolean"
typeof undefined // "undefined"
typeof Symbol() // "symbol"
typeof 42n // "bigint"
//历史遗留问题null 返回 object
typeof null // object
//复杂类型 除去函数返回function 其余返回object
object. prototype .toString.call
返回"[object, 类型]",注意返回的格式及大小写,前面是小写,后面是首字母大写
object.prototype.toString.call(1);//[object,number]
object.prototype.toString.call(null);//[object,null]
//内置对象也可以判断
object.prototype.toString.call([1,2]);//[object,Array]
object.prototype.toString.call(new Date);//[object,Date]
instanceof
防抖和节流
防抖:单位时间内只执行一次(应用场景:模糊搜索) 节流:间隔时间执行
//手写防抖
export default function debounce(fn:Function,delay:number){
clearTimeout(fn.id);
//不能使用箭头函数
return function(){
let arg = arguments
fn.id = setTimeout(()=>{
fn.apply(this,arg);
},delay)
}
}
//手写节流
export default function throttle(fn:Function,delay:number){
let timer:any=null;
//不能使用箭头函数
return function (){
let args = arguments;
if(timer){
return ;
}
timer = setTimeout(()=>{
fn.call(this,args);
timer = null;
},delay)
}
}
数组拍平(扁平化)
学习 : ( js数组拍平(数组扁平化)的八种方式 )
使用Infinity作为flat的参数,使得无需知道被扁平化的数组的维度。
new
使用new关键字时,发生了什么
- 创建一个空的简单
JavaScript对象(即{});- 为步骤1新创建的对象添加属性
__proto__,将该属性链接至构造函数的原型对象 ;- 将步骤1新创建的对象作为
this的上下文 ;- 如果该函数没有返回对象,则返回
this。
//模拟new
function creatObject(Con) {
//创建新对象
let obj = {};
//将obj的__proto__绑定构造函数的原型
//不推荐使用 ob.__protp__ = Con.constructor;
Object.setPrototypeOf(obj, Con.constructor);
// 执行构造函数并接受返回值
const res = Con.apply(obj, [].slice.call(arguments, 1));
//若构造函数的返回值为对象,返回构造函数的返回值
// 否则返回obj对象
return typeof res === 'object' ? res : obj
}
实现nstanceOf
js底层如何实现类型信息的存储
js 在底层存储变量的时候,会在变量的机器码的低位1-3位存储其类型信息
typeof 根据机器码来判断数据类型,null的机器码全为0。对象的机器码 前三位为000;
这一问题导致 typeof null === 'object' //true
instanceof
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
instanceof 的本质上是沿着实例对象的原型链查找,构造函数的原型是否在实例对象的原型链上
//实现 instanceof
// 实现instanceof本质上只要只要遍历实例对象的原型链,挨个往上查找看是否有与Fn的prototype相等的原型,直到最顶层Object还找不到,那么就返回false,否则结果就是true
function myInstanceof(obj,fnc){
if(!(obj && ['object','function'].includes(typeof obj))){
return false;
}
//获取obj的原型
let proto = Object.getPrototypeOf(obj);
//使用递归或者遍历
//递归
if(proto === fnc.prototype){
return true;
}
else if(proto === null ){
return false
}
else {
return myInstanceof(proto,fnc)
}
}