前端学习之路


  • 首页

  • 归档

  • 分类

  • 标签

  • 搜索
close
小朱

小朱

前端学习之路

168 日志
37 分类
37 标签
RSS
GitHub
友情链接
  • 极客学院
  • caniuse
  • codepen
  • JS Bin
  • Babel在线编译
  • Iconfinder图标
  • 在线JSON格式化
  • 智能图像压缩

JavaScript-基础知识

发表于 2017-08-26   |   分类于 FJ
  • 数据类型

    6种数据类型,其中包括5个基本数据类型:String、Number、Boolean、Null、Undefined,按值访问;1个复杂数据类型:Object,按引用访问。

  • html允许跨域的标签

    script、link、img、video、audio、frame、iframe、embed等有src属性的标签,以及object(data属性)、applet(code属性),css中的@font-face。

  • call、applay和bind的区别

    bind通常用来重新绑定函数体中的this并放回一个具有指定this的函数,多次bind是无效的。call和apply则表示重新指定this并调用返回结果,区别在于call采用多个实参的方式传参,apply则是使用一个数组。

    共同点:第一个参数指定为this,第二个参数起为参数传递。

    不同点:bind是用来返回具有特定this的函数,call和apply都是改变上下文中的this并立即执行这个函数。

  • 单页应用(SPA)和多页应用(MPA)

    单页应用将所有的活动局限于一个Web页面中,仅在该Web页面初始化时加载相应的HTML、JavaScript和CSS。一旦页面加载完成了,SPA不会因为用户的操作而进行页面的重新加载或跳转。取而代之的是利用JavaScript动态的变换HTML的内容,从而实现UI与用户的交互。由于避免了页面的重新加载,SPA可以提供较为流畅的用户体验,但不利于SEO。MVVM这类最适合做单页面应用。

    多页面一般每个页面都有一个入口页面,页面跳转用普通的<a href=\"\"></a>,会进行整个页面的刷新。

  • 发布订阅模式

    订阅发布模式定义了一种一对多的依赖关系,让多个订阅者对象同时监听某一个主题对象。这个主题对象在自身状态变化时,会通知所有订阅者对象,使它们能够自动更新自己的状态。当一个对象的改变需要同时改变其它对象,而且它还不知道具体有多少对象需要改变时,就可以使用订阅发布模式了。

    观察者模式与发布订阅模式很像,虽然两种模式都存在订阅者和发布者(具体观察者可认为是订阅者、具体目标可认为是发布者),区别是观察者模式是由具体目标调度的,而发布订阅模式是统一由调度中心调的,所以观察者模式的订阅者与发布者之间是存在依赖的,而发布订阅模式则不会。

  • 纯函数

    纯函数是这样一种函数,即相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,JavaScript中指不依赖于且不改变它作用域之外的变量状态的函数。只要是跟函数外部环境发生的交互就都是副作用,副作用可能包含但不限于:

    更改文件系统
    往数据库插入记录
    发送一个 http 请求
    可变数据
    打印/log等IO操作
    获取用户输入
    DOM 查询
    访问系统状态
    访问系统时钟

  • 属性特性

    ES5定义了一个名叫“属性描述符”的对象,用于描述属性的各种特征,只能在Object.defineProperty或Object.defineProperties中使用。属性描述符对象有:

    4个属性:

    configurable: 可配置性,表示能否修改属性特性,能否把属性修改为访问器属性,或者能否通过delete删除属性从而重新定义属性,默认true。设置为false后,再调用Object.defineProperty修改属性特性都会报错。
    enumerable: 可枚举性,表示能否通过for-in遍历得到属性,默认true。
    writable: 可写性,表示能否修改属性的值,默认为true。
    value: 数据属性,表示属性的值,默认为undefined。

    2个存取器属性,分别是get和set,可以代替value和writable,存取器和value、writable不能同时存在:

    get: 在读取属性时调用的函数,只指定get则表示属性为只读属性,默认值为undefined。
    set: 在写入属性时调用的函数,只指定set则表示属性为只写属性,默认值为undefined。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // 修改obj的name属性特性
    Object.defineProperty(obj, 'name', {
    writable: false,
    value: 'Tom',
    )};
    // 一次修改obj的多个属性特性
    Object.defineProperties(obj, {
    _year: {
    value: 2017,
    },
    edition: {
    value: 1,
    writable: true,
    },
    year: {
    get: function() {
    return this._year;
    },
    set: function() { ... }
    }
    });
    // 读取属性特性
    Object.getOwnPropertyDescriptor(obj, '_year');
    // 读取所有属性特性
    Object.getOwnPropertyDescriptors(obj);

    Object.preventExtensions(obj) 禁止扩展,禁止给对象添加属性和方法,原有的属性可修改
    Object.isExtensible(obj) 检查是否可扩展
    Object.seal() 密封对象,不可扩展、不可删除属性
    Object.isSeal() 检查是否密封
    Object.freeze() 冻结对象,不可扩展、不可删除、不可修改属性
    Object.isFrozen() 检查是否冻结

  • 事件冒泡

    事件冒泡,从内到外;事件捕获,从外到内;DOM事件流,从外到内再从内到外回到原点。事件冒泡允许多个操作被集中处理,把事件处理器添加到一个父级元素上,避免把事件处理器添加到多个子级元素上,可以优化性能,在页面动态添加新元素后,这些新增的元素也不用再绑定事件,它还可以让你在对象层的不同级别捕获事件。

    blur、focus、load、unload不冒泡。使用addEventListener绑定事件的方法第三个参数可以控制事件触发顺序,true为事件捕获,false为事件冒泡,默认为false。使用attachEvent绑定事件的方法不能控制事件触发顺序。

    阻止事件冒泡不能阻止对象默认行为,如submit按钮会提交表单数据。

    事件对象:

    1
    2
    3
    event.keyCode // 返回keydown何keyup事件发生的时候按键的代码
    event.target // 发生事件的目标元素
    event.currentTarget // 当前处理事件的元素

    阻止冒泡:

    1
    2
    3
    event.stopPropagation();
    event.stopImmediatePropagation(); // 除了该事件的冒泡行为被阻止之外,该元素绑定的后序相同类型事件的监听函数的执行也将被阻止
    window.event.cancelBubble = true; // IE下

    阻止默认行为:

    1
    2
    event.preventDefault();
    window.event.returnValue = false; // IE下

    在body上处理事件:

    1
    2
    3
    4
    5
    window.onload = function() {
    document.body.addEventListener('click', function(e){
    console.log(e.target.id);
    });
    };
  • cookie和session的区别与联系,怎么实现

    cookie和session都是用来跟踪浏览器用户身份的会话方式。cookie数据保存在客户端,session数据保存在服务器端。考虑到安全应当使用session,考虑到减轻服务器性能方面,应当使用cookie。

    如果web服务器端使用的是session,那么所有的数据都保存在服务器上,客户端每次请求服务器的时候会发送当前会话的sessionid,保存sessionid的方式可以采用cookie、url重写等方式,存储于浏览器内存中,服务器根据当前sessionid判断相应的用户数据标志,以确定用户是否登录或具有某种权限。由于数据是存储在服务器上面,所以你不能伪造,但是如果你能够获取某个登录用户的sessionid,用特殊的浏览器伪造该用户的请求也是能够成功的。sessionid是服务器和客户端链接时候随机分配的,一般来说是不会有重复,但如果有大量的并发请求,也不是没有重复的可能性。

    如果浏览器使用的是cookie,那么所有的数据都保存在浏览器端,比如你登录以后,服务器设置了cookie用户名,那么当你再次请求服务器的时候,浏览器会将用户名一块发送给服务器,这些变量有一定的特殊标记。如果不设置过期时间,则表示这个cookie的生命期为浏览器会话期间,关闭浏览器窗口cookie就消失。如果设置了的过期时间,浏览器会将cookie保存在客户端的硬盘上,下次再访问该网站的时候,浏览器先检查有没有cookie,如果有的话就读取该cookie,然后发送给服务器。如果直接将cookie文件拷贝到别人的浏览器目录下面,浏览器是不认的,因为它有一个index.dat文件,存储了cookie文件的建立时间,以及是否有修改,所以必须要先有该网站的cookie文件,并且要从保证时间上骗过浏览器,才能保证拷贝的cookie能生效。所以,cookie是可以伪造的,使用cookie被攻击的可能性比较大。

  • Web Storage包括哪几种方式,区别是什么,没出现之前怎么做的

    HTML5中的Web Storage包括了两种存储方式:sessionStorage和localStorage。在没有Web Storage之前,是通过Cookie来在客户端存储数据的。

    sessionStorage用于本地存储一个会话(session)中的数据,这些数据只有在同一个会话中的页面才能访问,刷新页面数据依旧存在,但是当会话结束也就是页面关闭后数据也随之销毁。因此sessionStorage不是一种持久化的本地存储,仅仅是会话级别的存储。

    localStorage用于持久化的本地存储,可用于所有同源窗口或标签页(相同的域名、协议和端口),除非主动删除数据,否则数据是永远不会过期的。

