IOS img标签下图片无法显示

IOS img 图片无法展示几种原因

  1. 定位问题,相同标签下 flex 布局加 position 定位出现问题,解决办法,选择一种
  2. 图片 src 不显示,base64 和 网络图片地址都不显示,解决办法, 给 img 添加父盒子,img 宽高设置 100%;
代码:
图片 src 不显示,base64 和 网络图片地址都不显示,解决办法, 给 img 添加父盒子,img 宽高设置 100%;
    <div>
        <img src="http://www.dz1995.com/wp-content/uploads/2017/03/bf28b7195f6f948df8c9d3637ef5a3f68fadb11849cc2-0XAbEw_fw658.jpg">
    </div>
    div {
        width: 200px;
        height: 200px;
    }

    div img {
        width: 100%;
        height: 100%;
    }

JavaScript 运行机制:Event Loop

一、为什么 JavaScript 是单线程?

JavaScript 是单线程运行的,也就是说同一时间只能干一件事。那么为什么 JavaScript 不能是多线程呢?这样可以高效运行啊!

首先 JavaScript 单线程和它的用的有关,作为浏览器脚本语言,他的用途是和用户互动以及操作 DOM 。这决定了它只能是单线程运行,否则会带来很复杂的同步问题。比如,JavaScript 有个俩个线程,一个线程在某个 DOM 节点上添加内容,另一个线程删除了这个节点,这是浏览器应该以哪个线程为主?

所以避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这问语言的核心特征,将来也不会改变。

为了利用多核 CPU 的计算能力,HTML5 提出一个 Web Worker 标准,允许 JavaScript 脚本创建多个线程,但是子线程完全受主线程控制,且不得操作 DOM ,所以这个新的标准并没有改变 JavaScript 单线程的本质。

二、任务队列

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行下一个任务,如果前一个任务耗时很长,后一个任务就不得不一直等着。

如果排队是因为计算量大,CPU忙不过来,倒也算了,但是很多时候CPU是闲着的,因为IO设备(输入输出设备)很慢(比如Ajax操作从网络读取数据),不得不等着结果出来,再往下执行。

JavaScript语言的设计者意识到,这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。

于是,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入”任务队列”(task queue)的任务,只有”任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

具体来说,异步执行的运行机制如下。(同步执行也是如此,因为它可以被视为没有异步任务的异步执行。)

