js实现一键打印网页中所有的图片

最近写了一个小工具,可以一键打印网页中所有的图片链接,效果如下:

实现思路主要分为两部分,一是如何获取网页中所有的图片链接,二是如何在浏览器的控制台打印图片。封装形式用了便捷的书签的方式,可以点一个标签来运行。

(代码链接在最后)

获取网页中所有图片链接

网页中图片主要有三种形式:一是img标签,二是css中的背景图片,三是在style中设置的背景图片。我们分别获取一下这三种图片:

img标签

用dom查询的api获取所有的img标签,返回src的数组

function getDomImage() {
    let imgList = [].slice.call(document.querySelectorAll('img')).map(item => item.src);
    return imgList;
}

有style属性的元素的背景图片

首先通过*[style]选出所有有style属性的标签,然后把内容拼成一个css格式的字符串。之后对这个字符串使用正则匹配url()中的链接,然后放到数组中返回。

function getStyleImage() {
    const imgList = [];
    let styleEles = [].slice.call(document.querySelectorAll("*[style]"));
    styleEles && styleEles.map(styleEle => {
        const styleStr = Object.entries(styleEle.style).filter(item => item[1]).map(item => item[0] + ':' + item[1]).join(';');
        let styleImages = styleStr.match(/url\((.*)\)/g);
        styleImages = styleImages && styleImages.map(item => item.replace(/url\(['"]*([^'"]*)['"]*\)/,'$1'));
        if(styleImages) imgList.push(...styleImages);
    });
    return imgList;
}

css中的背景图片

首先选出所有的style元素,然后获取textContent,之后也是通过正则匹配url()中的链接,然后放入数组返回。

function getCssImage() {
    const styleEles = document.querySelectorAll('style');
    return [].slice.call(styleEles).map(styleEle => {
        const css = styleEle.textContent;
        const cssImages = css.match(/url\((.*)\)/g);
        return cssImages && cssImages.map(item => item.replace(/url\((.*)\)/,'$1')) || [];
    });
}

去重

获取到这三种图片之后,合并到一个数组中。

    function getImages() {
        return getDomImage().concat(...getCssImage()).concat(...getStyleImage());
    }

但现在的数组中可能有重复的图片,因为一个图片可能在页面上出现多次。我们可以通过set来去重。

function uniqueArr(arr) {
    return Array.from(new Set(arr))
}

控制台打印图片

现在有了所有图片的链接,下一步就是打印到控制台了。浏览器console支持%c指定css样式,可以通过background-image的方式来设置图片。这是一种hack的方式。

先打印了一堆的空格,留出空间来显示背景图,然后在这段空白的文字区域实现图片。

    function formatConsole(list) {

        if (window.console) {
            var cons = console;
            if (cons) {
                list.forEach(item => {
                    cons.log("%c\n                                                                                    ", "font-size:100px;background:url('"+ item+"') no-repeat;background-size:contain;");
                    cons.log(item);
                });
            }
        }
    }

这三步完成之后组合调用一下:

let imgs = getImages();
imgs = uniqueArr(imgs);
formatConsole(imgs);

现在获取网页所有图片并打印到控制台的功能已经完成了。

浏览器新建标签

功能已经完成了,可是怎么使用呢。开发一个chrome插件可以,而且不止可以打印到控制台,还可以传到服务器之类的,能做更多的事情。

但我们现在只想打印到控制台,可以使用一种简单的方式:浏览器书签。

在浏览器中新建一个书签,比如内容为javascript:alert(1);,那么你在书签栏点击这个标签的时候就会执行这段js。

基于此,我们只需要把上面的代码再包装一层就可以用了:

javascript: (function() {
    //xxx
})()

总结

至此,我们已经实现了一键打印网页中所有的图片。可以新建一个书签,然后内容复制下面链接中的代码,之后点击书签栏的书签就可以运行了。

代码链接

console-all-image

vue组件原型调用

相信很多人用vuejs构建应用时都会用到一些全局方法,比如发ajax请求时喜欢用axios挂载到vue原型上,如下:

// 引入vue和axios
import Vue from 'vue'
import axios from 'axios'
// 然后挂载到原型上
Vue.prototype.$axios = axios

// 用axios.get()方法可以这样用
this.$axios.get()

