1.js中现在有多少种作用域?说说自己的理解
两种,全局作用域和局部作用域。最外层定义的变量拥有全局作用域,即对任何内部函数来说,都是可以访问的。而局部作用域一般只在函数内部可访问到,而对于函数外部是无法访问的。
当一个函数被调用执行的时候都会创建一个执行上下文。这个执行上下文可以抽象为一个对象,对象里面包括变量对象与作用域链。执行上下文可以激活另一个执行上下文,例如函数调用另一个函数(或者全局执行上下文调用全局函数)等等,逻辑上就成了一个堆栈。这被称为执行上下文堆栈。当执行流进入一个函数时,函数的上下文就会被推入一个栈中,如果在当前函数中调用另一个函数,当前函数就会暂停执行,并将执行流传递给被调用函数(被调用函数同事可能是其他函数的调用者),被调用者被推入堆栈。当被调用者的上下文结束后,将执行流交还给调用者,调用者的继续运行代码,直到结束后,栈将上下文弹出。
q
在作用域中定义的所有变量和函数都保存在变量对象中。但是不包含函数表达式和this(因为他不是一个变量)。
作用域链
作用域链本质上就是根据名称查找变量(标识符名称)的一套规则。规则非常简单,在自己的变量对象里找不到变量,就会从父级的变量对象查找,当抵达最外层的全局上下文中,无论找到还是没找到,查找过程都会停止。查找会在找到第一个匹配的变量时停止,被称为遮蔽效应
var x = 10;
(function foo(){
var y = 10;
(function bar(){
var z = 10;
console.log(x+y+z)![scope-chain.png][6]
})
})
Function对象有一个仅供 JavaScript 引擎存取的内部属性。
这个属性就是[[Scope]]
。[[Scope]]
包含了一个函数被创建的作用域中对象的集合。这个集合被称为函数的作用域链,它决定了哪些数据能被函数访问。
每个执行上下文都有自己的作用域链,用来解析标识符。当执行环境被创建时,它的作用域就会初始化为当前运行函数的[[Scope]]
属性中的对象。
答案:两种,全局作用域和局部作用域。最外层定义的变量拥有全局作用域,即对任何内部函数来说,都是可以访问的。而局部作用域一般只在函数内部可访问到,而对于函数外部是无法访问的。每个函数都有自己的执行上下文,这个执行上下文是一个对象,包含变量对象与作用域链。全局作用域中也有一个全局执行上下文。在作用域中定义的变量和函数都存储在变量对象中,当函数作用域内部使用一个变量或者函数的时候,会先从自己的变量对象当中找,如果没有找到就沿着作用域链往父级的执行上下文中找直到找不到为止。刚刚说到局部作用域一般只在函数内部可访问到,有个例外就是闭包。使用闭包就可以在全局作用域下调用函数内部的属性或者方法。
JavaScript执行过程:
在解释过程中,JavaScript引擎是严格按着作用域机制(scope)来执行的。JavaScript语法采用的是词法作用域(lexcical scope),也就是说 JavaScript的变量和函数作用域是在定义时决定的,而不是执行时决定的 ,由于词法作用域取决于源代码结构,所以 JavaScript解释器只需要通过静态分析就能确定每个变量、函数的作用域,这种作用域也称为静态作用域(static scope)。
衍生问题1:var a = 10 与 a = 10有什么区别?
两个命令都是对a变量进行赋值。区别在于后面多了一个沿作用域链查找的过程。
衍生问题2:知不知道eval作用域?
正常模式下,Javascript语言有两种变量作用域(scope):全局作用域和函数作用域。严格模式创设了第三种作用域:eval作用域。
正常模式下,eval语句的作用域,取决于它处于全局作用域,还是处于函数作用域。严格模式下,eval语句本身就是一个作用域,不再能够生成全局变量了,它所生成的变量只能用于eval内部。
"use strict";
var x = 2;
console.info(eval("var x = 5; x")); // 5
console.info(x); // 2
2.let和const在es5下的实现
1.实现let
(function(){
var a = 1;
})();
console.log(a)
2.实现 const
进入这次的主题,const 该怎么实现呢?const 声明一个只读的常量。一旦声明,常量的值就不能改变。有什么方法是可以限制一个值不能发生改变的呢?是的,需要用到 Object.defineProperty。其中有一个属性是这样的。writable:当前对象元素的值是否可修改。于是一切都好说的.于 ES5 环境没有 block 的概念,所以是无法百分百实现 const,只能是挂载到某个对象下,要么是全局的 window,要么就是自定义一个 object 来当容器。
var __const = function __const(data, value) {
window.data = value // 把要定义的data挂载到window下,并赋值value
Object.defineProperty(window, data, { // 利用Object.defineProperty的能力劫持当前对象,并修改其属性描述符
enumerable: false,
writable : false,
get: function () {
return value
},
set: function (data) {
if (data !== value) { // 当要对当前属性进行赋值时,则抛出错误!
throw new TypeError('Assignment to constant variable.')
} else {
return value
}
}
})
}
__const('a', 10)
console.log(a)
a = 20 //报错
代码有点略长,还有没有更简单一点的实现方法呢?至少不要这么长的代码demo了。答案是肯定的,只不过没有上面例子那么透彻。我们这次将用到es5的Object.freeze();
var f = Object.freeze({'name':'admin'});
console.log(f.name); //admin
f.name = 'hello'; // 严格模式下是会报错的
f.name; // 打印出admin ,值没有被改变
3.箭头函数和普通函数的区别(箭头函数和匿名函数的区别)?
1.箭头函数不绑定this,会捕获其所在的上下文的this值,作为自己的this值
function make () {
return ()=>{
console.log(this);
}
}
var testFunc = make.call({ name:'foo' });
testFunc(); //{name: "foo"}
testFunc.call({ name:'bar' }); //{name: "foo"}
箭头函数没有它自己的this值,箭头函数内的this值继承自外围作用域。在箭头函数中调用 this 时,仅仅是简单的沿着作用域链向上寻找,找到最近的一个 this 拿来使用。
例如:
(1)在对象中定义方法使用了箭头函数:
var obj = {
age:20,
getAge:()=>{
return this.age
}
}
obj.getAge(); //undefined
因为这时候的this是obj外面的window,而window是没有age属性的,所有会报undefined。
(2)在构造函数内使用箭头函数:
var Fun = (age)=>{
this.age = age;
}
var fun1 = new Fun(20)
箭头函数在创建时就绑定了this,不会指向对象实例。
2.箭头函数是匿名函数,不能作为构造函数,不能使用new
let FunConstructor = () => {
console.log('lll');
}
let fc = new FunConstructor();
3.箭头函数不绑定arguments,取而代之用rest参数...解决
function A(a){
console.log(arguments);
}
A(1,2,3,4,5,8); // [1, 2, 3, 4, 5, 8, callee: ƒ, Symbol(Symbol.iterator): ƒ]
let B = (b)=>{
console.log(arguments);
}
B(2,92,32,32); // Uncaught ReferenceError: arguments is not defined
let C = (...c) => {
console.log(c);
}
C(3,82,32,11323); // [3, 82, 32, 11323]
4.不能用yield命令,因此箭头函数不能用做Generator函数
衍生问题1:为什么箭头函数不能够使用new来实例化?
因为箭头函数是匿名函数,没有构造函数,也没有prototype。
衍生问题2:什么是arguments?
arguments 是一个类数组对象。代表传给一个function的参数列表。
虽然arguments对象并不是一个数组,但是访问单个参数的方式与访问数组元素的方式相同。
衍衍生问题1:什么是类数组对象(伪数组)?类数组如何转为数组?
什么是伪数组
1. 拥有 length 属性,其它属性(索引)为非负整数(对象中的索引会被当做字符串来处理,这里你可以当做是个非负整数串来理解)
2.不具有数组所具有的方法
var fakeArray = {
"0": "first",
"1": "second",
"2": "third",
length: 3
};
for (var i = 0; i < fakeArray.length; i++) {
console.log(fakeArray[i]);
}
常见的伪数组有:
- 函数内部的
arguments
- DOM 对象列表(比如通过
document.getElementsByTags
得到的列表) - jQuery 对象(比如
$("div")
)
伪数组是一个 Object,而真实的数组是一个 Array。
伪数组转化为数组的方式:
1.[].slice.call(obj) 这个等于Array.protype.slice.call(obj) (es5方法)
slice会把通过索引位置获取新的数组,该方法不会修改原数组,只是返回一个新的子数组.call会把this的指向改为传进去的obj
var obj = {
"0":"zhang",
"1":18,
length:2
}
var newarr = [].slice.call(obj)
console.log(newarr);
2.Array.form(obj),ES6的新语法
var newarr = Array.from(obj)
衍生问题2:箭头函数的缺点和使用场景?
缺点就是使用了的话就不能改变this的实例。
箭头函数适合于无复杂逻辑或者无副作用的纯函数场景下,例如:用在 map、reduce、filter 的回调函数定义中。
4.讲讲前端优化的方法?
首先从http层面优化:
1.资源压缩与合并
通过进行html压缩、css 压缩、js的压缩和混乱和文件合并。可以减少浏览器向服务器的请求次数。(其实css压缩与js的压缩和混乱比html压缩收益要大得多,同时css代码和js代码比html代码多得多,通过css压缩和js压缩带来流量的减少,会非常明显。所以对大公司来说,html压缩可有可无,但css压缩与js的压缩和混乱必须要有。)
2.利用浏览器缓存
使用强缓存、协商缓存的机制,可以减少浏览器向服务器的请求次数。
3.使用CDN加速
浏览器缓存始终只是为了提升二次访问的速度,对于首次访问的加速,我们需要从网络层面进行优化,最常见的手段就是CDN(Content Delivery Network,内容分发网络)加速。通过将静态资源(例如javascript,css,图片等等)缓存到离用户很近的相同网络运营商的CDN节点上,不但能提升用户的访问速度,还能节省服务器的带宽消耗,降低负载。
CDN是怎么做到加速的呢?
其实这是CDN服务商在全国各个省份部署计算节点,CDN加速将网站的内容缓存在网络边缘,不同地区的用户就会访问到离自己最近的相同网络线路上的CDN节点,当请求达到CDN节点后,节点会判断自己的内容缓存是否有效,如果有效,则立即响应缓存内容给用户,从而加快响应速度。如果CDN节点的缓存失效,它会根据服务配置去我们的内容源服务器获取最新的资源响应给用户,并将内容缓存下来以便响应给后续访问的用户。因此,一个地区内只要有一个用户先加载资源,在CDN中建立了缓存,该地区的其他后续用户都能因此而受益。
4.开启gzip
开启了gzip可以更进一步将文件压缩。
然后从代码层面优化:
1.减少重排跟重绘的次数,能重绘就别重排
1.减少重绘重排的次数
常见解决方式1:有的时候需要往 UI 标签中添加多个 LI 标签。这个时候不要一个一个的添加 LI 标签。而是在本地创建好了 LI 标签组一次性加载进去
常见解决方式2:将元素属性变为 none,然后在对其进行修改工作
2.能重绘就别重排
给元素设置某些属性的时候元素会脱离文档流,这样元素的改变就不会造成大面积的重排了,例如绝对定位,浏览器定位,使用 CSS3 的某些特性。可以参考这个网站查看具体哪些属性会导致重排重绘https://csstriggers.com/
2.使用防抖和节流
1.搜索框搜索内容时请求后端接口
不防抖的方式:输入内容:12345 , 内容发生改变就请求接口。这样会请求五次接口
防抖的方式:同样输入内容:12345,防抖时间设置为 500ms,500ms 内用户输入新的内容重新读条。正常情况下只会请求一次接口
2.图片进行懒加载
不节流的方式:滚动条一滚动就监听页面元素显示情况,显示的元素就进行图片加载。滚动条滚动事件是一个非常频繁的操作,滚动一丢丢就会执行几十次。
节流的方式:还是同样的操作,但是执行代码进行节流。设置节流时间 30ms 。这就不管这 30ms 滚动事件出发了几次,我的执行代码都只执行一次。
3.非核心代码使用异步加载
异步加载有三种方式:async、defer、动态脚本创建。通过使用非核心代码异步加载的方式来加快页面主要内容的渲染。
4.对于图片较多的页面采用懒加载的方式
衍生问题1:async、defer、动态脚本创建是什么?有什么区别?
1.defer属性(延迟脚本)
html4.01为<script>标签定义了defer属性,表明脚本会延迟到整个页面都解析完毕后再运行。延迟脚本总是按照指定它们的顺序执行。遇到</html>后再执行,会先于DOMContentLoaded事件执行。 defer属性只适用于外部脚本文件。
<script src="t1.js" defer> </script>
2.async属性(异步脚本)
Html5为<script>元素定义了async属性,与defer类似只适用于外部脚本文件,并告诉当前脚本不必等待其他脚本,也不必阻塞文档呈现。异步脚本一定会在页面load事件前执行,但可能会在DOMContentLoaded事件触发之前或之后执行。
但与defer不同的是,标记为async的脚本并不保证按照它们的先后顺序执行。
<script src="t1.js" async> </script>
3.动态创建DOM方式(使用最多)
通过document.createElement("script")创建新的script标签,为新创建的元素的src属性赋值,值即为需要加载的js文件地址。
//随时调用函数创建标签
function download() {
var element = document.createElement("script");
element.src = "t1.js";
document.body.appendChild(element);
}
5.说说下列程序打印结果
class A {
start(){
let a = 1
const x = setTimeout(()=>{
console.log(this);
},0)
const y = setTimeout(function(){
console.log(this);
},0)
}
}
const b = new A();
b.start()
第一个this打印的是A对象 ,第二个this打印的是window对象。
new 出来的this肯定指向是实例,箭头函数和普通函数就是this丢失问题,箭头函数this就是父级的this,setTimeout里的普通函数是隐式绑定window的。
衍生问题1:如果js类方法是static的话和不加有什么区别?
静态方法是指不需要声明类的实例就可以使用的方法。
class A {
static start(){
let a = 1
const x = setTimeout(()=>{
console.log(this);
},0)
const y = setTimeout(function(){
console.log(this);
},0)
}
}
const b = new A();
b.start() //报错
A.start() //可以执行
static定义的是类的静态方法,实例去调肯定报错。
6.什么是事件委托?
事件冒泡:JS中当出发某些具有冒泡性质的事件时,首先在触发元素寻找是否有相应的注册事件,如果没有再继续向上级父元素寻找是否有相应的注册事件作出相应,这就是事件冒泡。
事件委托:利用事件冒泡的特性,将本应该注册在子元素上的处理事件注册在父元素上,这样点击子元素时发现其本身没有相应事件就到父元素上寻找作出相应。
这样做的优势有:1.减少DOM操作,提高性能。2.随时可以添加子元素,添加的子元素会自动有相应的处理事件。
JavaScript高级程序设计上讲:事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
7.在浏览器控制台执行以下代码,输入的结果是?
答案是:4400 4401 4399 4400
js在执行之前,会将所有带var和function的进行提前定义和声明。(带var的提前声明,function声明和定义都完成了)
首先,在全局作用域下,进行预解释:
test=xxxfff000(开辟一个堆内存,里面存的是代码字符串)
var result(声明一个变量result)
var result2(声明一个变量result2)
-------------------------------
代码执行:
result=test() -->将test执行的返回结果赋值给result,是一个对象,再开辟一个堆内存,test执行,形成一个私有作用域A
再进行预解释和代码执行等一系列操作
result2=test() 同理
result.add() -->方法执行形成一个私有作用域
n++ 顺着作用域链向上寻找到test作用域A(A这个作用域不销毁,因为被全局变量result占用了)中的n为4399,n++ 》4400
(这时test这个作用域A下的n变成4400)
(1) console.log(n) //4400
==============================
result.add() -->方法执行形成一个新的私有作用域
n++ 顺着作用域链向上寻找到test作用域(A)中的n为4400,n++ 》4401
(2) console.log(n) //4401
===============================
(3) console.log(result.n) //4399
此时找的只是result对应的那个堆内存中的n
===============================
result2.add() -->方法执行形成一个私有作用域
n++ 顺着作用域链向上寻找到test作用域(B)中的n为4399,n++ 》4400
(3) console.log(n) //4400
解释2:
首先,题中定义了一个函数,名为test,这个函数内部分别又定义了一个数值变量n和一个闭包函数add,test函数的最后一行代码return{n:n,add:add},实际上是返回了一个object,而这个object中有一个属性n,它的值是n,还有一个方法add,它的值是add。
好了,函数解释清楚,再来看输出的问题。函数外部分别定义了两个变量,result和result2,他们都指向test函数,但是分属两个不同的作用域,这也就解释了答案中1和4,4不会在2的基础上继续n++。
1和2属于闭包函数的问题,可参考阮一峰老师的一篇博文(http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html),简单易懂,借用阮老师博客中所写,闭包的两个最大的用处:一个是可以读取到函数内部的变量,另一个就是让这些变量的值始终保持在内存中,具体可以测试。第二个用途就解释了同一个作用域中答案2会在1的基础上进行+1操作。
答案3是比较令人困惑的一项,追本溯源,前面提到过第6行代码返回了一个有着值为n的属性n和值为add的方法add的匿名对象,在这里,在这个匿名对象中,属性n和方法add是互不相关的,即使在闭包add中改变了变量n的值,result.n的值依然不变。
衍生问题1:下面运行的结果是?
答案: 1 2 3
- 第一步:进行预编译,var全局变量foo、匿名函数 function、var局部变量foo
- 第二步:代码自上而下、自左向右执行计算:
- 对全局变量foo进行赋值foo={n:1};注意:此值为对象,属于引用类型;
- 匿名函数传入参数foo={n:1}自执行;
- console.log(foo);打出数字1;
- 由于存在foo局部变量,那么对foo变量进行赋值foo={n:3},同时更改了引用类型的参数值,全局foo变量被重新赋值foo={n:3};
- 对局部变量foo进行重新赋值foo={n:2};
- console.log(foo);打出数字2;
- 全局变量foo={n:3},因此,console.log(foo);打出数字3;
问题:局部变量声明提前后不会覆盖原来的全局变量,导致foo 一开始为undefined 吗?
不会,如果变量已经有值了,那么关于这个变量的声明会被忽略。也就是会使用匿名函数传进来的值。
如果匿名函数没有传入foo参数的话,那么就由于变量提升的原因导致匿名函数内部的foo为undefined,会报错
var foo = {n:1};
(function(){
console.log(foo.n);
foo.n = 3
var foo
console.log(foo.n);
})()
console.log(foo.n);
再看下面这个例子:x是什么?
(function() {
var x=foo();
var foo=function foo() {
return "foobar"
};
return x;
})();
解释:
函数执行顺序:
①var x; //变量声明提前
②var foo; //变量声明提前
③x=foo(); //给x赋值,会报错,因为foo变量目前是undefined,不是函数类型
④foo=function foo() {return "foobar";}; //给foo赋值 foo变量“被提前”了,但是他的赋值(也就是函数)并没有被提前,从这一点上来说,和前面我们所讲的变量“被提前”是完全一致的,并且,由于“被提前”的变量的默认值是 undefined。 函数声明可以被提前,但函数表达式不能被提前
8.以下代码执行后, num 的值是?
var foo=function(x,y){
return x-y;
}
function foo(x,y){
return x+y;
}
var num=foo(1,2);
答案是 -1.这里考察的是变量提升。
规则
1. 变量声明、函数声明都会被提升到作用域顶处;
2. 当出现相同名称时,优先级为:变量声明(foo#1) < 函数声明(foo#2) < 变量赋值(foo#3)
也就是说:函数提升优先级高于变量提升,且不会被变量声明覆盖,但会被变量赋值覆盖
function foo(x,y){return x+y;}//函数声明优先于变量提升
var foo;
foo=function(x,y){return x-y;}//变量赋值覆盖了函数声明
var num=foo(1,2);
因此,num计算时是用的foo。答案为-1。
再例如:请给出这段代码的运行结果
var bb = 1;
function aa(bb) {
bb = 2;
alert(bb);
};
aa(bb);
alert(bb);
答案是 2 1
这道题考察是局部变量和参数传递的问题。在aa函数中,bb是以传值的方式传入的,在函数中,会重新定义一个bb变量,并将其值覆为2,并不影响函数体外的bb变量,所以其值仍然为1.
再看下面的例子:
function Foo() {}
let oldName = Foo.name;
Foo.name = "bar";
console.log([oldName, Foo.name]);
答案是: foo foo
考察了函数的name属性,使用函数定义方式时,会给function对象本身添加一个name属性,保存了函数的名称,很好理解oldName为"foo"。name属性时只读的,不允许修改,Foo.name = “bar”;之后,foo.name还是"foo",所以结果为[“foo”, “foo”]
还有这个题:
async function test() {
return 1;
}
async function call() {
const a = test();
const b = await test();
console.log(a, b)
}
call()
第一个a打印出的是一个promise { 1 },第二个b打印出的是1
9.下列代码存在几个变量没有被回收?
var i = 1;
var i = 2;
var add = function() {
var i = 0;
return function()
{
i++;
console.log(i);
}
}();
add();
答案:3个。
有3个变量没有被回收,首先是全局变量中的i,第二行中的var被忽略,但i=2会让i被重新赋值为2,因此只有1个。第二个是var add,这个变量也没有回收,他定义了一个匿名函数,并将它赋给了add。第三个就是闭包中的变量i,闭包中的局部变量是不会被回收的,因此是3个变量没有被回收。
10.下面的显示结果?
var x = new Boolean(false);
if (x) {
alert('hi');
}
var y = Boolean(0);
if (y) {
alert('hello');
}
此题考查的是 JS 的类型转换:
- if(x) 这里期望 x 是一个布尔类型的原始值,而 x 是一个对象,任何对象转为布尔值,都为得到 true(切记!在 JS 中,只有 0,-0,NaN,"",null,undefined 这六个值转布尔值时,结果为 false)。
- 题目的第二部分,一定要注意 y = Boolean(0),而不是 y = new Boolean(0)。这两个有很大区别,用 new 调用构造函数会新建一个布尔对象,此处没有加 new,进行的是显示类型转换,正如上述第一条所说,0 转换布尔,结果为 false,所以此时 y 的值就是 false。如果加了 new,那么 y 就是一个 Boolean 类型的对象,执行 if(y) 时,对象转布尔,始终是 true,所以结果会与不加 new 的时候相反。
11.下面代码输出什么?
console.log(1+ "2"+"2");
console.log(1+ +"2"+"2");
console.log("A"- "B"+"2");
console.log("A"- "B"+2);
答案是 122 32 NaN2 NaN
12.let in 和 let of有什么区别?
答:let in 遍历的是键(key),而let of 遍历的是值(value)
var arr = ['a','b']
for(let key in arr){
console.log(key); // 0 1
}
for(let key of arr){
console.log(key); // a b
}
13.说说下面代码的输出?
let obj = {
a:100,
log(){
a = 200
console.log(this.a);
}
}
obj.log()
let { log } = obj
log()
答案:100 200
14.map和filter有什么区别?
相同点:filter 和 map 都是对数组的操作,均返回一个新的数组
不同点:filter是满足条件的留下,是对原数组的过滤;map则是对原数组的加工,映射成一对一映射的新数组。
衍生问题1: ["1", "2", "3"].map(parseInt);输出什么?
答案是: [1,NaN,NaN]
因为map的callback函数有三个参数,正在遍历的元素, 元素索引(index), 原数组本身(array)。parseInt有两个参数,string和radix(进制)。只传入parseInt的话,map callback会自动忽略第三个参数array。而index参数不会被忽略。而不被忽略的index(0,1,2)就会被parseInt当做第二个参数。
将其拆开看:
parseInt("1",0);//上面说过第二个参数为进制,所以"1",被转为0进制不存在,所以返回Number类型的1。
parseInt("2",1);//此时将2转为1进制数,由于超过进制数1,所以返回NaN。
parseInt("3",2);//同理返回NaN。
所以最终的结果为[1,NaN,NaN]。
那么如果要得到[1,2,3]该怎么写?
["1","2","3"].map((x)=>{
return parseInt(x);
});
也可以简写为:
["1","2","3"].map(x=>parseInt(x));
衍生问题2:["1","2","3"].map(parseFloat) 又输出什么?
["1","2","3"].map(parseFloat);
直接输出 [1,2,3] 因为parsefloat没有第二个参数,也就是进制值。
衍生问题3:讲讲数组中的forEach、map、filter、some、every、reduce函数?
1.forEach是对原数组的遍历,会直接改变原数组。不会返回新数组。
1.map则是对原数组的加工,映射成一对一映射的新数组。
2.filter是对原数组的过滤,满足条件的留下。返回新数组。
3.some是对数组中每一项运行给定函数,如果该函数对任一项返回true,则返回true。
4.every对数组中每一项运行给定函数,如果该函数对每一项返回true,则返回true。
5.reduce() 是数组的归并方法,与forEach()、map()、filter()等迭代方法一样都会对数组每一项进行遍历,但是reduce() 可同时将前面数组项遍历产生的结果与当前遍历项进行运算。
15.为什么说js是弱类型语言(动态语言)?还有什么弱类型语言?有什么强类型语言(静态语言)?
静态语言(强类型语言)
静态语言是在编译时变量的数据类型即可确定的语言,多数静态类型语言要求在使用变量之前必须声明数据类型。
例如:C++、Java、Delphi、C#等。
动态语言(弱类型语言)
动态语言是在运行时确定数据类型的语言。变量使用之前不需要类型声明,通常变量的类型是被赋值的那个值的类型。
例如PHP/ASP/Ruby/Python/Perl/ABAP/SQL/JavaScript/Unix Shell等等。