(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。

(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。

(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

(4)主线程不断重复上面的第三步。

下图就是主线程和任务队列的示意图。
img
只要主线程空了,就会去读取”任务队列”,这就是JavaScript的运行机制。这个过程会不断重复。


三、事件和回调函数

“任务队列”是一个事件的队列(也可以理解成消息的队列),IO设备完成一项任务,就在”任务队列”中添加一个事件,表示相关的异步任务可以进入”执行栈”了。主线程读取”任务队列”,就是读取里面有哪些事件。

“任务队列”中的事件,除了IO设备的事件以外,还包括一些用户产生的事件(比如鼠标点击、页面滚动等等)。只要指定过回调函数,这些事件发生时就会进入”任务队列”,等待主线程读取。

所谓”回调函数”(callback),就是那些会被主线程挂起来的代码。异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。

“任务队列”是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。主线程的读取过程基本上是自动的,只要执行栈一清空,”任务队列”上第一位的事件就自动进入主线程。但是,由于存在后文提到的”定时器”功能,主线程首先要检查一下执行时间,某些事件只有到了规定的时间,才能返回主线程。

四、Event Loop

主线程从”任务队列”中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。

为了更好地理解Event Loop,请看下图(转引自Philip Roberts的演讲《Help, I’m stuck in an event-loop》)。
img
上图中,主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各种外部API,它们在”任务队列”中加入各种事件(click,load,done)。只要栈中的代码执行完毕,主线程就会去读取”任务队列”,依次执行那些事件所对应的回调函数。

执行栈中的代码(同步任务),总是在读取”任务队列”(异步任务)之前执行。请看下面这个例子。

    var req = new XMLHttpRequest();
    req.open('GET', url);    
    req.onload = function (){};    
    req.onerror = function (){};    
    req.send();

上面代码中的req.send方法是Ajax操作向服务器发送数据,它是一个异步任务,意味着只有当前脚本的所有代码执行完,系统才会去读取”任务队列”。所以,它与下面的写法等价。

    var req = new XMLHttpRequest();
    req.open('GET', url);
    req.send();
    req.onload = function (){};    
    req.onerror = function (){};   

也就是说,指定回调函数的部分(onload和onerror),在send()方法的前面或后面无关紧要,因为它们属于执行栈的一部分,系统总是执行完它们,才会去读取”任务队列”。


五、定时器

除了放置异步任务的事件,”任务队列”还可以放置定时事件,即指定某些代码在多少时间之后执行。这叫做”定时器”(timer)功能,也就是定时执行的代码。

定时器功能主要由setTimeout()和setInterval()这两个函数来完成,它们的内部运行机制完全一样,区别在于前者指定的代码是一次性执行,后者则为反复执行。以下主要讨论setTimeout()。

setTimeout()接受两个参数,第一个是回调函数,第二个是推迟执行的毫秒数。

console.log(1);
setTimeout(function(){console.log(2);},1000);
console.log(3);

上面代码的执行结果是1,3,2,因为setTimeout()将第二行推迟到1000毫秒之后执行。

如果将setTimeout()的第二个参数设为0,就表示当前代码执行完(执行栈清空)以后,立即执行(0毫秒间隔)指定的回调函数。

setTimeout(function(){console.log(1);}, 0);
console.log(2);

上面代码的执行结果总是2,1,因为只有在执行完第二行以后,系统才会去执行”任务队列”中的回调函数。

总之,setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,也就是说,尽可能早得执行。它在”任务队列”的尾部添加一个事件,因此要等到同步任务和”任务队列”现有的事件都处理完,才会得到执行。

HTML5标准规定了setTimeout()的第二个参数的最小值(最短间隔),不得低于4毫秒,如果低于这个值,就会自动增加。在此之前,老版本的浏览器都将最短间隔设为10毫秒。另外,对于那些DOM的变动(尤其是涉及页面重新渲染的部分),通常不会立即执行,而是每16毫秒执行一次。这时使用requestAnimationFrame()的效果要好于setTimeout()。

需要注意的是,setTimeout()只是将事件插入了”任务队列”,必须等到当前代码(执行栈)执行完,主线程才会去执行它指定的回调函数。要是当前代码耗时很长,有可能要等很久,所以并没有办法保证,回调函数一定会在setTimeout()指定的时间执行。

六、Node.js的Event Loop

Node.js也是单线程的Event Loop,但是它的运行机制不同于浏览器环境。

请看下面的示意图(作者@BusyRich)。

img

根据上图,Node.js的运行机制如下。

(1)V8 引擎解析 JavaScript 脚本。

(2)解析后的代码,调用 Node API。

(3)libuv 库负责 Node API 的执行。它将不同的任务分配给不同的线程,形成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎。

(4)V8 引擎再将结果返回给用户。

除了setTimeout和setInterval这两个方法,Node.js还提供了另外两个与”任务队列”有关的方法:process.nextTick和setImmediate。它们可以帮助我们加深对”任务队列”的理解。

process.nextTick方法可以在当前”执行栈”的尾部—-下一次Event Loop(主线程读取”任务队列”)之前—-触发回调函数。也就是说,它指定的任务总是发生在所有异步任务之前。setImmediate方法则是在当前”任务队列”的尾部添加事件,也就是说,它指定的任务总是在下一次Event Loop时执行,这与setTimeout(fn, 0)很像。请看下面的例子(via StackOverflow)。

process.nextTick(function A() {
  console.log(1);
  process.nextTick(function B(){console.log(2);});
});

setTimeout(function timeout() {
  console.log('TIMEOUT FIRED');
}, 0)
// 1
// 2
// TIMEOUT FIRED

上面代码中,由于process.nextTick方法指定的回调函数,总是在当前”执行栈”的尾部触发,所以不仅函数A比setTimeout指定的回调函数timeout先执行,而且函数B也比timeout先执行。这说明,如果有多个process.nextTick语句(不管它们是否嵌套),将全部在当前”执行栈”执行。

现在,再看setImmediate。

setImmediate(function A() {
  console.log(1);
  setImmediate(function B(){console.log(2);});
});

setTimeout(function timeout() {
  console.log('TIMEOUT FIRED');
}, 0);

上面代码中,setImmediate与setTimeout(fn,0)各自添加了一个回调函数A和timeout,都是在下一次Event Loop触发。那么,哪个回调函数先执行呢?答案是不确定。运行结果可能是1–TIMEOUT FIRED–2,也可能是TIMEOUT FIRED–1–2。

令人困惑的是,Node.js文档中称,setImmediate指定的回调函数,总是排在setTimeout前面。实际上,这种情况只发生在递归调用的时候。

setImmediate(function (){
  setImmediate(function A() {
    console.log(1);
    setImmediate(function B(){console.log(2);});
  });

  setTimeout(function timeout() {
    console.log('TIMEOUT FIRED');
  }, 0);
});
// 1
// TIMEOUT FIRED
// 2

上面代码中,setImmediate和setTimeout被封装在一个setImmediate里面,它的运行结果总是1–TIMEOUT FIRED–2,这时函数A一定在timeout前面触发。至于2排在TIMEOUT FIRED的后面(即函数B在timeout后面触发),是因为setImmediate总是将事件注册到下一轮Event Loop,所以函数A和timeout是在同一轮Loop执行,而函数B在下一轮Loop执行。

我们由此得到了process.nextTick和setImmediate的一个重要区别:多个process.nextTick语句总是在当前”执行栈”一次执行完,多个setImmediate可能则需要多次loop才能执行完。事实上,这正是Node.js 10.0版添加setImmediate方法的原因,否则像下面这样的递归调用process.nextTick,将会没完没了,主线程根本不会去读取”事件队列”!

process.nextTick(function foo() {
  process.nextTick(foo);
});

事实上,现在要是你写出递归的process.nextTick,Node.js会抛出一个警告,要求你改成setImmediate。

另外,由于process.nextTick指定的回调函数是在本次”事件循环”触发,而setImmediate指定的是在下次”事件循环”触发,所以很显然,前者总是比后者发生得早,而且执行效率也高(因为不用检查”任务队列”)。

js 垃圾回收机制

内存的生命周期

内存分配 -> 使用内存 -> 释放内存

js 环境中分配内存有如下声明周期:
  1. 内存分配:在我们申明变量,函数,对象的时候,系统会为我们自动分配内存。
  2. 内存使用:读写内存,也就是使用变量,函数等。
  3. 释放内存:使用完毕,由垃圾回收机制自动回收不在使用的内存。

js 内存的分配

为了让程序员不在费劲的分配内存,JavaScript 在定义变量的时候就完成了内存分配。
    var n = 123; // 给数值变量分配内存
    var s = "azerty"; // 给字符串分配内存

    var o = {
        a: 1,
        b: null
    }; // 给对象及其包含的值分配内存

    // 给数组及其包含的值分配内存(就像对象一样)
    var a = [1, null, "abra"]; 

    function f(a){
        return a + 2;
    } // 给函数(可调用的对象)分配内存

    // 函数表达式也能分配一个对象
    someElement.addEventListener('click', function(){
        someElement.style.backgroundColor = 'blue';
    }, false);
有些函数调用结果是分配函数的内存:
    var date = new Date(); // 分配一个 Date 对象

    var ele = document.createElement("div"); // 分配一个 DOM 对象
有些方法分配新的变量或者新对象:
    var s = "asdfsfd"
    var s2 = s.substr(0,3) // s2 是一个新的字符串
    // 因为字符串是不变量
    // JavaScript 可能决定不分配内存
    // 只是存储了[0-3] 的范围

    var a = ['abc', 'cbd'];
    var a2 = ['box', 'abs'];
    var a3 = a.concat(a2);
    // 新数组有四个元素, 是 a 连接 a2 的结果

js 内存的使用

使用值的过程实际上是对分配内存进行读取和写入操作。读取与写入可能是写入一个变量或者一个对象的属性值,甚至传递函数的参数。
    var a = 10; // 分配内存
    console.log(a); // 对内存的读取使用

js 的内存回收

js 有自动垃圾回收机制, 那么这个垃圾自动回收机制的原理是什么呢? 其实很简单,就是找出那些不再继续使用的值,然后释放其内存。
多少内存管理的问题都在这个阶段。在这里最艰的任务是找到不再需要使用的变量。
不再需要使用的变量也就是在生命周期结束的变量,是局部变量,局部变量只在函数的执行过程中存在,当函数运行结束,没有其他引用(闭包),那么该变量会被标记回收。
全局变量的生命周期直至浏览器卸载页面才会结束,也就是说全局变量不会被当成垃圾回收。
因为自动垃圾回收机制的存在,开发人员可以不关系也不注意内存释放的有关问题,但是无用内存释放这件事是客观存在的。不幸的是,即使不考虑垃圾回收对性能的影响,目前最新的垃圾回收算法,也无法智能回收所有的极端情况。

垃圾回收

引用

垃圾回收算法主要依赖于引用的概念
在内存管理感觉中,一个对象如果有访问另一个对象的权限(显示或者隐式),叫做一个对象引用另一个对象。
例如,一个javascript 对象具有对他原型的引用(隐式引用)和对它属性的引用(显示引用)。
在这里,“对象”的概念不仅特指 JavaScript 对象,还包括函数作用域(或者全局词法作用域)

引用计数垃圾收集

这是最初的垃圾回收算法。
引用计数算法定义“内存不再使用”的标准很简单,就是看一个对象是否有指向它的引用。如果没有其他对象指向它了,说明对象已经不再需要了。
    var o = {
        a: {
            b: 2
        }
    };
    // 两个对象被创建, 一个作为另一个的属性被引用, 另一个被分配给变量。
    // 很显然,没有一个可以被垃圾收集

    var o2 = o; // o2 变量是第二个对“对象”的引用

    o = 1; // 现在, “这个对象” 的原始引用 o 被 o2 替换了

    var oa = o2.a; // 引用“这个对象”的 a 属性
    // 现在,“这个对象”有两个引用了, 一个是 o2, 一个是 oa

    o2 = "ya"; // 最初的对象现在已经是零引用了
                         // 它可以被垃圾回收了
                         // 然后它的属性 a 的对象还在被 oa 引用, 所以还不能回收
    oa = null; // a 属性的那个喜爱那个现在也是零引用了
                         // 它可以被垃圾回收了
由上面可以看出,引用计算算法是个简单有效的算法,但它却存在一个致命的问题: 循环引用。
如果两个对象相互引用,尽管他们已不再使用,垃圾回收不会进行回收,导致内存泄露。
来看一个循环引用的例子:
    function f() {
        var o = {};
        var o2 = {};
        o.a = a2; // o 引用 o2
        o2.a = o; // o2 引用 o 这里

        return "azerty";
    }

    f();
上面我们申明了一个函数 f,其中包含两个相互引用的对象。在调用函数结束后,对象 o1 和 o2 实际已离开函数范围,因此不再需要了。但根据引用计数的原则,他们之间的相互引用依然存在,因此这部分内存不会被回收,内存泄露不可避免了。
再来看一个实际的例子:
    var div = document.createElement("div");
    div.onclick = function() {
            console.log("click");
    };
上面这种JS写法再普通不过了,创建一个DOM元素并绑定一个点击事件。 此时变量 div 有事件处理函数的引用,同时事件处理函数也有div的引用!(div变量可在函数内被访问)。 一个循序引用出现了,按上面所讲的算法,该部分内存无可避免的泄露了。
为了解决循环引用造成的问题,现代浏览器通过使用标记清除算法来实现垃圾回收。

标记清除算法

标记清除算法将“不再使用的对象”定义为“无法达到的对象”。 简单来说,就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。 凡是能从根部到达的对象,都是还需要使用的。 那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。
从这个概念可以看出,无法触及的对象包含了没有引用的对象这个概念(没有任何引用的对象也是无法触及的对象)。 但反之未必成立。

工作流程

  1. 垃圾收集器会在运行的时候会给存储在内存中的所有变量都加上标记。
  2. 从根部出发将能触及到的对象的标记清除。
  3. 那些还存在标记的变量被视为准备删除的变量。
  4. 最后垃圾收集器会执行最后一步内存清除的工作,销毁那些带标记的值并回收它们所占用的内存空间。

图片

循环引用不再是问题了

在看之前循环引用的例子:
function f(){
  var o = {};
  var o2 = {};
  o.a = o2; // o 引用 o2
  o2.a = o; // o2 引用 o

  return "azerty";
}

f();

函数调用返回之后,两个循环引用的对象在垃圾收集时从全局对象出发无法再获取他们的引用。 因此,他们将会被垃圾回收器回收。

内存泄露泄漏

程序的运行需要内存。只要程序提出要求,操作系统或者运行时(runtime)就必须供给内存。

对于持续运行的服务进程(daemon),必须及时释放不再用到的内存。
否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃。

本质上讲,内存泄漏就是由于疏忽或错误造成程序未能释放那些已经不再使用的内存,造成内存的浪费。

内存泄漏的识别方法

经验法则是,如果连续五次垃圾回收之后,内存占用一次比一次大,就有内存泄漏。 这就要求实时查看内存的占用情况。

在 Chrome 浏览器中,我们可以这样查看内存占用情况

  1. 打开开发者工具,选择 Performance 面板
  2. 在顶部勾选 Memory
  3. 点击左上角的 record 按钮
  4. 在页面上进行各种操作,模拟用户的使用情况
  5. 一段时间后,点击对话框的 stop 按钮,面板上就会显示这段时间的内存占用情况
来看一张效果图:

我们有两种方式来判定当前是否有内存泄漏:

  1. 多次快照后,比较每次快照中内存的占用情况,如果呈上升趋势,那么可以认为存在内存泄漏
  2. 某次快照后,看当前内存占用的趋势图,如果走势不平稳,呈上升趋势,那么可以认为存在内存泄漏

在服务器环境中使用 Node 提供的 process.memoryUsage 方法查看内存情况

console.log(process.memoryUsage());
// { 
//     rss: 27709440,
//     heapTotal: 5685248,
//     heapUsed: 3449392,
//     external: 8772 
// }

process.memoryUsage返回一个对象,包含了 Node 进程的内存占用信息。
该对象包含四个字段,单位是字节,含义如下:

  • rss(resident set size):所有内存占用,包括指令区和堆栈。
  • heapTotal:”堆”占用的内存,包括用到的和没用到的。
  • heapUsed:用到的堆的部分。
  • external: V8 引擎内部的 C++ 对象占用的内存。

判断内存泄漏,以heapUsed字段为准。

意外的全局变量
function foo() {
    bar1 = 'some text'; // 没有声明变量 实际上是全局变量 => window.bar1
    this.bar2 = 'some text' // 全局变量 => window.bar2
}
foo();

在这个例子中,意外的创建了两个全局变量 bar1 和 bar2

被遗忘的定时器和回调函数

在很多库中, 如果使用了观察者模式, 都会提供回调方法, 来调用一些回调函数。 要记得回收这些回调函数。举一个 setInterval的例子:
var serverData = loadData();
setInterval(function() {
    var renderer = document.getElementById('renderer');
    if(renderer) {
        renderer.innerHTML = JSON.stringify(serverData);
    }
}, 5000); // 每 5 秒调用一次

如果后续 renderer 元素被移除,整个定时器实际上没有任何作用。 但如果你没有回收定时器,整个定时器依然有效, 不但定时器无法被内存回收, 定时器函数中的依赖也无法回收。在这个案例中的 serverData 也无法被回收。

闭包

在 JS 开发中,我们会经常用到闭包,一个内部函数,有权访问包含其的外部函数中的变量。 下面这种情况下,闭包也会造成内存泄露:
    var theThing = null;
var replaceThing = function () {
  var originalThing = theThing;
  var unused = function () {
    if (originalThing) // 对于 'originalThing'的引用
      console.log("hi");
  };
  theThing = {
    longStr: new Array(1000000).join('*'),
    someMethod: function () {
      console.log("message");
    }
  };
};
setInterval(replaceThing, 1000);

这段代码,每次调用 replaceThing 时,theThing 获得了包含一个巨大的数组和一个对于新闭包 someMethod 的对象。
同时 unused 是一个引用了 originalThing 的闭包。
这个范例的关键在于,闭包之间是共享作用域的,尽管 unused 可能一直没有被调用,但是 someMethod 可能会被调用,就会导致无法对其内存进行回收。
当这段代码被反复执行时,内存会持续增长。

DOM 引用

很多时候, 我们对 Dom 的操作, 会把 Dom 的引用保存在一个数组或者 Map 中。

var elements = {
    image: document.getElementById('image')
};
function doStuff() {
    elements.image.src = 'http://example.com/image_name.png';
}
function removeImage() {
    document.body.removeChild(document.getElementById('image'));
    // 这个时候我们对于 #image 仍然有一个引用, Image 元素, 仍然无法被内存回收.
}

上述案例中,即使我们对于 image 元素进行了移除,但是仍然有对 image 元素的引用,依然无法对齐进行内存回收。
另外需要注意的一个点是,对于一个 Dom 树的叶子节点的引用。
举个例子: 如果我们引用了一个表格中的td元素,一旦在 Dom 中删除了整个表格,我们直观的觉得内存回收应该回收除了被引用的 td 外的其他元素。
但是事实上,这个 td 元素是整个表格的一个子元素,并保留对于其父元素的引用。
这就会导致对于整个表格,都无法进行内存回收。所以我们要小心处理对于 Dom 元素的引用。

如何避免内存泄漏

记住一个原则:不用的东西,及时归还。
  1. 减少不必要的全局变量,使用严格模式避免意外创建全局变量。
  2. 在你使用完数据后,及时解除引用(闭包中的变量,dom引用,定时器清除)。
  3. 组织好你的逻辑,避免死循环等造成浏览器卡顿,崩溃的问题。

参考

MDN-内存管理
JavaScript高级程序设计
JavaScript权威指南
JavaScript 内存泄漏教程
一种有趣的JavaScript内存泄漏
内存泄露

小程序开发

  1. 小程序看起来和 html + CSS + javascript 但是实际上深入后就会发现他们有很多的不同,这里面的坑就比较多了,比如小程序对 wxss 对标准的 css 支持多少没有明确文档说明,有时候出了问题,需要多方面考虑,就像我刚刚遇到一个问题 (mpvue 开发小程序) :class=”{action:isAce, unaction: !isAce}”,在低版本测试中发现样式并没有出来,看到 dom 结构上 class=”,unaction” 出现这样情况很无奈啊,但是 1.6 以上基础库就没有这问题。类似这样的坑还有好多,比如多次点击,事件跳转打开多个层
  2. 小程序中项目开发中有很多问题和难点,需要多方面的考虑,像 mpvue 中午生命周期没有触发等

  1. 适合H5页面,兼容ie10+,图片base64显示,主要功能点是FileReader和readAsDataURL
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>files-h5</title>
</head>
<body>
  <input type="file" id="file" onchange="showPreview(this, 'portrait')" />
  <img src="" id="portrait" style="width: 200px; height: 200px; display: block;" />
  <script>
  function showPreview(source, imgId) {
    var file = source.files[0];
    if(window.FileReader) {
      var fr = new FileReader();
      fr.onloadend = function(e) {
        document.getElementById(imgId).src = e.target.result;
      }
      fr.readAsDataURL(file);
    }
  }
  </script>
</body>
</html>
  1. 更适合PC端,兼容ie7+,主要功能点是window.URL.createObjectURL
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>files-pc</title>
</head>
<body>
  <input type="file" id="file" onchange="showPreview(this.id,'portrait')" />
  <img src="" id="portrait" style="width: 200px; height: 200px; display: block;" />
  <script type="text/javascript">
  /* 图片预览 */
  function showPreview(fileId, imgId) {
    var file = document.getElementById(fileId);
    var ua = navigator.userAgent.toLowerCase();
    var url = '';
    if(/msie/.test(ua)) {
      url = file.value;
    } else {
      url = window.URL.createObjectURL(file.files[0]);
    }
    document.getElementById(imgId).src = url;
  }
  </script>
</body>
</html>

vue 点击事件在UC浏览器下无效

今天早上朋友找我说遇到一个问题UC浏览器点击无效,听说折磨同事好久,我看看了,出了各种主意也没有解决,毕竟没有源代码无法根据问题判断原因所在,不过后来朋友告诉我解决了,用来一个方法 CSS3 新属性 ** pointer-events **

一、问题

  1. 在 UC 浏览器点击事件无效(其他浏览器无问题)。
    附上朋友源码:
<template>
            <div class="music" @click="playBtn">
                <div class="control"><-- 背景图片 --></control>
        </div>
</template>
<script>
        export default {
                data() {
                    return {
                        isPlay: true
                    }
                },
                methods: {
                    playBtn() {
                        if (this.isPlay) {
                            // 切换图片
                        }
                    }
                }
        }
</script>

二、分析原因(个人理解,不喜勿喷,有错误请指出,感谢)

** 因为移动端部分浏览器点击图片放大,所以会在子元素默认给你绑定一个事件,就会影响你父级元素绑定的事件。**
** 注意:子元素有背景图片或者子元素是img。 **
img 我添加默认事件可以理解,背景图片也影响是什么鬼,估计是检索url地址给元素绑定默认事件了。
我记得微信浏览器也可以点击放大图片,微信点击放大图片是通过微信API将图片传给微信,点击图片就可以放大了。但是 UC 这个就恶心了,你检索页面图片地址调用你自己的API,但是你绑定默认事件时别影响我正常事件啊。

三、解决方法 pointer-events

给子元素添加样式:

    .control {
        poninter-events: none;
    }

四、** pointer-events详解 **

简介

官方解释:

** pointer-events ** CSS 属性指定在什么情况下 (如果有) 某个特定的图形元素可以成为鼠标事件的 target。

个人理解和使用:

pointer-event:none; 顾名思义是鼠标事件拜拜的意思,元素使用了该属性,链接啊,点击什么的都统统失效了。

pointer-events:none 的作用是让元素实体“虚化”。例如一个应用pointer-events:none的按钮元素,则我们在页面上看到的这个按钮,只是一个虚幻的影子而已,您可以理解为海市蜃楼,幽灵的躯体。当我们用手触碰它的时候可以轻易地没有任何感觉地从中穿过去。

一、简介

开发者需要知道,用户正在离开页面,常用的方法是监听下面三个事件。
– pagehide
– beforeunload
– unload

但是,这些事件在手机上可能不会触发,页面就直接关闭了,因为手机系统可以将一个进程直接转入后台,然后沙死。
– 用户点击了一条系统通知,切换到另一个 App.
– 用户进入任务切换窗口, 切换到另一个 App.
– 用户点击了 home 按钮, 切换回主屏幕。
– 操作系统自动切换到另一个 App (比如,收到一个电话)。

上面这些情况,都会导致手机进程切换到后台,然后为了节省资源,可能就会杀死浏览器进程。
以前,页面被系统切换,以及系统清除浏览器进程,是无法监听到的。页面开发者想要指定,任何一种页面卸载情况下都会执行的代码,也是无法做到的,为了解决这个问题,就诞生了 Page Visibility API 。不管手机或桌面电脑,所有情况下,这个API 都会监听到页面的可见性发生变化。

visibilityState 这个 API 的意义在于,通过监听页面的可见性,可以预判网页卸载,还可以用来节省资源,减缓电能的消耗。比如, 一但用户不看网页,下面这些网页行为都是可以暂停的。
+ 对服务器的轮询
+ 网页动画
+ 正在播放的音频或视频

二、 document.visibilityState

这个 API 主要在 document 对象上,新增了一个 docuemnt.visibilityState 属性。该属性返回一个字符串,表示页面当前的可见性状态,共有三个可能的值
+ hidden: 页面彻底不可见。
+ visible: 页面至少一部分可见。
+ prerender: 页面即将或正在渲染,处于不可见状态

其中, hidden 状态和 visible 状态是所有浏览器都必须支持的,prerender 状态只在支持“预渲染”的浏览器上才会出现,比如 Chrome 浏览器就会有预渲染功能,可以在用户不可见的状态下,预先把页面渲染出来,等到用户要浏览的时候,直接展示渲染好的网页。

只要页面可见,哪怕只露出一个角, document.visibilityState 属性就返回 visible,只有以下四种情况,才会返回 hidden.
+ 浏览器最小化
+ 浏览器没有最小化,但是当前页面切换成了背景页面。
+ 浏览器将要卸载(onload)页面
+ 操作系统触发锁屏屏幕

可以看到,上面四种场景涵盖了页面可能被卸载的所有情况。也就是说,页面卸载之前, document.visibilityState 属性一定会变成 hidden。 事实上,这也是设计这个 API 的主要目的。

另外,早期版本的 API, 这个属性还有第四个值 unloaded, 表示页面即将卸载,现在已经被废弃了。
注意, document.visibilityState 属性只针对顶层窗口,内嵌的 页面的 document.visibilityState 属性由顶层窗口决定,使用 CSS 属性隐藏 页面 (比如: display: none), 并不会影响内嵌页面的可见性。

三、docuemnt.hidden

由于历史原因, 这个 API 还定义了 document.hidden 属性。 该属性只读,返回一个布尔值,表示单签页面是否可见。
当 document.visibilityState 属性返回 visible 时, document.hidden 属性返回 false; 其他情况下, 都返回 true。 该属性只是出于历史原因而保留的,只要有可能,都应该使用 document,visibilityState 属性, 而不是使用这个属性。

四、 visibilitychange事件

只要 document.visibilityState 属性发生变化,就会触发 visibilitychange 事件。 因此,可以通过监听这个事件 (通过 document.addEventListener()方法或document.onvisibilitychange 属性),跟踪页面可见性的变化。

document.addEventListener('visibilitychange', function () {
  // 用户离开了当前页面
  if (document.visibilityState === 'hidden') {
    document.title = '页面不可见';
  }

  // 用户打开或回到页面
  if (document.visibilityState === 'visible') {
    document.title = '页面可见';
  }
});

上面代码是 Page Visibility API 的最基本用法,可以监听可见性变化。

下面是另一个例子,一旦页面不可见,就暂停视频播放。

var vidElem = document.getElementById('video-demo');
document.addEventListener('visibilitychange', startStopVideo);

function startStopVideo() {
  if (document.visibilityState === 'hidden') {
    vidElem.pause();
  } else if (document.visibilityState === 'visible') {
    vidElem.play();
  }
}

五、页面卸载

下面专门讨论一下,如何正确监听页面卸载。
页面卸载可以分为三种情况。
+ 页面可见时,用户关闭 Tab 页或浏览器窗口。
+ 页面可见时,用户在当前窗口前往另一个页面。
+ 页面不可见是,用户或系统关闭浏览器窗口。

这三种情况,都会触发 visibilitychange事件。前两种情况,该事件在用户离开页面是触发; 最后一种情况,该事件在页面从可见状态变为不可见状态时触发。

由此可见,visibilitychange事件比pagehide、beforeunload、unload事件更可靠,所有情况下都会触发(从visible变为hidden)。因此,可以只监听这个事件,运行页面卸载时需要运行的代码,不用监听后面那三个事件。

甚至可以这样说,unload事件在任何情况下都不必监听,beforeunload事件只有一种适用场景,就是用户修改了表单,没有提交就离开当前页面。另一方面,指定了这两个事件的监听函数,浏览器就不会缓存当前页面。

最近遇到一个头疼的问题,跨域!!!

跨域9种解决方案

什么是跨域

说起跨域,就要知道什么是浏览器同源策略

浏览器同源策略:必须是 协议、域名、端口完全一致的 才符合同源策略

如果以上三项,有一项不同都涉及到跨域问题


为什么浏览器要设置同源策略呢?

没有同源策略限制的两大危险场景

浏览器是从两个方面去做这个同源策略的,一是针对接口的请求,二是针对Dom的查询。试想一下没有这样的限制上述两种动作有什么危险。

没有同源策略限制的接口请求

有一个小小的东西叫cookie大家应该知道,一般用来处理登录等场景,目的是让服务端知道谁发出的这次请求。如果你请求了接口进行登录,服务端验证通过后会在响应头加入Set-Cookie字段,然后下次再发请求的时候,浏览器会自动将cookie附加在HTTP请求的头字段Cookie中,服务端就能知道这个用户已经登录过了。知道这个之后,我们来看场景:
1.你准备去清空你的购物车,于是打开了买买买网站www.maimaimai.com,然后登录成功,一看,购物车东西这么少,不行,还得买多点。
2.你在看有什么东西买的过程中,你的好基友发给你一个链接www.nidongde.com,一脸yin笑地跟你说:“你懂的”,你毫不犹豫打开了。
3.你饶有兴致地浏览着www.nidongde.com,谁知这个网站暗地里做了些不可描述的事情!由于没有同源策略的限制,它向www.maimaimai.com发起了请求!聪明的你一定想到上面的话“服务端验证通过后会在响应头加入Set-Cookie字段,然后下次再发请求的时候,浏览器会自动将cookie附加在HTTP请求的头字段Cookie中”,这样一来,这个不法网站就相当于登录了你的账号,可以为所欲为了!如果这不是一个买买买账号,而是你的银行账号,那……
这就是传说中的CSRF攻击浅谈CSRF攻击方式
看了这波CSRF攻击我在想,即使有了同源策略限制,但cookie是明文的,还不是一样能拿下来。于是我看了一些cookie相关的文章聊一聊 cookieCookie/Session的机制与安全,知道了服务端可以设置httpOnly,使得前端无法操作cookie,如果没有这样的设置,像XSS攻击就可以去获取到cookieWeb安全测试之XSS;设置secure,则保证在https的加密通信中传输以防截获。

没有同源策略限制的Dom查询

1.有一天你刚睡醒,收到一封邮件,说是你的银行账号有风险,赶紧点进www.yinghang.com改密码。你吓尿了,赶紧点进去,还是熟悉的银行登录界面,你果断输入你的账号密码,登录进去看看钱有没有少了。
2.睡眼朦胧的你没看清楚,平时访问的银行网站是www.yinhang.com,而现在访问的是www.yinghang.com,这个钓鱼网站做了什么呢?

// HTML
<iframe name="yinhang" src="www.yinhang.com"></iframe>
// JS
// 由于没有同源策略的限制,钓鱼网站可以直接拿到别的网站的Dom
const iframe = window.frames['yinhang']
const node = iframe.document.getElementById('你输入账号密码的Input')
console.log(`拿到了这个${node},我还拿不到你刚刚输入的账号密码吗`)

由此我们知道,同源策略确实能规避一些危险,不是说有了同源策略就安全,只是说同源策略是一种浏览器最基本的安全机制,毕竟能提高一点攻击的成本。其实没有刺不穿的盾,只是攻击的成本和攻击成功后获得的利益成不成正比。

跨域解决方案

1、 通过jsonp跨域
2、 document.domain + iframe跨域
3、 location.hash + iframe
4、 window.name + iframe跨域
5、 postMessage跨域
6、 跨域资源共享(CORS)
7、 nginx代理跨域
8、 nodejs中间件代理跨域
9、 WebSocket协议跨域

跨域9种方式

1. jsonp :最常见的 jsonp 方法,利用浏览器请求静态资源

node 代码

const express = require("express");
const app = express();

app.get("/push", function (req, res) {
    console.log(req.query);
    res.send(`${req.query.callback}(${JSON.stringify({ data: req.query, status: 200 })})`);
});

app.listen(3001, () => {
    console.log("jsonp 跨域");
}); 

简单版前端

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
  </head>
  <body>
    <script type='text/javascript'>
      // 后端返回直接执行的方法,相当于执行这个方法,由于后端把返回的数据放在方法的参数里,所以这里能拿到res。
      window.jsonpCb = function (res) {
        console.log(res)
      }
    </script>
    <script src='http://localhost:9871/api/jsonp?msg=helloJsonp&cb=jsonpCb' type='text/javascript'></script>
  </body>
</html>

简单封装一下前端这个套路

function jsonp(obj) {
    return new Promise((resolve, reject) => {
        const script = document.createElement('script');
        let dataString = obj.url.indexOf('?') == -1 ? '?' : '&';
        for (let i in obj.data) {
            dataString += i + "=" + obj.data[i] + "&";
        }

        const jsonp = 'json_cd' + (Math.random().toString().replace('.', ''));
        script.src = obj.url + dataString + 'callback=' + jsonp;
        document.body.appendChild(script);
        window[jsonp] = (data) => {
            document.body.removeChild(script);
            resolve(data);
        }
    })
}

jsonp({
    url: 'http://localhost:3001/push',
    data: {
        name: 'dz',
        age: '26'
    }
}).then((data) => {
    console.log(data);
})

jsonp 缺点是只能发送 get 请求


2. document.domain + iframe 跨域

此方案仅限主域相同,子域不同的跨域应用场景

实现原理: 两个页面都通过 js 强制设置 document.domain 为基础主域,就实现了同域。

a. 父窗口: (http://www.domain.com/a.html)

<iframe id="iframe" src="http://child.domain.com/b.html"></iframe>
<script>
    document.domain = 'domain.com';
    var user = 'admin';
</script>

b. 子窗口: http://child.domain.com/b.html

<script>
    document.domain = 'domain.com';
    // 获取父窗口中变量
    alert('get js data from parent ---> ' + window.parent.user);
</script>

3. location.hash + iframe 跨域

实现原理: a 与 b 跨域相互同行,通过中间页面 c 来实现。 三个页面,不同域之间利用 iframe 的 location.hash 传值,相同域之间直接 js 访问来通信。

具体实现: A域:a.html -> B域:b.html -> A域: c.html。a 与 b 不同域只能通过 hash 值单向通信,b 与 c 也不同域也只能单向通信, 但 c 与 a 同域,所以 c 可以通过 parent.parent 访问 a 页面所有的对象。

a.html:  (http://www.domain1.com/a.html)

<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>
<script>
    var iframe = document.getElementById('iframe');

    // 向b.html传hash值
    setTimeout(function() {
        iframe.src = iframe.src + '#user=admin';
    }, 1000);

    // 开放给同域c.html的回调方法
    function onCallback(res) {
        alert('data from c.html ---> ' + res);
    }
</script>

b.html: (http://www.domain2.com/b.html)

<iframe id="iframe" src="http://www.domain1.com/c.html" style="display:none;"></iframe>
<script>
    var iframe = document.getElementById('iframe');

    // 监听a.html传来的hash值,再传给c.html
    window.onhashchange = function () {
        iframe.src = iframe.src + location.hash;
    };
</script>

c.html: (http://www.domain1.com/c.html)

<script>
    // 监听b.html传来的hash值
    window.onhashchange = function () {
        // 再通过操作同域a.html的js回调,将结果传回
        window.parent.parent.onCallback('hello: ' + location.hash.replace('#user=', ''));
    };
</script>

4. window.name + iframe 跨域

window.name 属性的独特之处: name 值在不同页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。

a.html: (http://www.domain1.com/a.html)

var proxy = function(url, callback) {
    var state = 0;
    var iframe = document.createElement('iframe');

    // 加载跨域页面
    iframe.src = url;

    // onload事件会触发2次,第1次加载跨域页,并留存数据于window.name
    iframe.onload = function() {
        if (state === 1) {
            // 第2次onload(同域proxy页)成功后,读取同域window.name中数据
            callback(iframe.contentWindow.name);
            destoryFrame();

        } else if (state === 0) {
            // 第1次onload(跨域页)成功后,切换到同域代理页面
            iframe.contentWindow.location = 'http://www.domain1.com/proxy.html';
            state = 1;
        }
    };

    document.body.appendChild(iframe);

    // 获取数据以后销毁这个iframe,释放内存;这也保证了安全(不被其他域frame js访问)
    function destoryFrame() {
        iframe.contentWindow.document.write('');
        iframe.contentWindow.close();
        document.body.removeChild(iframe);
    }
};

// 请求跨域b页面数据
proxy('http://www.domain2.com/b.html', function(data){
    alert(data);
});

proxy.html: (http://www.domain1.com/proxy.html)

中间代理页,与 a.html 同域,内容为空即可。

b.html: (http;//www.domain2.com/b.html)

<script>
    window.name = 'This is domain2 data!';
</script>

总结: 通过 iframe 的 src 属性由外域向本地域,跨域数据即由 iframe 的 window.name 从外域传递到本地域。这个就巧妙的绕过了浏览器的跨域限制,但同时它又是安全操作。

5. postMessage 跨域

postMessage 是HTML5 XMLHttpRequest Level 2 中的 API,而且是为数不多可以跨域操作的 window 属性之一, 它可用于解决以下方面的问题;

a.) 页面和其打开的新窗口的数据传递
b.) 多窗口之间消息传递
c.) 页面与嵌套的 iframe 消息传递
d.) 上面三个场景的跨域数据传递

用法: postmessage(data, origin) 方法接受俩个参数
data : html5 规范支持任意基本类型或可复制的兑现,但部分浏览器只支持字符串,所以传参时最好用 JSON.string() 序列化。
origin: 协议 + 主机 + 端口号, 也可以设置为 “*”,  表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为”/”。

1.)a.html:(http://www.domain1.com/a.html)

<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>
<script>       
    var iframe = document.getElementById('iframe');
    iframe.onload = function() {
        var data = {
            name: 'aym'
        };
        // 向domain2传送跨域数据
        iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain2.com');
    };

    // 接受domain2返回数据
    window.addEventListener('message', function(e) {
        alert('data from domain2 ---> ' + e.data);
    }, false);
</script>

2.)b.html:(http://www.domain2.com/b.html)

<script>
    // 接收domain1的数据
    window.addEventListener('message', function(e) {
        alert('data from domain1 ---> ' + e.data);

        var data = JSON.parse(e.data);
        if (data) {
            data.number = 16;

            // 处理后再发回domain1
            window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com');
        }
    }, false);
</script>

6.跨域资源共享(CORS)

普通跨域请求:只服务端设置Access-Control-Allow-Origin即可,前端无须设置,若要带cookie请求:前后端都需要设置。

需注意的是:由于同源策略的限制,所读取的cookie为跨域请求接口所在域的cookie,而非当前页。如果想实现当前页cookie的写入,可参考下文:七、nginx反向代理中设置proxy_cookie_domain 和 八、NodeJs中间件代理中cookieDomainRewrite参数的设置。

目前,所有浏览器都支持该功能(IE8+:IE8/9需要使用XDomainRequest对象来支持CORS)),CORS也已经成为主流的跨域解决方案。

1.  前端设置

1.)原生ajax

// 前端设置是否带cookie
xhr.withCredentials = true;

示例代码:

var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容

// 前端设置是否带cookie
xhr.withCredentials = true;

xhr.open('post', 'http://www.domain2.com:8080/login', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('user=admin');

xhr.onreadystatechange = function() {
    if (xhr.readyState == 4 && xhr.status == 200) {
        alert(xhr.responseText);
    }
};

2.)jQuery ajax

$.ajax({
    ...
   xhrFields: {
       withCredentials: true    // 前端设置是否带cookie
   },
   crossDomain: true,   // 会让请求头中包含跨域的额外信息,但不会含cookie
    ...
});

3.)vue框架

a.) axios设置:

axios.defaults.withCredentials = true

b.) vue-resource设置:

Vue.http.options.credentials = true
2、 服务端设置:

若后端设置成功,前端浏览器控制台则不会出现跨域报错信息,反之,说明没设成功。

1.)Java后台:

/*
 * 导入包:import javax.servlet.http.HttpServletResponse;
 * 接口参数中定义:HttpServletResponse response
 */

// 允许跨域访问的域名:若有端口需写全(协议+域名+端口),若没有端口末尾不用加'/'
response.setHeader("Access-Control-Allow-Origin", "http://www.domain1.com"); 

// 允许前端带认证cookie:启用此项后,上面的域名不能为'*',必须指定具体的域名,否则浏览器会提示
response.setHeader("Access-Control-Allow-Credentials", "true"); 

// 提示OPTIONS预检时,后端需要设置的两个常用自定义头
response.setHeader("Access-Control-Allow-Headers", "Content-Type,X-Requested-With");

2.)Nodejs后台示例:

var http = require('http');
var server = http.createServer();
var qs = require('querystring');

server.on('request', function(req, res) {
    var postData = '';

    // 数据块接收中
    req.addListener('data', function(chunk) {
        postData += chunk;
    });

    // 数据接收完毕
    req.addListener('end', function() {
        postData = qs.parse(postData);

        // 跨域后台设置
        res.writeHead(200, {
            'Access-Control-Allow-Credentials': 'true',     // 后端允许发送Cookie
            'Access-Control-Allow-Origin': 'http://www.domain1.com',    // 允许访问的域(协议+域名+端口)
            /* 
             * 此处设置的cookie还是domain2的而非domain1,因为后端也不能跨域写cookie(nginx反向代理可以实现),
             * 但只要domain2中写入一次cookie认证,后面的跨域接口都能从domain2中获取cookie,从而实现所有的接口都能跨域访问
             */
            'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly'  // HttpOnly的作用是让js无法读取cookie
        });

        res.write(JSON.stringify(postData));
        res.end();
    });
});