这样确实方便,不用每个用到axios的组件都去引入
类似如此,当我们要用到一些操作dom的方法时要怎么做呢,上面的例子纯属js的封装,没有涉及到dom;下面我用一个全局提示组件为例,类似element-ui的message组件为大家演示一遍如何封装一个包含操作dom的的全局组件的,步骤主要有3步:

1, 在componenets/Message 目录下新建一个Message.vue组件
<template>
<transition name="fade">
    <div class="message" :class="type" v-show="show">
      <i class="icon"></i>
      <span class="text">{{text}}</span>
    </div>
</transition>
</template>

<script type="text/ecmascript-6">
  export default {
    name: 'message',
    props: {
      type: {
        type: String,
        default: 'info',
      },
      text: {
        type: String,
        default: ''
      },
      show: {
        type: Boolean,
        default: false
      }
    }
  }
</script>

<style scoped lang="stylus">
//......
</style>
2, 在componenets/Message目录准备一个index.js
import Message from './Message.vue'

const MESSAGE = {
  duration: 3000, // 显示的时间 ms
  animateTime: 300, // 动画时间,表示这个组件切换show的动画时间
  install(Vue) {
    if (typeof window !== 'undefined' && window.Vue) {
      Vue = window.Vue
    }
    Vue.component('Message', Message)

    function msg(type, text, callBack) {
      let msg
      let duration = MESSAGE.duration
      if (typeof text === 'string') {
        msg = text
      } else if (text instanceof Object) {
        msg = text.text || ''
        if (text.duration) {
          duration = text.duration
        }
      }
      let VueMessage = Vue.extend({
        render(h) {
          let props = {
            type,
            text: msg,
            show: this.show
          }
          return h('message', {props})
        },
        data() {
          return {
            show: false
          }
        }
      })
      let newMessage = new VueMessage()
      let vm = newMessage.$mount()
      let el = vm.$el
      document.body.appendChild(el) // 把生成的提示的dom插入body中
      vm.show = true
      let t1 = setTimeout(() => {
        clearTimeout(t1)
        vm.show = false  //隐藏提示组件,此时会有300ms的动画效果,等动画效果过了再从body中移除dom
        let t2 = setTimeout(() => {
          clearTimeout(t2)
          document.body.removeChild(el) //从body中移除dom
          newMessage.$destroy()
          vm = null // 设置为null,好让js垃圾回收算法回收,释放内存

          callBack && (typeof callBack === 'function') && callBack() 
      // 如果有回调函数就执行,没有就不执行,用&&操作符,
      // 只有&&左边 的代码为true才执行&&右边的代码,避免用面条代码:
        }, MESSAGE.animateTime)
      }, duration)
    }

// 挂载到vue原型上,暴露四个方法
    Vue.prototype.$message = {
      info(text, callBack) {
        if (!text) return
        msg('info', text, callBack)
      },
      success(text, callBack) {
        if (!text) return
        msg('success', text, callBack)
      },
      error(text, callBack) {
        if (!text) return
        msg('error', text, callBack)
      },
      warning(text, callBack) {
        if (!text) return
        msg('warning', text, callBack)
      }
    }
  }
}
export default MESSAGE

上面的代码关键点就是用Vue.extend()构造出一个Vue子类实例,(注意我这里模板渲染只用到render函数,没有用template选项,因为template选项 要求装Vue时要加入模板编译器那块代码,用render函数更加简洁,只需要装运行时版本,Vue体积更加小);然后调用$mount()方法生成需要的dom,再拿到对应的$el,实例内部自己维护插入dom和移除dom的操作,对外暴露了四个方法info、success、error、warning方便不同的场景调用;类似的组件还有confrim组件、alert组件等,大同小异。

3,在main.js中引入components/Message/index.js,以插件形式安装

最后,当你需要用的时候就直接,特别适合在ajax回调函数里面用来提示

import Vue from 'vue'
import vMessage from './components/Message/index' 
Vue.use(vMessage)

this.$message.info('普通消息') 
this.$message.error('错误消息') 
this.$message.warning('警告消息') 
this.$message.success('成功消息')

Android Lint扫描规则说明(一)

主要内容

对Android Studio支持的六类Android Lint规则, 本文主要对AccessibilityInternationalization 两中类型所包含的14个项的说明,主要内容都是文档翻译,适当加一些自己的感想。

Accessibility

可访问性的检查,除了第一项之外,其他项更像是为某些自动化的工具做的准备工作,不影响APP的运行。

