宏任务与微任务
-
宏任务: Ajax Dom事件setTimeout/setInterval;UI rendering/UI事件;postMessage,MessageChannel. setImmediate,I/O(Node.js)
-
微任务:promise async/await Promise2.process.nextTick(Node.js); Object.observe(已废弃;Proxy 对象替代);MutaionObserver [ www.jb51.net/article/215…]
`setTimeout(() => {`
` ``console.log(``'定时器'``);`
` }, ``0``)`
`new` `Promise((resolve) => {`
` ``console.log(``'同步代码'``) `
` ``resolve(``'异步代码'``)`
`}).then((res) => {`
` ``console.log(res); `
`})`
`console.log(``'奥特曼'``);`
new Promise是创建一个构造函数 这个过程是同步的,而.then方法是异步的 所以代码先执行 同步>微任务>宏任务
js中的事件循环机制
JavaScript是单线程指的是同一时间只能干一件事情,只有前面的事情执行完,才能执行后面的事情。导致遇到耗时的任务时后面的代码无法执行。 同步任务 顺序执行
1.任务队列中 分为两大类 1.同步任务 2. 异步任务 2.同步代码>异步代码
- 执行栈 (1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。 (3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步,称为事件循环(Event Loop)。
`console.log(``'1'``);`
`setTimeout(function () {`
` ``console.log(``'2'``);`
` ``process.nextTick(function () {`
` ``console.log(``'3'``);`
` ``})`
` ``new` `Promise(function (resolve) {`
` ``console.log(``'4'``);`
` ``resolve();`
` ``}).then(function () {`
` ``console.log(``'5'``)`
` ``})`
`})`
`process.nextTick(function () {`
` ``console.log(``'6'``);`
`})`
`new` `Promise(function (resolve) {`
` ``console.log(``'7'``);`
` ``resolve();`
`}).then(function () {`
` ``console.log(``'8'``)`
`})`
`setTimeout(function () {`
` ``console.log(``'9'``);`
` ``process.nextTick(function () {`
` ``console.log(``'10'``);`
` ``})`
` ``new` `Promise(function (resolve) {`
` ``console.log(``'11'``);`
` ``resolve();`
` ``}).then(function () {`
` ``console.log(``'12'``)`
` ``})`
`})`
第一轮 执行外面同步代码 : 1 7
第二轮 执行 微任务 : 6 8
第三轮 宏任务 第一个setTimeout : 同步 2 4 微任务 3 5 第二个setTimeout:同步 9 11 微任务 10 12
整体答案: 1、7 、6、8、2、4、3、5、9、11、10、12
js的数据类型有哪些
1.基本类型 String、Number、Boolean、Null、Undefined、symbol(ES6)
2.引用类型:Object、Array、Date、Function、Error、RegExp、Math、Number、String、Boolean、Globle。
3.js内置类型有七种:String、Number、Boolean、Null、Undefined、Symbol(ES6)、Object [blog.csdn.net/qq_45471661…]
判断数据类型的方式
主要有四种常用方式 数据类型判断大概有四种typeof、instanceof、constructor、Object.prototype.toString.call()
1.typeof 基本数据类型中:Number,String,Boolean,undefined 以及引用数据类型中Function ,可以使用typeof检测数据类型,分别返回对应的数据类型小写字符。typeof 返回一个表示数据类型的字符串,返回结果包括:number、boolean、string、object、undefined、function、Symbol6种数据类型。
另:用typeof检测构造函数创建的Number,String,Boolean都返回object
基本数据类型中:null 。引用数据类型中的:Array,Object,Date,RegExp。不可以用typeof检测。都会返回小写的object
2 . instanceof 除了使用typeof来判断,还可以使用instanceof。instanceof运算符需要指定一个构造函数,或者说指定一个特定的类型,它用来判断这个构造函数的原型是否在给定对象的原型链上。(判断是否是某个类的实例)
3.constructor
constructor是prototype对象上的属性,指向构造函数。根据实例对象寻找属性的顺序,若实例对象上没有实例属性或方法时,就去原型链上寻找,因此,实例对象也是能使用constructor属性的
4 . 使用Object.prototype.toString.call()检测对象类型
可以通过toString() 来获取每个对象的类型。为了每个对象都能通过 Object.prototype.toString() 来检测,需要以 Function.prototype.call() 或者 Function.prototype.apply() 的形式来调用,传递要检查的对象作为第一个参数,称为thisArg。
//基本数据类型是没有检测出他们的类型
var str = 'hello';
alert(str instanceof String);//false
var bool = true;
alert(bool instanceof Boolean);//false
var num = 123;
alert(num instanceof Number);//false
var nul = null;
alert(nul instanceof Object);//false
var und = undefined;
alert(und instanceof Object);//false
var oDate = new Date();
alert(oDate instanceof Date);//true
var json = {};
alert(json instanceof Object);//true
var arr = [];
alert(arr instanceof Array);//true
var reg = /a/;
alert(reg instanceof RegExp);//true
var fun = function(){};
alert(fun instanceof Function);//true
var error = new Error();
alert(error instanceof Error);//true
//使用下面的方式创建num、str、boolean,是可以检测出类型
var num = new Number(123);
var str = new String('abcdef');
var boolean = new Boolean(true);
console.log(num instanceof Number)
console.log(num instanceof String)
- constructor:查看对象对应的构造函数
toString是Object原型对象上的一个方法,该方法默认返回其调用者的具体类型,更严格的讲,是 toString运行时this指向的对象类型, 返回的类型格式为[object xxx],xxx是具体的数据类型,其中包括:String,Number,Boolean,Undefined,Null,Function,Date,Array,RegExp,Error,HTMLDocument,… 基本上所有对象的类型都可以通过这个方法获取到
var str = 'hello';
console.log(Object.prototype.toString.cal(str));//[object String]
var bool = true;
console.log(Object.prototype.toString.cal(bool))//[object Boolean]
var num = 123;
console.log(Object.prototype.toString.cal(num));//[object Number]
var nul = null;
console.log(Object.prototype.toString.cal(nul));//[object Null]
var und = undefined;
console.log(Object.prototype.toString.cal(und));//[object Undefined]
var oDate = new Date();
console.log(Object.prototype.toString.cal(oDate));//[object Date]
var json = {};
console.log(Object.prototype.toString.cal(json));//[object Object]
var arr = [];
console.log(Object.prototype.toString.cal(arr));//[object Array]
var reg = /a/;
console.log(Object.prototype.toString.cal(reg));//[object RegExp]
var fun = function(){};
console.log(Object.prototype.toString.cal(fun));//[object Function]
var error = new Error();
console.log(Object.prototype.toString.cal(error));//[object Error]
apply bind call区别
1.call 方法第一个参数是要绑定给this的值,后面传入的是一个参数列表,当第一个参数为null、undefined的时候,默认指向window Fun.call(obj,'arg1', 'arg2')
2.apply接受两个参数,第一个参数是要绑定给this的值,第二个参数是一个参数数组。当第一个参数为null、undefined的时候,默认指向window Fun.apply(obj,['arg1', 'arg2'])
3.bind接受两个参数,第一个参数是要绑定给this的值,第二个参数是一个参数数组,这一点与apply相同。
区别 相同点:call() /apply() /bind() 都可以改变this指向,指向第一个参数 不同点:bind()需要手动执行
应用场景 (1)获取函数的参数数组
(2)对象冒充调用
function Box(name, age) { //构造函数模式
this.name = name;
this.age = age;
this.run = function() {
return this.name + this.age + '运行中...';
};
}
var box = new Box('Lee', 100); //构造模式调用
alert(box.run());
var o = new Object();
Box.call(o, 'Jack', 200) //对象冒充调用
alert(o.run());
call、apply都是立即调用。bind 方法不会立即执行,而是返回一个改变了上下文 this 后的函数,便于稍后调用。而原函数 中的 this 并没有被改变,依旧指向原来该指向的地方。bind应用场景:给参数指定默认参数、绑定构造函数
闭包
概念
- 闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包的常见的方式,就是在 一个函数内部创建另一个函数,通过另一个函数访问这个函数的局部变量。
理解闭包,首先理解javascript特殊的变量作用域,变量的作用于无非就是两种:全局变量,局部变量。
javascript语言的特殊处就是函数内部可以读取外部作用域中的变量。
我们有时候需要得到函数内的局部变量,但是在正常情况下,这是不能读取到的,这时候就需要用到闭包。在javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。闭包是指有权访问另一个函数作用域中的变量的函数。其本质是函数的作用域链中保存着外部函数变量对象的引用。
优缺点
- 优点:闭包因为长期驻扎在内存中。可以重复使用变量,不会造成变量污染
- 缺点:闭包会使函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,可能会导致内存泄露。解决方法是在退出函数之前,将不使用的变量全部删除。
应用场景
①函数作为参数被传递 ②函数作为返回值被返回 ③实际应用(隐藏数据):为什么说隐藏数据了呢,因为普通用户只能通过get、set等api对数据进行查看和更改等操作,没法对data直接更改,达到所谓隐藏数据的效果;jquery就利用了这一特性,必须调用$.ajax()才能访问内部属性方法。 封装功能时(需要使用私有的属性和方法), 函数防抖、函数节流
有时候需要用到函数内的局部变量,在正常情况下是不能读取到的,这个时候就需要用到闭包。
闭包可以封装对象的私有属性和方法,就是可以简单的理解成闭包就是一个私有作用域,可以定义属性和方法,vue中的data就是一种闭包的形式。
实例(封装对象的私有属性和方法)
隐藏数据 做一个简单的缓存工具
// 闭包隐藏数据,只提供 API
function createCache() {
const num=100
const data = {} // 闭包中的数据,被隐藏,不被外界访问
return {
num:num,
set: function (key, val) {
data[key] = val
},
get: function (key) {
return data[key]
}
}
}
const c = createCache()
console.log(c.num)//num此时就作为c私有属性
c.set('a', 100) //set此时作为c的私有方法
console.log( c.get('a') )
闭包作为回调函数,可以实现函数的复用
示例
<body>
<a href="#" id="as1">20</a>
<a href="#" id="as2">40</a>
</body>
<script>
function changeSize(size){
return function(){
document.body.style.fontSize=size+'px';
}
}
var size20=changeSize(20);
var size40=changeSize(40);
document.getElementById('as1').onclick=size20;
document.getElementById('as2').onclick=size40;
</script>
闭包的优点
(一)变量长期驻扎在内存中
(二)另一个就是可以重复使用变量,并且不会造成变量污染
①全局变量可以重复使用,但是容易造成变量污染。不同的地方定义了相同的全局变量,这样就会产生混乱。”
②局部变量仅在局部作用域内有效,不可以重复使用,不会造成变量污染。
③闭包结合了全局变量和局部变量的优点。可以重复使用变量,并且不会造成变量污染
闭包的缺点
由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
示例1.
//f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
function f1(){
var n=999;
nAdd=function(){n+=1}
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
nAdd();
result(); // 1000
函数节流和防抖
函数节流与函数防抖的区别:我们以一个案例来讲一下它们之间的区别:设定一个间隔时间为一秒,在一分钟内,不断的移动鼠标,让它触发一个函数,打印一些内容。函数防抖:会打印1次,在鼠标停止移动的一秒后打印。函数节流:会打印60次,因为在一分钟内有60秒,每秒会触发一次。总结:节流是为了限制函数的执行次数,而防抖是为了限制函数的执行时机。
函数节流:是确保函数特定的时间内至多执行一次。
函数防抖:是函数在特定的时间内不被再调用后执行
函数防抖
指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。通俗一点:在一段固定的时间内,只能触发一次函数,在多次触发事件时,只执行最后一次。
函数防抖是指在函数被高频触发时当停止触发后延时n秒再执行函数,(即每次触发都清理延时函数再次开始计时),一般用于resize scroll,mousemove
防抖例子:搜索功能,在用户输入结束以后才开始发送搜索请求,可以使用函数防抖来实现;
/**
* @function debounce 函数防抖
* @param {Function} fn 需要防抖的函数
* @param {Number} interval 间隔时间
* @return {Function} 经过防抖处理的函数
* */
function debounce(fn, interval) {
let timer = null; // 定时器
return function() {
// 清除上一次的定时器
clearTimeout(timer);
// 拿到当前的函数作用域
let _this = this;
// 拿到当前函数的参数数组
let args = Array.prototype.slice.call(arguments, 0);
// 开启倒计时定时器
timer = setTimeout(function() {
// 通过apply传递当前函数this,以及参数
fn.apply(_this, args);
// 默认300ms执行
}, interval || 300)
}
}
<body>
<input id="input" class="ipt" type="text">
</body>
<script>
// var ipt = document.getElementById('input')
var ipt = document.getElementsByTagName('input')[0]
// var ipt = document.querySelector('#input')
// var ipt = document.querySelector('.ipt')
//防抖
var debounce = function(func, delay) {
var timer = null
return function() {
var that = this;
var args = arguments;
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(function() {
func.apply(that, args);
}, delay)
}
}
ipt.addEventListener('keyup', debounce(function(e) {
console.log(e.target.value);
}, 400))
</script>
节流
节流:在一段时间内,只能触发一次函数。 做法:触发函数时判断是否到达了指定时间,如果到达了指定时间,执行;否则不执行弹出警告 防抖与节流的常见应用:
函数节流 原理 函数被高频出发时延时n秒后才会再次执行,防抖主要是用户触发一次时间后,延迟一段时间触发, 而节流会规定的事件内触发一次事件
节流例子:在input输入框中执行输入搜索事件;滚动条scroll事件
//就是限制一个函数在一定时间内只能执行一次。
//改变浏览器窗口尺寸,可以使用函数节流,避免函数不断执行;滚动条scroll事件,通过函数节流,避免函数不断执行。
/**
* @function throttle 函数节流
* @param {Function} fn 需要节流的函数
* @param {Number} interval 间隔时间
* @return {Function} 经过节流处理的函数
* */
function throttle(fn, interval) {
let timer = null; // 定时器
let firstTime = true; // 判断是否是第一次执行
// 利用闭包
return function() {
// 拿到函数的参数数组
let args = Array.prototype.slice.call(arguments, 0);
// 拿到当前的函数作用域
let _this = this;
// 如果是第一次执行的话,需要立即执行该函数
if(firstTime) {
// 通过apply,绑定当前函数的作用域以及传递参数
fn.apply(_this, args);
// 修改标识为null,释放内存
firstTime = null;
}
// 如果当前有正在等待执行的函数则直接返回
if(timer) return;
// 开启一个倒计时定时器
timer = setTimeout(function() {
// 通过apply,绑定当前函数的作用域以及传递参数
fn.apply(_this, args);
// 清除之前的定时器
timer = null;
// 默认300ms执行一次
}, interval || 300)
}
}
this
调用场景 1.普通函数 (this指向window)
function fn() {
console.log(this);
}
fn(); //相当于下面的window.fn();
window.fn();
// window调用了fn,所以this指向window
2.对象方法中this(this指向调用者)
对象方法中的this,指向当前对象(因为当前对象执行了方法)。
let pox = {
name: '小红',
run: function() {
console.log(this.name) //this
}
}
pox.run(); // pox 小红
//pox调用的run,所以run方法中的this指向pox
- call() /apply() /bind() 改变this指向(对象方法中的this.指向方法的调用者)
let pox={
name:'小红',
run:function(){
console.log(this.name)
}
}
// 对象方法中的this.指向方法的调用者。
pox.run();// pox 小红
pox.run.call(obj)// 小明
pox.run.apply(obj);// 小明
pox.run.bind(obj)();//小明
- class中的this指向new 后的实例对象
class中的 this时刻指向父级的上下文对象。并且不可以被 call()/apply()/bind()修改。
class Person{
constructor(name,age){
this.name=name;
this.age=age
}
say(){
console.log(`我叫${this.name}年龄是${this.age}`)
}
}
let lsy=new Person('web',21);
lsy.say();// 我叫web年龄是21
console.log(lsy);// {name:'web',age:21}
- 箭头函数没有自己的
this
对象
普通函数指向运行时所在的对象,箭头函数指向定义时上层作用域中的,
this
,箭头函数内部的this
指向是固定的,相比之下,普通函数的this
指向是可变的。箭头函数根本没有自己的
this
,导致内部的this
就是外层代码块的this
。正是因为它没有this
,所以也就不能用作构造函数。
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
var id = 21;
foo.call({ id: 42 });
// id: 42
setTimeout()
的参数是一个箭头函数,这个箭头函数的定义生效是在foo
函数生成时,而它的真正执行要等到 100 毫秒后。如果是普通函数,执行时this
应该指向全局对象window
,这时应该输出21
。但是,箭头函数导致this
总是指向函数定义生效时所在的对象(本例是{id: 42}
),所以打印出来的是42
。`
6.setTimeout中的this
setTimeout函数中的this,相当于普通函数中的this,因为setTimeout触发的函数执行,并不是外部对象执行的。
setTimeout中函数是箭头函数,this为当前对象。因为箭头函数中的this始终是父级上下文中的this.
总结
普通函数中调用,this指向window
对象方法中调用,this指向当前对象
call apply bind中调用, this指向被传入的对象
class中的方法中调用, this指向实例对象
箭头函数,this就是父级上下文中的this
原型与原型链
ECMAScript5篇查看 : juejin.cn/post/712313…
prototype 每一个函数都会有prototype属性,被称为显式原型。
proto 每一个实例对象都会有__proto__属性,其被称为隐式原型。
每个函数function都有一个prototype,即原型。每个对象都有一个__proto__
,为隐式原型。
如上图,每个对象都有一个__proto__
属性,指向创建该对象的函数的prototype。
获取对象时,如果这个对象身上本身没有这个属性时,它就会去他的原型__proto__上去找,如果还找不到,就去原型的原型上去找…一直找到最顶层(Object.prototype)为止,Object.prototype对象也有__proto__属性值为null。
instanceof的判断规则往下面的例子看
function Foo(){};
var f1=new Foo();
alert(f1 instanceof Foo) //会返回true
然后instanceof的判断规则就是:instanceof运算符的第一个变量是一个对象,暂时称为A;第二个变量一般是函数,称为B。沿着A的
__proto__
这条线来找,同时沿着B的prototype这条线来找,如果两条线能找到同一个引用,就是返回true,如果找到终点都没有重合就返回false。
基本类型和引用类型的区别
1.基本类型和引用类型的值
ECMAScript 变量可能包含两种不同的数据类型的值:基本类型值和引用类型值。基本 类型值指的是那些保存在栈内存中的简单数据段,即这种值完全保存在内存中的一个位置。 而引用类型值则是指那些保存在堆内存中的对象,意思是变量中保存的实际上只是一个指 针,这个指针指向内存中的另一个位置,该位置保存对象。 将一个值赋给变量时,解析器必须确定这个值是基本类型值,还是引用类型值。基本类 型值有以下几种:Undefined、Null、Boolean、Number 和 String。这些类型在内存中分别占 有固定大小的空间,他们的值保存在栈空间,我们通过按值来访问的。 PS:在某些语言中,字符串以对象的形式来表示,因此被认为是引用类型。ECMAScript 放弃这一传统。 如果赋值的是引用类型的值,则必须在堆内存中为这个值分配空间。由于这种值的大小 不固定,因此不能把它们保存到栈内存中。但内存地址大小的固定的,因此可以将内存地址 保存在栈内存中。这样,当查询引用类型的变量时,先从栈中读取内存地址,然后再通过地 址找到堆中的值。对于这种,我们把它叫做按引用访问。
区别 1.引用类型有动态属性,基本类型会报错
box = new Object(); //创建引用类型
box.name = 'Lee'; //新增一个属性
alert(box.name); //输出
var box = 'Lee'; //创建一个基本类型
box.age = 27; //给基本类型添加属性
alert(box.age); //undefined
2.变量复制时,基本类型复制的是值本身,而引用 类型复制的是地址。
var box = 'Lee'; //在栈内存生成一个 box 'Lee'
var box2 = box; //在栈内存再生成一个 box2 'Lee
在引用类型中,box2 其实就是 box,因为他们指向的是同一个对象。如果这个对象中的 name 属性被修改了,box2.name 和 box.name 输出的值都会被相应修改掉了。
var box = new Object(); //创建一个引用类型
box.name = 'Lee'; //新增一个属性
var box2 = box; //把引用地址赋值给 box2
3.:当使用 instanceof 检查基本类型的值时,它会返回 false
var num = 123; alert(num instanceof Number);//false
var box = [1, 2, 3];alert(box instanceof Array); //是否是数组true
4.instanceof可以判断一个变量是否为数组
let obj = {'name': 'kankk'};
let arr = [1, 2];
console.log(obj instanceof Object); // true
console.log(obj instanceof Array); // false
console.log(arr instanceof Object); // true
console.log(arr instanceof Array); // true
执行环境及作用域及作用域链
执行环境
执行环境是JavaScript 中最为重要的一个概念。执行环境定义了变量或函数有权访问的 其他数据,决定了它们各自的行为。 全局执行环境是最外围的执行环境。在 Web 浏览器中,全局执行环境被认为是 window 对象。因此所有的全局变量和函数都是作为 window 对象的属性和方法创建的。
作用域
作用域就是一个变量可以使用的范围,主要分为全局作用域和函数作用域和块级作用域
全局作用域就是Js中最外层的作用域
函数作用域是js通过函数创建的一个独立作用域,函数可以嵌套,所以作用域也可以嵌套
Es6中新增了块级作用域(由大括号包裹,比如:if(){},for(){}等)
作用域链
当前作用域外的变量都是自由变量,一个变量在当前作用域没有定义,但是被使用了,就会向上级作用域,一层一层依次查找,直至找到为止,找到这个变量后就会停止,不会继续查找这个变量,如果全局作用域都没有找到这个变量就会报错。这个自由变量查找的过程就是作用域链。
var box = 'blue'; //声明一个全局变量
function setBox() {
alert(box); //全局变量可以在函数里访问
}
setBox(); //执行函数
PS:当执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和 函数定义也随之销毁。如果是全局环境下,需要程序执行完毕,或者网页被关闭才会销毁。 PS:每个执行环境都有一个与之关联的变量对象,就好比全局的 window 可以调用变量 和属性一样。局部的环境也有一个类似 window 的变量对象,环境中定义的所有变量和函数 都保存在这个对象中。(我们无法访问这个变量对象,但解析器会处理数据时后台使用它)
函数里的局部作用域里的变量替换全局变量,但作用域仅限在函数体内这个局部环境。
var box = 'blue';
function setBox() {
var box = 'red'; //这里是局部变量,出来就不认识了
alert(box);
}
setBox();
alert(box);
通过传参,可以替换函数体内的局部变量,但作用域仅限在函数体内这个局部环境
var box = 'blue';
function setBox(box) { //通过传参,替换了全局变量
alert(box);
}
setBox('red');
alert(box);
函数体内还包含着函数,只有这个函数才可以访问内一层的函
var box = 'blue';
function setBox() {
function setColor() {
var b = 'orange';
alert(box);
alert(b);
}
setColor(); //setColor()的执行环境在 setBox()内
}
setBox();
每个函数被调用时都会创建自己的执行环境。当执行到这个函数时,函数的环境 就会被推到环境栈中去执行,而执行后又在环境栈中弹出(退出),把控制权交给上一级的执 行环境。
当代码在一个环境中执行时,就会形成一种叫做作用域链的东西。它的用途是保 证对执行环境中有访问权限的变量和函数进行有序访问。作用域链的前端,就是执行环境的 变量对象。 变量查询中,访问局部变量要比全局变量更快,因为不需要向上搜索作用域链。
没有块级作用域 块级作用域表示诸如 if 语句等有花括号封闭的代码块,所以,支持条件判断来定义变 量。
if (true) { //if 语句代码块没有局部作用域
var box = 'Lee';
}
alert(box)
for 循环语句也是如此
for (var i = 0; i < 10; i++) { //没有局部作用域
var box = 'Lee';
}
alert(i);//10
alert(box);//Lee
let var const 区别
变量提升
每个var声明的变量,function声明的函数存在变量提升。let const不存在变量提升 var声明的变量,function声明的函数存在变量提升
let const 不会变量提升
在js中声明之前未定义,会在js的最上方会形成一个预解析池,用来存储声明了但没有先定义的变量名
let
1.var命令会发生“变量提升”现象,即变量可以在声明之前使用,值为undefined
// var 的情况
console.log(foo); // 输出undefined
var foo = 2;
// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;
2.只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。隐蔽的死区问题
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
上面代码中,存在全局变量tmp
,但是块级作用域内let
又声明了一个局部变量tmp
,导致后者绑定这个块级作用域,所以在let
声明变量前,对tmp
赋值会报错。
3.不允许重复声明(let
不允许在相同作用域内,重复声明同一个变量)
// 报错
function func() {
let a = 10;
var a = 1;
}
// 报错
function func() {
let a = 10;
let a = 1;
}
4.为什么需要块级作用域 1.内层变量会覆盖外层变量
`if`代码块的外部使用外层的`tmp`变量,内部使用内层的`tmp`变量。但是,函数`f`执行后,输出结果为`undefined`,原因在于变量提升,导致内层的`tmp`变量覆盖了外层的`tmp`变量。
var tmp = new Date();
function f() {
console.log(tmp);
if (false) {
var tmp = 'hello world';
}
}
f(); // undefined
- 用来计数的循环变量泄露为全局变量。
变量`i`只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。
var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5
let块级作用域
函数有两个代码块,都声明了变量`n`,运行后输出 5。这表示外层代码块不受内层代码块的影响。如果两次都使用`var`定义变量`n`,最后输出的值才是 10。
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}
var
1 var声明作用域
var定义的变量,没有块的概念,可以跨块访问, 不能跨函数访问
function test() {
var message = "hello world"; // 局部变量
}
test();
console.log(message); // 报错
//函数test()调用时会创建变量message并给它赋值,调用之后变量随即被销毁。因此,在函数test()之外调用变量message会报错
//在函数内定义变量时省略var操作符,可以创建一个全局变量
function test() {
message = "hello world"; // 局部变量
}
test();
console.log(message); // hello world
//省略掉var操作符之后,message就变成了全局变量。只要调用一次函数test(),就会定义这个变量,并且可以在函数外部访问到。在局部作用域中定义的全局变量很难维护,不推荐这么做。在严格模式下,如果像这样给未声明的变量赋值,则会导致抛出ReferenceError。
2.var 声明提升 var在js中是支持预解析的,如下代码不会报错。这是因为使用var声明的变量会自动提升到函数作用域顶部:
function foo() {
console.log(age);
var age = 26;
}
foo(); // undefined
//javaScript引擎,在代码预编译时,javaScript引擎会自动将所有代码里面的var关键字声明的语句都会提升到当前作用域的顶端,如下代码:
function foo() {
var age;
console.log(age);
age = 26;
}
foo(); // undefined
会把声明提前,以下会先打印出undefined,再打印出10**
console.log(a)
var a = 10
console.log(a)
相当于
var a
console.log(a);//undefined
a = 10
console.log(a) //10
函数声明也是
**相当于把整个fn提到作用域的最上面,所以调用fn时会正常打印jack
fn('jack');//jack
function fn (name){
console.log(name)
}
函数表达式不行
**函数表达式,JavaScript会把var fn提到作用域最上面,没有吧函数提上去,所以会报错。**
fn("jack");//报错
var fn = function(name) {
console.log(name);
};
- 全局声明
var name = 'Matt';
console.log(window.name); // 'Matt'
let age = 26;
console.log(window.age); // undefined
变量提升
概念的字面意义上说,变量提升 意味着变量和函数的声明会在物理层面移动到代码的最前面,但这么说并不准确。实际上变量和函数声明在代码里的位置是不会动的,而是在编译阶段被放入内存中时变动。
- 函数和变量相比,会被优先提升。这意味着函数会被提升到更靠前的位置。
// Example 1 - 变量 y 被提升了,但是它的初始化没有
var x = 1; // 声明 + 初始化 x
console.log(x + " " + y); // 输出:'1 undefined'
var y = 2; // 声明 + 初始化 y
// Example 2 - 先赋值后声明也能连名带值被提升
var num1 = 3; // Declare and initialize num1
num2 = 4; // Initialize num2
console.log(num1 + " " + num2); // 输出:'3 4'
var num2; // Declare num2 for hoisting
// Example 3 - 同理
a = 'Cran'; // Initialize a
b = 'berry'; // Initialize b
console.log(a + " " + b); // 输出:'Cranberry'
var a, b; // Declare both a & b for hoisting
js的垃圾回收机制
一、 垃圾回收方式通常有两种方式
- 标记清除(mark and sweep) 这是JavaScript中最常用的垃圾回收方式。
(1)当变量进入执行环境时(函数中声明变量),就标记这个变量为“进入环境”,当变量离开环境时(函数执行结束),则将其标记为“离开环境”,离开环境之后还有的变量则是需要被删除的变量。
(2)垃圾回收器在运行的时候会给存储在内存中的所有变量都加上标记。
(3)去掉环境中的变量以及被环境中变量引用的变量的标记。
(4)之后再被加上标记的变量即是需要回收的变量(因为环境中的变量已经无法访问到这些变量)
(5)最后,垃圾收集器完成内存清除工作,销毁那些带标记的值,并回收他们所占用的内存空间。
- 引用计数(reference counting) 这种方式常常会引起内存泄漏,低版本的IE使用这种方式。机制就是跟踪一个值的引用次数,当声明一个变量并将一个引用类型赋值给该变量时该值引用次数加1,当这个变量指向其他一个时该值的引用次数便减一。当该值引用次数为0时,则说明没有办法再访问这个值了,被视为准备回收的对象,每当过一段时间开始垃圾回收的时候,就把被引用数为0的变量回收。引用计数方法可能导致循环引用,类似死锁,导致内存泄露。
示例
//objA和objB相互引用,两个对象的引用次数都是2。函数执行完成之后,objA和objB还将会继续存在,因为他们的引用次数永远不会是0。这样的相互引用如果说很大量的存在就会导致大量的内存泄露。
function problem() {
var objA = new Object();
var objB = new Object();
objA.someOtherObject = objB;
objB.anotherObject = objA;
}
二、 常见内存泄漏的原因
(1)全局变量引起的内存泄露
(2)闭包引起的内存泄露:慎用闭包
(3)dom清空或删除时,事件未清除导致的内存泄漏
(4)循环引用带来的内存泄露
深拷贝和浅拷贝
- 浅拷贝只是拷贝一层,更深层次对象级别的只拷贝引用。
- 浅拷贝是拷贝的内存地址,使新对象指向拷贝对象的内存地址。深拷贝是重新开辟一块内存空间,用来存放sources对象的值。
- 浅拷贝后改变target中的值,sources也会进行改变。深拷贝不会这样
一、浅拷贝的方式 它们的成员都是对原数组成员的引用,这就是浅拷贝 (1)concat
const a1 = [{ foo: 1 }];
const a2 = [{ bar: 2 }];
const a3 = a1.concat(a2);
(2)ES6的解构
const a1 = [{ foo: 1 }];
const a2 = [{ bar: 2 }];
const a4 = [...a1, ...a2];
(3)slice
let arr = [{
name:'xkx',
age:18
}]
let newarr = arr.slice(0,1)
(4) object.assign
Object.assign()
: 忽略enumerable
为false
的属性,只拷贝对象自身的可枚举的属性。
let aClone = { ...a };
// 等同于
let aClone = Object.assign({}, a);
(5) for in 遍历循环
function simpleClone(initalObj) {
var obj = {}
for (var i in initalObj) {
obj[i] = initalObj[i]
}
return obj
}
let obj = {
name: 'xxx',
age: 18
}
console.log(simpleClone(obj))
二、深拷贝的方式 1.JSON.parse,JSON.stringify
这种方法能正确处理的对象只有 Number, String, Boolean, Array, 扁平对象,即那些能够被 json 直接表示的数据结构。RegExp对象是无法通过这种方式深拷贝。
弊端:它会抛弃对象的constructor。也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object。
function deepClone(initalObj) {
var obj = {};
try {
obj = JSON.parse(JSON.stringify(initalObj));
}
return obj;
}
2.递归拷贝 www.jb51.net/article/990…
function deepClone(initalObj, finalObj) {
var obj = finalObj || {};
for (var i in initalObj) {
if (typeof initalObj[i] === 'object') {
obj[i] = (initalObj[i].constructor === Array) ? [] : {};
arguments.callee(initalObj[i], obj[i]);
} else {
obj[i] = initalObj[i];
}
}
return obj;
}
问题:但是当遇到两个互相引用的对象,会出现死循环的情况 为了避免相互引用的对象导致死循环的情况,则应该在遍历的时候判断是否相互引用对象,如果是则退出循环。
改进版
function deepClone(initalObj, finalObj) {
var obj = finalObj || {};
for (var i in initalObj) {
var prop = initalObj[i];
// 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况
if(prop === obj) {
continue;
}
if (typeof prop === 'object') {
obj[i] = (prop.constructor === Array) ? [] : {};
arguments.callee(prop, obj[i]);
} else {
obj[i] = prop;
}
}
return obj;
}
写法2
function getType(obj) {
//tostring会返回对应不同的标签的构造函数
var toString = Object.prototype.toString;
var map = {
'[object Boolean]': 'boolean',
'[object Number]': 'number',
'[object String]': 'string',
'[object Function]': 'function',
'[object Array]': 'array',
'[object Date]': 'date',
'[object RegExp]': 'regExp',
'[object Undefined]': 'undefined',
'[object Null]': 'null',
'[object Object]': 'object'
};
if (obj instanceof Element) {
return 'element';
}
return map[toString.call(obj)];
}
function deepClone(data){
var type = getType(data);
var obj;
if(type === 'array'){
obj = [];
} else if(type === 'object'){
obj = {};
} else {
//不再具有下一层次
return data;
}
if(type === 'array'){
for(var i = 0, len = data.length; i < len; i++){
obj.push(deepClone(data[i]));
}
} else if(type === 'object'){
for(var key in data){
obj[key] = deepClone(data[key]);
}
}
return obj;
}
通过object.create改进
function deepClone(initalObj, finalObj) {
var obj = finalObj || {};
for (var i in initalObj) {
var prop = initalObj[i];
// 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况
if(prop === obj) {
continue;
}
if (typeof prop === 'object') {
obj[i] = (prop.constructor === Array) ? [] : Object.create(prop);
} else {
obj[i] = prop;
}
}
return obj;
}
写法2 对于function类型,这里是直接赋值的,还是共享一个内存值。这是因为函数更多的是完成某些功能,有个输入值和返回值,而且对于上层业务而言更多的是完成业务功能,并不需要真正将函数深拷贝。
function getType(obj) {
//tostring会返回对应不同的标签的构造函数
var toString = Object.prototype.toString;
var map = {
'[object Boolean]': 'boolean',
'[object Number]': 'number',
'[object String]': 'string',
'[object Function]': 'function',
'[object Array]': 'array',
'[object Date]': 'date',
'[object RegExp]': 'regExp',
'[object Undefined]': 'undefined',
'[object Null]': 'null',
'[object Object]': 'object'
};
if (obj instanceof Element) {
return 'element';
}
return map[toString.call(obj)];
}
function deepClone(data){
var type = getType(data);
var obj;
if(type === 'array'){
obj = [];
} else if(type === 'object'){
obj = {};
} else {
//不再具有下一层次
return data;
}
if(type === 'array'){
for(var i = 0, len = data.length; i < len; i++){
obj.push(deepClone(data[i]));
}
} else if(type === 'object'){
for(var key in data){
obj[key] = deepClone(data[key]);
}
}
return obj;
}
写法3 使用树的广度优先遍历来实现
//这里为了阅读方便,只深拷贝对象,关于数组的判断参照上面的例子
function deepClone(data){
var obj = {};
var originQueue = [data];
var copyQueue = [obj];
//以下两个队列用来保存复制过程中访问过的对象,以此来避免对象环的问题(对象的某个属性值是对象本身)
var visitQueue = [];
var copyVisitQueue = [];
while(originQueue.length > 0){
var _data = originQueue.shift();
var _obj = copyQueue.shift();
visitQueue.push(_data);
copyVisitQueue.push(_obj);
for(var key in _data){
var _value = _data[key]
if(typeof _value !== 'object'){
_obj[key] = _value;
} else {
//使用indexOf可以发现数组中是否存在相同的对象(实现indexOf的难点就在于对象比较)
var index = visitQueue.indexOf(_value);
if(index >= 0){
// 出现环的情况不需要再取出遍历
_obj[key] = copyVisitQueue[index];
} else {
originQueue.push(_value);
_obj[key] = {};
copyQueue.push(_obj[key]);
}
}
}
}
return obj;
}
3.loadsh 的deepClone方法
4.Object.create
/ 写法一
const clone1 = {
__proto__: Object.getPrototypeOf(obj),
...obj
};
// 写法二
const clone2 = Object.assign(
Object.create(Object.getPrototypeOf(obj)),
obj
);
// 写法三
const clone3 = Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
)
for in 和for of的区别
blog.csdn.net/zhaofeiweb/… blog.csdn.net/weixin_4829…
for of
优点:
- 有着同
for...in
一样的简洁语法,但是没有for...in
那些缺点。 - 不同于
forEach
方法,它可以与break
、continue
和return
配合使用。 - 提供了遍历所有数据结构的统一操作接口。
一个数据结构只要部署了Symbol.iterator
属性,就被视为具有 iterator 接口,就可以用for...of
循环遍历它的成员。也就是说,for...of
循环内部调用的是数据结构的Symbol.iterator
方法。
for...of
循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如arguments
对象、DOM NodeList 对象)、后文的 Generator 对象,以及字符串。
ES6提出的语句,在可迭代对象(Array,Map,Set,String,TypedArray,arguments)上创建一个迭代循环。
for…of只能应用于可迭代对象,即拥有[Symbol.iterator] 属性的collection对象,并不适用于所有的object.
对于普通的对象,for...of
结构不能直接使用,会报错,必须部署了 Iterator 接口后才能使用。但是,这样情况下,for...in
循环依然可以用来遍历键名。
示例:
let obj = {
name: 'aa',
age: 12,
sex: 'man'
};
for (let item of obj) {
console.log(item);
}
let es6 = {
edition: 6,
committee: "TC39",
standard: "ECMA-262"
};
for (let e in es6) {
console.log(e);
}
// edition
// committee
// standard
for (let e of es6) {
console.log(e);
}
// TypeError: es6[Symbol.iterator] is not a function
解决方式:
(1)一种解决方法是,使用Object.keys
方法将对象的键名生成一个数组,然后遍历这个数组
for (var key of Object.keys(someObject)) {
console.log(key + ': ' + someObject[key]);
}
(2)使用 Generator 函数将对象重新包装一下。
const obj = { a: 1, b: 2, c: 3 }
function* entries(obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
}
for (let [key, value] of entries(obj)) {
console.log(key, '->', value);
}
(3)空对象obj
部署了数组arr
的Symbol.iterator
属性,结果obj
的for...of
循环,产生了与arr
完全一样的结果。
const arr = ['red', 'green', 'blue'];
for(let v of arr) {
console.log(v); // red green blue
}
const obj = {};
obj[Symbol.iterator] = arr[Symbol.iterator].bind(arr);
for(let v of obj) {
console.log(v); // red green blue
}
(4)不同于forEach
方法,它可以与break
、continue
和return
配合使用。
for (var n of fibonacci) {
if (n > 1000)
break;
console.log(n);
}
(5)
for...of
循环可以代替数组实例的forEach
方法。
const arr = ['red', 'green', 'blue'];
arr.forEach(function (element, index) {
console.log(element); // red green blue
console.log(index); // 0 1 2
});
(6)
JavaScript 原有的for...in
循环,只能获得对象的键名,不能直接获取键值。ES6 提供for...of
循环,允许遍历获得键值。
var arr = ['a', 'b', 'c', 'd'];
for (let a in arr) {
console.log(a); // 0 1 2 3
}
for (let a of arr) {
console.log(a); // a b c d
}
for...in
循环读取键名,for...of
循环读取键值。如果要通过for...of
循环,获取数组的索引,可以借助数组实例的entries
方法和keys
方法
for in
for in 既可以遍历数组又可以遍历对象 缺点:
- 数组的键名是数字,但是
for...in
循环是以字符串作为键名“0”、“1”、“2”等等。 for...in
循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键。- 某些情况下,
for...in
循环会以任意顺序遍历键名。
let arr = [{
name: 'aa',
age: 12
}, {
name: 'bb',
age: 13
}, {
name: 'cc',
age: 14
}];
for (let item in arr) {
console.log(item);
}
对象遍历属性
let obj = {
name: 'aa',
age: 12,
sex: 'man'
};
for (let item in obj) {
console.log(item);
}
区别
1.for…in 遍历(当前对象及其原型上的)每一个属性名称,for…of遍历(当前对象上的)每一个属性值,如果获取数组的索引,可以借助数组实例的entries
方法和keys
方法
var arr = ['a', 'b', 'c', 'd'];
for (let a in arr) {
console.log(a); // 0 1 2 3
}
for (let a of arr) {
console.log(a); // a b c d
}
2.for…in循环会遍历一个object所有的可枚举属性。
(1)对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor
方法可以获取该属性的描述对象。
let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
// {
// value: 123,
// writable: true,
// enumerable: true,
// configurable: true
// }
描述对象的enumerable
属性,称为“可枚举性”,如果该属性为false
,就表示某些操作会忽略当前属性。
有四个操作会忽略enumerable
为false
的属性。
for...in
循环:只遍历对象自身的和继承的可枚举的属性。Object.keys()
:返回对象自身的所有可枚举的属性的键名。JSON.stringify()
:只串行化对象自身的可枚举的属性。Object.assign()
: 忽略enumerable
为false
的属性,只拷贝对象自身的可枚举的属性。
这四个操作之中,前三个是 ES5 就有的,最后一个Object.assign()
是 ES6 新增的。其中,只有for...in
会返回继承的属性,其他三个方法都会忽略继承的属性,只处理对象自身的属性。引入“可枚举”(enumerable
)这个概念的最初目的,就是让某些属性可以规避掉for...in
操作,不然所有内部属性和方法都会被遍历到。比如,对象原型的toString
方法,以及数组的length
属性,就通过“可枚举性”,从而避免被for...in
遍历到。
3.for…of只能应用于可迭代对象,即拥有[Symbol.iterator] 属性的collection对象,并不适用于所有的object.
4.for...of
循环调用遍历器接口,数组的遍历器接口只返回具有数字索引的属性。这一点跟for...in
循环也不一样。
`for...of`循环不会返回数组`arr`的`foo`属性。
let arr = [3, 5, 7];
arr.foo = 'hello';
for (let i in arr) {
console.log(i); // "0", "1", "2", "foo"
}
for (let i of arr) {
console.log(i); // "3", "5", "7"
}
ES6 一共有 5 种方法可以遍历对象的属性及区别
developer.mozilla.org/zh-CN/docs/…
(1)for...in
for...in
循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。
var triangle = {a:1, b:2, c:3};
function ColoredTriangle() {
this.color = "red";
}
ColoredTriangle.prototype = triangle;
var obj = new ColoredTriangle();
for (var prop in obj) {
console.log("o." + prop + " = " + obj[prop]);
}
//原型上的可枚举数据也被迭代出来
//o.color = red
//o.a = 1
//o.b = 2
//o.c = 3
//使用hasOwnProperty()
for (var prop in obj) {
if( obj.hasOwnProperty( prop ) ) {
console.log("o." + prop + " = " + obj[prop]);
}
}
//o.color = red
(2)Object.keys(obj)
Object.keys
返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
验证方式
var obj = {};
obj.name = 'xkx';
obj.age = 18;
obj.run = function() { //创建一个 run()方法并返回值
return this.name + this.age + '运行中...';
};
// 给原型添加属性和方法
Object.prototype.gaga = function() {
console.log('gaga')
}
Object.prototype.names = 'names'
// 给原型添加一个可枚举的属性
Object.defineProperty(Object.prototype, "ages", {
enumerable: false,
configurable: false,
writable: false,
value: 20
});
// var descriptor = Object.create(null); // 没有继承的属性
// descriptor.value = 'static';
// // 默认没有 enumerable,没有 configurable,没有 writable
// Object.defineProperty(obj, 'key', descriptor);
// console.log(Object.getOwnPropertyDescriptor(obj, 'key'))
//显式
Object.defineProperty(obj, "key", {
enumerable: true,
configurable: false,
writable: false,
value: "static"
});
for (var i in obj) {
console.log(i)
}
console.log(Object.keys(obj))
(3)Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames
返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。
(4)Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols
返回一个数组,包含对象自身的所有 Symbol 属性的键名。
(5)Reflect.ownKeys(obj)
Reflect.ownKeys
返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
谈一下迭代器
概念
JavaScript 原有的表示“集合”的数据结构,主要是数组(Array
)和对象(Object
),ES6 又添加了Map
和Set
。这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,比如数组的成员是Map
,Map
的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构。
遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。 作用 一是为各种数据结构,提供一个统一的、简便的访问接口;
二是使得数据结构的成员能够按某种次序排列;
三是 ES6 创造了一种新的遍历命令for...of
循环,Iterator 接口主要供for...of
消费。
makeIterator
函数,它是一个遍历器生成函数
var it = makeIterator(['a', 'b']);
it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{value: undefined, done: true};
}
};
}
ES6 的有些数据结构原生具备 Iterator 接口(比如数组),即不用任何处理,就可以被
for...of
循环遍历。原因在于,这些数据结构原生部署了Symbol.iterator
属性(详见下文),另外一些数据结构没有(比如对象)。凡是部署了Symbol.iterator
属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器对象。
原生具备 Iterator 接口的数据结构如下。
- Array
- Map
- Set
- String
- TypedArray
- 函数的 arguments 对象
- NodeList 对象
类似数组的对象调用数组的Symbol.iterator
方法的例子
let iterable = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
console.log(item); // 'a', 'b', 'c'
}
普通对象部署数组的Symbol.iterator
方法,并无效果。
let iterable = {
a: 'a',
b: 'b',
c: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
console.log(item); // undefined, undefined, undefined
}
调用 Iterator 接口的场合
(1)解构赋值
(2)扩展运算符
(3)yield
(4)其他场合 由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口。下面是一些例子。
-
for...of
-
Array.from()
-
Map(), Set(), WeakMap(), WeakSet()(比如
new Map([['a',1],['b',2]])
) -
Promise.all()
-
Promise.race()
js中常见的设计模式
跨域及处理方法
es6中的proxy 及reflect
reflect
概述
(1)将Object
对象的一些明显属于语言内部的方法(比如Object.defineProperty
),放到Reflect
对象上。
(2)修改某些Object
方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)
在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)
则会返回false
。
应用
13个静态方法 每个静态方法都有相应的应用场景
具体参官网:es6.ruanyifeng.com/#docs/refle…
- Reflect.apply(target, thisArg, args)
- Reflect.construct(target, args)
- Reflect.get(target, name, receiver)
- Reflect.set(target, name, value, receiver)
- Reflect.defineProperty(target, name, desc)
- Reflect.deleteProperty(target, name)
- Reflect.has(target, name)
- Reflect.ownKeys(target)
- Reflect.isExtensible(target)
- Reflect.preventExtensions(target)
- Reflect.getOwnPropertyDescriptor(target, name)
- Reflect.getPrototypeOf(target)
- Reflect.setPrototypeOf(target, prototype)
示范 (1)判断类型
var x = {
toString() {
return "X";
},
};
x.toString(); // => "X"
Object.prototype.toString.call(x); // => "[object Object]"
Reflect.apply(Object.prototype.toString, x, []); // => "[object Object]"
Object.prototype.toString
1.对象或数组都具有 toLocaleString()、toString()和 valueOf()方法。其中 toString()和 valueOf() 无论重写了谁,都会返回相同的值。数组会讲每个值进行字符串形式的拼接,以逗号隔开
2.每个对象都有一个 toString() 方法,当该对象被表示为一个文本值时,或者一个对象以预期的字符串方式引用时自动调用。默认情况下,toString() 方法被每个 Object 对象继承。如果此方法在自定义对象中未被覆盖,toString() 返回 “[object type]”,其中 type 是对象的类型。
3.Number、Boolean、String、Array、Date、RegExp、Function这几种构造函数生成的对象,通过toString转换后会变成相应的字符串的形式,因为这些构造函数上封装了自己的toString方法
Number.prototype.hasOwnProperty('toString'); // true
Boolean.prototype.hasOwnProperty('toString'); // true
String.prototype.hasOwnProperty('toString'); // true
Array.prototype.hasOwnProperty('toString'); // true
Date.prototype.hasOwnProperty('toString'); // true
RegExp.prototype.hasOwnProperty('toString'); // true
Function.prototype.hasOwnProperty('toString'); // true
var num = new Number('123sd');
num.toString(); // 'NaN'
var str = new String('12df');
str.toString(); // '12df'
var bool = new Boolean('fd');
bool.toString(); // 'true'
var arr = new Array(1,2);
arr.toString(); // '1,2'
var d = new Date();
d.toString(); // "Sun Jul 22 2018 12:38:42 GMT+0800 (中国标准时间)"
var func = function () {}
func.toString(); // "function () {}"
其他
var obj = new Object({});
obj.toString(); // "[object Object]"
Math.toString(); // "[object Math]
大多数对象,toString()
方法都是重写了的,这时,需要用 call()
或 Reflect.apply()
等方法来调用
var x = {
toString() {
return "X";
},
};
x.toString(); // => "X"
Object.prototype.toString.call(x); // => "[object Object]"
Reflect.apply(Object.prototype.toString, x, []); // => "[object Object]"
[blog.csdn.net/u011140116/…] [zhuanlan.zhihu.com/p/118793721]