server.listen('8080');
console.log('Server is running at port 8080...');

7. nginx代理跨域

nginx配置解决iconfont跨域

浏览器跨域访问js、css、img等常规静态资源被同源策略许可,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在nginx的静态资源服务器中加入以下配置。

location / {
  add_header Access-Control-Allow-Origin *;
}
2、 nginx反向代理接口跨域

跨域原理: 同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题。

实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。

nginx具体配置:
#proxy服务器
server {
    listen       81;
    server_name  www.domain1.com;

    location / {
        proxy_pass   http://www.domain2.com:8080;  #反向代理
        proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
        index  index.html index.htm;

        # 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
        add_header Access-Control-Allow-Origin http://www.domain1.com;  #当前端只跨域不带cookie时,可为*
        add_header Access-Control-Allow-Credentials true;
    }
}

1.) 前端代码示例:

var xhr = new XMLHttpRequest();

// 前端开关:浏览器是否读写cookie
xhr.withCredentials = true;

// 访问nginx中的代理服务器
xhr.open('get', 'http://www.domain1.com:81/?user=admin', true);
xhr.send();

2.) Nodejs后台示例:

var http = require('http');
var server = http.createServer();
var qs = require('querystring');

server.on('request', function(req, res) {
    var params = qs.parse(req.url.substring(2));

    // 向前台写cookie
    res.writeHead(200, {
        'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly'   // HttpOnly:脚本无法读取
    });

    res.write(JSON.stringify(params));
    res.end();
});