ClickableViewAccessibility

可点击View的可访问性:如果重写onTouchEvent或使用OnTouchListener的View在检测到单击时没有实现performClick并调用它,则视图可能无法正确处理可访问性操作。理想情况下,处理单击操作的逻辑应该放在View#performClick中,因为当单击操作发生时,一些可访问性服务会调用performClick。

ContentDescription

非文本Widget描述:
– 首先,像ImageViews和ImageButtons这样的非文本Widget应该使用contentDescription属性指定文本对Widget进行说明,以便屏幕阅读器和其他可访问性工具能够充分描述和理解用户界面。
– 其次,如果一个非文本Widget在用户界面上只是一个装饰,不展示任何内容也不接受任何用户操作,就不需要给它提供描述性的contentDescription属性文本,而是使用tool属性ContentDescription抑制lint提醒。
– 第三,对于文本型Widget不能同时设置hintcontentDescription,否则hint将不会展示在界面上,只设置hint就可以。

参考:Make apps more accessible

GetContentDescriptionOverride

重写非文本Widget描述方法getContentDescription:重写View的getContentDescription方法,可能会阻止某些可访问性服务正确导航视图公开的内容。相反,当内容描述需要更改时,调用setContentDescription。

KeyboardInaccessibleWidget

键盘无法访问Widget:如果一个Widget声明了可以点击,但是没有声明可以获得焦点,这个Widget是无法通过键盘访问的,需要设置focusable=true。

LabelFor

缺少可访问标签:可编辑的控件例如EditText应该为hint属性赋值,或者在minSDKVersion大于等于17时,使用labelFor属性为EditText指定一个标签控件。标签控件可以是指定了text属性的文字控件如TextView,也可以是指定了contentDescription的非文字控件如ImageView。

如果被指定的标签控件如TextView在另外一个layout文件中,且使用layout属性引用了EditText所在的layout文件,可以ignore这个lint检查。

Internationalization

ByteOrderMark

查了一下这个ByteOrderMark,简称BOM,指的是一些标记字符。这种问题一般是“在不同编码格式的文件之间拷贝字符或者在某些文件系统上编辑了文件”导致的。

文件内BOM提醒:Lint会把文件中包含的BOM字符标记出来。因为Android工程中我们期望使用utf-8对文件和字符进行编码。BOM字符对utf-8来说不是必需的,而且有一些工具是不能正确处理带BOM字符的文本的。

参考:Android提示BOM错误排查UTF8最好不要带BOM

EnforceUTF8

资源文件编码格式非utf-8:XML文件对编码类型的支持比较广泛。然而有些工具不能正确某些类型编码的文件,而utf-8在Android应用中是一种被广泛支持的编码类型。使用utf-8对文件进行编码,可以防止在处理non-ASCII类型的字符时出现奇怪的问题。尤其是Gradle在合并XML类型的资源文件时,预先假定文件是使用utf-8进行编码的。

HardcodedText

硬编码文本属性:不要在layout文件或者代码中直接为文本控件设置text属性值。
– 在不同的位置多次使用相同的文本,如果需要修改文本则会引起多处修改。
– APP不能通过为文本提供一份翻译列表就可以适用新的语言的用户,而有很多工具可以快速的完成提供翻译列表的操作。
– 好的做法是,把text属性的文本值定义在string.xml文件中,方便国际化拓展。

SetTextI18n

TextView国际化:在调用TextView.setText给TextView赋值时,不能使用Number.toString例如图中的Integer.toString把数字转为字符串赋值,因为Number.toString不能正确处理分隔符和特定语言环境中的数字。

建议使用 String.format 指定合适的占位符进行赋值。

不能直接使用文本给TextView.setText,具体参照HardcodedText的说明。代码中可以使用@SuppressLint(“SetTextI18n”) 禁用lint的这项检查。

RelativeOverlap

RelativeOverlap是指RelativeLayout位于同一水平方向的两个Widget分别位于layout的左右两边,又都没有限制控件的长度,随着内容的增长,两个控件中间的距离不断缩小,最后会有部分重叠。所以,要控制一下边界。

Bidrrectional Text 双向文本

RtlEnabled

在API 17或更高版本上使用RTL属性需要在manifest文件的application标签中设置android:supportsRtl=”true”。如果已经开始在layout文件中加入RTL属性,但是没有完全使用RTL替代旧版的属性设置,可以设置android:supportsRtl=”false”规避lint的检查。

