开发过程中遇到的一些奇妙的事情(js):

开发过程中遇到的一些奇妙的事情:(基础知识,碰到了忘了又百度了又怕忘了就记下来了)

1. 变量、函数声明提前:

js中变量的声明和函数声明会上升,例如:

if ('a ' in window){
 var a =10;
console.log(true)  // true
}

var a上升到if语句之前。 函数的声明优于变量声明,这里的优于可以理解为晚于变量声明后,如果函数名和变量名相同,函数声明就能覆盖变量声明。

var tmp = new Date();

function f() {
  console.log(tmp);
  if (false) {
    var tmp = 'hello world';
  }
}

f(); //输出undefined
相当于
function f() {
var tmp;
  console.log(tmp);
  if (false) {
    tmp = 'hello world';
  }
}

es5只有函数作用域和全局作用域,没有块级作用域,因此if里面的var tmp会提升到f函数内部的顶部

 1       function Foo() {
 2            getName = function () { console.log (1); };
 3            return this;
 4        }
 5        Foo.getName = function () { console.log (2);};
 6        Foo.prototype.getName = function () { console.log (3);};
 7        var getName = function () { console.log (4);};
 8        function getName() { console.log (5);}
 9
10        //以下输出值为多少?
11        Foo.getName(); 
12        getName();
13        Foo().getName();
14        getName();
15        new Foo.getName();
16        new Foo().getName();
17        new new Foo().getName();

浏览器执行Js程序的时候,分两步:

(1)预解析
    在代码解读之前发生,相当于一个"仓库",放一些东西,比如var、function、参数等。
    预解析时变量都是未定义的,函数则是整个函数块。
    预解析时遇到重名的,会覆盖。变量与函数,留函数;函数与函数,留后面解析的。
    当代码在一个环境中执行时,会创建变量对象的一个作用域链。是域就要预解析。

(2)执行代码
    代码自上而下,从左往右的执行,读到表达式时,预解析仓库中的东西发生改变。
Foo.getName(); //2 
    相当于执行第5行的console
    
getName(); //4
    预解析里直接取,相当于执行第7行的console
    
Foo().getName(); // 1
首先Foo().getName()与Foo.getName()有什么差别呢,差别就在于Foo()与Foo,Foo()是已经把 
Foo()的内容执行了一遍的,Foo则是一个函数变量,Foo()里面return this;,这里链式调用的用法,
详细的介绍可以搜一下,Foo.getName() 中 getName()是Foo的属性,但是Foo().getName()中,
getName()不是Foo()的属性,而是全局变量var getName = function(){return(4);};,
Foo().getName()的意思是Foo()执行完后,回到全局中,再调用getName(),这样就叫链式调用。
那为什么结果是1不是4,因为Foo()执行的结果使 getName = function(){return(1);};,
所以再次调用getName(),结果就是1,
    
getName(); //1
    跟window.getName()一样,
    当于执行第2行的console
    
new Foo.getName(); // 2
    执行第5行的Foo.getName(),

    new操作符调用构造函数创建对象的步骤?
    (1)创建一个新对象;
    (2)将构造函数的作用域赋给新对象(因此this就指向了这个新对象)
    (3)执行构造函数中的代码(为这个新对象添加属性)
    (4)返回新对象
    点(.)的优先级高于new操作,遂相当于是:
        new (Foo.getName)();
    new操作符是要执行构造函数中的代码的;
    所以实际上将getName函数作为了构造函数来执行,遂打印2

new Foo().getName(); // 3
    Foo()执行返回this,此时this指向new出来的新实例对象,
    实例对象从本身找不到getName属性,顺着原型链找到第6行的getName,打印3
    执行第6行console

new new Foo().getName(); // 3
    以实例的getName方法为构造函数new实例,执行构造函数,打印3
    执行第6行

P.s 这道题目网上有很多。具体出处找不到了,但是很有意思。

2. 遍历节点的Js规范:
var divs = document.getElementsByTagName('div');
for (var i = 0; i < divs.length; i++) {
      doSomething(divs[i]);
}

这样的操作,假如获取节点长度(divs.length)的复杂度是O(n),获取某个子节点的复杂度也是O(n),则由于每次要计算节点长度,因而遍历一遍所有节点,这个算法的时间复杂度就为O(n^2)。用以下方法可降低时间复杂度:

var divs = document.getElementsByTagName('div');
var size = divs.length;
for (var i = 0; i < size; i++) {
    doSomething(divs[i]);
}

这里先把节点长度存到一个局部变量中,以后每次循环不再计算节点长度,因此总的时间复杂度变为了O(n) + 1;
TGuildeJs规范

3. 事件监听:
<ul id="list">
  <li id="item1">item1</li>
  <li id="item2">item2</li>
  <li id="item3">item3</li>
  <li id="item4">item4</li>
</ul>

a. 以前为li绑定事件都是获取li节点,然后绑定事件:

window.onload=function(){
  var ulNode=document.getElementById("list");
  var liNodes=ulNode.childNodes||ulNode.children;
  for(var i=0;i<liNodes.length;i++){
    liNodes[i].addEventListener('click',function(e){
      alert(e.target.innerHTML);
    },false);
  }
}

由上可以看出来,假如不停的删除或添加li,则function()也要不停的更改操作,易出错,因此推荐使用事件代理。在传统的事件处理中,你按照需要为每一个元素添加或者是删除事件处理器。然而,事件处理器将有可能导致内存泄露或者是性能下降——你用得越多这种风险就越大。JavaScript事件代理则是一种简单的技巧,通过它你可以把事件处理器添加到一个父级元素上,这样就避免了把事件处理器添加到多个子级元素上。
b. 使用事件代理
事件代理用到了两个在JavaSciprt事件中两个特性:事件冒泡以及目标元素。使用事件代理,我们可以把事件处理器添加到一个元素上,等待一个事件从它的子级元素里冒泡上来,并且可以得知这个事件是从哪个元素开始的。