server.listen('8080');
console.log('Server is running at port 8080...');

8. Nodejs中间件代理跨域

node中间件实现跨域代理,原理大致与nginx相同,都是通过启一个代理服务器,实现数据的转发,也可以通过设置cookieDomainRewrite参数修改响应头中cookie中域名,实现当前域的cookie写入,方便接口登录认证。

1、 非vue框架的跨域(2次跨域)

利用node + express + http-proxy-middleware搭建一个proxy服务器。

1.)前端代码示例:

var xhr = new XMLHttpRequest();

// 前端开关:浏览器是否读写cookie
xhr.withCredentials = true;

// 访问http-proxy-middleware代理服务器
xhr.open('get', 'http://www.domain1.com:3000/login?user=admin', true);
xhr.send();

2.)中间件服务器:

var express = require('express');
var proxy = require('http-proxy-middleware');
var app = express();

app.use('/', proxy({
    // 代理跨域目标接口
    target: 'http://www.domain2.com:8080',
    changeOrigin: true,

    // 修改响应头信息,实现跨域并允许带cookie
    onProxyRes: function(proxyRes, req, res) {
        res.header('Access-Control-Allow-Origin', 'http://www.domain1.com');
        res.header('Access-Control-Allow-Credentials', 'true');
    },

    // 修改响应信息中的cookie域名
    cookieDomainRewrite: 'www.domain1.com'  // 可以为false,表示不修改
}));

app.listen(3000);
console.log('Proxy server is listen at port 3000...');

3.)Nodejs后台同(六:nginx)

2、 vue框架的跨域(1次跨域)

利用node + webpack + webpack-dev-server代理接口跨域。在开发环境下,由于vue渲染服务和接口代理服务都是webpack-dev-server同一个,所以页面与代理接口之间不再跨域,无须设置headers跨域信息了。

webpack.config.js部分配置:

module.exports = {
    entry: {},
    module: {},
    ...
    devServer: {
        historyApiFallback: true,
        proxy: [{
            context: '/login',
            target: 'http://www.domain2.com:8080',  // 代理跨域目标接口
            changeOrigin: true,
            secure: false,  // 当代理某些https服务报错时用
            cookieDomainRewrite: 'www.domain1.com'  // 可以为false,表示不修改
        }],
        noInfo: true
    }
}

9. WebSocket协议跨域

WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。
原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。

1.)前端代码:

<div>user input:<input type="text"></div>
<script src="./socket.io.js"></script>
<script>
var socket = io('http://www.domain2.com:8080');

// 连接成功处理
socket.on('connect', function() {
    // 监听服务端消息
    socket.on('message', function(msg) {
        console.log('data from server: ---> ' + msg); 
    });

    // 监听服务端关闭
    socket.on('disconnect', function() { 
        console.log('Server socket has closed.'); 
    });
});

document.getElementsByTagName('input')[0].onblur = function() {
    socket.send(this.value);
};
</script>

2.)Nodejs socket后台:

var http = require('http');
var socket = require('socket.io');

// 启http服务
var server = http.createServer(function(req, res) {
    res.writeHead(200, {
        'Content-type': 'text/html'
    });
    res.end();
});

server.listen('8080');
console.log('Server is running at port 8080...');

// 监听socket连接
socket.listen(server).on('connection', function(client) {
    // 接收信息
    client.on('message', function(msg) {
        client.send('hello:' + msg);
        console.log('data from client: ---> ' + msg);
    });

    // 断开处理
    client.on('disconnect', function() {
        console.log('Client socket has closed.'); 
    });
});

1. 创建合并分支

1. 分支介绍

    git 每次提交都是一条时间线,称之为分支,在 git 这个分支为主分支 master 分支。

     HEAD 严格来说并不是指向提交,而是指向 master ,master 才是指向提交。所以 HEAD 指向当前分支。

2. 分支提交   

    当 git 创建分支,例如 dev 事,新建一个 dev 的指针,改 HEAD 指向 ,工作区的文件没有任何变化,不过从现在开始, 对工作区的修改和提交就针对 dev 分支了,如新的一次提交 dev 指针向前移步, 而 master 指针不变。

    之后我们就可以,合并分支删除 dev 

3. 代码练习

// 创建 dev 分支, 然后切换到 dev 分支;

    git checkout -b dev

// git checkout 命令加上 -b 参数表示创建并切换,相当于一下两条命令

    git branch dev

    git checkout dev

// 然后使用 git branch 查看分支,当前分支前面会有一个 * 号。

    git branch 

// 切换回 master 分支

    git checkout master

// 删除分支

    git branch -d dev

4. 冲突处理

// 合并分支

    git merge featurel

    $CONFLICT (content): Merge conflict in readme.txt
    $Automatic merge failed; fix conflicts and then commit the result.

告诉我们 readme.txt 文件发生冲突,必须手动解决冲突

// git status 也可以告诉我们冲突的文件

    git status 

$On branch master
$Your branch is ahead of ‘origin/master’ by 2 commits.
  (use “git push” to publish your local commits)

$You have unmerged paths.
  (fix conflicts and run “git commit”)
  (use “git merge –abort” to abort the merge)

$Unmerged paths:
  (use “git add <file>…” to mark resolution)

    both modified:   readme.txt

$no changes added to commit (use “git add” and/or “git commit -a”)

我们可以直接查看 readme.txt 内容

git 用 <<<<<<< ======= >>>>>>> 标记出不同分支内容,修改后保存

再提交:

    git add .

    git commit -m”修改文件”

// 用 git log 查看合并情况情况

    git log -graph -pretty=oneline -abbre-commit 

// 最后删除分支

    git branch -d featurel

    

       

按照公司要求,需要一个生成短信截图的程序,但是有些傻眼,是不是安排错了,作为一个有梦想前端当然不能为这点事情而难住。

程序应用自己不会写,重新学习成本太高,只能用 html 来写了。

在网上找了资料,发现 canvas  有个一 canvas.toDataURL(“image/png”)可以转换成 base64 编码,哈哈,这不就一半完成了!!!最大的难题已经解决。

市场上的一些js库,如:html2canvasdom-to-image,其本质也是通过toDataURL来转换成图片。

之后就是绘制 canvas 这里用到的一些简单的方法,然后用谷歌插件批量下载图片就搞定了!

【DOM 优化】

1. 缓存 DOM

const div = document.getElementById("div");

由于查询 DOM 比较耗时,在同一个节点无需多次查询的情况下,可以缓存 DOM

2. 减少 DOM 深度及 DOM 数量

HTML 中标签越多,标签的层级越深,浏览器解析 DOM 并绘制到浏览器中所花的时间就越长,所以尽可能减少 DOM 深度及 DOM 数量

3. 批量操作 DOM 

由于批量操作 DOM 比较耗时,且可能造成回流,因此尽量避免批量操作 DOM;如遇到批量操作 DOM 可以先用字符串拼接,再用 innerHTMl 更新

4. 批量操作 CSS 样式

通过切换 class 或者使用元素的 style.csstext 属性去批量操作元素的样式

5. 在内存中操作 DOM 

使用 DocumnetFragment 对象,让 DOM 操作发生在内存中,而不是页面里

6. DOM 元素离线更新

对 DOM 进行相关操作时,例、appendChild 等都可以使用 DocumentFragment 对象进行离线操作,带元素“组装”完成后再一次插入页面,或者使用 display: none 对元素隐藏,在元素“消失”后进行相关操作

7. DOM读写分离

浏览器具有惰性渲染机制,连续多次修改 DOM 可能只触发一次渲染。而如果修改 DOM 后,立即读取 DOM 。为了保证读取到的正确 DOM 值,会触发浏览器一次渲染。因此修改 DOM 操作要与访问 DOM 分开进行

8. 事件代理

事件代理是指将事件监听器注册在父级元素上,由于子元素的事件会通过事件冒泡的方式向上传播到父节点,因此可以由父节点的监听函数统一处理多个子元素的事件。

利用事件代理,可以减少内存使用,提高性能及降低代码复杂度。

9. 防抖和节流

使用函数节流(throttle)或函数去抖(debunce)限制某一个方法的频繁触发

10. 及时清理环境

及时消除对象引用,清除定时器,清除事件监听器,创建最小作用域变量,可以及时回收内存

减少重绘回流

【样式设置】

    1. 避免使用层级较深的选择器,或者其他一些复杂的选择器,以提高 CSS 渲染效率;

    2. 避免使用 CSS 表达式, CSS 表达式是动态设置 CSS 的属性,虽然强大方便,但是属于危险方法,它的问题就是在于计算频率很快。不仅仅是在页面显示和缩放时,就是在页面滚动,乃至移动鼠标时都会要重新计算一次;

    3. 元素适当地定义高度或者最小高度,否则元素的动态内容载入时,会出现页面元素的晃动或位置抖动,造成回流;

    4. 给图片设置尺寸, 如果图片不设置尺寸,首次载入时,占据空间从 0 到完全出现,上下左右都可能位移,发生回流;

    5. 不用使用 table 布局,因为一个小改动可能造成整个 table 重新布局,而且 table 渲染通常要 3 倍于同等元素时间

    6. 能够使用 CSS 实现的效果,尽量使用 CSS 而不使用 JS 实现;

【渲染层】

    1. 将需要多次重绘的元素独立为 render layer 渲染层, 如设置 absolute, 可以减少重绘范围;

    2. 对于一些进行动画的元素, 使用硬件渲染,从而避免重绘和回流; 

一、 简介

1. websocket 推送技术在 2008 诞生,2011 成为标准,现在所有浏览器都支持了。

