整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

JavaScript浏览器对象

OM--Browser Object Model

浏览器 对象 模型

window对象,是BOM中所有对象的核心

一、属性

A、位置类型--获得浏览器的位置

IE:

window.screenLeft--可以获得浏览器距屏幕左上角的左边距

alert(window.screenLeft);

window.screenTop--可以获得浏览器距屏幕左上角的上边距

alert(window.screenTop);

FF:

screenX--左边距

screenY--上边距

B、获得浏览器的尺寸

获得窗口宽度--document.documentElement.clientWidth

获得窗口高度--document.documentElement.clientHeight

C、关系类型

1.parent返回父窗口

2.top 返回顶层窗口

3.self===window

4.opener--开启者

D、stutas 设置窗口状态栏的文本

二、方法

A、窗体控制

IE

window.moveBy(x,y) 相对于当前位置,沿着x/y移动指定的像素,负数向反方向移动

window.moveBy(x,y) 相对于浏览器的左上角沿着x/y移动指定的像素,负数向反方向移动

B、窗口尺寸的改变

IE

resizeBy(x,y); 相对于当前窗体大小,调整宽度和高度

resizeTo(x,y) 把窗体调整为指定的宽度的高度

C、对窗体滚动条的控制

scrollBy(x,y) 相对于滚动条的位置移动的像素(前提有滚动条)

scrollTo(x,y) 相对于当前窗口的高度或宽度,移动到指定的像素

D、时间间隔的函数

1.setInterval("函数或者代码串",指定的时间(毫秒));

在指定的周期不断的执行函数或者代码串

var i=0;

setInterval(change,1000);

function change(){

alert(i);

i++;

}

2.clearInterval()--停止调用

window.onload=function(){

var bb=setInterval("alert('hah')",3000);

var aa=document.getElementById("stop");

aa.onclick=function(){

clearInterval(bb);

}

}

3.setTimeout("函数或代码串",毫秒)--在指定的毫秒数后,只执行一次代码

window.onload=function(){

var aa= setTimeout("alert('bbs')",3000);

var bb=document.getElementById("stop");

bb.onclick=function(){

clearTimeout(aa);

}

}

E、打开新的窗口

open(url,name,feafurse,replace);--在脚本中打开新的窗口

备JavaScript面试时应了解的事项。

JavaScript现在是一种非常流行的编程语言,基于该语言,派生了大量库和框架。 但是,无论高层生态系统如何发展,离不开原始的JavaScript。 在这里,我选择了4个JavaScript面试问题来测试程序员使用普通JavaScript的技能。

1.实现Array.prototype.map

如何手动实现Array.prototype.map方法?

熟练使用数组的内置方法并不难。但是,如果您只是熟悉语法而又不了解原理,那么很难真正理解JavaScript。

对于Array.prototype.map,它将创建一个新数组,其中将填充在调用数组中每个元素上调用提供的函数的结果。

如果引用lodash,我们可以编写一个map函数,如下所示:

function map(array, iteratee) { 
  let index = -1 
  const length = array == null ? 0 : array.length 
  const result = new Array(length) 
  while (++index < length) { 
    result[index] = iteratee(array[index], index, array) 
  } 
  return result
}

使用示例:

2. Object.defineProperty和代理

如何实现这种编码效果?

我们可以看到,当我们尝试连续打印obj.a三次时,会得到三种不同的结果。看起来多么不可思议!

您可以创建一个神秘的对象obj来实现此效果吗?

实际上,此问题有三种解决方案:

· 访问者属性

· Object.defineProperty

· 代理

根据ECMAScript,对象的属性可以采用两种形式:

从逻辑上讲,对象是属性的集合。每个属性都是数据属性或访问器属性:

· 数据属性将键值与ECMAScript语言值和一组布尔属性相关联。

· 访问器属性将键值与一个或两个访问器函数以及一组布尔属性相关联。访问器函数用于存储或检索与属性关联的ECMAScript语言值。

所谓的数据属性通常是我们写的:

let obj = { a: 1, b: 2}

我们对一个对象的属性只有两个操作:读取属性和设置属性。对于访问器属性,我们使用get和set方法定义属性,其编写方式如下:

let obj = { 
  get a(){ 
    console.log('triggle get a() method') 
    console.log('you can do anything as you want') 
    return 1 
  }, 
  set a(value){ 
    console.log('triggle set a() method') 
    console.log('you can do anything as you want') 
    console.log(`you are trying to assign ${value} to obj.a`) 
  }
}