Cookie Web Storage
4KB左右 5MB左右
自己封装setCookie、getCookie 提供setItem、getItem等api
可设置失效时间 localStorage永久保存、sessionStorage会话级别
HTTP规范的一部分,同源的请求都带Cookie 仅在客户端保存数据,不参与和服务器的通信
  • 正则表达式中test、exec、match

    test查找对应的字符串中是否存在模式,返回Boolean。当一个具有g标志的正则表达式调用test方法时,它的行为和exec相同,会从上个匹配的位置(index + 当前匹配项的长度)开始查找,这样我们就可以使用方法test来遍历字符串了。

    1
    2
    3
    4
    5
    6
    var str = "1a1b1c";
    var reg = new RegExp("1.", "g");
    console.log(reg.test(str)); // true
    console.log(reg.test(str)); // true
    console.log(reg.test(str)); // true
    console.log(reg.test(str)); // false

    exec查找并返回当前的匹配结果,并以数组的形式返回,如果不存在模式则返回null,否则总是返回一个长度为1的数组,其值就是当前匹配项,还有两个属性:index为当前匹配项的位置;input就是参数字符串。exec方法受参数g的影响。若指定了g,则下次调用exec时,会从上个匹配的位置(index+当前匹配项的长度)开始查找。

    1
    2
    3
    4
    var str = "1a1b1c";
    var reg = new RegExp("1.", "g");
    console.log(reg.exec(str)); // ["1a", index: 0, input: "1a1b1c"]
    console.log(reg.exec(str)); // ["1b", index: 2, input: "1a1b1c"]

    match是String对象的一个方法。match这个方法有点像exec,但exec是RegExp对象的方法。二者有一个不同点,就是对参数g的解释。如果指定了参数g,那么match一次返回所有的结果组成的数组,没有匹配则返回null。

    1
    2
    3
    var str = "1a1b1c";
    var reg = new RegExp("1.", "g");
    console.log(str.match(reg)); // ["1a", "1b", "1c"]
  • dom ready 的实现

    window.onload事件是等到页面上的所有资源被加载才激活,包括页面上的许多图片、音乐或falsh等资源。如果要做绑定事件、DOM操作某节点等事情,使用window.onload有点太“迟”了,比较影响用户体验。为了解决这个问题,Firefox为DOM纳入一个全新事件DOMContentLoaded,也就是后来所说的DOMReady,是在DOM树构建完毕执行,无需等待其它资源的加载,因此是在onload前加载的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    // DOM已加载,比window.onload快,可以有多个,按序执行
    jQuery(document).ready(function() {
    ...
    });
    // 也是在页面所有元素加载完毕后执行的,但是可加载多个函数,并且可以做到js和html完全分离
    $(window).load(function() {
    ...
    });
    $(window).load(function() {
    ...
    });
    // 页面关闭时引发
    $(window).unload(function() {
    ...
    });
    // IE浏览器没有添加DOMContentLoaded事件,以下是jQuery对该事件的兼容性实现
    function domReady() {
    if (readyBound) return;
    readyBound = true;
    if (document.addEventListener) {// 对于非IE的处理
    document.addEventListener("DOMContentLoaded", function() {
    document.removeEventListener("DOMContentLoaded", arguments.callee, false);
    jQuery.ready();
    }, false);
    } else if (document.attachEvent) {// 对于IE的处理
    // 如果是在iframe中,通过document的onReadyStateChange实现
    document.attachEvent("onReadyStateChange", function() {
    if (document.readyState === "complete") {
    document.detachEvent("onReadyStateChange", arguments.callee);
    jQuery.ready();
    }
    });
    // 如果页面不在iframe中,通过setTimeout不断调用documentElement的doScroll方法直到调用成功
    if (document.documentElement.doScroll && typeof window.frameElement === "undefined") {
    (function() {
    if (jQuery.isReady) return;
    try {
    document.documentElement.doScroll("left");
    } catch(error) {
    setTimeout(arguments.callee, 0);
    return;
    }
    jQuery.ready();
    })();
    }
    }
    jQuery.event.add(window, "load", jQuery.ready);
    }
  • img标签的图片怎么占高

    img外层要有div,如果已知图片宽高可直接指定div的宽高,然后将img的width样式设成100%。也可以使用预览图占位。

    获取图片原始尺寸:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // js获取
    var img = document.getElementsByTagName('img')[0];
    function getImgNaturalDimensions(img, callback) {
    var nWidth, nHeight
    if (img.naturalWidth) { // 现代浏览器
    nWidth = img.naturalWidth
    nHeight = img.naturalHeight
    } else { // IE6/7/8
    // js创建一个Image
    var imgae = new Image();
    image.src = img.src;
    image.onload = function() {
    callback(image.width, image.height);
    }
    // jQuery创建img
    $("<img/>").attr("src", $(img).attr("src")).load(function() {
    callback(image.width, image.height);
    });
    }
    return [nWidth, nHeight];
    }
  • js跨域的解决办法

    浏览器都有一个同源策略,其限制之一是不能通过ajax的方法去请求不同源中的文档;第二是限制浏览器中不同域的框架之间是不能进行js的交互操作的。但是不同的框架之间(父子或同辈),是能够获取到彼此的window对象的,但却不能使用获取到的window对象的属性和方法,postMessage方法除外,可以当做是只能获取到一个几乎无用的window对象。只要协议、域名、端口有任何一个不同,都被当作是不同的域。要解决跨域的问题,我们可以使用以下几种方法:

    方式一、通过jsonp跨域

    jsonp是利用<script>标签可以引入不同域上的js脚本文件来实现的,通过script标签引入一个js文件,这个js文件载入成功后会执行我们在url参数中指定的函数,并且会把我们需要的json数据作为参数传入。所以jsonp是需要服务器端的页面进行相应的配合的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    /*
    js原生写法,通过http://example.com/data.php?callback=dosomething得到的js文件,就是我们之前定义的dosomething函数,并且它的参数就是我们需要的json数据,这样就跨域获得了我们需要的数据。
    */
    <script>
    function dosomething(jsondata) {
    // 相应操作
    }
    </script>
    <script src="http://example.com/data.php?callback=dosomething"></script>
    /*
    jQuery写法,jQuery会自动生成一个全局函数来替换callback=?中的问号,之后获取到数据后又会自动销毁。$.getJSON方法会自动判断是否跨域,不跨域就调用普通的ajax方法,跨域会以异步加载js文件的形式来调用jsonp的回调函数。
    */
    <script>
    $.getJSON('http://example.com/data.php?callback=?', function(jsondata){
    // 相应操作
    });
    </script>


    方式二、通过修改document.domain来跨子域

    针对不同域的框架这种情况,是无法通过在页面中书写js代码来获取iframe中的东西的,这个时候可以使用document.domain,把这两个页面的document.domain都设成相同的域名就可以了。但要注意,只能把document.domain设置成自身或更高一级的父域,且主域必须相同,如果当前文档的domain就是要设置的域名,还是必须显示的设置
    document.domain。这样我们就可以通过js访问到iframe中的各种属性和对象了。

    http://www.example.com/a.html中:

    1
    2
    3
    4
    5
    6
    7
    <iframe src="http://example.com/b.html" id="iframe" onLoad="test()"></iframe>
    <script>
    document.domain = 'example.com'; // 设置成主域
    function test() {
    alert(document.getElementById('iframe').contentWindow);
    }
    </script>

    http://example.com/b.html中:

    1
    2
    3
    <script>
    // 在iframe载入的这个页面也设置document.domain,使之与主页面的document.domain相同
    </script>

    但是如果想在a.html页面中通过ajax直接请求b.html页面,即使设置了相同的document.domain也还是不行的,修改document.domain的方法只适用于不同子域的框架间的交互。如果想通过ajax的方法去与不同子域的页面交互,除了使用jsonp的方法外,还可以用一个隐藏的iframe来做一个代理。原理就是让这个iframe载入一个与你想要通过ajax获取数据的目标页面处在相同域的页面,这个iframe中的页面是可以正常使用ajax去获取你要的数据的,然后通过修改document.domain的方法,通过js完全控制这个iframe,这样就可以让iframe去发送ajax请求,然后收到的数据我们也可以获取到。

    方式三、使用window.name来进行跨域

    window.name属性有个特征,即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,即使这些页面是不同域的或url发生变化,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的,不会因新页面的载入而进行重置。window.name的值只能是字符串的形式,最大能允许2M左右甚至更大的一个容量,具体取决于不同的浏览器,但一般是够用了。例如www.example.com/a.html页面里的js需要获取另一个位于不同域的页面www.cnblogs.com/data.html里的数据,示例代码如下:

    www.cnblogs.com/data.html中:

    1
    2
    3
    <script>
    window.name = '我就是页面a.html想要的数据,所有可以转化成字符串来传递的数据都可以在这里使用!!!';
    </script>

    www.example.com/a.html中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <!doctype html>
    <html>
    <head>
    <meta charset="utf-8" />
    <title>window.name跨域</title>
    <script>
    function getData() {
    var iframe = document.getElementById('proxy');
    // 这个时候a.html与iframe已经是同源了,可以互相访问
    iframe.onload = function() {
    // 获取iframe里的window.name,也就是data.html页面给它设置的数据
    var data = iframe.contentWindow.name;
    alert(data);
    };
    // 这里的b.html为随便的一个页面,只要与a.html同源的就行
    iframe.src = 'b.html';
    }
    </script>
    </head>
    <body>
    <iframe id="proxy" src="http://www.cnblogs.com/data.html" style="display:none" onload="getData()" />
    </body>
    </html>

    方式四、使用HTML5中新引进的window.postMessage方法来跨域传送数据

    window.postMessage(message, targetOrigin)方法是html5新引进的特性,不支持IE6、7旧浏览器,可以向其它的window对象发送消息,无论这个window对象是属于同源或不同源,该方法的第一个参数message为要发送的消息,类型只能为字符串;第二个参数targetOrigin用来限定接收消息的那个window对象所在的域,如果不想限定域,可以使用通配符 * 。接收消息的window对象,通过监听自身的message事件来获取传过来的消息,消息内容储存在该事件对象的data属性中。

    test.com/a.html中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <script>
    function onLoad() {
    var iframe = document.getElementById("iframe");
    var win = iframe.contentWindow;
    // 向不同域发送消息
    win.postMessage('我是来自页面a的消息!', '*');
    }
    </script>
    <iframe id="iframe" src="http://www.test.com/b.html" onload="onLoad()" />

    www.test.com/b.html中:

    1
    2
    3
    4
    5
    6
    <script>
    window.onmessage = function(e) {
    e = e || event;
    alert(e.data);
    }
    </script>
  • 有分号和没分号的区别

    JavaScript 语句后应该加分号么?

    通常情况下,解释器会自动添加分号,但有些时候不加分号还是容易引起解析错误,特别是在代码压缩的情况下,所以出于严谨考虑,还是推荐在语句结束时添加分号。还有不要把分号单单认为只是用来结束某段代码,它还可以用来隔离某段代码和别人划清界限。

    如果遇到无法解析下去则javascript解析器会尝试给其添加一个分号,如果还是解析不了则报错。javascript解析器会尽可能多的去匹配,但也有几个例外,它们是retrun、break、continue,当javascript解析器解析到这几个关键字时,它不会把换行后的内容当成是自身的,而是直接在换行之前添加分号。

    JavaScript自动加分号规则,有3条:

    当有换行符(包括含有换行符的多行注释),并且下一个token没法跟前面的语法匹配时,会自动补分号。
    当有}时,如果缺少分号,会补分号。
    当程序源代码结束时,如果缺少分号,会补分号。

    真正会导致上下行解析出问题的 token 有5个:括号,方括号,正则开头的斜杠,加号,减号。实际代码中几乎没有用正则、加号、减号作为行首的情况,所以一行开头是括号或者方括号的时候加上分号就可以了,其它时候全部不需要。

    在return、break、continue、后自增、后自减五种语句中,换行符可以完全替代分号的作用。
    var if do while for continue break return with switch throw try debugger几种关键字开头的语句,以及空语句,上一行加不加分号影响不大。
    凡表达式语句和函数表达式语句,后面不加分号非常危险,情况极其复杂。
    凡(和[开头的语句,前面不加分号极度危险。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 例子1
    var a = 10;
    var b = 5;
    var c = a + b
    (a + b).toString() // b is not a function,同 var c = a + b(a + b).toString();
    // 例子2
    function test(){
    return
    123;
    }
    console.log(test()); // undefined,同 return;
  • 变量提升基础

    注意js运行时先执行变量提升,然后在从上往下执行。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    /*
    相当于:
    var a;
    function a() {}
    a = 1;
    */
    var a = 1;
    function a() {}
    console.log(a); // 输出 1
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    /*
    相当于:
    var a;
    a = {
    x: 'x',
    a: a, // 此时a是undefined
    };
    */
    var a = {
    x: 'x',
    a: a,
    };
    console.log(a); // 输出 {x: "x", a: undefined}
    1
    2
    3
    var a = {x:'x'};
    a.a = a;
    console.log(a); // 输出 {x: "x", a: {…}} a可以无限展开
  • prototype基础

    任何对象都有__proto__,只有函数对象才有prototype,prototype.constructor指回自己,函数被当作构造函数创建实例时它的prototype将作为实例的__proto__。在定义prototype是显示的将constructor指向函数自己,否则constructor将指向Object,通过constructor已经无法确定对象的类型了。还有显示定义了constructor后在对象实例中是可枚举的。

    1
    2
    3
    4
    5
    6
    7
    8
    function feed() {
    this.a = 'hello';
    }
    feed.a = 'word';
    var f = new feed();
    console.log(f.a); // 输出 'hello'
    console.log(f.__proto__.a); // 输出 undefined
    console.log(f.__proto__.constructor.a); // 输出 'word'
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function feed() {
    this.a = 'hello';
    }
    feed.prototype = {
    b: 'www',
    };
    console.log(feed.prototype.constructor); // 输出 ƒunction Object() { [native code] }
    feed.prototype = {
    constructor: feed,
    b: 'www',
    };
    console.log(feed.prototype.constructor); // 输出 ƒunction feed() { this.a = 'hello'; }
    var f = new feed();
    for(var key in f) {
    console.log(key); // 依次输出 a、constructor、b
    }
  • 左值和右值

    按字面意思,通俗地说,以赋值符号=为界,=左边的就是左值,=右边就是右值。更深一层,可以将L-value的L,理解成Location,表示定位、地址。将R-value的R理解成 Read,表示读取数据。现在的计算机数据放在内存。内存有两个很基本的属性:内存地址和内存里面放的数据。想象完全一样的箱子。每个箱子有个编号,用来区分到底是哪个箱子,箱子里面可以放东西。内存地址相当于箱子的编号,内存的数据,相当于箱子里面放的东西。变量名编译之后,会映射成内存地址。操作a=b的含义,其实就是将”b地址内存里面的数据”,放到”a地址内存”中。

    可以放在赋值符号左边的变量,即具有对应的可以由用户访问的存储单元,并且能够由用户去改变其值的量。左值表示存储在计算机内存的对象,而不是常量或计算的结果。或者说左值是代表一个内存地址值,并且通过这个内存地址,就可以对内存进行读并且写(主要是能写)操作;这也就是为什么左值可以被赋值的原因了。相对应的还有右值:当一个符号或者常量放在操作符右边的时候,计算机就读取他们的“右值”,也就是其代表的真实值。简单来说就是,左值相当于地址值,右值相当于数据值。右值指的是引用了一个存储在某个内存地址里的数据。

    1
    2
    3
    4
    1 = 2; // Uncaught ReferenceError: Invalid left-hand side in assignment
    const a; // Uncaught SyntaxError: Missing initializer in const declaration
    const b = 1;
    b = 2; // Uncaught TypeError: Assignment to constant variable
  • undefined

    undefined 表示一个未声明的变量,或已声明但没有赋值的变量,或一个并不存在的对象属性,函数没有返回值时,默认返回undefined。这是undefined的几种典型用法,而判断一个变量是不是undefined,用typeof函数,typeof函数主要是返回的是字符串,主要有:”number”、”string”、”boolean”、”object”、”function”、”undefined”。注意对于未声明的变量,typeof 也会返回undefined。

    null 是一个空的对象引用,undefined是声明但没有被赋值的变量,利用这两个就可以区分空对象指针和未经初始化的变量。

    1
    2
    console.log(null == undefined); // true
    console.log(null === undefined); // false
  • 实现super

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    Object.prototype.mysuper = function(){
    // 返回一个对函数的引用,该函数调用了当前函数,不能使用arguments.callee.name,arguments.callee.name的值为mysuper
    var caller = arguments.callee.caller;
    var name;
    for(var key in this){
    if(this[key] === caller){
    name = key;
    break;
    }
    }
    __proto = this.__proto__ || this.constructor.prototype;
    return __proto[name]();
    }
    function Class(){
    this.name = "class";
    this.setName = function(name){
    this.name = name;
    }
    this.getName = function(){
    return this.name;
    }
    this.method = function() {
    console.log("父类方法");
    };
    }
    function Test(){
    this.getName = function(){
    return 'sub-' + this.mysuper();
    }
    this.method = function() {
    this.mysuper();
    }
    }
    // 实现继承
    Test.prototype = new Class();
    Test.prototype.constructor = Test;
    var a = new Test();
    console.log(a.getName()); // sub-class
    a.method(); // 父类方法
  • 如何使用CSS实现如图效果,其中矩形:width=200px height=100px,三角形:底为40px 高为50px,并垂直居中于矩形右侧。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style>
    * {
    margin: 0;
    padding: 0;
    }
    .rec {
    width: 200px;
    height: 100px;
    background-color: #000;
    }
    /* 第一个 */
    .main1 {
    position: relative;
    }
    #triangle-right1 {
    width: 0;
    height: 0;
    border-top: 20px solid transparent;
    border-left: 50px solid red;
    border-bottom: 20px solid transparent;
    position: absolute;
    top: 50%;
    left: 200px;
    margin-top: -20px;
    }
    /* 第二个 */
    .main2 {
    margin-top: 10px;
    display: -webkit-flex;
    display: -o-flex; // 欧鹏浏览器
    display: -ms-flex; // ie
    display: -moz-flex; //火狐
    display: flex;
    align-items: center; // 侧轴,垂直方向
    }
    #triangle-right2 {
    width: 0;
    height: 0;
    border-top: 20px solid transparent;
    border-left: 50px solid red;
    border-bottom: 20px solid transparent;
    }
    </style>
    </head>
    <body>
    <div class="main1">
    <div class="rec"></div>
    <div id="triangle-right1"></div>
    </div>
    <div class="main2">
    <div class="rec"></div>
    <div id="triangle-right2"></div>
    </div>
    </body>
    </html>