2. 最大特点 是真正的双向平等对话,属于服务器推送技术的一种

扩展:服务器推送技术 

1. webpush 

Internet工程任务组的Webpush提议是一种使用HTTP版本2的简单协议,用于提供可以及时传递(或“推送”)的实时事件,例如传入呼叫或消息。该协议将所有实时事件合并到一个会话中,以确保更有效地使用网络和无线电资源。单个服务整合所有事件,在应用程序到达时将这些事件分发给应用程序。这只需要一个会话,避免重复的开销成本。

2. HTTP 服务器推送

HTTP服务器推送(也称为HTTP流)是一种用于将未经请求的(异步)数据从Web服务器发送到Web浏览器的机制。HTTP服务器推送可以通过几种机制中的任何一种来实现。

作为HTML5的一部分,WebSocket API允许Web服务器和客户端通过全双工 TCP连接进行通信。

3. pushlet 

在这种技术中,服务器利用持久的HTTP连接,使响应永久“打开”(即,服务器永远不会终止响应),有效地欺骗浏览器在初始页面加载被考虑之后保持“加载”模式完成。然后,服务器定期发送JavaScript片段以更新页面内容,从而实现推送功能。通过使用这种技术,客户端不需要Java applet或其他插件来保持与服务器的开放连接; 客户端会自动通知服务器推送的新事件。[11] [12]然而,这种方法的一个严重缺点是服务器缺乏对浏览器超时的控制; 如果浏览器端发生超时,则始终需要页面刷新。

4. 长轮询

5. Flash XMLSockt 中继

Cbox和其他聊天应用程序使用的这种技术在单像素Adobe Flash电影中使用了XMLSocket对象。在控制的JavaScript,客户端建立一个TCP连接到一个单向的服务器上的继电器。中继服务器不从该套接字读取任何内容; 相反,它立即向客户端发送唯一标识符。接下来,客户端发出HTTP请求到web服务器,包括它的这个标识符。然后,Web应用程序可以将发往客户端的消息推送到中继服务器的本地接口,中继服务器通过Flash套接字对它们进行中继。这种方法的优点在于它欣赏许多Web应用程序(包括聊天)的典型自然读写不对称性,因此它提供了高效率。因为它不接受即将离任的插座数据,中继服务器不需要轮询传出TCP连接,在所有的,从而能够容纳的并发连接打开数万人。在此模型中,规模限制是底层服务器操作系统的TCP堆栈。

6. 可靠的集团数据交付(RGDD)

在云计算等服务中,为了提高数据的可靠性和可用性,通常会将其推送(复制)到多台计算机上。例如,Hadoop分布式文件系统(HDFS)为存储的任何对象生成2个额外副本。RGDD专注于有效地将对象从一个位置转换为多个位置,同时通过在网络上的任何链接上发送对象的最小数量的副本(在最佳情况下只有一个)来节省带宽。例如,Datacast [15]是一种向数据中心内的许多节点传递的方案,它依赖于常规和结构化拓扑,DCCast [16]是一种跨数据中心交付的类似方法。

7. 推送通知

推送通知是从后端服务器或应用程序“推送”到用户界面的消息,例如(但不限于)移动应用程序和桌面应用程序。Apple于2009年首次推出推送通知。[17] 2010年,Google发布了自己的服务,即Google Cloud to Device Messaging。(此后它已被Google云消息传递Firebase云消息传递所取代。)[18] 2015年11月,微软宣布推出Windows通知服务将扩展为使用通用Windows平台架构,允许使用通用API调用和POST请求将推送数据发送到Windows 10,Windows 10移动版,Xbox以及其他支持的平台。[19]

特点包括:

(1)建立在 TCP 协议之上,服务器端的实现比较容易。