访问属性为我们提供了强大的元编程能力,因此我们可以通过以下方式满足我们的要求:

let obj = { 
  _initValue: 0, 
  get a() { 
    this._initValue++; 
    return this._initValue 
  }
}

console.log(obj.a, obj.a, obj.a)

第二种方法是使用Object.defineProperty,该方法的工作方式与我们用来访问属性的方法相同,除了不是直接声明访问属性,而是通过Object.defineProperty配置访问属性。

这使用起来更加灵活,因此我们可以这样编写:

let obj = {}Object.defineProperty(obj, 'a', { get: (function(){ let initValue = 0; return function(){ initValue++; return initValue } })()})console.log(obj.a, obj.a, obj.a)

在这里的get方法中,我们使用了一个闭包,以便我们需要使用的变量initValue隐藏在闭包中,并且不会污染其他范围。

第三种方法是使用代理。

使用代理,我们可以拦截对对象属性的访问。 只要我们使用代理来拦截对obj.a的访问,然后依次返回1、2和3,我们就可以在以下条件之前完成要求:

let initValue = 0;
let obj = new Proxy({}, { 
  get: function(item, property, itemProxy){ 
    if(property === 'a'){ 
      initValue++; 
      return initValue 
    } 
    return item[property] 
  }
})

console.log(obj.a, obj.a, obj.a)

为什么理解这个问题很重要?因为Object.defineProperty和Proxy给了我们强大的元编程能力,所以我们可以适当地修改对象以做一些特殊的事情。

在著名的前端框架Vue中,其核心机制之一是数据的双向绑定。在Vue2.0中,Vue通过使用Object.defineProperty实现了该机制。在Vue3.0中,使用Proxy完成此机制。

如果不掌握Vue之类的框架,您将无法真正理解。如果您掌握了这些原则,则只需学习Vue的一半,就可以获得两倍的结果。

3.范围和闭包

运行此代码的结果是什么?

function foo(a,b) { 
  console.log(b) 
  return { 
    foo:function(c){ 
      return foo(c,a); 
    } 
  };
}

let res = foo(0); 
res.foo(1); 
res.foo(2); 
res.foo(3);

上面的代码同时具有多个嵌套函数和三个foo嵌套函数,乍一看看起来非常繁琐。那么,我们如何理解这一点呢?

首先,请确保上面的代码中有多少个功能?我们可以看到在上面的代码中的两个地方都使用了关键字函数,因此上面的代码中有两个函数,即第一行函数foo(a,b) 和第四行 foo:function(c)。并且这两个函数具有相同的名称。

第二个问题:第5行的foo(c,a)调用哪个函数?如果不确定,让我们来看一个简单的示例:

var obj={ 
  fn:function (){ 
    console.log(fn); 
  }
};

obj.fn()

如果我们运行该代码,是否会引发异常? 答案是肯定的。

这是因为obj.fn()方法的上限是全局的,并且无法访问obj内部的fn方法。

回到前面的示例,以同样的逻辑,当我们调用foo(c,a)时,实际上是在第一行上调用foo函数。

当我们调用res.foo(1)时,将调用哪个foo? 显然,第4行的foo函数被调用。

因为这两个foo函数的工作方式不同,所以我们可以将其中一个的名称更改为bar,以使我们更容易理解代码。

function foo(a,b) { 
  console.log(b) 
  return { 
    bar:function(c){ 
      return foo(c,a); 
    } 
  };
}

let res = foo(0); 
res.bar(1); 
res.bar(2); 
res.bar(3);

此更改不会影响最终结果,但会使我们更容易理解代码。如果将来遇到类似的问题,请尝试此技巧。

每次调用一个函数时,都会创建一个新的作用域,因此我们可以绘制图表以帮助我们理解代码工作原理的逻辑。

当我们执行let res = foo(0);时,实际上是在执行foo(0,undefiend)。此时,将在程序中创建一个新的作用域,在当前作用域中a = 0,b = undefined。因此,我绘制的图看起来像这样。

然后将执行console.log(b),因此它第一次在控制台中打印出" undefined"。

然后执行res.bar(1),创建一个新范围,其中c = 1:

然后从上面的函数中再次调用foo(c,a),它实际上是foo(1,0),作用域如下所示:

在新作用域中,a的值为1,b的值为0,因此控制台将打印出0。

再次执行res.bar(2)。注意,res.bar(2)和res.bar(1)是并行关系,因此我们应该像这样绘制范围图:

因此,在此代码中,控制台也会打印出值0。

执行res.bar(3)的过程也是如此,控制台仍显示0。

因此,以上代码的最终结果是:

实际上,上述问题可以用其他方式改变。例如,可以将其更改为以下内容:

function foo(a,b) { 
  console.log(b) 
  return { 
    foo:function(c){ 
      return foo(c,a); 
    } 
  };
}

foo(0).foo(1).foo(2).foo(3);

在解决这个问题之前,我们要做的第一件事是区分两个不同的foo函数,因此可以将上面的代码更改为如下所示:

function foo(a,b) { 
  console.log(b) 
  return { 
    bar:function(c){ 
      return foo(c,a); 
    } 
  };
}

 foo(0).bar(1).bar(2).bar(3);

执行foo(0)时,作用域与以前相同,然后控制台将打印出" undefined"。

然后执行.bar(1)创建一个新的作用域。此参数1实际上是c的值。

然后.bar(1)方法再次调用foo(c,a),它实际上是foo(1,0)。这里的参数1实际上将是新作用域中a的值,而0将是新作用域中b的值。

因此,控制台随后输出了b的值,即0。

再次调用.bar(2),在新作用域中c的值为2:

然后.bar(2)调用foo(c,a),它实际上是foo(2,1),其中2是新作用域中a的值,而1是新作用域中b的值。

因此,控制台随后输出了b的值,即0。

然后它将执行.bar(3),该过程与之前相同,因此我将不扩展其描述,此步骤控制台将打印出2。

如上所述,代码运行的最终结果是:

好了,经过漫长的旅程,我们终于得到了答案。 这个问题很好地检验了受访者对封闭和范围的理解。

4.撰写 Compose

假设我们有一个看起来像这样的函数:

function compose (middleware) { // some code}

compose函数接受函数数组中间件:

let middleware = []
middleware.push((next) => { 
  console.log(1) 
  next() 
  console.log(1.1)
})

middleware.push((next) => { 
  console.log(2) 
  next() 
  console.log(2.1)
})

middleware.push(() => { 
  console.log(3)
})

let fn = compose(middleware)

fn()

当我们尝试执行fn时,它将调用中间件中的函数,并将下一个函数作为参数传递给每个小函数。

如果我们在一个小函数中执行next,则将调用中间件中该函数的next函数。而且,如果您接下来不执行,程序也不会崩溃。

执行完上面的代码后,我们得到以下结果:

1232.11.1

那么,我们如何编写一个compose函数来做到这一点呢?

首先,compose函数必须返回一个composed函数,因此我们可以编写如下代码:

function compose (middleware) { 
  return function () { }
}

然后,在返回的函数中,中间件的第一个函数开始执行。我们还将传递下一个函数作为其参数。所以让我们这样写:

function compose (middleware) { 
  return function () { 
    let f1 = middleware[0] 
    f1(function next(){ }) 
  }
}

下一个功能充当继续在中间件中运行的开关,如下所示:

function compose (middleware) { 
  return function () { 
    let f1 = middleware[0] 
    f1(function next(){ 
      let f2 = middleware[1] 
      f2(function next(){ ... }) 
    }) 
  }
}

然后继续在下一个函数中调用第三个函数…等待,这看起来像递归! 因此,我们可以编写一个递归函数来完成此嵌套调用:

function compose (middleware) { 
  return function () { 
    dispatch(0) 
    function dispatch (i) { 
      const fn = middleware[i] 
      if (!fn) return null 
      fn(function next () { 
        dispatch(i + 1) 
      }) 
    } 
  }
}

好的,这就是我们的撰写功能,所以让我们对其进行测试:

好吧,此功能完全可以完成其所需的工作。 但是我们也可以优化我们的compose函数可以支持异步函数。 我们可以改进以下代码:

function compose (middleware) { 
  return async function () { 
    await dispatch(0) 
    function async dispatch (i) { 
      const fn = middleware[i] 
      if (!fn) 
        return null 
      await fn(function next () { 
        dispatch(i + 1) 
      }) 
    } 
  }
}

实际上,以上的撰写功能是众所周知的节点框架koa的核心机制。

当我选择候选人时,我接受他/她对某些框架不熟悉。毕竟,JavaScript生态系统中有太多的库和框架,没有人能完全掌握它们。但是我确实希望候选人知道这些重要的原始JavaScript技巧,因为它们是所有库和框架的基础。

结论

实际上,我的草稿中还有其他一些面试问题,但由于本文篇幅有限,因此在此不再继续解释。稍后再与您分享。

本文主要涉及普通JavaScript,而不涉及浏览器,节点,框架,算法,设计模式等。如果您对这些主题也感兴趣,请随时发表评论。

感谢您的阅读!

(本文由闻数起舞翻译自bitfish的文章《Improve Your JavaScript Level With These 4 Questions》,转载请注明出处,原文链接:https://medium.com/javascript-in-plain-english/i-use-these-4-questions-to-find-outstanding-javascript-developers-4a468ea17155)

览器解析HTML文件的过程是网页呈现的关键步骤之一。具体介绍如下:


HTML文档的接收和预处理

  1. 网络请求处理:当用户输入URL或点击链接时,浏览器发起HTTP请求,服务器响应并返回HTML文件。此过程中,浏览器需要处理DNS查询、建立TCP连接等底层网络通信操作。
  2. 预解析优化:为了提高性能,现代浏览器在主线程解析HTML之前会启动一个预解析线程,提前下载HTML中链接的外部CSS和JS文件。这一步骤确保了后续渲染过程的顺畅进行。

解析为DOM树

  1. 词法分析和句法分析:浏览器的HTML解析器通过词法分析将HTML文本标记转化为符号序列,然后通过句法分析器按照HTML规范构建出DOM树。每个节点代表一个HTML元素,形成了多层次的树状结构。
  2. 生成对象接口:生成的DOM树是页面元素的结构化表示,提供了操作页面元素的接口,如JavaScript可以通过DOM API来动态修改页面内容和结构。

CSS解析与CSSOM树构建

  1. CSS文件加载与解析:浏览器解析HTML文件中的<link>标签引入的外部CSS文件和<style>标签中的内联CSS,生成CSSOM树。CSSOM树反映了CSS样式的层级和继承关系。
  2. CSS属性计算:包括层叠、继承等,确保每个元素对应的样式能够被准确计算。这些计算过程为后续的布局提供必要的样式信息。

JavaScript加载与执行

  1. 阻塞式加载:当解析器遇到<script>标签时,它会停止HTML的解析,转而先加载并执行JavaScript代码。这是因为JS可能会修改DOM结构或CSSOM树,从而影响已解析的部分。
  2. 异步和延迟加载:为了不影响页面的初步渲染,可以采用async或defer属性来异步加载JS文件,这样可以在后台进行JS的加载和执行,而不阻塞HTML解析。

渲染树的构建

  1. 合并DOM树和CSSOM树:有了DOM树和CSSOM树后,浏览器将它们组合成渲染树,这个树只包含显示界面所需的DOM节点及对应的样式信息。
  2. 不可见元素的排除:渲染树会忽略例如<head>、<meta>等不可见元素,只关注<body>内的可视化内容。

布局计算(Layout)

  1. 元素位置和尺寸确定:浏览器从渲染树根节点开始,递归地计算每个节点的精确位置和尺寸,这个过程也被称为“回流”或“重排”,是后续绘制的基础。
  2. 布局过程的优化:现代浏览器会尽量优化布局过程,例如通过流式布局的方式减少重复计算,确保高效地完成布局任务。

绘制(Paint)

  1. 像素级绘制:绘制是一个将布局计算后的各元素绘制成像素点的过程。这包括文本、颜色、边框、阴影以及替换元素的绘制。
  2. 层次化的绘制:为了高效地更新局部内容,浏览器会将页面分成若干层次(Layer),对每一层分别进行绘制,这样只需更新变化的部分。

因此,我们开发中要注意以下几点:

  • 避免过度使用全局脚本:尽量减少使用全局脚本或者将它们放在文档底部,以减少对HTML解析的阻塞。
  • 合理组织CSS和使用CSS预处理器:合理组织CSS文件的结构和覆盖规则,利用CSS预处理器进行模块化管理。
  • 利用浏览器缓存机制:通过设置合理的缓存策略,减少重复加载相同资源,提升二次访问的体验。
  • 优化图片和多媒体资源:适当压缩图片和优化多媒体资源的加载,减少网络传输时间和渲染负担。

综上所述,浏览器解析HTML文件是一个复杂而高度优化的过程,涉及从网络获取HTML文档到最终将其渲染到屏幕上的多个步骤。开发者需要深入理解这些步骤,以优化网页性能和用户体验。通过合理组织HTML结构、优化资源加载顺序、减少不必要的DOM操作和合理安排CSS和JavaScript的加载与执行,可以显著提升页面加载速度和运行效率。