React-基础知识

发表于 2017-08-26   |   分类于 FJ
  • react的优缺点

    优点:

    虚拟dom加diff算法减少dom操作来提高渲染性能
    虚拟dom也帮助我们解决了跨浏览器问题
    使用声明式语法JSX来重新定义视图开发
    代码更加模块化,重用代码更容易
    单向数据流,灵活、可预计、可控制
    将DOM操作抽象为状态的改变
    只从this.props和this.state生成HTML,非常的函数式编程
    能够快速上手react-native开发ios或者android项目

    缺点:

    本身只是View,如果是大型项目基本都需要加上React Router和Redux
    大多数坑没踩出来

  • AMD(Asynchronous Module Definition)、CMD(Common Module Definition)、CommonJS

    AMD、CMD、CommonJS是JS模块化开发的标准,CommonJS是用在服务器端的,同步的,如Node.js。AMD、CMD是用在浏览器端的,异步的,如RequireJS和SeaJS。其中,AMD先提出,CMD是根据CommonJS和AMD基础上提出的。

    CommonJS 加载模块是同步的,所以只有加载完成才能执行后面的操作。像Node.js主要用于服务器的编程,加载的模块文件一般都已经存在本地硬盘,所以加载起来比较快,不用考虑异步加载的方式,所以CommonJS规范比较适用。但如果是浏览器环境,要从服务器加载模块,这是就必须采用异步模式,所以就有了AMD和CMD解决方案。

    AMD和CMD的区别有以下几点:

> **AMD推崇依赖前置,CMD推崇依赖就近。AMD和CMD最大的区别是对依赖模块的执行时机处理不同** 
> 对于依赖的模块AMD是提前执行,CMD是延迟执行。不过RequireJS从2.0开始,也改成可以延迟执行。 
> AMD的api默认是一个当多个用,CMD严格的区分推崇职责单一。例如:AMD里require分全局的和局部的。CMD里面没有全局的require,提供seajs.use()来实现模块系统的加载启动。CMD里每个API都简单纯粹。 

UMD(Universal Module Definition 通用模块规范),因为AMD,CommonJS规范是两种不一致的规范,虽然他们应用的场景也不太一致,但是人们仍然是期望有一种统一的规范来支持这两种规范。于是,UMD规范诞生了。
  • 为什么不能改变原有state,需要使用setState并传入修改的部分state,执行setState后怎么更新

    是否调用render进行再次渲染是由state是否变化决定的。setState本质是通过一个队列机制实现state更新的,执行setState时,会将要更新的state合并后放入状态队列,而不会立刻更新state,队列机制可以批量更新state。如果不通过setState而直接修改this.state,那么这次state的变化不会放入状态队列中,下次调用setState再进行状态队列进行合并时,会忽略之前直接直接对state修改,这样就无法合并到这次变化,实际也就没有将想要的state更新上去。setState是异步的。

    只要调用了setState,就会在合并后生成一个新的state,只要判断state指向的是否是同一个内存地址就知道state是否发生了变化,以此来决定是否进行render重新渲染。如果不用这种方式都直接操作this.state,也就是同一块内存,要侦测state是否发生变化(如angular中的deepwatch)来决定是否重新渲染会非常复杂和低效。redux的reducer中返回新的对象也是同理。react状态管理库还有redux-sagas、redux-observable、MobX等。

  • 为什么组件中定义的方法要bind(this)

    ES5里,function内部的this默认为window,在严格模式下,this为undefined。ES5的写法React.createClass会自动绑定每个方法的this到当前组件实例,ES6的写法class XXX extends Component则不会自动为方法绑定this。

    bind()方法会创建一个新函数,当这个新函数被调用时,它的this值是传递给bind()的第一个参数,它的参数是bind()的其它参数和原本参数,这个函数不论怎么调用都有同样的this,也就是当前类的实例,否则this为undefined。绑定this的几种方式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 方式一
    <div onClick={this.handleClick.bind(this)}>save</div>
    // 方式二
    constructor() {
    ...
    this.handleClick = this.handleClick.bind(this);
    }
    // 方式三
    <div onClick={() => this.handleClick()}>save</div>
    // 方式四
    handleClik = () => {}
    <div onClick={this.handleClick}>save</div>
    // 方式五
    ES6 Decorator,core-decorator.js提供了@autobind修饰器

    方式一会在每次点击时通过bind创建一个新方法,一般使用方式二、方式三或方式四,其中方式三的箭头函数总是匿名的,如果打算移除监听事件可使用方式四。方式二在构造函数里绑定this后方法是属于实例的,而定义在类里的是非静态函数,在类的prototype上,实例的__proto__原型上。方式四使用箭头函数定义的方法也是这个效果。

  • 数据获取为什么一定要在componentDidMount里面调用

    不建议在constructor和componentWillMount里写的原因是:会阻碍组件的实例化,阻碍组件的渲染;在componentWillMount里面触发setState不会重新渲染,在componentDidMount里面会触发setState会重新渲染,有副作用的代码都会集中在componentDidMount方法里。

  • webpack 打包常用

    webpack.optimize.CommonsChunkPlugin 分离应用和第三方

    webpack.optimize.UglifyJsPlugin 清除打包后文件中的注释和copyright

    webpack.optimize.DedupePlugin 保证重复的代码不被打包到bundle文件

    extract-text-webpack-plugin 独立打包样式

    optimize-css-assets-webpack-plugin css去重

    html-webpack-plugin 根据模板创建html入口文件,为引入的外部资源如script、link动态添加每次compile后的hash,防止引用缓存的外部文件问题

    require.ensure 代码分割,可跟react-router的getComponent配合使用

    常用loader:style-loader、css-loader、less-loader、url-loader、file-loader、babel-loader、react-hot

  • redux-thunk源码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // ES6 写法
    function createThunkMiddleware(extraArgument) {
    return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
    return action(dispatch, getState, extraArgument);
    }
    return next(action);
    };
    }
    // ES5写法
    function createThunkMiddleware(extraArgument) {
    return function (_ref) {
    var dispatch = _ref.dispatch,
    getState = _ref.getState;
    return function (next) {
    return function (action) {
    if (typeof action === 'function') {
    return action(dispatch, getState, extraArgument);
    }
    return next(action);
    };
    };
    };
    }
  • web端路由react-router和react-native的navigator有什么区别

    纯属个人理解:web端路由是保证url和UI一致的,进行的是局部刷新;react-native的Navigator是管理导航堆栈的,让用户清楚的知道自己当前所处的页面和返回的页面,进行的是整屏刷新。

  • Vue,React,Angular

    Vue。轻量级框架,双向数据绑定,语法和原理所需要的学习成本不是很高。具有高覆盖率的文档,绝大部分操作已被记录在案。但是如果一个操作没有文档记录,能在线上找到解决方案的几率也很小,因为Vue不如Angular或React更流行。Vue也有被称为Flux架构实现的Vuex,移动端跨平台方案Weex使用Vue的语法,但是Weex并不太完善。

    React。React通过使用VirtualDOM获得高效,单向数据流,可以使用JSX语法。与框架相关的Redux是一个非常棒的类Flux架构的实现,掌握React的知识后,可以直接上手基于ReactNative的移动客户端开发。随着React将会有一个能够向后兼容的重写版React Fiber的消息放出,React的”第二春”很快就会到来。

    AngularJS。以HTML为中心,适配和扩展了传统的HTML来呈现动态内容,双向数据绑定,自定义指令,是一个比较完善的前端框架,适合大型项目。入门很容易,但框架复杂、深入后概念很多,学习中较难理解,文档例子非常少。使用脏检查,当watcher越来越多时会变得越来越慢。

Emmet-基础

发表于 2017-07-10   |   分类于 HTML

前两天参加JavaScript培训,老师在写HTML的时候提到Zen Coding,我之前没有听到过这个东西,但写起来感觉跟Emmet一样,回来特意查了一下。

简介

Zen Coding 改名为 Emmet,是一种快速编写HTML/CSS代码的方法。输入缩写代码,光标移到末尾,按扩展键(Tab键),即可得到代码片段。

HTML基本用法

! —- 生成html页面结构如下。

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
</body>
</html>

元素名如div —- 生成元素

1
<div></div>

#如div#section1 —- 指定元素id

1
<div id="section1"></div>

.如div.className1 —- 元素类名

1
<div class="className1"></div>

[]如a[alt title=name] —- 指定元素属性,用空格分隔多个属性,属性值用=赋值

1
<a href="" alt="" title="name"></a>

{}如a{click me} —- 文本

1
<a href="">click me</a>

$如 —- 自增符号,@-表示倒序,@指定开始序号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!-- ul>li.item$*3 -->
<ul>
<li class="item1"></li>
<li class="item2"></li>
<li class="item3"></li>
</ul>
<!-- ul>li.item$@3*3 -->
<ul>
<li class="item3"></li>
<li class="item4"></li>
<li class="item5"></li>
</ul>
<!-- ul>li.item$@-*3 -->
<ul>
<li class="item3"></li>
<li class="item2"></li>
<li class="item1"></li>
</ul>
<!-- ul>li.item$@-3*3 -->
<ul>
<li class="item5"></li>
<li class="item4"></li>
<li class="item3"></li>
</ul>

>如ul>li — 子元素

1
2
3
<ul>
<li></li>
</ul>

*如li*3 —- 指定个数的元素

1
2
3
<li></li>
<li></li>
<li></li>

+如div+div —- 兄弟元素

1
2
<div></div>
<div></div>

^如div+div>p>span+em^bq —- 上级元素,多个表示向上几级

1
2
3
4
5
<div></div>
<div>
<p><span></span><em></em></p>
<blockquote></blockquote>
</div>

()如div>(header>ul>li*3)+section+footer —- 群组

1
2
3
4
5
6
7
8
9
10
11
<div>
<header>
<ul>
<li></li>
<li></li>
<li></li>
</ul>
</header>
<section></section>
<footer></footer>
</div>

CSS基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
div {
<!-- m:20 -->
margin: 20px;
<!-- mt:20 mb、ml、mr同理 -->
margin-top: 20px;
<!-- m:20-30 -->
margin: 20px 30px;
<!-- pos:a -->
position: absolute;
<!-- fl:l -->
float: left;
<!-- bd+ -->
border: 1px solid #000;
<!-- bdrs:3 -->
border-radius: 3px;
<!-- -wmso-bdrs:3 -->
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
border-radius: 3px;
}