(2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。

(3)数据格式比较轻量,性能开销小,通信高效。

(4)可以发送文本,也可以发送二进制数据。

(5)没有同源限制,客户端可以与任意服务器通信。

(6)协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

ws://example.com:80/some/path


二、客户端示例

var ws = new WebSocket("wss://echo.websocket.org");

ws.onopen = function(evt) { 
  console.log("Connection open ..."); 
  ws.send("Hello WebSockets!");
};

ws.onmessage = function(evt) {
  console.log( "Received Message: " + evt.data);
  ws.close();
};

ws.onclose = function(evt) {
  console.log("Connection closed.");
};    


三、客户端 API

4.1 构造函数

var ws = new WebSocket('ws://localhost:8080');

4.2 webSocket.readyState

readyState 属性返回实例对象的当前状态,共有四种

CONNECTING:值为0,表示正在连接。
OPEN:值为1,表示连接成功,可以通信了。
CLOSING:值为2,表示连接正在关闭。
CLOSED:值为3,表示连接已经关闭,或者打开连接失败。

示例

switch (ws.readyState) {
  case WebSocket.CONNECTING:
    // do something
    break;
  case WebSocket.OPEN:
    // do something
    break;
  case WebSocket.CLOSING:
    // do something
    break;
  case WebSocket.CLOSED:
    // do something
    break;
  default:
    // this never happens
    break;
}

4.3 webSocket.onopen

实例对象的 onopen 属性,用于指定连接成功后的回调函数

ws.onopen = function () {
  ws.send('Hello Server!');
}

如果指定多个回调函数,可以使用 addEventListnenr 方法

ws.addEventListener('open', function (event) {
  ws.send('Hello Server!');
});

4.4 webSocket.onclose 

实例对象的 onclose 属性,用于指定连接关闭后的回调函数

ws.onclose = function(event) {
  var code = event.code;
  var reason = event.reason;
  var wasClean = event.wasClean;
  // handle close event
};

ws.addEventListener("close", function(event) {
  var code = event.code;
  var reason = event.reason;
  var wasClean = event.wasClean;
  // handle close event
});

4.5 webSocket.onmessage

 实例对象的 onmessage 属性,用于指定收到服务器数据后的回调函数

ws.onmessage = function(event) {
  var data = event.data;
  // 处理数据
};

ws.addEventListener("message", function(event) {
  var data = event.data;
  // 处理数据
});

注意,服务器数据可能是文本,也可能是二进制数据(blob 对象或 Arraybuffer 对象)

ws.onmessage = function(event){
  if(typeof event.data === String) {
    console.log("Received data string");
  }

  if(event.data instanceof ArrayBuffer){
    var buffer = event.data;
    console.log("Received arraybuffer");
  }
}

出来动态判断收到的数据类型,也可以使用 binaryType 属性,显式指定收到的二进制数据类

// Sending canvas ImageData as ArrayBuffer
var img = canvas_context.getImageData(0, 0, 400, 320);
var binary = new Uint8Array(img.data.length);
for (var i = 0; i < img.data.length; i++) {
  binary[i] = img.data[i];
}
ws.send(binary.buffer);

型。

// 收到的是 blob 数据
ws.binaryType = "blob";
ws.onmessage = function(e) {
  console.log(e.data.size);
};

// 收到的是 ArrayBuffer 数据
ws.binaryType = "arraybuffer";
ws.onmessage = function(e) {
  console.log(e.data.byteLength);
};

4.6 webSocket.sed()

实例对象的 send() 方法用于向服务器发送数据

发送文本的例子

ws.send('your message');

发送 Blob 对象的例子

var file = document
  .querySelector('input[type="file"]')
  .files[0];
ws.send(file);

发送 ArrayBuffer 对象的例子

// Sending canvas ImageData as ArrayBuffer
var img = canvas_context.getImageData(0, 0, 400, 320);
var binary = new Uint8Array(img.data.length);
for (var i = 0; i < img.data.length; i++) {
  binary[i] = img.data[i];
}
ws.send(binary.buffer);

4.7 webSocket.bufferedAmount

实例对象的bufferedAmount属性,表示还有多少字节的二进制数据没有发送出去。它可以用来判断发送是否结束。

var data = new ArrayBuffer(10000000);
socket.send(data);

if (socket.bufferedAmount === 0) {
  // 发送完毕
} else {
  // 发送还没结束
}

4.8 webSocket.onerror 

实例对象的onerror属性,用于指定报错时的回调函数。

socket.onerror = function(event) {
  // handle error event
};

socket.addEventListener("error", function(event) {
  // handle error event
});

1. oncontextmenu=”window.event.returnValue=false” 将彻底屏蔽鼠标右键 

    < table border oncontextmenu=return(false)>< td>no< /table> 可用于 Table 

2. < body onselectstart=”return false”> 取消选取、防止复制 
 
3. onpaste=”return false” 不准粘贴 
 
4. oncopy=”return false;” oncut=”return false;” 防止复制 
 
5. < link rel=”Shortcut Icon” href=”favicon.ico”> IE 地址栏前换成自己的图标 
 
6. < link rel=”Bookmark” href=”favicon.ico”> 可以在收藏夹中显示出你的图标 
 
7. < input style=”ime-mode:disabled”> 关闭输入法 
 
8. 永远都会带着框架 < script language=”JavaScript”>< !– if (window == top)top.location.href = “frames.htm”; //frames.htm 为框架网页 // –>< /script> 
 
9. 防止被人 frame < SCRIPT LANGUAGE=JAVASCRIPT>< !–  if (top.location != self.location)top.location=self.location; // –>< /SCRIPT> 
 
10. 网页将不能被另存为 

<noscript>< iframe src=*.html>< /iframe></noscript>  

11.  查看网页源代码

<input type=button value=查看网页源代码 onclick="window.location='view-source:' + window.location.href">

12.删除时确认 

 <a href="javascript:if(confirm('确实要删除吗?'))location='boos.asp?&areyou=删除&page=1'">删除</a>

13. 取得控件的绝对位置  // Javascript

    function getIE(e) {
        var t = e.offsetTop; var l = e.offsetLeft; while (e = e.offsetParent) {
            t += e.offsetTop; l += e.offsetLeft;  } alert("top=" + t + "/nleft=" + l);
    } 

//VBScript < script language=”VBScript”>< !– function getIE() dim t,l,a,b set a=document.all.img1 t=document.all.img1.offsetTop l=document.all.img1.offsetLeft while a.tagName< >”BODY” set a = a.offsetParent t=t+a.offsetTop l=l+a.offsetLeft wend msgbox “top=”&t&chr(13)&”left=”&l,64,”得到控件的位置” end function –>< /script> 
 

14. 光标是停在文本框文字的最后

    <input type=text name=text1 value=”123″ onfocus="cc()">
    <script language=”javascript”>
        function cc() {
            var e = event.srcElement;
            var r = e.createTextRange();
            r.moveStart("character", e.value.length);
            r.collapse(true);
            r.select();
        }
    </script>

15. 判断上一页的来源 

 document.referrer 

16. 最小化、最大化、关闭窗口 < object id=hh1 classid=”clsid:ADB880A6-D8FF-11CF-9377-00AA003B7A11″>  < param name=”Command” value=”Minimize”>< /object> < object id=hh2 classid=”clsid:ADB880A6-D8FF-11CF-9377-00AA003B7A11″>  < param name=”Command” value=”Maximize”>< /object> < OBJECT id=hh3 classid=”clsid:adb880a6-d8ff-11cf-9377-00aa003b7a11″> 
< PARAM NAME=”Command” VALUE=”Close”>< /OBJECT> < input type=button value=最小化 onclick=hh1.Click()> < input type=button value=最大化 onclick=hh2.Click()> < input type=button value=关闭 onclick=hh3.Click()> 本例适用于 IE 
 

17.屏蔽功能键 

 function look() {
      if (event.shiftKey) alert("禁止按 Shift 键!"); //可以换成 ALT CTRL 
 }
document.onkeydown = look;

 

18. 网页不会被缓存 

<META HTTP-EQUIV="pragma" CONTENT="no-cache"> <META HTTP-EQUIV="Cache-Control" CONTENT="no-cache, must-revalidate"> <META HTTP-EQUIV="expires" CONTENT="Wed, 26 Feb 1997 08:21:57 GMT"> 或者<META HTTP-EQUIV="expires" CONTENT="0"> 

19.怎样让表单没有凹凸感? 

<input type=text style="border:1 solid #000000">  
或 
<input type=text style="border-left:none; border-right:none; border-top:none; border-bottom: 1px solid #000000">< /textarea> 

 
20.< div>< span>&< layer>的区别?  < div>(division)用来定义大段的页面元素,会产生转行  < span>用来定义同一行内的元素,跟< div>的唯一区别是不产生转行  < layer>是 ns 的标记,ie 不支持,相当于< div> 
 

21.让弹出窗口总是在最上面: 

<body onblur="this.focus();"> 

22.不要滚动条?

//  让竖条没有:  
<body style="overflow:scroll;overflow-y:hidden">  </body> 
// 让横条没有: 
 <body style="overflow:scroll;overflow-x:hidden">  </body>  
// 两个都去掉?更简单了  
<body scroll="no">  </body>  

23.怎样去掉图片链接点击后,图片周围的虚线?

 <a href="#" onFocus="this.blur()"><img src="logo.jpg" border=0></a> 

24.电子邮件处理提交表单 

<form name="form1" method="post" action="mailto:****@***.com" enctype="text/plain">  
   <input type=submit> 
</form> 

 
25.在打开的子窗口刷新父窗口的代码里如何写? window.opener.location.reload()  
 
26.如何设定打开页面的大小 < body onload=”top.resizeTo(300,200);”> 打开页面的位置< body onload=”top.moveBy(300,200);”> 
 
27.在页面中如何加入不是满铺的背景图片,拉动页面时背景图不动  < STYLE>  body  {background-image:url(logo.gif); background-repeat:no-repeat;  background-position:center;background-attachment: fixed}  < /STYLE>  
 
28. 检查一段字符串是否全由数字组成 < script language=”Javascript”>< !– function checkNum(str){return str.match(//D/)==null} alert(checkNum(“1232142141”)) alert(checkNum(“123214214a1”)) // –>< /script> 
 
29. 获得一个窗口的大小 document.body.clientWidth; document.body.clientHeight 
 
30. 怎么判断是否是字符 if (/[^/x00-/xff]/g.test(s)) alert(“含有汉字”); else alert(“全是字符”); 
 
31.TEXTAREA 自适应文字行数的多少 < textarea rows=1 name=s1 cols=27 onpropertychange=”this.style.posHeight=this.scrollHeight”> < /textarea> 
 
32. 日期减去天数等于第二个日期 
< script language=Javascript> function cc(dd,dadd) { //可以加上错误处理 var a = new Date(dd) a = a.valueOf() a = a – dadd * 24 * 60 * 60 * 1000 a = new Date(a) alert(a.getFullYear() + “年” + (a.getMonth() + 1) + “月” + a.getDate() + “日”) } cc(“12/23/2002”,2) < /script> 
 
33. 选择了哪一个 Radio < HTML>< script language=”vbscript”> function checkme() for each ob in radio1 if ob.checked then window.alert ob.value next end function < /script>< BODY> < INPUT name=”radio1″ type=”radio” value=”style” checked>Style < INPUT name=”radio1″ type=”radio” value=”barcode”>Barcode < INPUT type=”button” value=”check” onclick=”checkme()”> < /BODY>< /HTML> 
 
34.脚本永不出错 < SCRIPT LANGUAGE=”JavaScript”>  < !– Hide  function killErrors() {  return true;  }  window.onerror = killErrors;  // –>  < /SCRIPT> 
 
35.ENTER 键可以让光标移到下一个输入框 <input onkeydown=”if(event.keyCode==13)event.keyCode=9″> 
 
36. 检测某个网站的链接速度: 把如下代码加入< body>区域中: < script language=Javascript> tim=1 setInterval(“tim++”,100) 
b=1 var autourl=new Array() autourl[1]=”www.njcatv.net” autourl[2]=”javacool.3322.net” autourl[3]=”www.sina.com.cn” autourl[4]=”www.nuaa.edu.cn” autourl[5]=”www.cctv.com” function butt(){ document.write(“< form name=autof>”) for(var i=1;i< autourl.length;i++) document.write(“< input type=text name=txt”+i+” size=10 value=测试中……> =》< input type=text  name=url”+i+” size=40> =》< input type=button value=GO  
onclick=window.open(this.form.url”+i+”.value)> “) document.write(“< input type=submit value=刷新>< /form>”) } butt() function auto(url){ document.forms[0][“url”+b].value=url if(tim>200) {document.forms[0][“txt”+b].value=”链接超时”} else {document.forms[0][“txt”+b].value=”时间”+tim/10+”秒”} b++ } function run(){for(var i=1;i< autourl.length;i++)document.write(“< img src=http://”+autourl+”/”+Math.random()+”  
 
width=1 height=1  
 
onerror=auto(“http://”+autourl+””)>”)} run()< /script> 
 
37. 各种样式的光标 auto :标准光标 default :标准箭头 hand :手形光标 wait :等待光标 text :I 形光标 vertical-text :水平 I 形光标 no-drop :不可拖动光标 not-allowed :无效光标 
help :?帮助光标 all-scroll :三角方向标 move :移动标 crosshair :十字标 e-resize n-resize nw-resize w-resize s-resize se-resize sw-resize 
 
38.页面进入和退出的特效 进入页面< meta http-equiv=”Page-Enter” content=”revealTrans(duration=x, transition=y)”> 推出页面< meta http-equiv=”Page-Exit” content=”revealTrans(duration=x, transition=y)”>   这个是页面被载入和调出时的一些特效。duration 表示特效的持续时间,以秒为单位。 transition 表示使用哪种特效,取值为 
 
1-23:   0 矩形缩小    1 矩形扩大    2 圆形缩小   3 圆形扩大    4 下到上刷新    5 上到下刷新   6 左到右刷新    7 右到左刷新    8 竖百叶窗   9 横百叶窗    10 错位横百叶窗    11 错位竖百叶窗   12 点扩散    13 左右到中间刷新    14 中间到左右刷新   15 中间到上下   16 上下到中间    17 右下到左上   18 右上到左下    19 左上到右下    20 左下到右上   21 横条    22 竖条    23 以上22 种随机选择一种 
 
39.在规定时间内跳转 < META http-equiv=V=”REFRESH” content=”5;URL=http://www.51js.com”>  
 
40.网页是否被检索 < meta name=”ROBOTS” content=”属性值”>   其中属性值有以下一些:   属性值为”all”: 文件将被检索,且页上链接可被查询;   属性值为”none”: 文件不被检索,而且不查询页上的链接;   属性值为”index”: 文件将被检索;   属性值为”follow”: 查询页上的链接;   属性值为”noindex”: 文件不检索,但可被查询链接;   属性值为”nofollow”: 文件不被检索,但可查询页上的链接。 
 
41.回车 用客户端脚本在页面添加document的onkeydown事件,让页面在接受到回车事件后,进行Tab 键的功能,即只要把 event 的 keyCode 由 13 变为 9 Javascript 代码如下: 
 
<script language=”javascript” for=”document” event=”onkeydown”> <!–   if(event.keyCode==13)      event.keyCode=9; –> </script> 这样的处理方式,可以实现焦点往下移动,但对于按钮也起同样的作用,一般的客户在输入完 资料以后,跳到按钮后,最好能直接按”回车”进行数据的提交.因此,对上面的方法要进行一下 修改,应该对于”提交”按钮不进行焦点转移.而直接激活提交. 因此我对上面的代码进行了一个修改,即判断事件的”源”,是否为提交按钮,代码如下: <script language=”javascript” for=”document” event=”onkeydown”> <!–   if(event.keyCode==13 && event.srcElement.type!=’button’ && event.srcElement.type!=’submit’ && event.srcElement.type!=’reset’ && event.srcElement.type!=’textarea’ && event.srcElement.type!=”)      event.keyCode=9; –> </script> 判断是否为 button, 是因为在 HTML 上会有 type=”button” 判断是否为 submit,是因为 HTML 上会有 type=”submit” 判断是否为reset,是因为 HTML 上的”重置”应该要被执行 判断是否为空,是因为对于 HTML 上的”<a>链接”也应该被执行,这种情况发生的情况不多,可 以使用”tabindex=-1″的方式来取消链接获得焦点. 

在 css 样式上 -webkit-box-orient:vertical;  加上注释, 

如下:

/* autoprefixer: off */
 -webkit-box-orient: vertical; 
 /* autoprefixer: on */

打包时必须使用这种方法打包,否则打包后  -webkit-box-orient: vertical 便会消失

网上解决方案是这样的,但是我在我的项目中发现不起作用,

解决方案

optimize-css-assets-webpack-plugin这个插件的问题
注释掉webpack.prod.conf.js中下面的代码

new OptimizeCSSPlugin({
  cssProcessorOptions: config.build.productionSourceMap
    ? { safe: true, map: { inline: false } }
    : { safe: true }
}),

组件传值

1. 父向子组件传值,和传递方法

父组件 app-home 

子组件 app-header

// — app-home.html 页面

<app-header [msg]="msg" [run]='run' [getDataFromChild]="getDataFromChild"></app-header>


// — app-home.ts 

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-home',
  templateUrl: './app-home.html',
  styleUrls: ['./app-home.css']
})
export class HomeComponent implements OnInit {

  public msg="我是home组件的msg";

  constructor() { }

  ngOnInit() {
  }

  run(){

    alert('这是home组件的run方法');
  }

  getDataFromChild(childData){  /*父组件*/
    alert(childData+"1111");
  }

}


// — app.header.ts 页面

import { Component, OnInit,Input} from '@angular/core';

@Component({
  selector: 'app-header',
  templateUrl: './app-header.html',
  styleUrls: ['./app-header.css']
})
export class FooterComponent implements OnInit {

  @Input()  msg;


  @Input()  run;  /*接收父组件传过来的run方法*/

  @Input()  getDataFromChild; 


  public msginfo='这是子组件的数据';

  constructor() { }

  ngOnInit() {
  }

  sendParent(){  /*子组件自己的方法*/
      this.getDataFromChild(this.msginfo);  /*子组件调用父组件的方法*/

  }

}

1.2 子组件用广播方式调用父组件方法

// — app-home.html

<app-footer [msg]="msg" [run]='run' (toparent)="getDataFromChild"></app-footer>

// — app-home.ts 

import { Component, OnInit } from '@angular/core';
import {Http,Jsonp} from "@angular/http";
@Component({
  selector: 'app-news',
  templateUrl: './app-home.html',
  styleUrls: ['./app-home.css']
})
export class NewsComponent implements OnInit {
   public msg="msg";
   public name="张三";

   public list=[];
  constructor(private http:Http,private jsonp:Jsonp) { }

  ngOnInit() {
  }

  getDataFromChild(childData){  /*父组件*/
    alert(childData+"1111");
  }
}

//— app-header.ts

import { Component, OnInit,Input,Output,EventEmitter } from '@angular/core';

@Component({
  selector: 'app-header',
  templateUrl: './app-header.html',
  styleUrls: ['./app-header.css']
})
export class HeaderComponent implements OnInit {

   @Input()  msg:string;  /*通过 Input  接收父组件传过来的msg*/

   @Input()  name:string; 


   //EventEmitter实现子组件给父组件传值

   @Output() toparent=new EventEmitter();

  constructor() { }
  ngOnInit() {}

  //requestData

  requestData(){

    //调用父组件的方法请求数据

    this.toparent.emit('这是子组件的值');
  }

}

2. 父组件获取子组件值

// app-home.html

<app-header #header></app-header>

// app-home.ts

//ViewChild
import { Component, OnInit, ViewChild } from "@angular/core";

@Component({
  selector: "app-home",
  templateUrl: "./app-home.html",
  styleUrls: ["./app-home.css"]
})
export class ProductComponent implements OnInit {
  @ViewChild("header") header; /*定义子组件 注意括号里面的东西和 #header 对应起来  */

  constructor() {}

  ngOnInit() {}

  getChildData() {
    // this.header.run();   /*执行子组件的方法*/

    alert(this.header.msg); /*获取子组件的数据*/

    //  alert(this.header.name);

    this.header.name = "我是父组件改变后的cart name";
  }
}

// app-header.ts

import { Component, OnInit } from "@angular/core";

@Component({
  selector: "app-header",
  templateUrl: "./app-header.html",
  styleUrls: ["./app-header.css"]
})
export class CartComponent implements OnInit {
  constructor() {}

  public msg = "我是子组件的数据";

  public name = "子组件";

  ngOnInit() {}

  run() {
    alert("这是子组件的方法11");
  }
}

路由之间传参

在网上找到一个不错的写的,分享给大家:

一般我们页面跳转传递参数都是这样的格式:

http://angular.io/api?uid=1&username=moon

但是在SPA单页应用中却是下面的结果居多【初级视频都是这样敷衍的】

http://angular.io/api/1/moon

一、 地址栏传参

实现从product页面跳转到product-detail页面

1:在app-routing.module.ts中配置路由。

const routes: Routes = [
{
    path: 'product',
    component: ProductComponent,
 },
 {
    path: 'product-detail',
    component: ProductDetailComponent,
  }
];

2:在app-routing.module.ts中配置路由。

constructor(
    private router: Router,   //这里需要注入Router模块
){}
 
jumpHandle(){
    //这是在html中绑定的click跳转事件
    this.router.navigate(['product-detail'], {
        queryParams: {
            productId: '1',
            title: 'moon'
        }
    });
}


3:在product-detail.ts中获取传递过来的参数productId、title

constructor(
    private activatedRoute: ActivatedRoute,   //这里需要注入ActivatedRoute模块
) {
    activatedRoute.queryParams.subscribe(queryParams => {
        let productId = queryParams.productId;
        let title = queryParams.title;
    });
}


二、 在查询中传递参数

 



//传递数据
<a [routerLink]="['/stock']" [queryParams]="{id: 1}">股票详情</a>
// http://localhost:4200/stock?id=1



// 接受参数
import { ActivatedRoute } from '@amgular/router';

export class StockComponent implements OnInit {

    private stockId: number;    
    
    constructor(private routeInfo: ActivatedRoute)
    
    ngOnInit() {
        this.stockId = this.routeInfo.snapshot.queryParams['id'];
    }
    
}

三、 在路径中传递参数

//修改配置
const routes: Routes = [
  {path: '', redirectTo: '/index', pathMatch: 'full'},
  {path: 'index', component: IndexComponent},
  {path: 'stock/:id', component: StocksComponent },
  {path: '**', component: ErrorPageComponent }
];


//传递数据
...
<a [routerLink]="['/stock', 1]">股票详情</a>
// http://localhost:4200/stock/1



//接受参数
...
import { ActivatedRoute } from '@amgular/router';

export class StockComponent implements OnInit {

    private stockId: number;    
    
    constructor(private activedRoute: ActivatedRoute)
    
    ngOnInit() {
        this.stockId = this.activedRoute.snapshot.params['id'];
    }
    
}

使用snapshot快照的方式传递数据,因为初始化一次,路由到自身不能传递参数,需要使用订阅模式。

this.activedRoute.params.subscribe((params: Params) => this.stockId = params['id']);

 

四、在路由中配置

//路由配置配置
const routes: Routes = [
  {path: '', redirectTo: '/index', pathMatch: 'full'},
  {path: 'index', component: IndexComponent, data: {title: 'Index Page'}},
  {path: 'stock/:id', component: StocksComponent, data: {title: 'Stock Page'}},
  {path: '**', component: ErrorPageComponent, data: {title: 'Stock Page'}}
];

//接受参数
this.title = this.routeInfo.snapshot.date[0]['title'];

最近学习了 angular 表单。

1. 表单绑定数据,目前有两种方法:

    1. [(ngModel)]=”name”;  // 双向绑定属性值

    2. formControlNam=”name”;  // 按名称FormControl将现有FormGroup的a 同步到表单控件元素。

例如:

import {Component} from '@angular/core';
import {FormControl, FormGroup, Validators} from '@angular/forms';
 
@Component({
  selector: 'example-app',
  template: `
    <form [formGroup]="form" (ngSubmit)="onSubmit()"> <!-- 这里必须写 [formGroup]="form" 绑定下方 new FormGroup()  -->
      <div *ngIf="first.invalid"> Name is too short. </div>
 
      <input formControlName="first" placeholder="First name"> <!-- formControlName 绑定的是下方 new FormGroup() 中 first -->
      <input formControlName="last" placeholder="Last name">
 
      <button type="submit">Submit</button>
   </form>
   <button (click)="setValue()">Set preset value</button>
  `,
})
export class SimpleFormGroup {
  form = new FormGroup({
    first: new FormControl('Nancy', Validators.minLength(2)),  last: new FormControl('Drew'), // 'Nancy' 是 值, validators.minLength(2) 是angular默认表单验证,也可以自定义验证
  });
 
  get first(): any { return this.form.get('first'); } // 必须写 这个方法,之后才可以获取到表单的值
 
  onSubmit(): void {
    console.log(this.form.value);  // {first: 'Nancy', last: 'Drew'} // 获取 值
  }
 
  setValue() { this.form.setValue({first: 'Carson', last: 'Drew'}); } // 改变 first 与 last 表单的值
}

app.module.ts 引入必须 

import { FormsModule, ReactiveFormsModule } from “@angular/forms”;

否者 不可以用 formControlName

注:formControlName 与 ngModel 使用

在Angular v6中不支持将ngModel输入属性和ngModelChange事件与反应式表单指令一起使用,并且将在Angular v7中删除。

现已弃用:

content_copy<form [formGroup]="form"> <input formControlName="first" [(ngModel)]="value"> </form> <!-- 不要一起用了 -->
content_copythis.value = 'some value';

由于一些原因,这已被弃用。首先,开发人员发现这种模式令人困惑。看起来似乎ngModel正在使用实际的指令,但实际上它是ngModel在反应式表单指令上命名的输入/输出属性,它简单地近似(某些)其行为。具体来说,它允许获取/设置值和拦截值事件。然而,ngModel其他一些功能 – 比如延迟更新ngModelOptions或导出指令 – 根本不起作用,这可以理解地引起一些混乱。

此外,这种模式混合了模板驱动和反应形式策略,我们通常不建议这样做,因为它没有利用这两种策略的全部好处。在模板中设置值违反了反应形式背后的模板无关原则,而在类中添加FormControlFormGroup层则消除了在模板中定义表单的便利性。

类型

var a = 1; 隐式; var a:number = 1; 显示声明

var arr = []

类型:

基本类型:

联合类型:—– 选择

函数签名:

success:(a, b)=>void;

复合类型(Object Type):

var a:{x:xx, y:xx};

number, string, boolean, null, undefined, enum, any;

函数类型:

  1. 参数

    • 函数签名
  2. 返回值

function show(a:number, b:number)


外部变量

declare var $;


接口-interface

  • 和 java 不同
  • 接口:约定、限制;

真面向对象

class 写法, extend, 多继承

访问修饰符:

  1. public 共有 任何人可以访问
  2. private 私有 只有类内部可以访问
  3. protected 受保护-友元 只有子类可以访问

泛型– 别跟 “any” 搞混了 泛– 宽泛

class Calc<T> {
    a:T;
    b:T;
    show (c  :T) {
        alert(a + b + c)
    }
} var obj = new Calc<number>();

obj.a = 12;
obj.b = 'abc'; // 报错 }

Array — 泛型

interface Array<T> { 

    reverse() : T[]; 

    sort(compare?: a(a:T, b:T) =>number):T[];

    …

}

// 泛型:类型当参数传入

数组完成写法:var arr:Array<number> = new Array<number>();

layout 响应式布局工具

  • Breakpoints 屏幕尺寸
  • BreakpointObserver 观察器
  • MediaMatcher 媒体查询匹配

import { Component, OnInit } from "@angular/core";
import {
  BreakpointObserver,
  BreakpointState,
  MediaMatcher,
  Breakpoints
} from "@angular/cdk/layout";
import { Observable } from "rxjs/Observable";
@Component({
  selector: "layout",
  templateUrl: "./layout.component.html",
  styleUrls: ["./layout.component.scss"]
})
export class LayoutComponent implements OnInit {
  isHandset: Observable<BreakpointState>;
  constructor(public breakpointObserver: BreakpointObserver, public media: MediaMatcher) {}

  ngOnInit() {
    // 手持设备
    let Handset = Breakpoints.Handset;
    // 手持landscape屏
    let HandsetLandscape = Breakpoints.HandsetLandscape;
    //手持portrait屏
    let HandsetPortrait = Breakpoints.HandsetPortrait;
    // 多媒体
    let Medium = Breakpoints.Medium;
    // 平板电脑
    let Tablet = Breakpoints.Tablet;
    // 平板电脑 Landscape
    let TabletLandscape = Breakpoints.TabletLandscape;
    // 平板电脑 Portrait
    let TabletPortrait = Breakpoints.TabletPortrait;
    // web
    let Web = Breakpoints.Web;
    // web landscape
    let WebLandscape = Breakpoints.WebLandscape;
    // web portrait
    let WebPortrait = Breakpoints.WebPortrait;
    // 大屏幕
    let Large = Breakpoints.Large;
    // 更大屏幕
    let XLarge = Breakpoints.XLarge;
    // 更小屏幕
    let XSmall = Breakpoints.XSmall;
    // 小屏幕
    let Small = Breakpoints.Small;

    // 是否满足多个条件
    this.breakpointObserver
      .observe([
        Handset,
        HandsetLandscape,
        Handset,
        Medium,
        Tablet,
        TabletLandscape,
        TabletPortrait,
        Web,
        WebLandscape,
        WebPortrait,
        Large,
        XLarge,
        Small,
        XSmall
      ])
      .subscribe(res => {
        console.log(res);
      });
      this.breakpointObserver
      .observe(Breakpoints.Handset)
        .subscribe((state: BreakpointState) => {
        if (state.matches) {
          console.log('手机设备尺寸内');
        } else {
          console.log('非手机设备尺寸内');
        }
      });

  }
}