RtlCompat

API 17以后给文本控件提供了一个textAlignment属性来控制水平方向文字的对齐方式。但是,如果APP支持的版本包含小于 API 17 的版本,那么必须要设置gravity或者layout_gravity属性,因为老版本的系统会忽略textAlignment属性值。

RtlHardcoded

强制方向设置:在文本对齐和控件对齐中使用Gravity.LEFT/Gravity.RIGHT 的方式指定在左侧或者右侧对齐的情形,或在文字从右往左书写的国家或者地区造成困扰。使用Gravity.START/Gravity.END 就可以解决这个问题。同理,在layout文件中设置gravity/layout_gravity 属性时使用start/end 替代left/right

属性paddingLeft/paddingRight和属性layout_marginLeft/layout_marginRight也需要替换为paddingStart/paddingEndlayout_marginStart/layout_marginEnd

如果APP支持的最小API版本小于 API 17,那么需要同时提供left/right属性和start/end属性,因为低版本的系统会忽略start/end属性。

RtlSymmetry

margin|padding左右对称:如果对一个layout对象指定一边的内边距或外边距,那么应该对另一边指定同样大小的内边距或外边距。

参考文献

双向绑定Proxy比defineproperty优劣

前言

双向绑定其实已经是一个老掉牙的问题了,只要涉及到MVVM框架就不得不谈的知识点,但它毕竟是Vue的三要素之一

Vue三要素

  • 响应式: 例如如何监听数据变化,其中的实现方法就是我们提到的双向绑定
  • 模板引擎: 如何解析模板
  • 渲染: Vue如何将监听到的数据变化和解析后的HTML进行渲染

可以实现双向绑定的方法有很多,KnockoutJS基于观察者模式的双向绑定,Ember基于数据模型的双向绑定,Angular基于脏检查的双向绑定,本篇文章我们重点讲面试中常见的基于数据劫持的双向绑定。

常见的基于数据劫持的双向绑定有两种实现,一个是目前Vue在用的Object.defineProperty,另一个是ES2015中新增的Proxy,而Vue的作者宣称将在Vue3.0版本后加入Proxy从而代替Object.defineProperty,通过本文你也可以知道为什么Vue未来会选择Proxy。

严格来讲Proxy应该被称为『代理』而非『劫持』,不过由于作用有很多相似之处,我们在下文中就不再做区分,统一叫『劫持』。

基于Object.defineProperty双向绑定的特点

关于Object.defineProperty的文章在网络上已经汗牛充栋,我们不想花过多时间在Object.defineProperty上面,本节我们主要讲解Object.defineProperty的特点,方便接下来与Proxy进行对比。

极简版的双向绑定

我们都知道,Object.defineProperty的作用就是劫持一个对象的属性,通常我们对属性的getter和setter方法进行劫持,在对象的属性发生变化时进行特定的操作。
我们就对对象obj的text属性进行劫持,在获取此属性的值时打印’get val’,在更改属性值的时候对DOM进行操作,这就是一个极简的双向绑定。

const obj = {};
Object.defineProperty(obj, 'text', {
  get: function() {
    console.log('get val'); 
  },
  set: function(newVal) {
    console.log('set val:' + newVal);
    document.getElementById('input').value = newVal;
    document.getElementById('span').innerHTML = newVal;
  }
});

const input = document.getElementById('input');
input.addEventListener('keyup', function(e){
  obj.text = e.target.value;
})

Object.defineProperty的缺陷

Object.defineProperty的第一个缺陷,无法监听数组变化。 然而Vue的文档提到了Vue是可以检测到数组变化的,但是只有以下八种方法,vm.items[indexOfItem] = newValue这种是无法检测的。

push()
pop()
shift()
unshift()
splice()
sort()
reverse()

其实作者在这里用了一些奇技淫巧,把无法监听数组的情况hack掉了,以下是方法示例。

const aryMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];
const arrayAugmentations = [];

aryMethods.forEach((method)=> {

    // 这里是原生Array的原型方法
    let original = Array.prototype[method];

   // 将push, pop等封装好的方法定义在对象arrayAugmentations的属性上
   // 注意:是属性而非原型属性
    arrayAugmentations[method] = function () {
        console.log('我被改变啦!');

        // 调用对应的原生方法并返回结果
        return original.apply(this, arguments);
    };

});