JavaScript-基础2

发表于 2017-07-05   |   分类于 JavaScript

最近参加了一次为时两天的 JavaScript 培训,回顾课程内容并进行整理,其中参考了如下两篇老师的文章。

JavaScript 编程技法入门
JS 编程道场

简介

函数是第一等公民。
JavaScript 的核心是 函数 和 对象 。

1
2
3
typeof Number // 'function'
typeof String // 'function'
typeof Function // 'function'

简化版 jquery 选择器实现

通过对 javascript 原生操作的封装,实现简化版的 jquery 类库。这里的代码并不复杂,个人感觉主要是要有类库的概念和实现思路,考虑将项目中使用的工具类逐步封装成类库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var $ = function(selector) {
var domObj = document.querySelector(selector);
var result = {
'0': domObj,
show: function() { domObj.style.display = 'none'; },
hide: function() { domObj.style.display = 'block'; },
append: function(str) {
domObj.innerHTML += str;
},
val: function(value) {
if (value === undefined) {
return domObj.value;
} else {
domObj.value = value;
}
},
on: function(action, callback) {
// 特定浏览器,这里是针对Chrome的写法
if (domObj.addEventListener) {
domObj.addEventListener(action, callback);
}
}
};
return result;
}

对象

js 是解释型动态语言,可随时添加属性或方法,很灵活。

1
2
3
4
5
var obj1 = new Object();
obj1.name = 'object1';
obj1.getName = function() {
return this.name;
};

推荐使用如下字面量形式的定义方式。

1
2
3
4
5
6
var obj2 = {
name: 'object2',
getName: function() {
return this.name;
}
};

遍历对象的属性和方法。

1
2
3
for(var key in obj2) {
console.log(key + '=' + obj2[key]);
}

函数

函数的三种定义方式如下:

1
2
3
4
5
6
7
8
9
10
// 匿名函数,通过一个变量来指向一个匿名函数,当这个函数不再需要时,JS的运行时环境会自动收集内存垃圾
var func1 = function(name) {
return 'hello,' + name;
};
// 具名函数,有变量提升,也就是在这段代码的前面是可以调用func2方法的
function func2 (name) {
return 'hello,' + name;
};
// 函数对象,这也表明了函数其实是Function类型的对象
var func3 = new Function('name', 'return "hello," + name');

函数体的内部只有在函数被调用时才会进入,下面 demo 的运行结果是先后输出 1、2、3、4、5。使用 Chrome 调试断点进入时,打开 Sources 注意观察 Scope 的变化,可以看到运行到 func1()时 Scope 只有 Globle,当进入到调用的方法时,会有 Local,里面是函数内部定义的变量或方法,当函数内部用到了外面的变量时,会有 Closure,里面是引用的外部变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
console.log(1);
var func1 = function() {// 进来之后有Local
console.log(3);
var a = 0;
function func2() { // 进来之后有Closure
console.log(5);
var f = a + 5;
}
console.log(4);
func2();
}
console.log(2);
debugger;
func1(); // 这时只有Global

作用域

this 取决于在哪调用,而不是在哪定义,通过“对象.”调用的,this 都是这个对象,直接调用的 this 都是 window。

1
2
3
4
5
6
7
8
9
10
11
var func = function(name) {
this.name = name;
};
func('jobs'); // 进入func,this是window
var o = {
setName: func
};
o.setName('jobs'); // 进入func,this是o
var method = o.setName;
method('jobs'); // 进入func,this是window
var obj = new func('jobs');// 进入func,this是obj

通过函数的 call、apply、bind 方法进行调用可改变作用域,call 和 apply 只是传递参数的方式不同。

1
2
3
4
var o = {};
func.call(o, 'jobs'); // 绑定作用域为o
func.apply(o, ['jobs']);
func.bind(o)('jobs');

模拟类

函数名的第一个字母大写,表示是用来构造对象的,不是用来执行的。

用方式一模拟类,o1 和 o2 有自己的 name 属性,但第三个输出结果为 false,这说明 o1.sayHello 和 o2.sayHello 指向不同内存,这样存在内存的浪费。

1
2
3
4
5
6
7
8
9
10
11
// 方式一
var Person = function(name) {
this.name = name;
this.sayHello = function() {};
};
var o1 = {}, o2 = {};
Person.call(o1, 'jobs');
Person.call(o2, 'gates');
console.log(o1.name); // 输出jobs
console.log(o2.name); // 输出gates
console.log(o1.sayHello === o2.sayHello); // 输出false

用方式一模拟类,第三个输出结果为 true,这时不存在内存浪费了,但破坏了封装,在外部也可以调用 sayHello 方法。

1
2
3
4
5
6
7
8
9
10
11
12
// 方式二
function sayHello() {}
var Person = function(name) {
this.name = name;
this.sayHello = sayHello;
};
var o1 = {}, o2 = {};
Person.call(o1, 'jobs');
Person.call(o2, 'gates');
console.log(o1.name); // 输出jobs
console.log(o2.name); // 输出gates
console.log(o1.sayHello === o2.sayHello); // 输出true

用方式三模拟类,通过即时函数封装作用域后,可以比较好的模拟类了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 方式三
var Person = function() {
// private,在外部无法直接调用
function sayHello() {}
var Person = function(name) {
this.name = name;
this.sayHello = sayHello;
};
// public
return Person;
}(); // 即时函数,封装作用域
var o1 = {}, o2 = {};
Person.call(o1, 'jobs');
Person.call(o2, 'gates');
console.log(o1.sayHello === o2.sayHello); // 输出true

用方式四模拟类,在即时函数封装作用域的基础上,又添加了命名空间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 方式四
// 模仿命名空间,可封装类库给别人使用
var cplh = cplh || {};
// 模仿封装
(function(g) {
// 模仿private,没有加到Person上
function sayHello() {}
var Person = function(name) {
this.name = name;
this.sayHello = sayHello;
};
// 模仿public
g.Person = Person;
})(cplh); // 加上括号来说明这是一个表达式
// 模仿初始化
var o1 = {}, o2 = {};
cplh.Person.call(o1, 'jobs');
cplh.Person.call(o2, 'gates');
console.log(o1.sayHello === o2.sayHello); // 输出true

原型

所有对象都有原型对象__proto__,只有函数对象有原型属性prototype,当这个函数被用作构造函数来创建实例时,该函数的原型属性(prototype)将被作为原型赋值给所有对象实例,也就达到了重用方法的作用,即所有实例的原型引用的是函数的原型属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var i = 5;
var str = 'abc';
var Person = function() {};
var person = new Person();
// 所有对象都有原型对象
i.__proto__ // 有 [Number: 0]
str.__proto__ // 有 [String: '']
Person.__proto__ // 有 [Function]
// 只有函数对象有原型属性
i.prototype // undefined
str.prototype // undefined
Person.prototype // 有 [Object]
// 所有实例的原型引用的是函数的原型属性
console.log(i.__proto__ === Number.prototype) // 输出true
console.log(str.__proto__ === String.prototype) // 输出true
console.log(person.__proto__ === Person.prototype) // 输出true

在执行完 var Person = function() {}; 语句时,会分配三块内存,Person 变量的内存、function 函数的内存、protptype 指向对象的内存,这个对象的 constructor 又指向 Person 指向的函数。

1
2
3
4
var Person = function() {};
console.log(Person);
console.log(Person.prototype); // 输出一个Object,有constructor属性
console.log(Person.prototype.constructor === Person); // true

JavaScript 是动态语言,取决于运行时,不是定义时,因此下面的代码中示例一的对象 o 是有 sayHello 方法的。通过如示例二、示例三、示例四的方式给 Function、String、jquery 等全局函数的 prototype 添加方法来实现共有的新功能。

1
2
3
4
5
6
// 示例一
var o = {};
o.__proto__ = Person.prototype;
console.log(o instanceof Person); // 输出 true
Person.prototype.sayHello = function() {};
console.log(o.sayHello); // 输出 function() {}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 示例二
Function.prototype.method = function(name,fn) {
this.prototype[name] = fn;
return this;
};
function Person (name) {
this.name = name;
};
Person.method('sayHello',function() {
return 'Hello,' + this.name;
}).method('show',function() {
return 'show';
});
var jobs = new Person('jobs');
console.log('Hello,jobs' === jobs.sayHello()); // 输出 true
console.log('show' === jobs.show()); // 输出 true
1
2
3
4
5
6
7
8
9
10
// 示例三
String.prototype.fill = function(id){
// var sql = "select * from " + this;
$.getJSON('/' + this, null functon(json, textStatus){
var data = JSON.parse(json);
$(id).append(data);
});
};
// 给id为wellTable01的元素添加从wells表中或接口中查出的数据
"wells".fill('#wellTable01');
1
2
3
4
5
6
7
// 示例四
(function(){
var myMethod = function() {
console.log('自定义方法');
};
$.prototype.myMethod = myMethod;
})();

实现类

利用原型实现类,注意下面方式一到方式四的区别,方式一到方式三的 o 对象已经有了 sayHello 方法,达到了重用方法的目的,但使用了__proto__,理论上 JavaScript 中是不让用的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 方式一
// 构造函数
var Person = function(name) {
this.name = name;
};
// 方法体
var proto = {
sayHello: function(){}
};
// 创建对象
var o = {};
// 赋值方法
o.__proto__ = proto;
// 初始化
Person.call(o, 'jobs');
console.log(o.sayHello);

