分类 前端 下的文章

面试的时候被问到“平常用闭包都做过哪些事情?”

闭包的概念

《你不知道的JavaScript(上卷)》

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行

关于闭包的概念,仅此一句就够了。

解读闭包

正常情况下,当词法作用域内的代码执行完毕后,在这个词法作用域内定义的变量都会被回收掉。
不正常的情况下,也就是有闭包的时候。这个词法作用域内定义的变量“可以被记住”,而不会被回收掉。记住这些变量的是一个函数。

function P(){
    let a = "a"
    let b = "b"
    return function innerFunc(){
        return a+b
    }
}
// 在作用域外部访问到了P作用域内部定义的ab变量
// ab 
P()()

在上面的例子中,innerFunc “记住了”所在的词法作用域(a和b变量),产生了闭包。
innerFunc 函数在 P 的作用域外部执行时,也能访问到内部的词法作用域。

使用闭包的例子

1.模拟定义一个对象的私有属性

const P = (function (){
    let _gender = ""
    function People(name,gender){
        this.name = name
        _gender = gender
    }
    People.prototype.getGender = function (){
        return _gender
    }
    return People
})()

const people = new P("小明","male")
//  { name: '小明' }
console.log(people)
// male
console.log(people.getGender())

2.给标签添加事件

有作用域的地方就有闭包

const container = [{},{},{}]

for (let i = 0;i<container.length;i++){
    container[i].run = function () {
        console.log("当前是第"+ ++i +"个容器")
    }
}
// 当前是第1个容器
container[0].run()
// 当前是第3个容器
container[2].run()

用 let 声明 i 变量,循环体运行了3次,一共就创建了3个块级作用域。然后通过三个匿名函数“记住了”这3个块级作用域(i变量 ),创建了3个闭包。

vue3官方的中文文档还没出来。这几天在看vue3官方英文的文档。其实直接读英文文档是比较困难的,多次阅读才能对其表达的意思理解。那么,就从第三篇开始翻译吧。

前提:没有直译,并且认为直译是很 low 的做法。按照自己的理解组织语言,难免会有错误的地方。望不吝赐教。

原文 : https://v3.vuejs.org/guide/instance.htm

应用实例和组件实例

创建应用实例

每个 Vue 应用都起源于一个 vue 应用实例。可以通过 Vue 库的 createApp 方法创建一个vue应用实例。

const app = Vue.createApp({ /* options */ })

vue 应用实例会被注册到全局,供此应用内部的组件去调用它。

const app = Vue.createApp({})
app.component('SearchInput', SearchInputComponent)
app.directive('focus', FocusDirective)
app.use(LocalePlugin)

vue应用实例暴露的很多接口都返回其自身,这让我们可以链式地组织代码。

Vue.createApp({})
  .component('SearchInput', SearchInputComponent)
  .directive('focus', FocusDirective)
  .use(LocalePlugin)

根组件

可以给 createApp 传入一个参数来配置根组件,根组件是渲染的起点。

vue应用实例必须挂载于一个 Dom 节点上才能生效。比如我们想把应用实例挂载在<div id="app"></div> 上,可以给 mount 方法传入 #app

const RootComponent = { /* options */ }
const app = Vue.createApp(RootComponent)
const vm = app.mount('#app')

和vue应用实例的大多数方法不一样,mount 方法不会返回vue应用实例本身,而是返回根组件实例。

vue 在一定程度上受到 mvvm 模式的启发,所以我们一般约定使用 vm(ViewModel的简写) 标识符表示组件实例。

上面例子中,我们写页面只用了一个组件。其实大多数的真实场景是:一个vue应用由许多可复用的组件组成了一个树状的结构。比如 Todo 应用可能会是这种结构:

Root Component
└─ TodoList
   ├─ TodoItem
   │  ├─ DeleteTodoButton
   │  └─ EditTodoButton
   └─ TodoListFooter
      ├─ ClearTodosButton
      └─ TodoListStatistics

所有的组件都有其自己的实例---vm。对于像 Tode 这种,一次渲染多个组件的应用,在当前应用中,所有的组件都共享同一个应用实例。

后面会详细的介绍组件系统。现在你只需要知道:根组件和其他组件并没有太大区别。配置选项与相应组件实例相同。

组件实例的属性

在手册的开头部分,我们就邂逅了 data 属性。在 data 中定义的属性通过组件实例暴露了出来。

const app = Vue.createApp({
  data() {
    return { count: 4 }
  }
})

const vm = app.mount('#app')

console.log(vm.count) // => 4

在组件实例上,存在很多选项供用户使用,比如methods, props, computed, injectsetup 。这些内容会在手册后面详细介绍。在组件实例上定义的所有属性,不管是通过什么方式定义的,都可以在组件模板上直接使用。

Vue 暴露了一些组件实例的内部属性,如$attrs$emit。为避免内部属性和用户定义的属性产生冲突。内部属性的前缀都是 $

生命周期钩子

所有组件实例创建后都需要经历一系列的初始化过程,比如初始化“数据监听”,编译模板,给DOM上挂载,数据变化时更新DOM。在这个过程中可以运行一些生命周期钩子函数。让用户可以在特定的阶段运行自定义的代码。

比如 created钩子,可以在组件创建完成(created)后 运行用户自定义的代码。