let list = ['a', 'b', 'c'];
// 将我们要监听的数组的原型指针指向上面定义的空数组对象
// 别忘了这个空数组的属性上定义了我们封装好的push等方法
list.__proto__ = arrayAugmentations;
list.push('d');  // 我被改变啦! 4

// 这里的list2没有被重新定义原型指针,所以就正常输出
let list2 = ['a', 'b', 'c'];
list2.push('d');  // 4

由于只针对了八种方法进行了hack,所以其他数组的属性也是检测不到的,其中的坑很多,可以阅读上面提到的文档。
我们应该注意到在上文中的实现里,我们多次用遍历方法遍历对象的属性,这就引出了Object.defineProperty的第二个缺陷,只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历,如果属性值也是对象那么需要深度遍历,显然能劫持一个完整的对象是更好的选择。

Object.keys(value).forEach(key => this.convert(key, value[key]));

Proxy实现的双向绑定的特点

Proxy在ES2015规范中被正式发布,它在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写,我们可以这样认为,Proxy是Object.defineProperty的全方位加强版

Proxy可以直接监听对象而非属性

我们还是以上文中用Object.defineProperty实现的极简版双向绑定为例,用Proxy进行改写。

const input = document.getElementById('input');
const p = document.getElementById('p');
const obj = {};

const newObj = new Proxy(obj, {
  get: function(target, key, receiver) {
    console.log(`getting ${key}!`);
    return Reflect.get(target, key, receiver);
  },
  set: function(target, key, value, receiver) {
    console.log(target, key, value, receiver);
    if (key === 'text') {
      input.value = value;
      p.innerHTML = value;
    }
    return Reflect.set(target, key, value, receiver);
  },
});

input.addEventListener('keyup', function(e) {
  newObj.text = e.target.value;
});

我们可以看到,Proxy直接可以劫持整个对象,并返回一个新对象,不管是操作便利程度还是底层功能上都远强于Object.defineProperty。

Proxy可以直接监听数组的变化

当我们对数组进行操作(push、shift、splice等)时,会触发对应的方法名称和length的变化,我们可以借此进行操作,以上文中Object.defineProperty无法生效的列表渲染为例。

const list = document.getElementById('list');
const btn = document.getElementById('btn');

// 渲染列表
const Render = {
  // 初始化
  init: function(arr) {
    const fragment = document.createDocumentFragment();
    for (let i = 0; i &lt; arr.length; i++) {
      const li = document.createElement('li');
      li.textContent = arr[i];
      fragment.appendChild(li);
    }
    list.appendChild(fragment);
  },
  // 我们只考虑了增加的情况,仅作为示例
  change: function(val) {
    const li = document.createElement('li');
    li.textContent = val;
    list.appendChild(li);
  },
};

// 初始数组
const arr = [1, 2, 3, 4];

// 监听数组
const newArr = new Proxy(arr, {
  get: function(target, key, receiver) {
    console.log(key);
    return Reflect.get(target, key, receiver);
  },
  set: function(target, key, value, receiver) {
    console.log(target, key, value, receiver);
    if (key !== 'length') {
      Render.change(value);
    }
    return Reflect.set(target, key, value, receiver);
  },
});

// 初始化
window.onload = function() {
    Render.init(arr);
}

// push数字
btn.addEventListener('click', function() {
  newArr.push(6);
});

很显然,Proxy不需要那么多hack(即使hack也无法完美实现监听)就可以无压力监听数组的变化,我们都知道,标准永远优先于hack。

Proxy的其他优势

Proxy有多达13种拦截方法,不限于apply、ownKeys、deleteProperty、has等等是Object.defineProperty不具备的。
Proxy返回的是一个新对象,我们可以只操作新的对象达到目的,而Object.defineProperty只能遍历对象属性直接修改。
Proxy作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利。
当然,Proxy的劣势就是兼容性问题,而且无法用polyfill磨平,因此Vue的作者才声明需要等到下个大版本(3.0)才能用Proxy重写。

基于数据的动态转换过滤器

这里直接使用 Vue作为例子,我们通过模拟一个不断有新数据产生的实时数据集,然后我们需要通过一个过滤参数将过滤后的数据展示出来。