方式二只是将 proto 作为 Person 的属性使用的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 方式二
// 构造函数
var Person = function(name) {
this.name = name;
};
// 方法体
Person.proto = {
sayHello: function(){}
};
// 创建对象
var o = {};
// 赋值方法
o.__proto__ = Person.proto;
// 初始化
Person.call(o, 'jobs');
console.log(o.sayHello);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 方式三
// 构造函数
var Person = function(name) {
this.name = name;
};
// 方法体
Person.prototype = {
sayHello: function(){}
};
// 创建对象
var o = {};
// 赋值方法
o.__proto__ = Person.prototype;
// 初始化
Person.call(o, 'jobs');
console.log(o.sayHello);
1
2
3
4
5
6
7
8
9
10
11
// 方式四
// 构造函数
var Person = function(name) {
this.name = name;
};
// 方法体
Person.prototype = {
sayHello: function(){}
};
var o = new Person('jobs');
console.log(Person.prototype.sayHello);

方式四使用 new 创建的对象,也就是说函数 Person 被用作构造函数来创建实例了,这时该函数 prototype 将被作为原型__proto__赋值给对象的实例,也就相当于如下代码。注意当函数名的第一个字母大写时,表示是用来构造对象的,不是用来执行的,该函数的 prototype 是给构造出来的对象用的,函数自己用不了。

1
2
3
4
5
6
7
// var o = new Person('jobs'); 等同于以下代码
// 创建对象
var o = {};
// 赋值方法
o.__proto__ = Person.prototype;
// 初始化
Person.call(o, 'jobs');

当定义一个当函数被用作构造函时,这个函数会默认在函数体开头和结尾添加如下代码,如果函数 return 的不是对象,在使用 new 实例化对象时这个 return 语句会被忽略,因为需要返回一个对象。

1
2
3
4
5
6
7
8
9
10
function Class() {
// 默认添加
var this = {};
...
// 会被忽略,因为需要返回对象
return 1;
...
// 默认添加
return this;
}

实现继承

封装自己的框架,要有类、方法、复用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
function Class(options) {
var Klass = function() {
// 如果Class有初始化
if(typeof this.initialize === 'function'){
this.initialize.apply(this, arguments);
}
};
for(var attr in options) {
Klass.prototype[attr] = options[attr];
}
Klass.extends = function(proto) {
// 定义子类
var Child = function() {
Klass.apply(this, arguments);
};
// var f = {};
// f.__proto__ = this.prototype;
// // 相当于上面的,不能使用__proto__
// var f = new Klass();
// Child.prototype = f;
// 继承基类
// 不能使用 Child.prototype = this.prototype; 这样extends添加的方法会影响到基类
var F = function() {
/*
这是定义F函数的原因,如果不显示定义constructor的指向,constructor将指向Object,通过constructor已经无法确定对象的类型了。
这样显示定义constructor属性的在实例中是可枚举的。
*/
this.constructor = Child;
};
F.prototype = this.prototype;// 继承
var f = new F();
Child.prototype = f;
// 类的实例方法
for(var attr2 in proto) {
Child.prototype[attr2] = proto[attr2];
}
return Child;
};
return Klass;
};
// 方式一
var Person1 = new Class();// 函数对象可以执行
var jobs1 = new Person();
// 方式二
var Person2 = new Class({
show: function() {}
});
var jobs2 = new Person();
// 方式三
var Person3 = new Class({
show: function() {}
});
var Actor = Person3.extends({
sayHello: function(){}
});
var jobs3 = new Actor();

封装商业框架,有 Model 和 View。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
var Adobe = (function() {
var Model = function() {};
// 实例方法
Model.prototype = {
constructor: Model,
get: function() {},
set: function() {}
}
// 绑定作用域,在context作用域上执行func函数
function proxy (func, context) {
return function() {
func.apply(context, arguments);
};
}
var View = function () {
//Hack: 绑定事件,这段代码不明白
var events = this.events || {};
for(var item in events){
var func = proxy(this[events[item]], this);
var reg = /(.+)\s+(.+)/;
var result = reg.exec(item);
$(result[2]).bind(result[1], func);
}
};
View.extend = Model.extend = function(options) {
var Parent = this;
// 构造函数
var Klass = function() {
if(typeof this.initialize === 'function'){
this.initialize.apply(this, arguments);
}
Parent.apply(this, arguments);
};
// 继承
var F = function() {
this.constructor = Klass;
};
F.prototype = this.prototype;
Klass.prototype = new F();
Klass.prototype.__super__ = this.prototype;
// 扩展
for(var name in options){
Klass.prototype[name] = options[name];
}
return Klass;
};
return {
Model: Model,
View: View
}
})();
// 使用代码
var Todo = Adobe.Model.extend({
initialize: function(title) {
if(typeof title === 'string'){
this.set({title: title});
}
}
});
var todo = new Todo('Test');
console.log(typeof todo.get === 'function');
console.log(typeof todo.set === 'function');
console.log(typeof todo.initialize === 'function');

练习

1
2
3
4
5
6
7
8
9
10
11
function fun() {}
var f = new fun();
f.__proto__; // fun.prototype
f.__proto__.__proto__; // Object.prototype
f.__proto__.__proto__.__proto__; // null
f.__proto__.__proto__.__proto__.__proto__; // 报错
fun.__proto__; // Function.prototype
fun.__proto__.__proto__; // Object.prototype
f.prototype; // undefined
fun.prototype; // { constructor: fun }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Demo {
a = 1;
m1() {}
m2 = () => {};
static s2 = 2;
}
// 等同写法
function Demo() {
this.a = 1;
this.m2 = () => {};
}
Demo.prototype.m1 = function () {};
Demo.s2 = 2;

React-Native-BackAndroid

发表于 2017-07-03   |   分类于 React Native

BackAndroid 物理返回键的使用

在android上,点击物理返回键时,通常会进行返回上个页面的操作,如果已经回到主页、没有上一级页面,则会弹出是否退出应用的提示。在RN中,对物理返回键的监听尽量放在最上层的组件中,示例代码如下。如果哪个页面单独定义了点击返会的操作,如给出提示是否保存当前页面,可在这个组件中再进行物理返回键的监听,注意最后要return true,这样就不会进入到全局的监听方法了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import {
...
Platform,
BackAndroid,
} from 'react-native';
class App extends Component {
constructor(props){
super(props);
}
componentWillMount() {
if (Platform.OS === 'android') {
BackAndroid.addEventListener('hardwareBackPress', this.onBackAndroid);
}
}
componentWillUnmount() {
if (Platform.OS === 'android') {
BackAndroid.removeEventListener('hardwareBackPress', this.onBackAndroid);
}
}
onBackAndroid = () => {
const navigator = this.refs.navigator;
const routers = navigator.getCurrentRoutes();
if (routers.length > 1) {
navigator.pop(); // 没到主页,直接弹出到上一个页面
return true; // 接管默认行为
} else {
// 到了主页了,最近2秒内按过back键,退出应用;否则弹出提示
if (this.lastBackPressed && this.lastBackPressed + 2000 >= Date.now()) {
return false;
}
this.lastBackPressed = Date.now();
Toast.info('再按一次退出应用');
return true;
}
}
render () {
return (
<Navigator
ref="navigator"
initialRoute={initialRoute}
shadowHidden={true}
translucent={true}
configureScene={this.configureScene}
renderScene={this.renderScene}
/>
);
}
}

React-Diff算法

发表于 2017-06-28   |   分类于 React

Diff算法

React.js 相对于直接操作原生DOM有很大的性能优势,很大程度上都要归功于virtual DOM的batching和diff。batching把所有的DOM操作搜集起来,一次性提交给真实的DOM。diff算法时间复杂度也从标准的的Diff算法的O(n^3)降到了O(n)。React.js将标准的的Diff算法复杂度O(n^3)直接降低到O(n)基于的假设:

  • 两个相同组件产生类似的DOM结构,不同的组件产生不同的DOM结构
  • 对于同一层次的一组子节点,它们可以通过唯一的id进行区分

在比较两个React节点树时,React首先比较根元素,随后的行为取决于根元素类型:

  • 只要根节点类型不同,React就会销毁旧节点树,创建新节点树
  • 当节点类型相同时,React比较两者的属性,不改变DOM节点,只更新当前节点需要改变的属性
  • 如果节点需要更新样式属性,React仅会更新当前节点需要改变的样式属性

React的Diff算法实际上只会对树进行逐层比较。在实现自己的组件时,保持稳定的DOM结构会有助于性能的提升。例如,我们有时可以通过CSS隐藏或显示某些节点,而不是真的移除或添加DOM节点。

key属性在diff算法中的作用

