JS一些基础问题解答
1.如何获取一个大于等于0且小于等于9的随机整数?
function random() { var result = Math.random() * 10; return Math.floor(result); }
2.想要去除一个字符串的第一个字符,有哪些方法可以实现?
str = str.substr(1);// str = str.substring(1); str = str.slice(1); str = str.match(/^.(.*)/)[1];//返回带有2个参数的数组。经测试var str=ABC;返回【“ABC”,“BC”】; str.replace(str.charAt(0),"");
3对一个数组(每项都是数值)求和,有哪些方法?
//1.forEach循环遍历方法 arr1.forEach(function(item){ result1+=item; }); console.log(result1); //2.forin循环遍历方法 for(var i in arr1){ result2+= arr1[i]; } console.log(result2); //3.for循环遍历方法 for(var i=0; i<arr1.length; i++){ result3+=arr1[i]; } console.log(result3); //4.map方法 arr1.map(function (item) { return result4+=item; }) console.log(result4); //5.reduce方法 var sum =function(result,item){ return result+=item; }; console.log(arr1.reduce(sum,0));
4.正则表达式中,量词的贪婪模式与惰性模式有什么区别?
在讲贪婪模式和惰性模式之前,先回顾一下JS正则基础:
写法基础:
①不需要双引号,直接用//包含 => /wehfwue123123/.test();
②反斜杠\表示转义 =>/\.jpg$/
③用法基础:.test(str);
语法:
①锚点类
/^a/=>以"a"开头
/\.jpg$/=>以".jpg"结尾
②字符类
[abc]:a或b或c
[0-9]:一个数字
[a-z]:一个字母
. :任意字符
③元字符
^:在[]里面用表示非,在[]外面用表示开头
\d:[0-9]
\s:空白符
\w:[A-Za-z0-9_]
\D:[^\d]-非数字
\S:非空白符
④量词
{m,n}:m到n次
元字符表示:
*:{0,}
?:{0,1}
+:{1,}
难点:贪婪模式/惰性模式
贪婪模式——在匹配成功的前提下,尽可能多的去匹配
惰性模式——在匹配成功的前提下,尽可能少的去匹配
解释一:码文并茂
使用正则表达式中的贪婪、惰性的量词可以控制表达式匹配过程,我们知道量词?、*、+的意义,可以指定相关模式出现的次数,默认的情况下我们使用的是贪婪量词,它的匹配过程是从整个字符串开始查看,如果不匹配就去掉最后一个,再看看是否匹配,如此循环一直到匹配或字符串空为止,如:
vars ="abbbaabbbaaabbb1234";
varre1=/.*bbb/g;//*是贪婪量词
re1.test(s);
这个匹配过程将从整个字符串开始:
re1.test("abbbaabbbaaabbb1234");//false ,则去掉最后一个字符4再继续
re1.test("abbbaabbbaaabbb123");//false ,则去掉最后一个字符3再继续
re1.test("abbbaabbbaaabbb12");//false ,则去掉最后一个字符2再继续
re1.test("abbbaabbbaaabbb1");//false ,则去掉最后一个字符1再继续
re1.test("abbbaabbbaaabbb");//true ,结束
在贪婪量词的后面加多一个?就变成了惰性量词,它的匹配过程相反,是从前面第一个开始,不匹配则加一个,如此循环直到字符串结束,以上面的为例子。
vars ="abbbaabbbaaabbb1234";
varre1=/.*?bbb/g;//*?是惰性量词
re1.test(s);
它的匹配过程如下:
re1.test("a");//false, 再加一个
re1.test("ab");//false, 再加一个
re1.test("abb");//false, 再加一个
re1.test("abbb");//true, 匹配了,保存这个结果,再从下一个开始
re1.test("a");//false, 再加一个
re1.test("aa");//false, 再加一个
re1.test("aab");//false, 再加一个
re1.test("aabb");//false, 再加一个
re1.test("aabbb");//true, 匹配了,保存这个结果,再从下一个开始
......
三.解释二:直戳原理
贪婪与惰性模式区别如下:
一、从语法角度看
贪婪模式用于匹配优先量词修饰的子表达式,匹配优先量词包括:“{m,n}”、“{m,}”、“?”、“*”和“+”。
惰性模式用于匹配忽略优先量词修饰子表达式,匹配忽略优先量词包括:“{m,n}?”、“{m,}?”、“??”、“*?”和“+?”。
二、从应用角度看
两者均影响被量词修饰的子表达式匹配行为,贪婪模式在匹配成功的前提下尽可能多地匹配,而惰性模式则在匹配成功的前提下尽可能少匹配。惰性模式只被部分NFA引擎支持。
三、从匹配原理看
能达到同样匹配结果的情况下,通常贪婪模式效率较高。
惰性模式都可通过修改量词修饰的子表达式转换为贪婪模式。
贪婪模式可以与固化分组结合,提升匹配效率,而惰性模式不行。
5 JSON.stringify兼容
JSON.stringify函数在ie6/7中不支持,如何兼容?
if(!window.JSON){ window.JSON = { stringify: function(obj){ var result = ""; for(var key in obj){ if(typeof obj[key] == "string"){ // 如果属性值是String类型,属性值需要加上双引号 result += "\"" + key + "\":\"" + obj[key] + "\","; }else if(obj[key] instanceof RegExp){ // 如果属性是正则表达式,属性值只保留一对空大括号{} result += "\"" + key + "\":{},"; }else if(typeof obj[key] == "undefined" || obj[key] instanceof Function){ // 如果属性值是undefined, 该属性被忽略。忽略方法。 }else if(obj[key] instanceof Array){ // 如果属性值是数组 result += "\"" + key + "\":["; var arr = obj[key]; for(var item in arr){ if(typeof arr[item] == "string"){ // 如果数组项是String类型,需要加上双引号 result += "\"" + arr[item] + "\","; }else if(arr[item] instanceof RegExp){ // 如果属数组项是正则表达式,只保留一对空大括号{} result += "{},"; }else if(typeof arr[item] == "undefined" || arr[item] instanceof Function){ // 如果数组项是undefined, 则显示null。如果是函数,则显示null?。 result += null +","; }else if(arr[item] instanceof Object){ //如果数组项是对象(非正则,非函数,非null),调用本函数处理 result += this.stringify(arr[item]) +","; }else{ result += arr[item] + ","; } } result = result.slice(0,-1)+"]," }else if(obj[key] instanceof Object){ // 如果属性值是对象(非null,非函数,非正则),调用本函数处理 result += "\"" + key + "\":" + this.stringify(obj[key]) + ","; }else{ result += "\"" + key + "\":" + obj[key] + ","; } } // 去除最后一个逗号,两边加{} return "{" + result.slice(0,-1) + "}"; } }; }
6 如何复制一个对象?
我们知道,对象作为引用类型,使用运算符=时,只是复制了对象的地址。
比如如下代码
- var obj1 = {a:1};
- var obj2 = obj1;
- obj2.a = 2; // 此时obj1.a ===
修改对象obj2同时会改变obj1,那么如果我们需要克隆出一个独立但属性、方法完全一样的对象,该如何实现?
解释一、数组的深浅拷贝
在使用JavaScript对数组进行操作的时候,我们经常需要将数组进行备份,事实证明如果只是简单的将它赋予其他变量,那么我们只要更改其中的任何一个,然后其他的也会跟着改变,这就导致了问题的发生。
var arr = ["One","Two","Three"];var arrto = arr; arrto[1] = "test"; document.writeln("数组的原始值:" + arr + "<br />");//Export:数组的原始值:One,test,Threedocument.writeln("数组的新值:" + arrto + "<br />");//Export:数组的新值:One,test,Three
像上面的这种直接赋值的方式就是浅拷贝,很多时候,这样并不是我们想要得到的结果,其实我们想要的是arr的值不变,不是吗?
方法一:js的slice函数
对于array对象的slice函数, 返回一个数组的一段。(仍为数组) arrayObj.slice(start, [end]) 参数 arrayObj 必选项。一个 Array 对象。 start 必选项。arrayObj 中所指定的部分的开始元素是从零开始计算的下标。 end 可选项。arrayObj 中所指定的部分的结束元素是从零开始计算的下标。 说明 slice 方法返回一个 Array 对象,其中包含了 arrayObj 的指定部分。 slice 方法一直复制到 end 所指定的元素,但是不包括该元素。如果 start 为负,将它作为 length + start处理,此处 length 为数组的长度。如果 end 为负,就将它作为 length + end 处理,此处 length 为数组的长度。如果省略 end ,那么 slice 方法将一直复制到 arrayObj 的结尾。如果 end 出现在 start 之前,不复制任何元素到新数组中。
var arr = ["One","Two","Three"];
var arrtoo = arr.slice(0);
arrtoo[1] = "set Map";
document.writeln("数组的原始值:" + arr + "<br />");//Export:数组的原始值:One,Two,Three
document.writeln("数组的新值:" + arrtoo + "<br />");//Export:数组的新值:One,set Map,Three
方法二:js的concat方法
concat() 方法用于连接两个或多个数组。
该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本。
语法
arrayObject.concat(arrayX,arrayX,......,arrayX)
说明
返回一个新的数组。该数组是通过把所有 arrayX 参数添加到 arrayObject 中生成的。如果要进行 concat() 操作的参数是数组,那么添加的是数组中的元素,而不是数组。
var arr = ["One","Two","Three"];
var arrtooo = arr.concat();
arrtooo[1] = "set Map To";
document.writeln("数组的原始值:" + arr + "<br />");//Export:数组的原始值:One,Two,Three
document.writeln("数组的新值:" + arrtooo + "<br />");//Export:数组的新值:One,set Map To,Three
二、对象的深浅拷贝
var a={name:'yy',age:26};
var b=new Object();
b.name=a.name;
b.age=a.age;
a.name='xx';
console.log(b);//Object { name="yy", age=26}
console.log(a);//Object { name="xx", age=26}
就是把对象的属性遍历一遍,赋给一个新的对象。
var deepCopy= function(source) {
var result={};
for (var key in source) {
result[key] = typeof source[key]===’object’? deepCoyp(source[key]): source[key];
}
return result;
}
//情况1 :被复制的对象内部没有数组或者字面量对象 var obj = {name:"jerry",age:1}; function copy(object) { var copyobj = {}; for (var att in object){ copyobj[att] = object[att]; } return copyobj ; } //test var copyobj =copy(obj); copyobj.name="Tom"; console.log(copyobj.name);//Tom console.log(obj.name);//没有改变仍然是jerry //情况2:被复制的对象内有数组或者对象字面量 var obj2 = {name:["aa"],address:{home:"hz"}}; var copyobj2 = copy(obj2); copyobj2.name[0]="bb";//改变复制的值 copyobj2.address.home = "zz"; console.log(obj2.name[0]);//会被改变 console.log(obj2.address.home); //会被改变 //解决情况2的办法:深度拷贝: function deepcopy(copy,object) { var copy = copy||{}; for(var att in object){ if(typeof object[att]=="object"){ copy[att]=(object[att].constructor === Array)?[]:{}; deepcopy(copy[att],object[att]); } else{ copy[att] = object[att]; } } } //test var copyobj2 = {}; var obj2 = {name:["aa"],address:{home:"hz"}}; deepcopy(copyobj2,obj2); copyobj2.name[0]="bb";//改变复制的值 copyobj2.address.home = "zz"; console.log(obj2.name[0]);//aa 没有变化 console.log(obj2.address.home); //hz 没有变化
7 JS和强类型语言(比如C++)在类型方面的主要区别?
JS和强类型语言(比如C++)在类型方面的主要有哪些区别?
(1)强类型定义语言:强制数据类型定义的语言。也就是说,一旦一个变量被指定了某个数据类型,如果不经过强制转换,那么它就永远是这个数据类型了。
举个例子:如果你定义了一个整型变量a,那么程序根本不可能将a当作字符串类型处理。强类型定义语言是类型安全的语言。
(2)弱类型定义语言:数据类型可以被忽略的语言。它与强类型定义语言相反, 一个变量可以赋不同数据类型的值。
强类型定义语言在速度上可能略逊色于弱类型定义语言,但是强类型定义语言带来的严谨性能够有效的避免许多错误。
8 JS中有哪些场景会出现隐式类型转换?
一、数字运算符
做+操作时,数字被隐式转换成字符串,实际上做的是字符串连接操作。
做除了加法以外的运算操作时,字符串被隐式转换成数字,实际上做的是数值计算。
二、.点号操作符
数字、字符串等直接量在做.操作调用方法时,隐式地将类型转换成对象。
三、if语句
if()括号里的表达式部分会被隐式转化为布尔类型进行判别。
四、= =等号
等号左右两边会被转化为同一种类型再进行比较
9.JS中有哪些识别类型的方法(包括用于识别某种具体类型的方法)?
1、typeof:可以识别标准类型/除Null外;不能识别具体对象类型(Function除外);
2、instanceof:可以判别内置对象类型;不能判别原始类型,识别自定义对象类型;
3、Object.prototype.toString.call():可以识别标准类型、内置对象类型;不能识别自定义对象类型;
4、constructor:可以判别标准类型(Underfined/Null除外),可以判别内置对象类型,可以判别自定义对象类型;
//克隆封装函数 function clone(obj){ var o; if (typeof obj == 'object') { if (obj === null) { o = null; }else if (obj instanceof Array) { // 当为 Array 类型 o = []; for(var i = 0, len = obj.length; i < len; i++){ o.push(clone(obj[i])); } }else if (obj instanceof Date) { // 当为 Date 类型 o = new Date(); o.setTime(obj.getTime()); }else{ // 当为 Objec 类型,这个要写在后面 o = {}; for(var k in obj){ o[k] = clone(obj[k]); } } }else{ // 当为除 null 以外的原始类型及函数类型时(函数的深拷贝可以使用等号) o = obj; } return o; }
10.思考题:函数声明和函数表达式定义同一个函数时,执行的是哪个?
// 以下代码执行时,三次打印分别输出什么?为什么? function add1(i){ console.log("函数声明:"+(i+1)); } add1(1); var add1 = function(i){ console.log("函数表达式:"+(i+10)); } add1(1); function add1(i) { console.log("函数声明:"+(i+100)); } add1(1);
实际代码执行的顺序是这样的
var add1; function add1(i){ console.log("函数声明:"+(i+1)); } //覆盖了前一个 function add1(i) { console.log("函数声明:"+(i+100)); } add1(1);//得101 //再次覆盖了前面的 add1 = function(i){ console.log("函数表达式:"+(i+10)); } add1(1);//得11 add1(1);//得11
- 函数声明:101
- 函数表达式:11
- 函数表达式:11
- 当函数申明和函数表达式同时定义一个函数时,执行函数表达式定义的函数,
- 第一个由于在函数表达式定义之前调用,所以调用的是函数申明定义的函数,又由于函数申明重复定义了这个函数所以执行最后函数申明定义的函数,
- 第二个由于在函数表达式定义的函数后面正常执行函数表达式定义的函数得到结果11,
- 第三个当函数申明和函数表达式同时定义一个函数时,执行函数表达式定义的函数,所以是11
11 对象方法中定义的子函数,子函数执行时this指向哪里?
三个问题:
-
以下代码中打印的this是个什么对象?
-
这段代码能否实现使myNumber.value加1的功能?
-
在不放弃helper函数的前提下,有哪些修改方法可以实现正确的功能?
var myNumber = { value: 1, add: function(i){ var helper = function(i){ console.log(this); this.value += i; } helper(i); } } myNumber.add(1);
解答:this指向window对象,因为当一个函数并非一个对象的属性时,它被当作一个函数来调用,此时this被绑定到全局对象上。
不能实现。
方法1:通过apply方法改变this的指向
方法2:通过call方法,改变this的指向
方法3:把this赋值给一个变量,把这个变量给内部的匿名函数使用
-
var myNumber = { value: 1, add: function(i){ var helper = function(i){ console.log(this.myNumber); this.myNumber.value += i; } helper(i); } } myNumber.add(1); //(2) var myNumber = { value: 1, add: function(i){ var helper = function(i){ console.log(this); this.value += i; } helper.apply(myNumber,[i]); } } myNumber.add(1); //(3) var myNumber = { value: 1, add: function(i){ var helper = function(i){ console.log(this); this.value += i; } helper.bind(myNumber,i)(); } } myNumber.add(1); //(4) var myNumber = { value: 1, add: function(i){ var helper = function(i){ console.log(this); this.value += i; } helper.call(myNumber,i); } } myNumber.add(1);
12在变量作用域方面,函数声明和函数表达式有什么区别?
函数声明:函数声明会在程序执行时就被创建好函数, 在被调用时初始化词法环境.
函数表达式:函数表达式只会在运行到表达式语句时被创建和初始化环境.
总结:函数声明就算在代码最后去编写, 在代码头部也可以被执行,因为在js预处理时会自动把函数声明提升到代码的最顶部.而函数表达式只会在当前语句时被执行.表达式和声明都是函数对象, 只是被创建的时间不同.
- 以函数声明的方式定义的函数,函数可以在函数声明之前调用,而函数表达式的函数只能在声明之后调用.
- 以函数声明的方式定义的函数仅可出现在全局中,或者嵌套在其他的函数中,但是它们不能出现在循环,条件或者try/catch/finally中,而函数表达式可以在任何地方使用.
13.闭包的应用场景有哪些?
//场景1:保存现场。如果不进行捕获则不会正确弹出li节点对应的文本值 var li = document.getElementsByTagName("li"); for(var i=0;i<li.length;i++){ li[i].onclick=(function (i) { return function () { alert(li[i].firstChild.nodeValue); } })(i); } //场景2:封装。保证外部直接访问不到变量num但可以通过 setNum和getNum方法类似于java里的私有变量。 (function () { var num =0; function getN() { return num ; } function setN(x) { num=x; } window.getNum=getN; window.setNum=setN; })(); alert(getNum()); setNum(12); alert(getNum()); // 场景3:减少全局变量 如果不使用闭包使a自增则需要将a变量放到外部 function foo2() { var a = 0; return function foo3() { a++; alert(a); } } var foo4 = foo2(); foo4(); foo4(); // 场景4:性能优化:减少函数定义时间和内存消耗 // 如果add函数越复杂则减少的时间就越多 var sum = (function() { var add = function(i, j){ return i+j; } return function(i,j) { add(i, j); } })() var startTime = new Date(); for(var i = 0; i< 1000000; i++) { sum(1,1); } var endTime = new Date(); console.log(endTime - startTime);
14.原型继承和类继承有什么区别?
类继承
JS中其实是没有类的概念的,所谓的类也是模拟出来的。特别是当我们是用new 关键字的时候,就使得“类”的概念就越像其他语言中的类了。类式继承是在函数对象内调用父类的构造函数,使得自身获得父类的方法和属性。call和 apply方法为类式继承提供了支持。通过改变this的作用环境,使得子类本身具有父类的各种属性
原型继承
原型构造对象是把现有的对象作为原型创建新的对象,被创建的对象继承的是原型对象的原型,即隐藏的属性_proto_,在浏览器中可以看见。每一个对象都有原型。原型是可以修改的。当一个对象需要调用某个方法时,它会去最近的原型上查找该方法,如果没有找到,它会再次往上继续查找。这样逐级查找,一直找到了要找的方法,或者没有此方法。 这些查找的原型构成了该对象的原型链,Object原型最后指向的是null。我们说的原型继承,就是将父对象的原型方法给子对象的原型。子类本身并不拥有这些方法和属性。
对比
和原型对比起来,构造函数(类)继承的方法都会存在父对象之中,每一次实例,都会将funciton保存在内存中,这样的做法毫无以为会带来性能上的问题。其次类式继承是不可变的。在运行时,无法修改或者添加新的方法。类式继承可以多重继承,调用多个构造函数即可,原型继承可以通过修改原型来修改子对象。
15.实现一个Circle类
编程实现:
a.创建一个圆(Circle)的类,并定义该类的一个属性(半径)和两个方法(周长和面积),其中圆的半径可以通过构造函数初始化
b.创建圆的一个对象,并调用该对象的方法计算圆的周长和面积
function Circle(r){ this.radius = r; } /*获取圆对象周长*/ Circle.prototype.getPerimeter = function(){ return 2*Math.PI*this.radius; } /*获取圆对象面积*/ Circle.prototype.getArea = function(){ return Math.PI*Math.pow(this.radius, 2); } var circle = new Circle(5); console.log("圆半径:"+circle.radius); console.log("圆周长:"+circle.getPerimeter()); console.log("圆面积:"+circle.getArea());
16.请使用Js代码写出一个类继承的模型
请使用Js代码写出一个类继承的模型,需包含以下实现:
-
定义父类和子类,并创建父类和子类的属性和方法
-
子类继承父类的属性和方法
-
在创建子类对象时,调用父类构造函数
//父类 function Person(name,age){ //父类属性 this.name=name; this.age=age; } //父类方法 Person.prototype={ _p1:function(){ return "I am " +this.name+","+this.age+" years old.";} } //子类 function Student(name,sex,job){ Person.apply(this,arguments);//继承父类属性 this.job=job;//子类属性 } //继承父类方法 Student.prototype=new Person(); Student.prototype.constructor=Student; //子类方法 Student.prototype._s=function(){ return "I am a " +this.job; } var lili = new Student("lili", 21, "student"); console.log(lili._p1()+lili._s());