热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

javascript高级程序设计中很多值得仔细推敲的地方

用法1:正则表达式实现格式化输出varf'9999999'.replace(\d{1,3}(?(\d{3})+$)g,'$&,'

用法1:正则表达式实现格式化输出 

var f = '9999999'.replace(/\d{1,3}(?=(\d{3})+$)/g, '$&,');
console.log(f);
后面的?=表示只有相等的情况下才会保存前面的部分!如果是match,我们看看他打印什么
 console.log('9999999'.match((/\d{1,3}(?=(\d{3})+$)/g)));
因为match在全局情况下会保存所有的符合条件的结果,所以打印["9", "999"],但是如果把上面的 加号变成 星号,就会打印["9", "999", "999"]
面试题2:如何对数组进行去重?

 Array.prototype.unique=function()
{
var result=[],comb=[];
for(var i=0,j=this.length;i{
result[arr[i]]=arr[i];//去重
}
//处理下标[1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 8: 8, 1200: 1200]
//这时候已经去重了,但是还是有很多空缺,这些空缺的元素值为undefined!
//下面进行去除空缺的操作!
for (var j=0 ; j if(j in result)
{
//因为for..in会跳过稀疏数组,所以这种方式还是可以的!
comb.push(result[j]);
}
}
return comb;
}
var arr=[1,3,4,4,4,8,1200,5,2,1];
var result=arr.unique();
console.log(result);
我们知道jQuery.unique也能去重,我们看看他的源码

 Array.prototype.unique=function() {
var elem,
duplicates = [],
j = 0,
i = 0;
this.sort();//首先排序
while ( (elem = this[i++]) ) {
if ( elem === this[ i ] ) {
j = duplicates.push( i );
//把下标保存起来,这些下标是要被移除的!
}
}
//duplicates数组里面放的全部是重复的下标[1,3]
//push方法返回修改后数组的长度!
while ( j-- ) {
//移除重复项目!
this.splice( duplicates[ j ], 1 );
}
};
var arr=[2,3,2,3];
arr.unique();
console.log(arr);
思路:先排序再次去重
复习题3:关于arguments对象的总结?

非严格模式:

arguments对象和命名参数的是同步的,但是不是说读取这两个值会访问相同的内存空间‘;如果只是传入了一个参数,那么arguments[1]的设置不会反映到命名参数中(因为arguments的长度是由传入的参数个数决定的,而不是形参的个数);没有传入的参数自动赋值为undefined

严格模式:

重写arguments的值会导致语法错误!

注意:借助arguments虽然可以模拟重载,但是只是参数个数的重载,如果两个函数名相同那么后面的函数会覆盖前面的函数。ECMAScript中所有参数传递的都是值,不可能通过引用传递参数!

复习题4:调用函数参数传入值还是按照引用传递?

 var arr=[123];
var obj1={};
var obj2={};
function foo(v1,v2,v3)
{
arr.push(123);
//这种方式没有完全修改外部的Object对象,而只是为该对象添加属性!
v2.name="qinliang";
//这种方式完全修改了外部的Object对象,所以和外面的object对象联系都断了!
v3={sex:"female"};
}
foo(arr,obj1,obj2);
console.log(arr);//打印[123]
console.log(obj1);//Object {name: "qinliang"}
console.log(obj2);//Object {}
数字和字符串把值直接复制进去了,放入到 中,而数组对象和Object对象把 变量地址也也复制进 中,这时候v1,v2,v3和外面的相同,指向同一个对象,如果里面不重新赋值而仅仅添加元素,那么会对外部的元素造成引用,如果 重新赋值那么地址和内容完全不同了,这时候里面的改变不会反映到外部了!
参数是按照值来传递的,而不是按照引用来传递的:

  function Person()
{}
//按照引用来传入,这里obj是具有person对象的引用
//其实当复制保存着对象的某个变量的时候,操作的是对象的引用
//但是为对象添加属性的时候操作的是实际的对象
function setName(obj)
{
obj.name="qinliang";
//如果是按照引用来传入的,那么person就会被修改为指向name为
//fkl的新的对象,但是访问的时候还是指向qinliang这个对象,这说明在
//函数内部修改了参数的值,但是原始的引用仍然保持不变,说明是按值传递
//实际上在内部重写的时候变量引用的就是一个局部变量了,函数执行完毕之后会销毁!
obj=new Object();
obj.name="fkl";
}
var person=new Person();
setName(person);
console.log(person.name);//打印qinliang
复习题5:基本类型和引用类型的差别

(1)保存方式不同
 基本类型:5中基本数据类型,如Undefined,Null,Boolean,Number,String是按照值来访问的,因为可以操作保存在变量中的实际的值!

引用类型:引用类型的值是保存在内存中的对象,Javascript不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间(其实对变量赋值就是操作内存空间),在操作对象的时候,实际上是在操作对象的引用而不是实际的对象,为此,引用类型的值是按照引用访问的!

        注意:虽然访问变量有按值和按引用两种方式,但是参数只能按值传递!

(2)复制基本类型和引用类型时候是不同的

 基本类型:复制的是副本,两者互不干扰

引用类型:复制的是指针,而这个指针指向存储在堆中的一个对象,复制后改变一个变量会影响另外一个变量

function c()
{
var obj={v:'1'};
obj.func=function()
{
obj={v:"3"};
}
return obj;
}
var c1=c();
//此时返回的对象具有v属性和func函数!
console.log(c1);
c1.func();
//打印1
console.log(c1.v);
//里面是对obj重新赋值,而不是修改属性
该例子表明,对属性的完全修改不会影响对象本身!
 function c()
{
var obj={v:'1'};
obj.func=function()
{
obj.v="qinliang";
}
return obj;
}
var c1=c();
//此时返回的对象具有v属性和func函数!
console.log(c1);
c1.func();
//打印qinliang
console.log(c1.v);
//里面是对obj修改属性而不是重新定义
对属性的修改会反映到对象本身上面!
上面的两个例子还是比较晦涩难懂,那么我们修改为如下:

function c()
{
var sex='female';
var name="klfang";
var obj={
v:'1',
func:function(){
obj.v="qinliang";
console.log(this);//这里的this就是外部拿着的引用,该引用就是obj对象!
console.log(sex+name);//这里构成了闭包,可以访问到外部函数的局部变量!
}};
return obj;
}
//这时候c1保存了内部的obj对象的引用(这里就相当于变量赋值),记住:是引用!
var c1=c();
//此时因为c1保存了对内部obj的引用,所以他调用该内部的函数时候this就是内部的obj对象!
//调用该func函数时候,其实就是obj在调用该函数,同时因为func构成了闭包可以访问外部的变量!
c1.func();
//c1中保存的就是obj对象!
console.log(c1);
//我们调用了这个函数过后如果没有必要再次访问内部的func函数了,那么我们就销毁对该对象的引用
//销毁了对obj的引用,那么func也不会占用外部的变量sex,name了,可以安全的销毁了!
c1=null;
这也是对变量属性进行修改,所以可以反映到c1这个外部变量和obj这个内部变量中,因为这里都是 引用的传递!见该图
   function c()
{
var sex='female';
var name="klfang";
var obj={
v:'1',
func:function(){
obj={sex:"female"};
//这里对变量进行了修改了,所以这时候obj就是局部变量,而且他保存的引用是这个
//新对象的引用,而不是外部c1保存的对象(原来的obj对象)
console.log(this);
console.log(sex+name);
}};
return obj;
}
var c1=c();
//c1指向了内部的obj对象,他保存的是引用!
c1.func();
//调用该函数,this还是原来的obj对象,同时sex/female来自于外部环境!
console.log(c1);
//c1保存的是原来的obj对象
c1=null;
//解除引用,于是内部可以销毁环境了!
该例子实现了在内部obj 完全的覆盖,这时候obj就成了局部新变量了,所以对他的修改只是对局部变量的修改,不会反映到外部的变量中, 因为他们不是指向同一个对象了!结果见 该图

下面我们通过这个比较经典的例子来看看:

function createCompare(name)
{
return function(obj1,obj2)
{
var value1=obj1[name];
var value2=obj2[name];
if(value1 {
return -1;
}else if(value1>value2)
{
return 1;
}else
{
return 0;
}

}
}
var compare=createCompare('name');
//只是这里返回的是一个函数,而上面的例子返回了对象字面量,这时候compare获取了对
//内部这个函数对象的引用,记住:是引用!
var result=compare({name:"qinliang"},{name:"fkl"});
//我们通过这个引用获取到原来的匿名函数,然后进行比较
console.log(result);
//如果我们不需要这个匿名函数了,那么我们把引用设置为null,那么可以销毁外部环境的变量了,如name
compare=null;
下面这种构造函数也是 隐式返回了一个对象,但是该对象具有函数,所以他也会保持对外部的局部变量的引用从而形成闭包,因此当我们不用这个引用的时候要 解除引用

  function c()
{
this.obj={v:"1"};
var sex="male";
this.func=function()
{
this.obj={v:"qinliang"};
//这时候this还是new出来的对象,现在我通过this绑定到对该对象的修改
//所以他的修改会反映到外部变量中!如果把this.obj修改为obj那么就不会反映到
//外部变量中!
console.log(sex);
//虽然构造函数没有明确的return,但是他还是构成了闭包了!打印male
}
}
var c=new c();
//这时候c保存了对构建的对象的引用!
c.func();
//通过这个引用调用了函数,因为引用指向了这个new出来的对象
//所以在func中this依然是new出来的这个对象!
console.log(c);

如果我们不解除引用,那么因为闭包的存在,其具有"封闭性+持久性"等特点,导致内部的作用域无法被销毁

function a()
{
var i=0;
function b()
{
console.log(++i);
}
return b;
}
var c=a();
//用变量c保持对闭包函数的引用,只要我们不解除引用
//那么我们多次调用c函数,那么内部的i是一直变化的,这就是
//持久性的表现,如果不是闭包那么无法实现对数据状态的保存
//因为函数调用结束后,就会采用“标记清除”销毁内部变量
c();
c();
c();//多次调用,内部的i不断在增加
c=null;//这里解除引用,于是内部的i可以销毁了

如果是基本类型就容易理解了,因为基本类型返回的是副本,而且两种互不影响

function add(num)
{
num+=10;
return num;
}
num=10;
//JS中值传递的时候传递的是副本,而且是值本身!
//两者之间是完全独立的!
console.log(add(num));
console.log(num);
通过下面的例子我们知道其实函数,对象等引用类型全部是通过引用方式传递的

function person()
{
this.name="qinliang";
this.age=19;
}
function show1()
{
console.log(this.name);
}
var p=new person();
//这时候对象p的name属性中保存了show1的引用
//因为show1不是基本类型,所以只是把他的引用地址复制到p对象的name属性上!
p.name=show1;
//name属性获取了show1的引用地址,所以打印函数!
console.log(p.name);
//因为函数的名字就是引用地址!
console.log(show1);
复习6:匿名函数中this都具有全局特效,所以匿名函数中this通常指向window

  var name='qinliang';
var obj={
name:"fkl",
getName:function()
{
return this.name;
}
}
console.log((obj.getName)());
//第二行在代码调用的方法前加上了一个括号,虽然加上括号以后好像是在引用一个函数,但是this的值得到了
//维持,所以打印fkl
console.log((obj.getName=obj.getName)());
//这时候首先发生了赋值,所以this的值无法得到维持!
把函数保存到本地,这时候调用对象成了window对象了。除非上面那种通过 返回一个闭包来保持this对象!

 var sex="female";
var obj={
name:"qinliang",
prop:{
sex:"male",
getSex:function()
{
return this.sex;
}
}
}
var result=obj.prop.getSex();
//因为调用对象是prop对象(最后一个点前面的对象是调用对象)
console.log(result);
var func=obj.prop.getSex;
//这时候我们用func变量保存到到getSex这个函数对象的引用,注意:是引用!
console.log(func());
//打印female,这时候把函数保存到了本地了,所以相当于window.func()!
构造函数显示返回了对象,那么隐式返回的对象被覆盖了

 function another(name)
{
this.name=name;//这个new出来的对象已经无法引用他了!
return {"badName":name,sex:"global",getFunc(){return this.sex}}
}
var u=new another("xx");
//这时候的u对象就是显示返回的对象,于是u.getFunc调用时候this
//还是该对象!
console.log(u.getFunc());
通过这个 博客你将会明白,函数引用可以改变this的指向,但是函数调用无法改变函数的作用域!关于this的其它的用法,可以参考 这篇博客,不过我们还是要强调一点,那就是 匿名函数的作用域具有全局特效!

复习7:那些特殊情况下会导致我们直接定义的函数或者变量具有全局作用域呢

情况1:通过Function完成的方式

var i=100;
function local()
{
var i=10;
(new Function("console.log(i)"))();
}
//打印100,也就是说通过Function构造的函数具有全局特效!
local();
函数中定义的变量没有var开头会变成全局变量

function f()
{
a=0;
//打印0
console.log(a);
}
var a=4;
f();
//打印0
console.log(a);
setTimeout中的this执行全局变量

var name="fkl";
function test()
{
var name="qinliang";
setTimeout(function(){console.log(this.name);},0);
}
test();
window.eval类型解析出来的变量具有全局特效

function Test1()
{
window.eval('var b=1;');
}
Test1();
alert(b);//打印1


推荐阅读
author-avatar
手机用户2502860901
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有