整合营销服务商

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

免费咨询热线:

使用JS文件调用Google AdSense广告的方

使用JS文件调用Google AdSense广告的方法

网页中插入 Google AdSense 广告,一般将源代码直接嵌入主题模板页面。事实上 Google AdSense 支持并允许使用 JS 文件调用。当然前提是不要因任何原因修改代码,或手动影响广告的定位。

对于通常的 HTML 静态网站,一般的做法是将公共头部和公共尾部都放到单独的 JavaScript 文件里,例如 header.js 和 footer.js,这样单独编辑这两个 JS 文件,即可同时修改全部网站内容,因此我们可以考虑把 Google AdSense 的代码放到这个头文件里。具体做法如下:

首先,登陆 Google AdSense 后台,获取广告代码,通常代码如下:

<script data-ad-client="ca-pub-YOUR-ID" async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>

这样,我们把如下 JavaScript 代码放到 header.js 头文件里即可,代码如下。

var js=document.createElement("script");

js.setAttribute('data-ad-client', 'ca-pub-YOUR-ID');

js.setAttribute('async', true);

js.setAttribute('src', 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js');

document.head.appendChild(js);

上面的代码里,需要把 ca-pub-YOUR-ID 替换为用户自己ID号码。

这段代码和原始的 Google AdSense 的 JavaScript 原理完全一样,实际显示效果也一样。

三元:https://juejin.im/post/5dd8b3a851882572f56b578f?utm_source=bigezhang.com#heading-6


首先需要明白的是,机器是读不懂 JS 代码,机器只能理解特定的机器码,那如果要让 JS 的逻辑在机器上运行起来,就必须将 JS 的代码翻译成机器码,然后让机器识别。JS属于解释型语言,对于解释型的语言说,解释器会对源代码做如下分析:

1.通过词法分析和语法分析生成 AST(抽象语法树)

2.生成字节码

然后解释器根据字节码来执行程序。但 JS 整个执行的过程其实会比这个更加复杂,接下来就来一一地拆解。

1.生成 AST

生成 AST 分为两步——词法分析和语法分析。

词法分析即分词,它的工作就是将一行行的代码分解成一个个token。 比如下面一行代码:

let name='sanyuan'

其中会把句子分解成四个部分:



即解析成了四个token,这就是词法分析的作用。

接下来语法分析阶段,将生成的这些 token 数据,根据一定的语法规则转化为AST。举个例子:

let name='sanyuan'
console.log(name)

最后生成的 AST 是这样的:



当生成了 AST 之后,编译器/解释器后续的工作都要依靠 AST 而不是源代码。顺便补充一句,babel 的工作原理就是将 ES6 的代码解析生成ES6的AST,然后将 ES6 的 AST 转换为 ES5 的AST,最后才将 ES5 的 AST 转化为具体的 ES5 代码。

回到 V8 本身,生成 AST 后,接下来会生成执行上下文

2. 生成字节码

开头就已经提到过了,生成 AST 之后,直接通过 V8 的解释器(也叫Ignition)来生成字节码。但是字节码并不能让机器直接运行,那你可能就会说了,不能执行还转成字节码干嘛,直接把 AST 转换成机器码不就得了,让机器直接执行。确实,在 V8 的早期是这么做的,但后来因为机器码的体积太大,引发了严重的内存占用问题。

给一张对比图让大家直观地感受以下三者代码量的差异:



很容易得出,字节码是比机器码轻量得多的代码。那 V8 为什么要使用字节码,字节码到底是个什么东西?

子节码是介于AST 和 机器码之间的一种代码,但是与特定类型的机器码无关,字节码需要通过解释器将其转换为机器码然后执行。

字节码仍然需要转换为机器码,但和原来不同的是,现在不用一次性将全部的字节码都转换成机器码,而是通过解释器来逐行执行字节码,省去了生成二进制文件的操作,这样就大大降低了内存的压力。

3. 执行代码

接下来,就进入到字节码解释执行的阶段啦!

在执行字节码的过程中,如果发现某一部分代码重复出现,那么 V8 将它记做热点代码(HotSpot),然后将这么代码编译成机器码保存起来,这个用来编译的工具就是V8的编译器(也叫做TurboFan) , 因此在这样的机制下,代码执行的时间越久,那么执行效率会越来越高,因为有越来越多的字节码被标记为热点代码,遇到它们时直接执行相应的机器码,不用再次将转换为机器码。

其实当你听到有人说 JS 就是一门解释器语言的时候,其实这个说法是有问题的。因为字节码不仅配合了解释器,而且还和编译器打交道,所以 JS 并不是完全的解释型语言。而编译器和解释器的 根本区别在于前者会编译生成二进制文件但后者不会。

并且,这种字节码跟编译器和解释器结合的技术,我们称之为即时编译, 也就是我们经常听到的JIT。

这就是 V8 中执行一段JS代码的整个过程,梳理一下:

  1. 首先通过词法分析和语法分析生成 AST
  2. 将 AST 转换为字节码
  3. 由解释器逐行执行字节码,遇到热点代码启动编译器进行编译,生成对应的机器码, 以优化执行效率。

javascript 是一门单线程的语言,在同一个时间只能做完成一件任务,如果有多个任务,就必须排队,前面一个任务完成,再去执行后面的任务。作为浏览器端的脚本语言,javascript 的主要功能是用来和用户交互以及操作 dom。假设 javascript 不是单线程语言,在一个线程里我们给某个 dom 节点增加内容的时候,另一个线程同时正在删除这个 dom 节点的内容,则会造成混乱。

由于 js 单线程的设计,假设 js 程序的执行都是同步。如果执行一些耗时较长的程序,例如 ajax 请求,在请求开始至请求响应的这段时间内,当前的工作线程一直是空闲状态, ajax 请求后面的 js 代码只能等待请求结束后执行,因此会导致 js 阻塞的问题。

javascript 单线程指的是浏览器中负责解释和执行 javascript 代码的只有一个线程,即为 js 引擎线程,但是浏览器的渲染进程是提供多个线程的,如下:

  1. js 引擎线程
  2. 事件触发线程
  3. 定时器触发线程
  4. 异步 http 请求线程
  5. GUI 渲染线程

一、异步 & 同步

为解决上述类似上述 js 阻塞的问题,js 引入了同步和异步的概念。

1、什么是同步?

“同步”就是后一个任务等待前一个任务结束后再去执行。

2、什么是异步?

“异步”与同步不同,每一个异步任务都有一个或多个回调函数。webapi 会在其相应的时机里将回调函数添加进入消息队列中,不直接执行,然后再去执行后面的任务。直至当前同步任务执行完毕后,再把消息队列中的消息添加进入执行栈进行执行。

异步任务在浏览器中一般是以下:

  1. 网络请求
  2. 计时器
  3. DOM 监听事件
  4. ...

二、什么是执行栈(stack)、堆(heap)、事件队列(task queue)?

1、执行栈

“栈”是一种数据结构,是一种线性表。特点为 LIFO,即先进后出 (last in, first out)。

利用数组的 push 和 shift 可以实现压栈和出栈的操作。

在代码运行的过程中,函数的调用会形成一个由若干帧组成的栈。

function foo(b) {
  let a=10;
  return a + b + 11;
}

function bar(x) {
  let y=3;
  return foo(x * y);
}

console.log(bar(7))

上面代码最终会在控制台打印42,下面梳理一下它的执行顺序。

  1. console.log 函数作为第一帧压入栈中。
  2. 调用 bar,第二帧被压入栈中。帧中包含着 bar 的变量对象。
  3. bar 调用 foo,foo 做一位第三帧被压入栈中,帧中包含着 foo 的变量对象。
  4. foo 执行完毕然后返回。被弹出栈。
  5. bar 执行完毕然后返回,被弹出栈。
  6. log 函数接收到 bar 的返回值。执行完毕后,出栈。此时栈已空。

2、堆

对象被分配在堆中,堆是一个用来表示一大块(通常是非结构化的)内存区域的计算机术语。

堆和栈的区别

首先,stack 是有结构的,每个区块按照一定次序存放,可以明确知道每个区块的大小;heap 是没有结构的,数据可以任意存放。因此,

stack 的寻址速度要快于 heap。

其次,每个线程分配一个 stack,每个进程分配一个 heap,也就是说,stack 是线程独占的,heap 是线程共用的。

此外,stack 创建的时候,大小是确定的,数据从超过这个大小,就发生 stack overflow 错误,而 heap 的大小是不确定的,

需要的话可以不断增加。

public void Method1()
{
    int i=4;

    int y=2;

    class1 cls1=new class1();
}

上面代码这三个变量和一个对象实例在内存中的存放方式如下。

从上图可以看到,i、y和cls1都存放在stack,因为它们占用内存空间都是确定的,而且本身也属于局部变量。但是,cls1指向的对象实例存放在heap,因为它的大小不确定。作为一条规则可以记住,所有的对象都存放在heap。

接下来的问题是,当Method1方法运行结束,会发生什么事?

回答是整个stack被清空,i、y和cls1这三个变量消失,因为它们是局部变量,区块一旦运行结束,就没必要再存在了。而heap之中的那个对象实例继续存在,直到系统的垃圾清理机制(garbage collector)将这块内存回收。因此,一般来说,内存泄漏都发生在heap,即某些内存空间不再被使用了,却因为种种原因,没有被系统回收。

3、事件队列和事件循环

队列是一种数据结构,也是一种特殊的线性表。特点为 FIFO,即先进先出(first in, first out)

利用数组的 push 和 pop 可实现入队和出队的操作。

事件循环和事件队列的维护是由事件触发线程控制的。

事件触发线程线程同样是由浏览器渲染引擎提供的,它会维护一个事件队列。

js 引擎遇到上文所列的异步任务后,会交个相应的线程去维护异步任务,等待某个时机,然后由事件触发线程将异步任务对应的回调函数加入到事件队列中,事件队列中的函数等待被执行。

js 引擎在执行过程中,遇到同步任务,会将任务直接压入执行栈中执行,当执行栈为空(即 js 引擎线程空闲), 事件触发线程 会从事件队列中取出一个任务(即异步任务的回调函数)放入执行在栈中执行。

执行完了之后,执行栈再次为空,事件触发线程会重复上一步的操作,再从事件队列中取出一个消息,这种机制就被称为 事件循环 (Event Loop)机制。

为了更好地理解Event Loop,请看下图(转引自Philip Roberts的演讲《Help, I'm stuck in an event-loop》)。

例子代码:

console.log('script start')

setTimeout(()=> {
  console.log('timer 1 over')
}, 1000)

setTimeout(()=> {
  console.log('timer 2 over')
}, 0)

console.log('script end')

// script start
// script end
// timer 2 over
// timer 1 over

模拟 js 引擎对其执行过程:

第一轮事件循环:

  1. console.log 为同步任务,入栈,打印“script start”。出栈。
  2. setTimeout 为异步任务,入栈,交给定时器触发线程处理(在1秒后加入将回调加入事件队列)。出栈。
  3. setTimeout 为异步任务,入栈,交给定时器触发线程处理(在4ms之内将回调加入事件队列)。出栈。
  4. console.log 为同步任务,入栈,打印"script end"。出栈。

此时,执行栈为空,js 引擎线程空闲。便从事件队列中读取任务,此时队列如下:

第二轮事件循环

  1. js 引擎线程从事件队列中读取 cb2 加入执行栈并执行,打印”time 2 over“。出栈。

第三轮事件循环

  1. js 引擎从事件队列中读取 cb1 加入执行栈中并执行,打印”time 1 over“ 。出栈。

注意点:

上面,timer 2 的延时为 0ms,HTML5标准规定 setTimeout 第二个参数不得小于4(不同浏览器最小值会不一样),不足会自动增加,所以 "timer 2 over" 还是会在 "script end" 之后。

就算延时为0ms,只是 time 2 的回调函数会立即加入事件队列而已,回调的执行还是得等到执行栈为空时执行。

四、宏任务 & 微任务

在 ES6 新增 Promise 处理异步后,js 执行引擎的处理过程又发生了新的变化。

看代码:

console.log('script start')

setTimeout(function() {
    console.log('timer over')
}, 0)

Promise.resolve().then(function() {
    console.log('promise1')
}).then(function() {
    console.log('promise2')
})

console.log('script end')

// script start
// script end
// promise1
// promise2
// timer over

这里又新增了两个新的概念, macrotask (宏任务)和 microtask (微任务)。

所有的任务都划分到宏任务和微任务下:

  • macrotask : script 主代码块、setTimeout、setInterval、requestAnimationFrame、node 中的setimmediate 等。
  • microtask : Promise.then catch finally、MutationObserver、node 中的process.nextTick 等。

js 引擎首先执行主代码块。

执行栈每次执行的代码就是一个宏任务,包括任务队列(宏任务队列)中的。执行栈中的任务执行完毕后,js 引擎会从宏任务队列中去添加任务到执行栈中,即同样是事件循环的机制。

当在执行宏任务遇到微任务 Promise.then 时,会创建一个微任务,并加入到微任务队列中的队尾。

微任务是在宏任务执行的时候创建的,而在下一个宏任务执行之前,浏览器会对页面重新渲染(task >> render >> task(任务队列中读取))。 同时,在上一个宏任务执行完成后,页面渲染之前,会执行当前微任务队列中的所有微任务。

所以上述代码的执行过程就可以解释了。

js 引擎执行 promise.then 时,promise1、promise2 被认为是两个微任务按照代码的先后顺序被加入到微任务队列中,script end执行后,空。

此时当前宏任务(script 主代码块)执行完毕,并不从当前宏任务队列中读取任务。而是立马清空当前宏任务所产生的微任务队列。将两个微任务依次放入执行栈中执行。执行完毕,打印 promise1、promise2。栈空。 此时,第一轮事件循环结束。

紧接着,再去读取宏任务队列中的任务,time over 被打印。栈空。

因此,宏任务和微任务的执行机制如下:

  1. 执行一个宏任务(栈中没有就从宏任务队列中获取)
  2. 执行过程中遇到微任务,就将它添加到微任务的任务队列中
  3. 宏任务执行完毕,立即执行当前微任务队列中的所有微任务(依次执行)
  4. 当前所有微任务执行完毕后,开始检查渲染,GUI 线程接管渲染
  5. 渲染完毕后,JS 引擎继续开始下一个宏任务,从宏任务队列中获取

async & await

因为,async 和 await 本质上还是基于 Promise 的封装,而 Promise 是属于微任务的一种。所以使用 await 关键字与 Promise.then 效果类似:

setTimeout(_=> console.log(4))

async function main() {
  console.log(1)
  await Promise.resolve()
  console.log(3)
}

main()

console.log(2)
// 1
// 2
// 3
// 4

async 函数在 await 之前的代码都是同步执行的, 可以理解为 await 之前的代码都属于 new Promise 时传入的代码,await 之后的所有代码都是 Promise.then 中的回调,即在微任务队列中。

五、总结

  1. js 单线程实际上解释执行 js 代码的只有一个线程,但是浏览器的渲染是多线程的。
  2. 异步和同步的概念与区别,异步任务有哪些。
  3. 栈、堆、队列的特点和使用场景。
  4. 事件队列以及事件循环机制。
  5. es6 下,宏任务与微任务的执行过程。

参考:

  • JavaScript 异步与事件循环
  • 并发模型与事件循环
  • 微任务、宏任务与Event-Loop
  • JavaScript 运行机制详解:再谈Event Loop
  • JS事件循环
  • [译] 深入理解 JavaScript 事件循环(二)— task and microtask
  • Help, I'm stuck in an event-loop

原文作者:大芒果哇

原文地址:https://www.cnblogs.com/shenggao/p/13799566.html