文章目录
- 1.js基本数据类型
- 2.堆和栈有什么区别?
- 3.js作用域
- 4.var,let,const的区别
- 5.null 和 undefined 的区别?
- 6.js类型判断
- 7.判断一个数组(typeof 数组和日期都是object,如何具体的判断)
- 8.this的指向
- 9.js同步和异步
- 10.解决异步回调地狱
- 11.promise
- 11.事件流
- 11.事件循环
- 12.事件委托
- 13.闭包 函数可以维持其创建时候的作用域
- 14.原型链
- 15.原型属性
- 16.继承
- 17.作用域链以及函数的一生
- 18.防抖节流
- 19.callbindapply实现 apply
- 20.深拷贝浅拷贝,对象里面存着自己用hash
- 21.实现new 是this和原型链的知识点
- 22.跨域
- 23.代码执行过程
- 24.解决setTimeout中的this指向问题
1.js基本数据类型
值类型(基本类型):字符串(String)、数字(Number)、布尔(Boolean)、对空(Null)、未定义(Undefined)、Symbol。
引用数据类型:对象(Object)、数组(Array)、函数(Function)。
**注:**Symbol 是 ES6 引入了一种新的原始数据类型,表示独一无二的值。
基本类型的值存在栈中,引用数据类型的地址存在栈中,内容存在堆中。
2.堆和栈有什么区别?
堆和栈的概念存在于数据结构中和操作系统内存中。
在数据结构中,栈中数据的存取方式为先进后出。而堆是一个优先队列,是按优先级来进行排序的,优先级可以按照大小来规定。完全二叉树是堆的一种实现方式。
在操作系统中,内存被分为栈区和堆区。
栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
堆区内存一般由程序员分配释放,若程序员不释放,程序结束时可能由垃圾回收机制回收。
3.js作用域
全局作用域:在全局定义的变量
局部作用域:在函数内定义的变量
块级作用域:用let声明的变量
4.var,let,const的区别
var声明的变量存在变量提升
let 声明的变量只在 let 命令所在的代码块内有效。
在同一个块级作用域,不能重复声明。
不存在变量声明,有暂时性死区。
const 除了具有 let 的上述特点外,其还具备一个特点,即 const 定义的变量,一旦定义后,就不能修改,即 const 声明的为常量。const声明的常量必须初始化。
const保存的是地址,地址不可变,但是地址保存的数据可以改变。
5.null 和 undefined 的区别?
首先 Undefined
和 Null
都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined
和 null
。
undefined
代表的含义是未定义,null
代表的含义是空对象。一般变量声明了但还没有定义的时候会返回 undefined
,null
主要用于赋值给一些可能会返回对象的变量,作为初始化。
undefined
在 js 中不是一个保留字,这意味着我们可以使用 undefined
来作为一个变量名,这样的做法是非常危险的,它会影响我们对 undefined
值的判断。但是我们可以通过一些方法获得安全的 undefined
值,比如说 void 0
。
当我们对两种类型使用 typeof
进行判断的时候,Null
类型化会返回 “object
”,这是一个历史遗留的问题。当我们使用双等号对两种类型的值进行比较时会返回 true
,使用三个等号时会返回 false
。
6.js类型判断
typeof “John” // 返回 string
typeof 3.14 // 返回 number
typeof false // 返回 boolean
typeof [1,2,3,4] // 返回 object
typeof {name:‘John’, age:34} // 返回 object
7.判断一个数组(typeof 数组和日期都是object,如何具体的判断)
1.instanceof
使用instanceof来检测某个对象是否是数组的实例,该检测会返回一个布尔型(boolean),如果是数组的话,返回true,否则的话返回false;
arr instanceof Array 会返回一个true 说明 arr是一个数组的实例
instanceof 不能判断一个对象具体属于哪种类型。 但是属于array的还能有啥??
实现instanceof
// instanceof 的内部实现
function instance_of(L, R) {//L 表左表达式,R 表示右表达式,即L为变量,R为类型
// 取 R 的显示原型
var prototype = R.prototype
// 取 L 的隐式原型
L = L.__proto__
// 判断对象(L)的类型是否严格等于类型(R)的显式原型
while (true) {
if (L === null) {
return false
}
// 这里重点:当 prototype 严格等于 L 时,返回 true
if (prototype === L) {
return true
}
L = L.__proto__ //继续往原型上面找
}
}
2.constructor
var obj = {}; obj.constructor //输出f Object
var arr =[]; arr.constructor //输出arr
3.Object.prototype.toString.call(‘这里传入要检测的对象’)
就是因为好多对象都复写这个toString方法 所以要绑定一下
4.Array.isArray(‘这里传入要检测的对象’)
这个就是数组的特有方法
8.this的指向
默认绑定:全局环境中,this默认绑定到window。
隐式绑定:一般地,被直接对象所包含的函数调用时,也称为方法调用,this隐式绑定到该直接对象。
隐式丢失:隐式丢失是指被隐式绑定的函数丢失绑定对象,从而默认绑定到window。
显式绑定:通过call()、apply()、bind()方法把对象绑定到this上,叫做显式绑定。
new绑定:如果函数或者方法调用之前带有关键字new,它就构成构造函数调用。指向实例对象。
函数是否在 new 中调用?如果是的话 this 绑定的是新创建的对象。
函数是否通过 call、apply、bind调用?如果是的话 this 绑定的是指定的对象。
函数是否在就是在函数中使用某个上下文对象中调用(隐式绑定)?如果是的话,this 绑定的是这个函数的this
如果都不是的话,使用默认绑定。默认绑定this指向全局 settimeout的this就是全局
例子
for(var ii=0 ;ii<=5;ii++){
setTimeout(()=>
{
console.log('ii',ii)
},ii*1000
)
}
//这里会输出六次6 因为settimeout会在循环结束后执行
9.js同步和异步
- 同步任务:会立即执行的任务 for循环
- 异步任务:不会立即执行的任务(异步任务又分为宏任务与微任务)
- 宏任务: ajax settimeout dom操作 微任务:then catch finally
- 异步不按照代码顺序执行,异步的执行效率更高。
10.解决异步回调地狱
promise、generator、async/await
11.promise
实现:一步步实现promise
Promise 对象有以下两个特点:
1、对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:
- pending: 初始状态,不是成功或失败状态。
- fulfilled: 意味着操作成功完成。
- rejected: 意味着操作失败。
只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise 这个名字的由来,它的英语意思就是「承诺」,表示其他手段无法改变。
2、一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise 对象的状态改变,只有两种可能:从 Pending 变为 Resolved 和从 Pending 变为 Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
Promise 优缺点
优点:
有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。
缺点
Promise 也有一些缺点。首先,无法取消 Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。第三,当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
new Promise(function (resolve, reject) {
var a = 0;
var b = 1;
if (b == 0) reject("Divide zero");
else resolve(a / b);
}).then(function (value) {
console.log("a / b = " + value);
}).catch(function (err) {
console.log(err);
}).finally(function () {
console.log("End");
});
a / b = 0
VM36:11 End
//这个end是一定会执行的
Promise 类有 .then() .catch() 和 .finally() 三个方法,这三个方法的参数都是一个函数,.then() 可以将参数中的函数添加到当前 Promise 的正常执行序列,.catch() 则是设定 Promise 的异常处理序列,.finally() 是在 Promise 执行的最后一定会执行的序列。
两种写法
promise.then((res)=>{}).catch((err)=>{})
promise.then((res)=>{},(err)=>{})
Promise.all方法,Promise.race方法
Promise.all 接收promise实例数组,返回一个新的 Promise 实例。
var p = Promise.all([p1,p2,p3]);
上面代码中,Promise.all 方法接受一个数组作为参数,p1、p2、p3 都是 Promise 对象的实例。
p 的状态由 p1、p2、p3 决定,分成两种情况。
- (1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
- (2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
下面是一个具体的例子。
// 生成一个Promise对象的数组
var promises = [2, 3, 5, 7, 11, 13].map(function(id){ return getJSON("/post/" + id + ".json"); }); Promise.all(promises).then(function(posts) { // ... }).catch(function(reason){ // ... });
Promise.race 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
var p = Promise.race([p1,p2,p3]);
上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给p的返回值。一般是定时器时间短的那个。
如果Promise.all方法和Promise.race方法的参数,不是Promise实例,就会先调用下面讲到的Promise.resolve方法,将参数转为Promise实例,再进一步处理。
await async
async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。
async 函数中可能会有 await 表达式,async 函数执行时**,如果遇到 await 就会先暂停执行 ,等到触发的异步操作完成后,恢复 async 函数的执行并返回解析值。**
await 关键字仅在 async function 中有效。如果在 async function 函数体外使用 await ,你只会得到一个语法错误。
具体例子
先同步再异步,异步里面
–先一个宏任务 这个宏任务执行完执行宏任务(new Promise())里面的微任务,执行完所有的微任务再去执行宏任务。
宏任务:
setTimeout
setInterval
微任务:
Promise.then catch finally
async function async1 () {
console.log('async1 start')
await async2();
console.log('async1 end')}
async function async2 () {
console.log('async2')}
console.log('script start')
setTimeout(function () {
console.log('setTimeout')}, 0)
async1();
new Promise (
function (resolve)
{ console.log('promise1')
resolve();})
.then (
function () {
console.log('promise2')})
console.log('script end')
script start async1 start async2 promise1 script end promise2 async1 end setTimeout
解析:
先执行主代码块,所以第一行打印script start
setTimeout()是另一个宏任务,所以先放在宏任务队列。
然后执行async1,async1里先打印async1 start
执行await async2 , 先执行async2,打印async2, 返回promise对象。await会阻塞async1后面的代码执行,所以先跳出来继续执行后面的代码。
然后执行new Promise 打印promise1
把then里面的函数加入微任务队列
打印script end
到这里第一个宏任务执行完毕,开始执行微任务then,打印promise2。
then执行完后,await才算是执行结束了,后面的代码不再被阻塞,所以打印async1 end
这时候继续执行第二个宏任务setTimeout,打印setTimeout
11.事件流
HTML中与javascript交互是通过事件驱动来实现的,例如鼠标点击事件onclick、页面的滚动事件onscroll等等,可以向文档或者文档中的元素添加事件侦听器来预订事件。想要知道这些事件是在什么时候进行调用的,就需要了解一下“事件流”的概念。
什么是事件流:事件流描述的是从页面中接收事件的顺序,DOM2级事件流包括下面几个阶段。
事件捕获阶段
处于目标阶段
事件冒泡阶段
addEventListener:addEventListener 是DOM2 级事件新增的指定事件处理程序的操作,这个方法接收3个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序。
IE只支持事件冒泡。
11.事件循环
主线程持续从消息队列中取消息,执行的过程。
有浏览器渲染进程,里面又包含gui渲染线程,js引擎线程,这两个一般是互斥的。
12.事件委托
简介:事件委托指的是,不在事件的发生地(直接dom)上设置监听函数,而是在其父元素上设置监听函数,通过事件冒泡,父元素可以监听到子元素上事件的触发,通过判断事件发生元素DOM的类型,来做出不同的响应。
举例:最经典的就是ul和li标签的事件监听,比如我们在添加事件时候,采用事件委托机制,不会在li标签上直接添加,而是在ul父元素上添加。
好处:比较合适动态元素的绑定,新添加的子元素也会有监听函数,也可以有事件触发机制。
13.闭包 函数可以维持其创建时候的作用域
闭包就是一个函数引用另一个函数的变量,因为变量被引用着所以不会被回收,因此可以用来封装一个私有变量。这是优点也是缺点,不必要的闭包只会增加内存消耗。
例子:
var add = (function () { var counter = 0; return function () {return counter += 1;}})(); add();add();add(); // 计数器为 3
https://github.com/ZengLingYong/Blog/issues/16
14.原型链
-每个函数都有prototype属性,这个属性的是一个对象 里面存着一个属性constructor(创造者) 指向这个函数
所以对象的prototype的__proto__是Object.prototype
–每个对象拥有一个原型对象(proto),对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型对象,并从中继承方法和 属性,一层一层、以此类推。这种关系常被称为原型链 。
– 层层向上直到一个对象的原型对象为 null 。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。
就是记住 对象的__proto__指向自己构造函数的prototype
自己的proto也是一个对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9pKzfJo7-1627820377879)(C:\Users\XUETING\AppData\Roaming\Typora\typora-user-images\image-20210731162336482.png)]
15.原型属性
原型属性 就是原型上的属性呗 实例对象都能访问到
静态属性就是对象自带的 对象的name length arguments caller 这个属性 实例对象是访问不到的 只有这个构造函数可以访问到
实例属性 新建的实例对象自己加的 自己可以访问到
16.继承
- 原型继承(又称为原型链继承)将子级的原型对象设置为父级的一个实例
// 优点:可以继承父类的原型对象的属性和方法
// 缺点:当构造函数中的属性为引用类型值时,修改次属性值,
//所有的实例都会共享次修改后的属性值
// 不能在父级构造函数调用时传参
//父级构造函数
function SuperType(name)
{ this.name = name;
this.colors = ["red", "green", "blue"];
}
//父级原型对象
SuperType.prototype.method =
function() {
console.log(this.name);
console.log(this.colors); };
//然后
function SubType() {}
//子级原型对象:将子级的原型对象设置为父级的一个实例
SubType.prototype = new SuperType();
//思考:可以传参吗?SuperType函数会执行吗?
2.构造继承(又称为 call 继承) 通过使用 call、apply 方法可以在子级构造函数上执行父级构造函数+
//优点是直接继承父级构造函数中的属性和方法
//缺点是无法继承原型链上的属性和方法。
//子级构造函数
function SubType(name) {
//父级构造函数浅拷贝给子级构造函数
SuperType.call(this, name);
//这里的this是没有原型上的方法的 }
3.组合继承(类似于原型继承)上面的原型继承 会更改父级的方法 构造继承不会更改因为继承不了 所以他俩混合就是原型式继承
// 优点:可以继承父类的原型,父级构造函数中的属性和方法
// 缺点:调用了两次父级的构造函数,有些许损耗性能,并且子级的构造函数的属性会和原型上面的属性重合
//子级构造函数
function SubType(name) {
SuperType.call(this, name);
//调用父级构造函数,传参
}
//子级原型对象:将子级的原型对象设置为父级的一个实例
SubType.prototype = new SuperType();
//调用父级构造函数,不传参
console.log( SubType.prototype.constructor)
SubType.prototype.constructor = SubType; //指定constructor
//这里就是如何不赋值的话指向的是supertype的
//因为是supertype的一个实例对象 指向自己的构造者呗
4.原型式继承
//与原型链继承类似 就是用了Object.create 这个方法
SubType.prototype = Object.create(SuperType.prototype);
5.寄生式继承(原型式继承进阶)
//寄生式继承
function createAnother(obj) {
var tempObject = Object.create(obj);
// 通过调用函数创建一个新对象
tempObject.sayHi = function ()
{
//以某种方式增强或丰富这个对象
console.log("hi"); };
return tempObject; //返回这个对象
}
//子级原型对象
SubType.prototype = createAnother(SuperType.prototype);
6.寄生组合式继承(寄生式继承 + 构造继承) –只调用一次父级构造函数,并且还能继承原型链上面的方法。
//寄生式继承
function createAnother(obj, constructor) {
var tempObject = Object.create(obj); // 通过调用函数创建一个新对象
tempObject.constructor = constructor; //以某种方式增强或丰富这个对象 这里是这样增强
return tempObject; //返回这个对象
}
//子级原型对象
SubType.prototype = createAnother(SuperType.prototype);
17.作用域链以及函数的一生
1.js运行环境 js运行环境 有全局环境(代码运行起来就会进去全局环境)和函数环境(函数被调用执行就会进入函数环境)
2.执行上下文环境
就是当前代码的运行环境,作用是保存当前代码运行时需要的数据,变量呀函数声明还有this值
在全局环境和函数环境中会创建执行上下文
3.执行上下文栈
按照函数调用的顺序来管理执行上下文
栈底一直是全局的上下文,栈顶是正在执行的函数
4.每个执行上下文都有一个与之关联的变量对象和一个作用链子
5.又要讲到函数
函数的一个阶段
定义 定义的时候就会确定这个scope属性保存所有父级执行上下文环境中的变量对象
三种定义的方式
函数声明 function a(){}
函数表达式 var a = function(){}
Funcition构造函数 var a = new Function()
调用
四种调用的形式
作为函数直接调用(定时器函数,立即执行函数),就是默认绑定this指向全局
作为对象的方法调用 ,就是隐式绑定,指向这个对象
通过call/apply间接调用 就是显示绑定指向所指的
作为构造函数调用 就是new绑定指向实例对象
执行
就是这里的执行上下文
首先创建执行上下文(
会生成变量对象,建立作用域链,确定this的指向
)
其次执行上下文阶段(变量进行赋值,执行其他的代码)
结束
就是return 了 ,会终止当前的执行上下文,从那个栈中出去。
6.作用域(控制变量与函数的可访问范围和可见性)
有全局作用域,函数作用域,块作用域
7.作用域链
就是当前自己的执行上下文环境加上所有父级执行上下文环境的一系列变量对象组成
就是自己的变量对象加上所有父级的变量对象
VO == 变量对象
18.防抖节流
节流和防抖大致相同,不同点在于:
- 防抖是“随机应变”,以密集的事件触发处的末尾为下一次事件触发的计时开始处
- 节流是“铁打不动”,始终以第一次事件触发为起始点,忽略
time
时间内的所有事件,第一次事件发生time
事件之后触发第二次事件
防抖
防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时,执行最后一次
场景:输入框,登录时
function debounce(func, delay) {
let timeout = null
//用闭包是因为原因是 timeout 变量是在 debounce
// 函数内部初始化的时候声明的,但是之后每一次
//触发返回的箭头函数的时候都需要读取 timeout 变量,
//所以要使用闭包
return function() {
timeout && clearTimeout(timeout) // 如果持续触发,那么就清除定时器,定时器的回调就不会执行。
timeout = setTimeout(() => {
func.apply(this, arguments)//每次调用箭头函数的时候为了确保上下文环境为当前的 this,就需要使用 apply 语法
}, delay)
}
}
box.onmousemove = debounce(function (e) {
box.innerHTML = `${e.clientX}, ${e.clientY}`
}, 1000)
节流
n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
场景:滑动的时候。
节流的意思是让函数有节制地执行,而不是毫无节制的触发一次就执行一次。什么叫有节制呢?就是在一段时间内,只执行一次。
同样,我们分解一下:
-
持续触发并不会执行多次
-
到一定时间再去执行
思考一下,持续触发,并不会执行,但是到时间了就会执行。抓取一个关键的点:就是执行的时机。要做到控制执行的时机,我们可以通过一个开关,与定时器setTimeout结合完成。
函数执行的前提条件是开关打开,持续触发时,持续关闭开关,等到setTimeout到时间了,再把开关打开,函数就会执行了。
//利用开关 function throttle(func, delay) { let run = true return function () { if (!run) { return // 如果开关关闭了,那就直接不执行下边的代码 } run = false // 持续触发的话,run一直是false,就会停在上边的判断那里 setTimeout(() => { func.apply(this, arguments) run = true // 定时器到时间之后,会把开关打开,我们的函数就会被执行 }, delay) } } //利用时间 function throtte(func, delay) { let activeTime = 0; return () => { const current = Date.now(); if (current - activeTime > delay) { //大于这个时间才去执行 func.apply(this, arguments); activeTime = Date.now(); } }; } //二者结合 function throttled(fn, delay) { let timer = null let starttime = Date.now() return function () { let curTime = Date.now() // 当前时间 let context = this let args = arguments clearTimeout(timer) if (curTime - starttime > delay) { fn.apply(context, args) starttime = Date.now() } else { timer = setTimeout(fn, delay); } }
}
``
19.callbindapply实现 apply
call 会立即调用函数执行,bind不会
1.fn.call(this,arg1,arg2) 返回值与fn的普通调用一样
Function.prototype.call = function (obj){
obj = obj ? Object(obj):window
var args =[]
let args = [...arguments].slice(1)
let result = obj.fn(...args)
for(var i=1,len=arguments.length;i<len;i++){
args.push("arguments["+i+"]")
}
obj.fn = this;//给this
eval("obj.fn"+args+")") //这里是执行原函数
delete obj.fn //最后要删除
}
2.fn.apply(this,[arg1,arg2]) 只不过是一个数组 返回值与fn的普通调用一样
Function.prototype.apply=function(obj,arr){
obj = obj ? Object(obj) :window
obj.fn = this
if(!arr){
obj.fn()
}
else{
var args=[]
for(var i=1,len=arguments.length;i<len;i++){
args.push("arguments["+i+"]")
}
eval("obj.fn"+args+")") //这里是执行原函数
}
delete obj.fn //最后要删除
}
3.fn.bind(this,arg1,arg2) 返回值是一个原函数的拷贝
Function.prototype.myBind = function() {
var thatFunc = this,
thatArg = arguments[0];
var args = Array.prototype.slice.call(arguments, 1)
if (typeof thatFunc !== 'function') {
throw new TypeError('Function.prototype.bind - ' +
'what is trying to be bound is not callable');
}
var fBound = function() {
return thatFunc.apply(this instanceof fBound
? this
: thatArg,
args.concat(Array.prototype.slice.call(arguments)));
};
var fNOP = function() {};
if (thatFunc.prototype) {
fNOP.prototype = thatFunc.prototype;
}
fBound.prototype = new fNOP();
return fBound;
}
20.深拷贝浅拷贝,对象里面存着自己用hash
浅拷贝
浅拷贝对于基本的数据类型就是复制过来了值,对于引用数据类型就是数组,是把数组的地址复制过来了,然后这两个还是指向同一个空间,修改都会发生改变
深拷贝
就是浅拷贝加上递归
•拷贝基本类型的值
•拷贝引用类型时(比如数组和或对象),使用递归,把父对象中所有属于引用类型的对象都遍历赋给子对象即可。
深拷贝的方式1 JSON.parse(JSON.stringify())
深拷贝的方式2 自定义函数
遇到的问题就是
还有就是性能优化 可以把map改成wekmap 还有就是判断类型万一既不是数组也不是对象 ,然后看看是不是可遍历的,如果是可遍历的就继续遍历,不是的话就返回。
·这个对象里面存了自己 就是循环引用了 会导致死循环,然后解决办法试试额外开辟一个空间,
来存储当前的对象和拷贝对象之间的关系,当需要拷贝当前对象时,先去存储空间找,有没有拷贝过这个对象,如果有的话直接返回,没有的话继续拷贝。
·
//可以选择map这种结构
const target = {
field1: 1, field2: 2, field3: { child: 'child' }, field4: [2, 4, 8]
}
target.target = target
const clone = (target, map = new Map()) => {
if (typeof target === 'object') {
console.log('是一个对象',target,map)
let cloneTarget = Array.isArray(target) ? [] : {}
if (map.get(target)) {
console.log('这里设置过map',map,target)
//如果map中有这个 对象 就直接返回 那直接返回会克隆成功吗
return map.get(target)//获取target的value值
}
else{
map.set(target, cloneTarget) //没有的话 就存一下key值 可以就是一直clone传的值
console.log('这里设置新map',map,target, cloneTarget)
}
for (let key in target) {
cloneTarget[key] = clone(target[key], map)
}
return cloneTarget
}
else {
console.log('不是对象',target)
return target
}
}
clone(target) //这里返回的就是一个新的了
21.实现new 是this和原型链的知识点
步骤
-
创建一个新的空的对象
-
将构造函数的作用域赋给新对象(因此this就指向了这个新对象) 就是连上原型链
-
执行构造函数中的代码(为这个新对象添加属性)
-
如果这个函数有返回值 有的时候会返回一个对象,则返回;否则,就会默认返回新对象
function shinew () {
//不能用new 要用下面这个代替
var lian = Array.prototype.shift.apply(arguments);
//这里的意思是arguments 用一下原型的shift方法
返回参数中的第一个,参数中的第一个就是构造函数
var obj = Object.create(lian.prototype);
//将构造函数的原型链指过去 这里和下面一样
// var obj = new Object();
// obj.__proto__ = lian.prototype
var result = lian.apply(obj, arguments);
//这里是执行构造函数中的代码(为这个新对象添加属性)。
this指向新对象 参数就是 传过来的参数们
//这里是在执行构造函数 然后改变了this指向
//这里的result是构造函数的返回值
还是要判断的
return result instanceof Object ? result : obj;
//如果该构造函数没有返回对象,则返回 this。
( 实际是返回一个空对象,
new Object()就是返回一个空对象{} )
//构造函数返回值得特性
//没有 return 语句,返回 this,即实例对象
//有 return 语句,且返回值为基本数据类型,
构造函数会忽略 return 的值,依然返回 this 对象
//有 return 语句,且返回值为引用数据类型,
构造函数会返回 return 的值 }
const Fun = function (name) {
this.name = name;
return name };
console.log(shinew(Fun, '小明'));
22.跨域
1.jsonp 就是 json with padding的缩写 只支持get请求
$.ajax ({
url: url,
dataType: 'jsonp',
success: function (data)
{
alert("身高: " + data.height + ", 体重: " + data.weight);
},});
2.CORS是Cross-Origin -Resource -Sharing的缩写。
if (req.url === '/data') {
res.setHeader('Access-Control-Allow-Origin', '*'); //这里的*就是可以访问所有的 就可以 res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With');
res.setHeader('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS'); res.setHeader('Content-Type', 'text/json');
res.writeHead('200', 'OK');
res.end(JSON.stringify(data));
}
3.反向代理 就是呗 这个同源限制是浏览器的限制 不是服务器的限制
反向代理是利用代理服务器接收到请求之后,转发给真正的服务器,并把结果返回到浏览器上。
module.exports =
{ devServer: {
proxy: { '^/':
{
target: 'http://localhost:8082/' // api server
}
}
}
}
23.代码执行过程
声明提升
先看有无函数,函数整体会提前,如果有重复的就会覆盖,
再看变量,变量知识声明提升,赋的值会留在原地,如果有重复的就会忽略。
24.解决setTimeout中的this指向问题
1.将当前对象的this存为一个变量,定时器内的函数利用闭包来访问这个变量,如下
function(){
let that =this
setTimeout(function(){
console.log(that.num); //利用闭包访问that,that是一个指向obj的指针
}, 1000)
}
2.利用bind()方法
setTimeout(function(){
console.log(this.num);
}.bind(this), 1000) //利用bind()将this绑定到这个函数上
3.箭头函数
setTimeout(() => {
console.log(this.num);
}, 1000) //箭头函数中的this总是指向外层调用者,也就是Obj
4.利用 setTimeout 函数的第三个参数,会作为回调函数的第一个参数传入
for (var i = 0; i < 10; i++) {
setTimeout(i => {
console.log(i);
}, 1000, i)
}
5.利用函数自执行的方式,把当前 for 循环过程中的 i 传递进去,构建出块级作用域
for (var i = 0; i < 10; i++) {
(i => {
setTimeout(() => {
console.log(i);
}, 1000)
})(i)
}
6.很多其它的方案只是把 console.log(i) 放到一个函数里面,因为 setTimeout 函数的第一个参数只接受函数以及字符串,如果是 js 语句的话,js 引擎应该会自动在该语句外面包裹一层函数
for (var i = 0; i < 10; i++) {
setTimeout((() => {
console.log(i);
})(), 1000)
}