使用浏览器的MutationObserver的功能,对页面上元素的改变进行监听,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="react.js"></script>
<script src="react-dom.js"></script>
<script src="jquery.js"></script>
<script src="babel.min.js"></script>
</head>
<body>
<div id="container"></div>
<style>
</style>
<script type="text/javascript">
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
var observeMutationSupport = !!MutationObserver;
var changedNodes = [];
if(observeMutationSupport) {
var observer = new MutationObserver(function(mutations) {
changedNodes = mutations;
mutations.forEach(function(mutation){
console.log(mutation);
});
});
var options = {
'childList': true, // 子节点的变动
'attributes': true, // 属性的变动
'characterData': true, // 节点内容或节点文本的变动
'subtree': true, // 所有后代节点的变动
'attributeOldValue': true, // 表示观察attributes变动时,是否需要记录变动前的属性值
'characterDataOldValue': true // 表示观察characterData变动时,是否需要记录变动前的值
};
observer.observe(document.body, options);
}
</script>
<script type="text/babel">
function LifeCycle(name) {
var obj = {
name: name
};
for(var n in Cycle){
obj[n] = Cycle[n];
}
return obj;
}
var Cycle = {
getInitialState: function() {
console.log(this.name, 'getInitialState');
return {};
},
getDefaultProps: function() {
console.log(this.name, 'getDefaultProps');
return {};
},
componentWillMount: function() {
console.log(this.name, 'componentWillMount');
},
componentDidMount: function() {
console.log(this.name, 'componentDidMount');
},
componentWillReceiveProps: function() {
console.log(this.name, 'componentWillReceiveProps');
},
shouldComponentUpdate: function() {
console.log(this.name, 'shouldComponentUpdate');
return true;
},
componentWillUpdate: function() {
console.log(this.name, 'componentWillUpdate');
},
componentDidUpdate: function() {
console.log(this.name, 'componentDidUpdate');
},
componentWillUnmount: function() {
console.log(this.name, 'componentWillUnmount');
}
};
// 定义Ctrip组件
var Ctrip = React.createClass({
mixins: [LifeCycle('Ctrip')],
render: function() {
console.log('Ctrip', 'render');
return (
<ul>
<li>ctrip</li>
<li>
<ul>
{this.props.children}
</ul>
</li>
</ul>
);
}
});
// 定义Elong组件
var Elong = React.createClass({
mixins: [LifeCycle('Elong')],
render: function() {
console.log('Elong', 'render');
return (
<li>elong</li>
);
}
});
// 定义Qunar组件
var Qunar = React.createClass({
mixins: [LifeCycle('Qunar')],
render: function() {
console.log('Qunar', 'render');
return (
<li>qunar</li>
);
}
});
// 定义Ly组件
var Ly = React.createClass({
mixins: [LifeCycle('Ly')],
render: function () {
console.log('Ly', 'render');
return (
<li>ly</li>
);
}
});
console.log('------------first------------');
ReactDOM.render(
<Ctrip><Elong></Elong><Qunar></Qunar></Ctrip>,
document.getElementById('container')
);
setTimeout(function() {
console.log('------------second------------');
ReactDOM.render(
<Ctrip><Elong></Elong><Ly></Ly><Qunar></Qunar></Ctrip>,
document.getElementById('container')
);
}, 1000);
</script>
</body>
</html>

常规的做法就是将Elong和Qunar组件先删除,然后一次创建和插入Elong、Ly和Qunar组件。通过运行结果可以看出,在React中,Elong组件不变,先将Qunar组件进行删除,然后在创建并插入Ly组件,最后再创建并插入Qunar组件,比常规的做法省去了对Elong组件的删除操作。下面再将主逻辑代码稍作调整,给每个组件添加key属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
console.log('------------first------------');
ReactDOM.render(
<Ctrip><Elong key="Elong"></Elong><Qunar key="Qunar"></Qunar></Ctrip>,
document.getElementById('container')
);
setTimeout(function() {
console.log('------------second------------');
ReactDOM.render(
<Ctrip><Elong key="Elong"></Elong><Ly key="Ly"></Ly><Qunar key="Qunar"></Qunar></Ctrip>,
document.getElementById('container')
);
}, 1000);
...

这次的Diff算法与之前有很大不同,Elong组件不变,Qunar组件不变,只是在Qunar组件之前创建并插入了Ly组件。可见使用key属性可提高渲染性能。

Redux-saga-基础(未完成)

发表于 2017-06-20   |   分类于 Redux

参考
Redux-saga 中文文档
redux-saga
redux-saga-beginner-tutorial 有点简单
redux-saga-chat-example 有点复杂
redux-saga-examples 有好几个例子

Redux-saga简介

redux-saga 是一个用于管理 Redux 应用异步操作的中间件。Sagas 是通过 Generator 函数来创建的,因为使用了 Generator,redux-saga 让你可以用同步的方式写异步代码。

Sagas 不同于 Thunks,Thunks 是在 action 被创建时调用,而 Sagas 只会在应用启动时调用(但初始启动的 Sagas 可能会动态调用其他 Sagas)。 Sagas 可以被看作是在后台运行的进程。Sagas 监听发起的 action,然后决定基于这个 action 来做什么:是发起一个异步调用(比如一个 Ajax 请求),还是发起其他的 action 到 Store,甚至是调用其他的 Sagas。

在 redux-saga 的世界里,所有的任务都通用 yield Effects 来完成(译注:Effect 可以看作是 redux-saga 的任务单元)。

基本概念

当 Saga 启动了(不管是初始启动或是稍后动态启动),middleware 会自动将它的 take / put 连接至 store。 这 2 个 Effect 可以被看作是一种 Saga 的输入/输出(Input/Output)。

call

call 创建了一条描述结果的信息,不立即执行异步调用,还有apply,像js的call和apply,也可以使用 call Effect 进行同步调用,call 不仅可以用来调用返回 Promise 的函数。我们也可以用它来调用其他 Generator 函数,同步的
call 和 apply 非常适合返回 Promise 结果的函数

cps

cps 可以用来处理 Node 风格的函数,表示的是延续传递风格

take

take 就像 call 和 put,它创建另一个命令对象,告诉 middleware 等待一个特定的 action,它将会暂停 Generator 直到一个匹配的 action 被发起了

fork

fork 任务会在后台启动,调用者也可以继续它自己的流程,而不用等待被 fork 的任务结束,异步的

cancel

如果任务仍在运行,它会被中止,如果任务已完成,那什么也不会发生。一旦任务被 fork,可以使用 yield cancel(task) 来中止任务执行。取消正在运行的任务,将抛出 SagaCancellationException 错误。未被捕获的 SagaCancellationException 不会向上冒泡。yield cancel(task) 不会等待被取消的任务完成(即执行其 catch 区块)。
/
const [users, repos] = yield [
call(fetch, ‘/users’),
call(fetch, ‘/repos’)
];
generator 会被阻塞直到所有的 effects 都执行完毕,或者当一个 effect 被拒绝 (就像 Promise.all 的行为)
/

race

race 同时启动多个任务,但又不想等待所有任务完成,只希望拿到胜利者,即第一个被 resolve(或 reject)的任务;它会自动取消那些失败的 Effects
yield call(delay, 1000)

put

put 这个函数用于创建 dispatch Effect

使用 take 组织代码有一个小问题。在 takeEvery 的情况中,被调用的任务无法控制何时被调用, 它们将在每次 action 被匹配时一遍又一遍地被调用。并且它们也无法控制何时停止监听。
而在 take 的情况中,控制恰恰相反。与 action 被 推向(pushed) 任务处理函数不同,Saga 是自己主动 拉取(pulling) action 的。 看起来就像是 Saga 在执行一个普通的函数调用 action = getNextAction(),这个函数将在 action 被发起时 resolve。

takeEvery

takeEvery 在每个 action 来到时派生一个新的任务

takeLatest

isCancelError

使用

使用redux-saga写todo

React-Native-基础3

发表于 2017-06-17   |   分类于 React Native

已经用react-native开发过3个项目,经过这段时间的使用,对react-native总结如下。

优点

  • 个人感觉react-native的主要优点还是快速构建页面,无论是使用antd-mobile的组件库还是使用react-native官方提供的组件,都能相对快速的搭建页面,但是,无论使用哪种方式也都存在着同一个问题,就是样式问题,如果要完全按照UI设计做出一套页面,还是不太容易。
  • 个人感觉react-native在页面交互上还是比较方便的,可以通过组件、组件提供的方法进行事件的控制,如页面跳转、点击、关联刷新等,目前很少发现有不支持的事件。
  • 个人感觉,在android上开发的react-native项目,调试出一版ios的困难并不大,如果开发中使用的第三方依赖都是兼容ios的且不考虑ios适配问题,预计一周时间是可以调出一版兼容ios的。
  • 开源的第三方组件比较多,目前使用过的第三方依赖可参考React-Native-依赖,而且已经确认友盟、bugtags都可以支持react-native。

缺点

项目名称修改不方便

react-native项目名称修改不方便,个人认为目前就是不能修改项目名称。使用react-native-cli初始化的项目,android目录和ios目录中都有很多地方使用了项目名称,不是修改根目录文件夹的名称就能修改项目名称的,因此如果想使用已有的稳定项目生成新的项目,只能先初始化一个新的项目,再手动移代码,很不方便。

目前已经试验过针对android的项目名称修改的方法,参见React-Native-基础1中“项目名称修改”部分,但针对ios的项目名称修改还没有找到解决办法,因此还不敢使用这种方法。

不支持web端

直接使用react-native开发的项目,可以做到兼容android和ios,但不能运行在web端。react-native-web声称能将react-native开发的项目运行在web端,个人只试验过将直接初始化的项目运行在web端,但对于已经使用了很多第三方依赖的项目运行在web端报错了,想要将react-native开发的项目运行在web端不是那么容易的。京东是自己实现了从React Native向React JS进行转换的方案。

开发中常见错误

开发中常见两个错误,截图如下,目前只在开发环境有,打包后的生产环境暂时还没发现。开发环境下OOM错误非常频繁,查找react-native在github上的issues列表建议将使用图片的ListView换成FlatList。

多个异步请求 redux-saga