<template>
    <div class="userinfo">
        <div>
        <label for="type">Type Filter: </label>
        <select name="type" id="type" v-model="typeFilter">
            <option value="none">None</option>
            <option v-for="type in typesSet" :value="type" :key="type">{{type}}</option>
        </select>
        <ul>
            <li v-for="item in filteredtypeDataset" :key="item.timestamp">Timestamp: {{item.timestamp}} - Type: {{item.type}} - Value: {{item.value}}</li>
        </ul>
        </div>
    </div>
</template>

“`vue.js
<script>
export default {
name: 'Userinfo',
data() {
return {
dataset: [],
typeFilter: 'none',
typesSet: [ 'nini', 'honghong', 'mingming' ],
};
},
props: [],
components: {},
computed: {
filteredtypeDataset() {
if (this.typeFilter === 'none') {
return this.dataset
}
return this.dataset.filter(item => item.type === this.typeFilter)
}
},
created() {},
methods: {
},
mounted() {
// 模拟流式数据集
setInterval(() => {
const randomType = this.typesSet[Math.round(Math.random() * (this.typesSet.length – 1))]
this.dataset.push({
type: randomType,
timestamp: Date.now(),
value: Math.random().toString(32).substr(2)
})
}, 1e3)
}
};
</script>

<pre><code class="line-numbers">进行构建的数据应用中,也是根据这个 typeSet 来提前生成了一个用于过滤数据的过滤器。

但有的时候前端的数据应用并不知道来自其他数据服务的数据内容究竟有哪些过滤项,那么我们便需要根据数据应用所得到的实际数据来生成过滤器。
“`js

methods: {
mockDataSource(typesSet) {
const dataset = [];
const timer = setInterval(() =&gt; {
const randomType = typesSet[Math.round(Math.random() * (typesSet.length – 1))]
dataset.push({
type: randomType,
timestamp: Date.now(),
value: Math.random().toString(32).substr(2)
})
}, 1e3)
return { dataset, stop() {
clearInterval(timer)
}
}
}
},
mounted() {
// 模拟流式数据集
setInterval(() =&gt; {
const dataSource = this.mockDataSource(Array(10).fill(1).map((_, i) =&gt; `type${i + 1}`)).dataset.type
this.dataset.push({
type: dataSource,
timestamp: Date.now(),
value: Math.random().toString(32).substr(2)
})
}, 1e3)
}

这段代码中我们模拟了一个包含多种可过滤数据type的流式数据集,且该数据集过滤字段内容是“不可预知”的。

使用 Prisma GraphQL API

概览

Prisma API 基于 HTTP 协议。这意味着您可以使用任何您喜欢的 HTTP 工具/库与 Prisma API 进行通信。

以下是 createUser mutation 的 HTTP POST 请求的结构:

Header

  • Authorization:携带用于验证请求的 service token(前缀 Bearer); 仅在使用 service secret 部署服务时才需要。

  • Content-Type:指定请求正文(JSON)的格式,通常为 application/json

Body(JSON)

  • query:要发送到 API 的 GraphQL 操作; 请注意,尽管该字段被称为 query,但它也用于 mutations!

  • variables:一个 JSON 对象,包含在提交的 GraphQL 操作中定义的 query 中的变量。

此页面上的所有示例均基于具有以下 service 配置的 Prisma service:

prisma.yml

datamodel: datamodel.prisma
secret: my-secret-42

datamodel.prisma

type User {
  id: ID! @unique
  name: String!
}

curl

curl 是一个命令行工具,除此之外,它允许您向 URL 发送 HTTP 请求。

以下是使用 curl 发送 createUser mutation 到 Prisma API 的方法:

curl '__YOUR_PRISMA_ENDPOINT__' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer __YOUR_SERVICE_TOKEN__' \
--data-binary '{"query":"mutation ($name: String!) { createUser(data: { name: $name }) { id } }","variables":{"name":"Sarah"}}'

要试用此示例,请使用 Prisma service 中的相应值替换 __YOUR_PRISMA_ENDPOINT____YOUR_SERVICE_TOKEN__ 占位符,并将相应的代码段粘贴到终端中。

这相对于向 Prisma API 发送了以下的 mutation 请求:

mutation {
  createUser(data: {
    name: "Sarah"
  }) {
    id
  }
}

试试这个例子:

  1. 用 Prisma service 中的相应值替换 __YOUR_PRISMA_ENDPOINT____YOUR_SERVICE_TOKEN__占位符

  2. 将相应代码段粘贴到终端中,然后按Enter键