Vue.createApp({
  data() {
    return { count: 1 }
  },
  created() {
    // `this` points to the vm instance
    console.log('count is: ' + this.count) // => "count is: 1"
  }
})

还有一些其他的钩子在组件实例生命周期的不同阶段运行,如 mounted,updated,unmounted。这些钩子的this指向当前组件实例的上下文。

注意:

不要使用箭头函数,因为箭头函数本身没有 this,所以在箭头函数中,会把this当作一个变量来对待,在其作用域链上查找 this。

生命周期图

下面就是组件实例的生命周期,慢慢去理解吧。
image

如果你使用百度搜索“使用 python 运行 js 程序”之类的关键字,得到的结果无非是"pyv8","pyexecjs,","js2py","直接操作node"。
那么很遗憾,以上四种方案都很不ok。

我花费很长时间,查阅了大量资料,总结出一个道理:如果在百度里很长时间都找不到的答案,不妨去 Google 一下,使用英文关键字。

这里介绍的是 PyMiniRacer ,它是一个很棒的库,可以完美替代谷歌的 pyv8 ,让你在任何版本的Python里都能通过 pip 很轻松的安装。

PyMiniRacer 仓库

特点:轻,安装方便,使用v8引擎(快),可以记录上下文环境,项目在积极维护中

安装:pip install py_mini_racer
使用:(更多用法见github)

from py_mini_racer import py_mini_racer
ctx = py_mini_racer.MiniRacer()
ctx.eval("""
function escramble_758(){
    var a,b,c
    a='+1 '
    b='84-'
    a+='425-'
    b+='7450'
    c='9'
    return a+c+b;
}
""")
ctx.call("escramble_758")

分别介绍一下其他库的缺点:

pyv8

2013 年开始就不维护了,支持python2 和早期python3 ,对python版本有要求,安装难,安装了不一定能用。

pyexecjs

这个也不维护了,作者的话:“pyexecjs性能差,推荐大家去用性能更好的pyv8”
pyexecjs 本身依赖js运行环境.首先在python中调用pyexecjs,然后pyexecjs再去启动nodejs。性能差

js2py

这个库的核心是“将js 代码翻译成 py”,自然会缺失很多js的系统函数,最后出现很多奇怪的bug

dukpy

这个库是基于 duktape 引擎的,不支持 es6。可能会出现很多奇怪的bug


最后,可以在 https://stackoverflow.com/questions/10136319/executing-javascript-from-python 发现更多的 “在 python 中运行js”的答案

网页自动刷新小助手

设计初心

  1. 为什么要写这个小助手?

为了刷帖子的访问量

  1. 有替代方案案吗?

有,而且有许多。如“流量精灵”, 有些浏览器自带有刷新网页的插件。

但是,作为一个程序猿。明明知道其实现原理很简单,为什么不尝试自己做一个呢?

  1. 使用了哪些技术

最初写的时候很粗糙,这次优化了一下下。

  1. 使用 bootstrap4 实现了响应式布局
  2. 使用 vue 框架。没办法,这框架太好用了,用一次就爱上了。

实现原理

使用 iframe 标签加载目标网页。通过多次修改 iframe 标签的 src 属性来实现多次访问目标网页。

代码

Demo

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>网页自动刷新小助手</title>
    <link href="https://cdn.bootcss.com/twitter-bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcss.com/vue/2.6.11/vue.min.js"></script>
</head>
<body>
<div class="navbar navbar-expand-md bg-dark text-light w-100 border-top border-warning">
    <a href="" class="nav-item text-light nav-link navbar-brand">
        网页自动刷新小助手
    </a>
</div>

<div id="app" class="container">
    <div class="row">
        <div class="card aside col-md-4 my-3">
            <div class="card-body">
                <div class="form-group">
                    <label for="">
                        链接
                        <small>(必须以 http:// 或 https:// 开头)</small>
                    </label>
                    <input type="text" v-model="src" class="form-control" placeholder="请输入链接">
                </div>
                <div class="form-group">
                    <label for="">刷新次数</label>
                    <input type="number" v-model="times" class="form-control">
                </div>
                <div class="form-group">
                    <label for="">刷新间隔时间(毫秒)</label>
                    <input type="number" v-model="interval" class="form-control">
                </div>
                <div class="btn-group-sm">
                    <button class="btn btn-primary" @click="start">开始刷</button>
                    <button class="btn btn-danger" @click="stop">停止</button>
                </div>
            </div>
        </div>
        <div class="col-md-8 my-3">
            <div class="alert alert-info">
                {{ message }}
            </div>
            <iframe class="mt-2 w-100 h-100" ref="iframe_box" frameborder="0"></iframe>
        </div>
    </div>
</div>

<script>
    let App = new Vue({
            el: '#app',
            data: {
                src: "https://api.xygeng.cn/dailywd/api/get.php",
                times: 10,
                interval: 2000,
                job_id: "",
                count: 1,
                message: '网页预览小窗口'
            },
            methods: {
                start() {
                    this.message = '任务将会在' + this.interval / 1000 + 's 后开始';

                    this.job_id = setInterval(() => {
                        this.message = '任务正在进行中 , 第' + this.count + '次'
                        if (++this.count > this.times) this.stop()
                        this.$refs.iframe_box.src = this.src
                    }, this.interval)
                },
                stop() {
                    this.message = '任务已结束'
                    clearInterval(this.job_id)
                    this.count = 1;
                }
            }
        }
    )
</script>
</body>
</html>