window.onload=function(){
  e = e || window.event;/*在函数体内不用使用event = event || window.event; 来标准化Event 对象;*/
  target = e.target || e.srcElement;
  var ulNode=document.getElementById("list");
  ulNode.addEventListener('click',function(e){
       if(target.nodeName.toUpperCase()=="LI"){/*获取目标元素,判断目标事件是否为li*/
         alert(e.target.innerHTML);
       }
     },false);/*false 默认参数,冒泡阶段*/
};
4. 一次完整ajax是如何实现的?

5. 一次完整Http请求是如何实现的?

6. 对跨作用域的变量处理建议:
  • 将常用的跨作用域变量储存在局部变量中,然后直接访问局部变量。
7. 关于作用域链
  • 每一个JS函数都表示一个对象,Function是对象的一个实例,因此Function对象拥有可以访问的属性、方法,和不可以访问仅供JS引擎存取的内部属性。其中一个内部属性是[[scope]];
    [[scope]]包含了一个函数被创建的作用域对象的集合,这个集合就是作用域链,作用域链决定了函数有权访问哪些变量和方法,作用域链中每个对象成为可变对象。
function sum(x,y){
     var a =10;
     return x+y+a;
}
sum(2,3)
  1. 当sum函数创建之后,sum函数的内部属性[[scope]]所包含的作用域链插入了一个可变对象,此时的可变对象又称为全局对象,包含着
    诸如window,navigator,document,sum本身 等等。
  2. 当sum(2,3)执行时,会创建一个执行环境,多次执行函数会创建多个执行环境,函数执行完毕后,执行环境会自动销毁。
    每个执行环境都有自己的作用域链,用于解析标识符,当执行环境被创建时,它的作用域链初始化为当前函数的[[scope]]属性中的对象,函数的作用域链会插入一个可变对象,此时的可变对象称为活动对象,包含着所有函数内部的局部变量,参数,参数集合(arguments),this。当执行环境被销毁时,活动对象也被销毁。
  3. 活动对象会被推入作用域的最前端,因此在函数内部访问变量的时候会先反问活动对象,再访问外部的对象,因此才会有第6条的内容。
8. 闭包:

在理解上述的关于作用域链的描述之后,闭包会更容易理解。
常见闭包场景,就是函数A内部返回一个函数B,这样函数B可以访问函数A内部的变量,即使在函数A执行完之后自身的执行环境被销毁。

function sum(a,b){
return function add(){
    return a+b;
  }
}
var t = sum(1,2) 
t();
  1. 当sum函数创建时,会产生一个全局对象。
  2. 当sum(1,2)执行时,会产生一个活动对象,包含a,b。
  3. 当add函数创建时(及闭包创建),它的[[scope]]属性被初始化为以上的两个对象。
  4. 当t()执行时(及闭包执行),会创建一个执行环境,它的作用域链与属性[[scope]]中所引用的两个相同的作用域链对象一起被初始化,然后一个活动对象被闭包自身创建。
  5. 此时闭包的作用域链包含3个对象,一个全局对象,一个sum活动对象,一个自身的活动对象。所以闭包可以访问外部函数的属性。
  6. 外部函数执行环境被销毁,但由于外部函数的活动对象被闭包引用,因此这些活动对象无法被销毁,这意味着脚本中的闭包需要更多内存消耗。
9. 关于使用"use strict"

对于这个问题,既简要又最重要的答案是,use strict 是一种在JavaScript代码运行时自动实行更严格解析和错误处理的方法。那些被忽略或默默失败了的代码错误,会产生错误或抛出异常。通常而言,这是一个很好的做法。

严格模式的一些主要优点包括:

  1. 使调试更加容易。那些被忽略或默默失败了的代码错误,会产生错误或抛出异常,因此尽早提醒你代码中的问题,你才能更快地指引到它们的源代码。
  2. 防止意外的全局变量。如果没有严格模式,将值分配给一个未声明的变量会自动创建该名称的全局变量。这是JavaScript中最常见的错误之一。在严格模式下,这样做的话会抛出错误。
"use strict"
function f(){
  b=2;
  console.log(b)  //ERROR
}
f();
  1. 消除 this 强制。如果没有严格模式,引用null或未定义的值到 this 值会自动强制到全局变量。这可能会导致许多令人头痛的问题和让人恨不得拔自己头发的bug。在严格模式下,引用 null或未定义的 this 值会抛出错误。
  2. 不允许重复的属性名称或参数值。当检测到对象(例如,var object = {foo: "bar", foo: "baz"};)中重复命名的属性,或检测到函数中(例如,function foo(val1, val2, val1){})重复命名的参数时,严格模式会抛出错误,因此捕捉几乎可以肯定是代码中的bug可以避免浪费大量的跟踪时间。
  3. 使eval() 更安全。在严格模式和非严格模式下,eval() 的行为方式有所不同。最显而易见的是,在严格模式下,变量和声明在 eval() 语句内部的函数不会在包含范围内创建(它们会在非严格模式下的包含范围中被创建,这也是一个常见的问题源)。
  4. 在 delete使用无效时抛出错误。delete操作符(用于从对象中删除属性)不能用在对象不可配置的属性上。当试图删除一个不可配置的属性时,非严格代码将默默地失败,而严格模式将在这样的情况下抛出异常。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容