fetch(Node.JS)

fetch 允许您使用 JavaScrip t向 URL 发送 HTTP 请求。

以下是在 node 脚本中,使用 fetch 向 Prisma API 发送 createUser mutation 的方法:

const fetch = require('node-fetch')

const endpoint = '__YOUR_PRISMA_ENDPOINT__'

const query = `
mutation($name: String!) {
  createUser(data: {
    name: $name
  }) {
    id
  }
}
`

const variables = { name: 'Sarah' }

fetch(endpoint, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization:
      'Bearer __YOUR_SERVICE_TOKEN__',
  },
  body: JSON.stringify({ query: query, variables: variables }),
})
  .then(response =&gt; response.json())
  .then(data =&gt; console.log(JSON.stringify(data)))

这相当于向 Prisma API 发送了如下 mutation:

mutation {
  createUser(data: {
    name: "Sarah"
  }) {
    id
  }
}

试试这个例子:

  1. 用 Prisma service 中的相应值替换__YOUR_PRISMA_ENDPOINT____YOUR_SERVICE_TOKEN__占位符

  2. 将相应代码段存储在名为的 script.js 的 JavaScript 文件中

  3. script.js 同一目录下,运行 yarn add node-fetch 安装 node-fetch

  4. 使用以下 terminal 命令行执行脚本: node script.js

graphql-request

graphql-request 库是 fetch 上包装的一个轻量级库,您可以使用它来保存和编写样板代码。它主要用于需要与 GraphQL API 通信的脚本和较小的应用程序。

使用 request

request 不支持将 headers 传递给请求(尚未)。因此,此特定示例假定您的 Prisma 服务已部署,并且没有 service secret。

const { request } = require('graphql-request')

const query = `
mutation($name: String!) {
  createUser(data: {
    name: $name
  }) {
    id
  }
}
`

const variables = { name: 'Sarah' }

request('__YOUR_PRISMA_ENDPOINT__', query,  variables)
  .then(data =&gt; console.log(data))

试试这个例子:

  1. 用 Prisma service 中的相应值替换__YOUR_PRISMA_ENDPOINT____YOUR_SERVICE_TOKEN__占位符

  2. 将相应代码段存储在名为的 script.js 的 JavaScript 文件中

  3. script.js 同一目录下,运行库 yarn add graphql-request 命令安装 graphql-request

  4. 使用以下 terminal 命令行执行脚本: node script.js

使用 GraphQLClient

const { GraphQLClient } = require('graphql-request')

const client = new GraphQLClient('__YOUR_PRISMA_ENDPOINT__', {
  headers: {
    Authorization: 'Bearer __YOUR_SERVICE_TOKEN__',
  },
})

const query = `
mutation($name: String!) {
  createUser(data: {
    name: $name
  }) {
    id
  }
}
`

const variables = { name: 'Sarah' }

client.request(query, variables)
  .then(data =&gt; console.log(data))

试试这个例子:

  1. 用 Prisma service 中的相应值替换__YOUR_PRISMA_ENDPOINT____YOUR_SERVICE_TOKEN__占位符

  2. 将相应代码段存储在名为的 script.js 的 JavaScript 文件中

  3. script.js 同一目录下,运行库 yarn add graphql-request 命令安装 graphql-request

  4. 使用以下 terminal 命令行执行脚本:node script.js

GraphQL Playground

GraphQL Playground 是一个 GraphQL IDE,允许您向 GraphQL API 发送 queries,mutations 和 subscriptions。

您可以通过将终端,导航到 service 的 prisma.yml 所在目录,然后运行以下命令,来打开 Prisma API 的 Playground:

prisma playground

Apollo Client

Apollo Client 是一个复杂的 GraphQL client 库,常用于大型前端应用程序。虽然所有前面的例子中都使用的类似工具来发送 queries 和 mutations 一样,Apollo Client 暴露了用于发送 queries 和 mutations 的专用方法:querymutate

query 查询

const { ApolloClient } = require('apollo-boost')
const gql = require('graphql-tag')

const endpoint = 'https://eu1.prisma.sh/nikolas-burk/demodofin/dev'

const client = new ApolloClient({
  uri: endpoint
});

const query = gql`
  query {
    users {
      id
      name
    }
  }
`

