js的几个问题
问题一:浏览器的console里会打印出什么?
1 | var a = 10; |
答案:undefined
解释:使用var关键字声明的变量被提升并在内存中为其赋值为undefined.但是初始化恰好发生在你在代码中写入它们的地方。另外,var声明的变量是函数作用域,而let和const是块作用域。所以,这就是这个所从的样子:
1 | var a = 10; // 全局作用域 |
问题二:如果是有const或let代替var,输出是否一样?
1 | var a = 10; |
答案:ReferenceError: a is not defined
解释:let和const允许你声明一个变量被限制在一个块级作用域,或语句或表达式中。不像var,这些变量不会被提升,并且具有所谓的temporal dead zone(TDZ)。尝试在TDZ中访问这些变量将抛出一个ReferenceError,因为它们只能在执行到达声明才可被访问。
1 | var a = 10; // 全局作用域 |
问题三:”newArray”中的元素是什么?
1 | var array = []; |
答案:[3,3,3]
解释:在for loop的头部声明一个带有var关键字的变量,为该变量创建一个绑定(存储空间)。
1 | // 误解作用域:认为存在块级作用域 |
如果你声明一个具有块级作用域的变量,则会为每个循环迭代创建一个新绑定。
1 | // 使用ES6块级作用域绑定 |
解决这个问题的另一种方法是使用闭包
1 | let array = []; |
为啥let可以,可以参考这篇文章
问题四:如果我们在浏览器控制台中运行’foo’函数,是否会导致堆栈溢出错误?
1 | function foo() { |
答案:不会
解释:JavaScript并发模型基于“事件循环”。当我说“浏览器是JS的家(归宿)”时,我真正的意思是浏览器提供运行时环境来执行我们的JavaScript代码。浏览器的主要组件包括调用堆栈 ,事件循环 ,任务队列 和 Web API。像setTimeout
,setInterval
和Promise
这样的全局函数不是JavaScript的一部分,而是Web API
的一部分。JavaScript环境的可视化表示如下所示:
JS调用堆栈是后进先出
(LIFO)
。引擎一次从堆栈中获取一个函数,并从上到下依次运行代码。每次遇到一些异步代码(如setTimeout)时,它都会将其交给Web API(箭头1)。因此,每当触发事件时,callback都会被发送到任务队列(箭头2)。Event Loop不断监视任务队列,并按照排队顺序一次处理一个callback。每当调用堆栈为空时,循环检索回调并将其放入堆栈(箭头3)进行处理。请记住,如果调用堆栈不为空,则事件循环不会将任何callbacks推送到堆栈。
现在,有了这些知识,让我们试着回答上述问题:
步骤
1.调用foo()
将把foo
函数放进调用栈。
2.在处理内部代码时,JS引擎遇到setTimeout
。
3.然后它将foo
回调移交给WebAPI(箭头1)并从函数返回。调用堆栈再次为空。
4.计时器设置为0
,因此foo
将被发送到任务队列(箭头2)。
5.因为,我们的调用堆栈是空的,事件循环将选择foo回调
并将其推送到调用堆栈进行处理。
6.进程再次重复,堆栈不会溢出 。
问题五:如果我们在控制台中运行以下函数,页面的UI(tab页)是否仍然响应?
1 | function foo() { |
答案:不会
解释:大多数时候,我看到开发人员假设在事件循环的蓝图中只有一个任务队列(笔: 也叫task queue
或event queue
或callback queue
)。但事实并非如此。我们可以有多个任务队列。由浏览器选择任意的队列并在其中处理callbacks。
在高层次上来看,JavaScript中有宏任务和微任务。setTimeout
回调是macrotasks
而Promise
回调是 microtasks
。主要的区别在于他们的执行仪式。宏任务在单个循环周期中一次一个地推入堆栈,但是微任务队列总是在执行返回到event loop
(包括任何额外排队的项)之前清空。因此,如果你将这些项快速的添加到这个你正在处理的队列,那么你将永远在处理微任务。
在执行返回事件循环之前,微任务队列总是被清空
现在,当你在控制台中运行以下代码段时:
1 | function foo() { |
每次调用’foo’都会继续在微任务队列上添加另一个’foo’回调,因此事件循环无法继续处理其他事件(scroll
,click
等),直到该队列完全清空为止。因此,它会阻止渲染。
问题六:我们可以在不引起TypeError的情况下以某种方式使用以下语句的扩展语法吗?
1 | var obj = { x: 1, y: 2, z: 3 }; |
答案:可以, 通过是对象iterables
解释:拓展运算符和for-of
语句迭代iterable
对象。数组或Map是具有默认迭代行为的内置iterable
。对象不是可迭代的,但你可以使用iterable
和iterator协议
使它们可迭代。
在Mozilla文档中,如果一个对象实现了@@iterator方法,那么它就是可迭代的,这意味着这个对象(或者它原型链上的一个对象)必须有一个带有@@iterator键的属性,这个键可以通过常量Symbol.iterator获得。
上述陈述可能看起来有点冗长,但下面的例子会更有意义:
1 | var obj = { x: 1, y: 2, z: 3 }; |
你还可以使用generator函数来自定义对象的迭代行为:
1 | var obj = { x: 1, y: 2, z: 3 }; |
问题七:运行以下代码片段时,控制台上会打印什么?
1 | var obj = { a: 1, b: 2 }; |
Object.setPrototypeOf
方法是针对对象实例的,而不是构造函数(类),此方法修改的是对象实例的内部属性[prototype]
,也就是_proto_
属性所指向的对象,它只是修改了特定对象上的原型对象,对于构造函数的prototype
指向的原型对象没有影响。那是不是此方法就不能针对构造函数了,因为构造函数本身也是function(类)的实例。
语法结构:
Obejct.setPrototypeOf(obj,proto);
参数解析:
(1).obj:必需,对其设置原型的对象。
(2).proto:必需,新的原型对象。
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
语法结构:
Object.defineProperty(obj, prop, descriptor);
obj:要定义属性的对象。
prop:要定义或修改的属性的名称或 Symbol 。
descriptor:要定义或修改的属性描述符。
返回值–>被传递给函数的对象。
答案:a,b,c
解释: for-in
循环遍历对象本身的可枚举属性以及对象从其原型继承的属性。可枚举属性是可以在for-in
循环期间包含和访问的属性。
1 | var obj = { a: 1, b: 2 }; |
现在掌握了这些知识,应该很容易理解为什么我们的代码会打印出这些特定的属性:
1 | var obj = { a: 1, b: 2 }; // a, b are both enumerables properties |
问题八:xGetter()将输出什么值?
1 | var x = 10; |
答案:10
解释:当我们将x初始化为全局作用域时,它将成为window对象的属性(假设它是浏览器环境而不是严格模式)。看下面代码:
1 | var x = 10; // 全局作用域 |
我们可以断言:
1 | window.x === 10; // true |
this将始终指向调用该方法的对象。因此,在foo.getX()
的情况下,this
指向foo
对象返回值90。而在xGetter()
的情况下,this
指向window
对象返回值10。
要检索foo.x
的值,我们可以通过使用Function.prototype.bind
将 this
的值绑定到foo
对象来创建新函数。
1 | let getFooX = foo.getX.bind(foo); |
作者:xiaohesong
链接:https://www.jianshu.com/p/aec200057e33
来源:简书