在业务逻辑比较复杂、需要多次网络请求及本地数据库操作的情况下,例如下面的业务,写出来的代码会很复杂,原因就是使用redux发起了多个异步action,除了图片上传,个人感觉应该是使用同步请求的逻辑会容易些,因为后面的处理是基于前一个请求结果进行的,但fetch没有同步请求方法,所以逻辑写起来就复杂些。目前已知的解决发起多个异步请求的方法有redux-sequence-action和redux-sage,redux-sequence-action在web端使用过,但感觉还是有些局限,redux-sage应该是比较好的解决方案,但目前还没研究过怎么使用。

“带有图片的本地数据进行web端提交后,无论提交是否成功,都需要进行本地保存;如果提交成功,根据请求返回结果删除本地业务数据、更新图片关系表,再将图片上传到web端,并根据图片上传成功的返回结果更新本地图片关系表。”

打包耗时

打包问题,就像react-native项目有时需要很多次启动才能启动成功(最多一次启动了6、7次),打包通常也是需要多次,几乎没有一次就能打包成功的时候,这时候代码不能修改,只能干等,后期希望引入持续集成解决这个问题,也能解决使用不同机器打的包可能不一致的问题。

其它问题

  • 样式不好调整,如果要完全按照UI设计做出一套页面,还是不太容易,而且目前还做过屏幕适配,不知道好不好处理
  • react-native版本升级有时变动较大,如0.44去掉了Navigator和BackAndroid,最主要的是许多第三方依赖对高版本rn支持的不好
  • react-native或其它组件可能存在手机适配问题,目前发现的是在工程机“VT5000”上,ListView的Item如果包装了滑动删除,几乎无法触发Item的onPress事件
  • 开发环境和打包后的生产环境可能不一致,具体原因还不清楚,怀疑是开发时打开浏览器调试能使用了一些浏览器的解析功能,导致离开浏览器的打包后的生产环境可能存在问题
  • 数据库操作使用的react-native-sqlite-storage,个人感觉还是不太好用,多个查询的嵌套看上去很乱,写起来也不方便
  • 本人没有做过原生开发,不了解更新升级,京东在GMTC会议演讲中提到更新升级需要依赖第三方框架,在我目前的项目中更新升级时本地库的处理还没有查找解决方案
  • Small等插件化框架是支持原生的,目前没有看到支持react-native的文档,有没有支持react-native插件化的框架还不清楚
  • 复杂场景下内存性能有瓶颈,页面跳转可能卡顿,有些偶发问题很难复现也不好定位原因,目前发现的是ListView中数据重复的问题,还没定位原因
  • react-native项目运行时有时需要android studio先编译,这就需要保持开发人员android studio环境的一致,有时候我运行项目没问题,但其它人运行就有问题,需要打开android studio看一下编译信息

React-Native-Android使用百度地图

发表于 2017-06-17   |   分类于 React Native

百度地图key值

  • http://developer.baidu.com/,注册开发者帐号
  • 进入百度地图->API控制台,创建应用,应用类型选择Android SDK,输入发布版SHA1和包名提交即可

显示地图及定位

使用react-native-baidu-map依赖。

  • NPM install

    1
    npm install react-native-baidu-map --save
  • android/setting.gradle:

    1
    2
    include ':react-native-baidu-map'
    project(':react-native-baidu-map').projectDir = new File(settingsDir, '../node_modules/react-native-baidu-map/android')
  • android/app/build.gradle:

    1
    compile project(':react-native-baidu-map')
  • MainApplication.java:

    1
    2
    3
    4
    5
    6
    import org.lovebing.reactnative.baidumap.BaiduMapPackage;
    return Arrays.<ReactPackage>asList(
    ...
    , new BaiduMapPackage(getApplicationContext())
    ...
  • AndroidManifest.xml:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    <!--用于进行网络定位-->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"></uses-permission>
    <!--用于访问GPS定位-->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"></uses-permission>
    <!--获取运营商信息,用于支持提供运营商信息相关的接口-->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>
    <!--用于访问wifi网络信息,wifi信息会用于进行网络定位-->
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"></uses-permission>
    <!--这个权限用于获取wifi的获取权限,wifi信息会用来进行网络定位-->
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"></uses-permission>
    <!--用于访问网络,网络定位需要上网-->
    <uses-permission android:name="android.permission.INTERNET"></uses-permission>
    <!--用于读取手机当前的状态-->
    <uses-permission android:name="android.permission.READ_PHONE_STATE"></uses-permission>
    <!--写入扩展存储,向扩展卡写入数据,用于写入缓存定位数据-->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
    <application
    android:name=".MainApplication"
    android:allowBackup="true"
    android:label="@string/app_name"
    android:icon="@mipmap/ic_launcher"
    android:theme="@style/AppTheme">
    <service android:name="com.amap.api.location.APSService"></service>
    <meta-data
    android:name="com.baidu.lbsapi.API_KEY"
    android:value="baidu-key"/><!--百度地图key值-->
    ...
  • Usage

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    import React, { Component } from 'react';
    import {
    View,
    Text,
    TouchableHighlight,
    StyleSheet,
    InteractionManager,
    Dimensions,
    Platform,
    StatusBar,
    NativeAppEventEmitter,
    Alert,
    } from 'react-native';
    import { Button } from 'antd-mobile';
    import { MapView, MapTypes, MapModule, Geolocation } from 'react-native-baidu-map';
    class BaiduLocationTest extends Component {
    constructor(props) {
    super(props);
    this.state = {
    mayType: MapTypes.NORMAL,
    zoom: 15,
    center: {
    longitude: 113.981718,
    latitude: 22.542449
    },
    trafficEnabled: false,
    baiduHeatMapEnabled: false,
    markers: [{
    longitude: 113.981718,
    latitude: 22.542449,
    title: "Window of the world"
    },{
    longitude: 113.995516,
    latitude: 22.537642,
    title: ""
    }]
    };
    this.timer = null;
    }
    componentDidMount() {
    }
    componentWillUnmount () {
    this.timer && clearInterval(this.timer);
    }
    getLocation = () => {
    this.timer = setInterval(
    () => {
    Geolocation.getCurrentPosition()
    .then(data => {
    console.log(JSON.stringify(data));
    this.setState({
    marker: {
    latitude: data.latitude,
    longitude: data.longitude,
    title: 'Your location'
    },
    center: {
    latitude: data.latitude,
    longitude: data.longitude,
    rand: Math.random()
    }
    });
    })
    .catch(e =>{
    console.warn(e, 'error');
    })
    },
    2000
    );
    }
    render() {
    return (
    <View style={styles.container}>
    <MapView
    trafficEnabled={this.state.trafficEnabled}
    baiduHeatMapEnabled={this.state.baiduHeatMapEnabled}
    zoom={this.state.zoom}
    mapType={this.state.mapType}
    center={this.state.center}
    marker={this.state.marker}
    markers={this.state.markers}
    style={styles.map}
    onMarkerClick={(e) => {
    console.warn(JSON.stringify(e));
    }}
    onMapClick={(e) => {
    }}
    >
    </MapView>
    <View style={styles.row}>
    <Button style={styles.btn} onClick={this.getLocation}>获取位置信息</Button>
    </View>
    </View>
    );
    }
    }
    const styles = StyleSheet.create({
    button: {
    margin: 20,
    },
    row: {
    flexDirection: 'row',
    height: 40
    },
    container: {
    flex: 1,
    justifyContent: 'flex-start',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
    },
    map: {
    width: Dimensions.get('window').width,
    height: Dimensions.get('window').height - 200,
    marginBottom: 16
    }
    });
    export default BaiduLocationTest;

Fetch-Timeout-Wrapper

发表于 2017-06-17   |   分类于 React Native

在使用react-native进行开发时,用到网络请求的地方,如果服务端未启动或存在网络问题,会弹出Network request failed,想自己设置客户端超时时间及超时提示信息,在react-native问题列表和网上查找后,已确认react-native用的fetch没有直接提供timeout设置的。

在网上查到了使用ES6的promise封装fetch的方式,自己修改成如下timeout_fetch.js工具类,经过验证可以解决使用fetch时timeout的设置问题,将项目所有使用fetch的地方替换成使用timeout_fetch即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/*
config中配置了系统默认的请求超时时间,方便后期统一修改
*/
import { default as config } from '../config/base';
/**
* @param {Promise} fetch_promise fetch请求返回的Promise
* @param {number} timeout 超时时间,单位为毫秒
* @return 返回Promise
*/
function promise_fetch(fetch_promise, timeout) {
let timeout_fn = null;
// 这是一个可以被reject的promise
let timeout_promise = new Promise((resolve, reject) => {
timeout_fn = () => {
const error = new Error('网络请求超时');
reject(error);
};
});
/*
这里使用Promise.race,以先返回的 resolve 或 reject 的结果来传入后续绑定的回调。
timeout不是请求连接超时的含义,它表示请求的response时间,fetch的timeout即使超时发生了,本次请求也不会被abort丢弃掉,它在后台仍然会发送到服务器端,只是本次请求的响应内容被丢弃而已。
*/
const abortable_promise = Promise.race([
fetch_promise,
timeout_promise
]);
setTimeout(function() {
timeout_fn();
}, timeout);
return abortable_promise;
}
/**
* @param {string} url 请求地址
* @param {object} params 请求参数,其中timeout是超时设置,如果没有取全局配置的默认值
* @return 返回Promise
*/
export function timeout_fetch(url, params) {
return new Promise((resolve, reject) => {
promise_fetch(fetch(url, params), params.timeout ? params.timeout : config.clientTimeout * 1000)
.then((responseData) => {
resolve(responseData);
})
.catch((err) => {
reject(err);
});
});
}
1…111213…17
© 2021 小朱
由 Hexo 强力驱动
主题 - NexT.Pisces