client.query({
  query: query,
})
  .then(data =&gt; console.log(data))

mutate 变换

const { ApolloClient } = require('apollo-boost')
const gql = require('graphql-tag')

const endpoint = 'https://eu1.prisma.sh/nikolas-burk/demodofin/dev'

const client = new ApolloClient({
  uri: endpoint
});

const mutation = gql`
  mutation($name: String!) {
    createUser(data: {
      name: $name
    }) {
      id
    }
  }
  `

const variables = { name: 'Sarah' }

client.mutate({
  mutation: mutation,
  variables: variables
})
  .then(data =&gt; console.log(data))

参考链接:https://github.com/graphql/graphql-js

浅议Swift和OC不同

浅议Swift和OC不同

Objective-C几乎只有面向对象编程,Swift更注重值类型的数据结构,而objective-c遵循C语官的老一套,注重指针和索引!Swift是静态类型语言,而Objective-C是动态类型语言。本节会从数据结构、编程思路和语言特性三个角度来比较Swift和Objective-C这两种语言的异同。从比较当中我们也更能体会,尽管两者都是为iOS开发而定制的语言,但是Objectivec和Swift有着天壤之别。

数据结构

Swift为什么将String,·Array和Dictionary设计成值类型
关键词: #引用类型 #值类型 #值类型 #协议

在OC中,String,Array,和Dictionary均被设计为引用类型.
* 值引用相比引用类型,最大的优势在于可以高效的使用内存。值类型在栈上操作,引用类型在堆上操作。栈上的操作仅仅是耽搁指针的上下移动,而堆上的操作则牵连涉合并、移位、重新链接等。也就是说,Swift这样的设计大幅度减少了堆上的内存分配和回收的次数。同时copy-on-wirte又将值传递和复制的开销降到最低。
* Swift将String,Array和Dictionary设计成值类型,也是为了线程安全。通过Swift的let的设置,使得这些数据达到了真正意义上的“不变”,也从根本上解决了多线程中的内存访问和操作的顺序问题。
* Swift将String,Array和Dictionary设计成值类型还可以提升API的灵活度,例如,通过实现Collection这样的协议,可以遍历String,使得整个开发更加灵活、高效。

语言特性

OC和Swift动态特性的理解
关键词: #动态特性 #@tuntime #面向协议编程

runtime其实就是 Objective-C代 的动态机制runtime执行的是编译后的代码,这时它可以动态加载对象、添加方法、修改属性、传递信息等。具体的过程是,在Obective中,对象调用方法时,如[self.tablevrew reload].经历了两个阶段:

  • 编译阶段: 编译器(compiler)会把这句话翻译成ohjc_msgSend(self.tableView .
  • 运行阶段: 接收者(self.tableView) 会响应这个消息,期间可能会直接执行,转发消息,也可能会找不到方法导致程序崩溃.

    所以.整个流程是:编器翻译->给接收者发送消息一>接收者响响应消息。
    例如,在[self.tableview reload]中.self.tableview 及时接受者。reload就是消息。所以,方法调用的格式在编译器看来就是[receiver message]。

    其中接受者如何响应代码,就发生在运行时(runtime)。runtime执行的是编译后的代码,这是他客户已动态加载对象、添加方法、修改属性、传递信息等。runtime的运行时机制就是Objective-C的动态特性

    Swift目前被认为是一门静态语言.它的动态将性都是通过桥接OC 来实现的.如果要把其动态特性写得更“Swift“一点,则可以用Protocol 来处理.比如,可以将OC中的 reflection 这样写:

    if([someImage respondsToSelector:@selector(shake)]){
        [some Image performSelector:shake];
    } 
    

    在Swift中可以这样写:

    if let shakeableImage=someImage as?Shakeable{ 
        shakeableImage.shake()
    }
    

动画编辑器TimeLine动作流设计与实现

背景

最近在着手绘本课堂的设计与实现,中间有不少困难点需要攻克,大致分为以下几个模块:
– 容器配置化、工程化。
– 组件的集中管理,插件机制。
– 场景的切换和设计。
– TimeLine动作流处理。
– 多端同步的处理。
以上几点中,对于TimeLine动作流的处理尤为重要,同时也是最不好做的一个模块,下面简述一下怎么对TimeLine动作流进行处理。

以下具体内容请上confluence进行访问阅读:动画编辑器TimeLine动作流设计与实现