javascript技巧及总结

之前的一些关于js的博客

1. 关键字

1.1. this的使用场景

简单说:谁调用,谁this

//1.构造函数
function Car(name,color){
	this.name = name;
	this.color = color;
}
//2.事件中代表元素
  • 作为纯粹的函数调用this指向全局对象。如:(function(){console.log(this)}())打印结果是window
  • this是一个关键字,它代表函数运行时,自动生成一个内部对象,只能在函数内部使用
  • 作为对象的方法调用this指向调用对象
  • 作为构造函数被调用this指向新的对象(new会改变this的指向)
  • apply调用this指向apply方法的第一个参数
  • this总是指向函数的直接调用者(而并非间接调用者);
  • 如果有new关键字,this指向new出来的那个对象;
  • 在事件中,this指向这个事件的对象,特殊的是,IE中的attachEvent中的this总是指向全局对象Window

1.2. new

使用new时,

  1. 创建一个空对象
  2. this变量引用该对象,同时还继承了该函数的原型。
  3. 属性和方法被加入到this所引用的对象中,即执行构造函数。
  4. 新创建的对象由this所引用,并且最后隐式的返回this。

当代码 new Foo(…) 执行时,会发生以下事情:

  • 一个继承自 Foo.prototype 的新对象被创建。
  • 使用指定的参数调用构造函数 Foo ,并将 this 绑定到新创建的对象。new Foo等同于 new Foo(),也就是没有指定参数列表,Foo 不带任何参数调用的情况。
  • 由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤1创建的对象。(一般情况下,构造函数不返回值,但是用户可以选择主动返回对象,来覆盖正常的对象创建步骤)

你始终可以对已定义的对象添加新的属性。但是,这不会影响任何其他对象。要将新属性添加到相同类型的所有对象,你必须将该属性添加到Car对象类型的定义中。

你可以使用 Function.prototype 属性将共享属性添加到以前定义的对象类型。这定义了一个由该函数创建的所有对象共享的属性,而不仅仅是对象类型的其中一个实例。

例1:如下,

function fn() {
    this.a = 0;
    this.b = function() {
        alert(this.a)
    }
}
fn.prototype = {
    b: function() {
        this.a = 20;
        alert(this.a);
    },
    c: function() {
        this.a = 30;
        alert(this.a);
    }
}
var myfn = new fn();
myfn.b();
myfn.c();

解析:
这个例子主要是 原型链的考察
首先,this指向的都是myfn。
然后b()在fn(),及fn.prototype中都存在,所以将按照原型链进行追溯
c()则不同,在fn()中没有找到,所以,追溯prototype。

1.3. delete

总的来说,delete不能删除直接对象(如let/var/const声明的),也无法删除不可配置的属性(设置属性的configurable为false)

与通常的看法不同,delete操作符与直接释放内存无关。内存管理通过断开引用来间接完成的,查看内存管理页可了解详情。

delete 操作符会从某个对象上移除指定属性。成功删除的时候回返回 true,否则返回 false。但是,以下情况需要重点考虑:

  • 如果你试图删除的属性不存在,那么delete将不会起任何作用,但仍会返回true
  • 如果对象的原型链上有一个与待删除属性同名的属性,那么删除属性之后,对象会使用原型链上的那个属性(也就是说,delete操作只会在自身的属性上起作用
  • 任何使用 var 声明的属性不能从全局作用域或函数的作用域中删除。

    - 这样的话,delete操作不能删除任何在全局作用域中的函数(无论这个函数是来自于函数声明或函数表达式)

    - 除了在全局作用域中的函数不能被删除,在对象(object)中的函数是能够用delete操作删除的。
  • 任何用let或const声明的属性不能够从它被声明的作用域中删除。
  • 不可设置的(Non-configurable)属性不能被移除。这意味着像Math, Array, Object内置对象的属性以及使用Object.defineProperty()方法设置为不可设置的属性不能被删除。

1.3.1. 例子

例1

//当一个属性被设置为不可设置,delete操作将不会有任何效果,并且会返回false。在严格模式下会抛出语法错误(SyntaxError)。
var Employee = {};
Object.defineProperty(Employee, 'name', {configurable: false});
console.log(delete Employee.name);  // returns false

//var, let以及const创建的不可设置的属性不能被delete操作删除。
var nameOther = 'XYZ';

// 通过以下方法获取全局属性:
Object.getOwnPropertyDescriptor(window, 'nameOther');  

// 输出: Object {value: "XYZ", 
//                  writable: true, 
//                  enumerable: true,
//                  configurable: false}

// 因为“nameOther”使用var关键词添加,
// 它被设置为不可设置(non-configurable)
delete nameOther;   // return false
// 在全局作用域创建 adminName 属性
adminName = 'xyz';            

// 在全局作用域创建 empCount 属性
// 因为我们使用了 var,它会标记为不可配置。同样 let 或 const 也是不可配置的。
var empCount = 43;

EmployeeDetails = {
  name: 'xyz',
  age: 5,
  designation: 'Developer'
};

// adminName 是全局作用域的一个属性。
// 因为它不是用 var 创建的,所在可以删除。
// 因此,它是可配置的。
delete adminName;       // 返回 true

// 相反,empCount 是不可配置的, 
// 因为创建它时使用了 var。
delete empCount;       // 返回 false 

// delete 可用于删除对象的属性
delete EmployeeDetails.name; // 返回 true 

// 甚至属性不存在,它也会返回 "true"
delete EmployeeDetails.salary; // 返回 true 

// delete 对内建静态属性不起作用
delete Math.PI; // 返回 false 

// EmployeeDetails 是全局作用域的一个属性。
// 因为定义它的时候没有使用 "var",它被标记为可配置。
delete EmployeeDetails;   // 返回 true

function f() {
  var z = 44;

  // delete 对局部变量名不起作用
  delete z;     // 返回 false
}

例子2

var output = (function(x){
    delete x;
    return x;
})(0);
  
console.log(output);

解析

输出是 0。 delete 操作符是将object的属性删去的操作。但是这里的 x 是并不是对象的属性, delete 操作符并不能作用。

例子3

var x = { foo : 1};
var output = (function(){
    delete x.foo;
    return x.foo;
})();

console.log(output);

解析

输出是 undefined。x虽然是全局变量,但是它是一个object。delete作用在x.foo上,成功的将x.foo删去。所以返回undefined

例子4

ar Employee = {
    company: 'xyz'
}
var emp1 = Object.create(Employee);
delete emp1.company
console.log(emp1.company);

解析

输出是 xyz,这里的 emp1 通过 prototype 继承了 Employee的 company。emp1自己并没有company属性。所以delete操作符的作用是无效的。

例子5

var trees = ["xyz","xxxx","test","ryan","apple"];
delete trees[3];
  
console.log(trees.length);
//输出是5。因为delete操作符并不是影响数组的长度。

2. 数据类型

2.1. 类型转换

例子1

console.log(1 +  "2" + "2");//122
console.log(1 +  +"2" + "2");//32,因为+"2"中的+为一元运算符,所以+"2"转为了2
console.log(1 +  -"1" + "2");//02,同上
console.log(+"1" +  "1" + "2");//112
console.log( "A" - "B" + "2");//NaN2
console.log( "A" - "B" + 2);//NaN

解析

这里的根本问题是,JavaScript(ECMAScript)是一种弱类型语言,它可对值进行自动类型转换,以适应正在执行的操作

例子2

//隐式类型转换
console.log(false == '0')//true
console.log(false === '0')//false

2.2. undefined

undefined 和 not defined 的区别

JavaScript 未声明变量直接使用会抛出异常:var name is not defined,如果没有处理异常,代码就停止运行了。
但是,使用typeof undeclared_variable并不会产生异常,会直接返回 undefined。

undefined和null的区别

undefined说明没有这个属性(no house);null表示有属性,但这个属性没值(家徒四壁)

2.3. 类型校验

2.3.1. typeof的使用。

值得注意的时候typeof null的结果为object
typeof的结果如下
number,boolean,string,function,object,undefined

typeof ''==='string';
typeof('') === 'string';
''.constructor===String
typeof null //object

2.3.2. toString的使用

var arr = [1,'abc',true,new Function(),null,void 0,{},[]];
var obj = {};arr.map((item)=>{console.log(toString.call(item))});
//[object Number]
//[object String]
//[object Boolean]
//[object Function]
//[object Null]
//[object Undefined]
//[object Object]
//[object Array]
funcion Func(){}
var f=new Func();
toString.call(f);//[object Object]

2.3.3. instanceof的使用

``

2.4. 关键数据类型

2.4.1. Object API

例子1

var a={},
    b={key:'b'},
    c={key:'c'};
 
a[b]=123;
a[c]=456;
 
console.log(a[b]);//456

解析

当设置对象属性时,JavaScript会暗中字符串化参数值。在这种情况下,由于 b 和 c都是对象,因此它们都将被转换为"[object Object]"。结果就是, a[b]和a[c]均相当于a["[object Object]"] ,并可以互换使用。因此,设置或引用 a[c]和设置或引用 a[b]完全相同。

2.4.1.1. Object.freeze()

在vue的源码中看到如下一行代码引起了我的好奇

export const emptyObject = Object.freeze({})

查阅MDN后备忘如下:

Object.freeze() 方法可以冻结一个对象,冻结指的是不能向这个对象添加新的属性,不能修改其已有属性的值,不能删除已有属性,以及不能修改该对象已有属性的可枚举性、可配置性、可写性。也就是说,这个对象永远是不可变的。该方法返回被冻结的对象。

要使对象不可变,需要递归冻结每个类型为对象的属性(深冻结)。当你知道对象在引用图中不包含任何 环 (循环引用)时,将根据你的设计逐个使用该模式,否则将触发无限循环。对 deepFreeze() 的增强将是具有接收路径(例如Array)参数的内部函数,以便当对象进入不变时,可以递归地调用 deepFreeze() 。你仍然有冻结不应冻结的对象的风险,例如[window]。

2.4.1.2. Object.keys

Object.keys() 方法会返回一个由一个 给定对象的自身可枚举属性*组成的数组,数组中属性名的排列顺序和使用 for…in 循环遍历该对象时返回的顺序一致 ( 两者的主要区别是 一个 for-in 循环还会枚举其原型链上的属性)。

MSDN上的解释很清楚,不做阐述。示例如下

var obj = {a:1,b:2};
console.log(1,Object.keys(obj));//1 Array[2] , 0:'a',1:'b'
Object.prototype.c=3;
console.log(2,Object.keys(obj));//2 Array[2] , 0:'a',1:'b'
for(key in obj){
	console.log(3,key);//3 "a" 3 "b" 3 "c"
}
//由上述代码可验证MDN说明中Object.keys返回的是"给定对象的自身可枚举属性"

2.4.2. String API

concat() – 将两个或多个字符的文本组合起来,返回一个新的字符串。
indexOf() – 返回字符串中一个子串第一处出现的索引。如果没有匹配项,返回 -1 。
charAt() – 返回指定位置的字符。
lastIndexOf() – 返回字符串中一个子串最后一处出现的索引,如果没有匹配项,返回 -1 。
match() – 检查一个字符串是否匹配一个正则表达式。
substr() 函数 -- 返回从string的startPos位置,长度为length的字符串
substring() – 返回字符串的一个子串。传入参数是起始位置和结束位置。
slice() – 提取字符串的一部分,并返回一个新字符串。
replace() – 用来查找匹配一个正则表达式的字符串,然后使用新字符串代替匹配的字符串。
search() – 执行一个正则表达式匹配查找。如果查找成功,返回字符串中匹配的索引值。否则返回 -1 。
split() – 通过将字符串划分成子串,将一个字符串做成一个字符串数组。
length – 返回字符串的长度,所谓字符串的长度是指其包含的字符的个数。
toLowerCase() – 将整个字符串转成小写字母。
toUpperCase() – 将整个字符串转成大写字母。

2.4.3. Array api

2.4.3.1. 例子
2.4.3.1.1. 清空数组
var arrayList = ['a','b','c','d','e','f'];
//1
arrayList = [];
//2
arrayList.length=0;
2.4.3.1.2. reverse
var arr1 = "john".split('');
var arr2 = arr1.reverse();
var arr3 = "jones".split('');
arr2.push(arr3);
console.log("array 1: length=" + arr1.length + " last=" + arr1.slice(-1));
console.log("array 2: length=" + arr2.length + " last=" + arr2.slice(-1));
//输出结果是:
"array 1: length=5 last=j,o,n,e,s"
"array 2: length=5 last=j,o,n,e,s"

解析

  • 调用数组对象的 reverse() 方法并不只返回反顺序的阵列,它也反转了数组本身的顺序(即,在这种情况下,指的是 arr1)。
     
  • reverse() 方法返回一个到数组本身的引用(在这种情况下即,arr1)。其结果为,arr2 仅仅是一个到 arr1的引用(而不是副本)。因此,当对 arr2做了任何事情(即当我们调用 arr2.push(arr3);)时,arr1 也会受到影响,因为 arr1 和 arr2 引用的是同一个对象。
     
  • 传递数组到另一个数组的 push() 方法会让整个数组作为单个元素映射到数组的末端。其结果是,语句 arr2.push(arr3); 在其整体中添加 arr3 作为一个单一的元素到 arr2 的末端(也就是说,它并没有连接两个数组,连接数组是 concat() 方法的目的)。
     
  • JavaScript标榜数组方法调用中的负数下标,例如 slice() 可作为引用数组末尾元素的方法:例如,-1下标表示数组中的最后一个元素
2.4.3.1.3. 数组去重
//1.使用filter
[1,2,2,4,9,6,7,5,2,3,5,6,5].filter((item,index,arr)=>{return arr.indexOf(item)===index});//[1, 2, 4, 9, 6, 7, 5, 3]
//2.使用object,利用Object的key不重复性.但这种方法影响原有顺序
var obj = {};[1,2,2,4,9,6,7,5,2,3,5,6,5].filter((item,index,arr)=>{obj[item]=null;})
Object.keys(obj)
//3.还有就是

3. 技巧

搜集了一些有意思的东西。

参考:js代码常见技巧总结

3.1. 清空数组

可以使用arr.length=0来清空数组,但是需要注意时间问题。实际测试arr.length=0耗时较长。如下所示,时间差了将近7倍。这也许就是因为arr.length=0是真的清空了原本的数组对象耗费了时间,而arr=[]并没有清空。

//使用arr=[]来清空
//但这种方法有严重缺陷!它实际上并没有清空原本的数组对象! 
//它创建了一个新的、空的数组对象[],然后将它赋给原本的**数组变量**array,因此原本非空的数组对象并没有清空,而是等待垃圾回收机制处理。
var b=Date.now(),arr = [1,2,3,4,5];
for(var i=0;i<100000000;i++){
	arr=[];
}
var e=Date.now();
console.log(e-b)//2158

//使用arr.length=0来清空
var b=Date.now(),arr = [1,2,3,4,5];
for(var i=0;i<100000000;i++){
	arr.length=0;
}
var e=Date.now();
console.log(e-b)//14626

3.2. 大括号的使用

大括号的使用主要是2个方面:

3.2.1. 第1,不要省略大括号,即使可以忽略

比如:

for(var i =1;i<10 ;i++)
    console.log(i) //此处原则上可以忽略大括号

上述语句并没有问题,但是如果后期函数体内增加了其他语句的时候,很容易忘记补上大括号,因此建议都带上大括号;

3.2.2. 第2,大括号的必须跟在前一个语句的同一行

这个问题非常容易被忽略,通常我们都觉得大括号是跟在语句的同一行还是下一行只是习惯问题,但是实际上不是的!看下面这个例子:

function func(){
  return 
  {
    name:'xxx'
  }
}
var res = func()
console.log(res)//输出undefined

是不是觉得很奇怪,看代码第一感觉应该是输出一个包含name属性的对象。请注意,由于js的 分号插入机制:如果语句没有使用分号结束,会自动补充分号。因此上面的代码实际相当于如下写法:

function func(){
  return undefined;//自动插入分号
  {
    name:'xxx'
  }
}

正确的写法应该是:

function func(){
  return {
    name:'xxx'
  }
}
var res = func()
console.log(res)//输出{name:'xxx'}

3.3. 避免链式声明

如下代码

function test(){
 //这里链式声明变量v1/v2
 //但是因为运算符优先级问题,实际执行顺序为:var v1 = (v2 = 0)
 //这样v2就成为了全局变量
 var v1 = v2 = 0;
 console.log(v1,v2);
}

test()//0 0
console.log(v1)//Uncaught ReferenceError: v1 is not defined(…)
console.log(v2)//0	这就证明了v2已经成为了全局变量

3.4. 单一var原则

这个主要是因为在js中存在变量提升(es6以前,es6中不再存在该问题),以及由变量提升带来的其它问题

function test(){
 console.log(v3);//
 var v3 = 0;
 console.log(v3);
}

test()
//第一个打印这里本应该是报错Uncaught ReferenceError: v3 is not defined(…),
//但是因为存在变量提升,所以实际执行顺序为
//var v3;
//console.log(v3)
//v3 = 0;
//console.log(v3)
//所以打印结果变成了undefined了
//undefined
//0

3.5. 使用for循环,缓存长度

特别是dom遍历,这样做效率更好

function test(){
 var arr = [];
 for(var len = arr.length,i=0;i<len;i++){
  //do something
 }/
}

3.6. 使用for…in的同时,使用hasOwnProperty

这个主要是因为for…in循环会循环原型链上的内容,使用hasOwnProperty可以减少 不必要的麻烦.当然也可以使用Object.keys()/Object.prototype.values()[上述两个方法不包括原型链和不可枚举的]/Object.getOwnPropertyNames()[不包括原型链,但是包括不可枚举的]

3.7. 使用===代替==

类型的隐式转换问题

3.8. 使用parseInt()带上第二个参数

避免以0开头的字符串被错认为八进制(es3)

3.9. 标准内置对象的分类

标准内置对象的分类

3.9.1. 值属性

这些全局属性返回一个简单值,这些值没有自己的属性和方法。

  • Infinity
  • NaN
  • undefined
  • null 字面量

3.9.2. 函数属性

全局函数可以直接调用,不需要在调用时指定所属对象,执行结束后会将结果直接返回给调用者。

  • eval()
  • uneval()
  • isFinite()
  • isNaN()
  • parseFloat()
  • parseInt()
  • decodeURI()
  • decodeURIComponent()
  • encodeURI()
  • encodeURIComponent()

3.10. isFinite()

Finite指的是有限数。因此,当isFinite()的参数是一个大于Number.MIN_VALUE小于Number.MAX_VALUE的数字、或者是可以转换为这个区间的数字时,isFinite()将返回true,否则返回false

3.11. void

javascript中void
void该运算符指定要计算一个表达式但是不论该表达式原来是否有自己的返回值,其返回值都为undefined。void运算符的操作数可以是任意类型。

返回undefined,(对于为什么不直接使用undefined,是因为undefined不是关键字,意味着它随时可能被篡改成其他值。。。)。

3.11.1. void 0

在es5中有一些有特殊意义的字符串,如undefinednullInfinityNaN等。在window作用域下,这些都是只读的、不可更改的,但是在函数作用域下,却可以更改。如下:

//在window作用域下
Infinity=1
console.log(Infinity)//Infinity
var Infinity=1
console.log(Infinity)//Infinity
//在函数作用域下
function test(){
    Infinity = 1;
    console.log('----',Infinity)//---- Infinity
}
function test(){
    var Infinity = 1;
    console.log('----',Infinity)//----1
}

如上,因此这些类型就不再“可靠”了,需要使用更可靠的值或者表达式来替代

3.11.2. void 5+5和void (5+5)

这个本质上是运算符的优先级问题。void 5+5等价于(void 5)+5

3.12. NaN对比

在javascript中一个神奇的属性。NaN 与所有值都不相等,包括它自己。NaN 不是常量,可以把它设置为其他值
判断是否是一个数字可以用isNaN()方法,但是有时候,我们希望NaNNaN是相等的,要怎么做呢?

//下边是vue源码中的一段代码
 set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val//获取旧值
   //这个地方为什么要对自身进行对比呢?
   //今天看Object.is()方法的pollfily时,忽然知道这里是为什么了.
   //假设var n = parseInt('abc');//这里的n为NaN
   //无论n==n还是n===n都是false,又因为除了NaN之外,所有的值自身与自身相比都是true
   //所以自身对比就可以知道值为NaN了
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {//自定义setter
        customSetter()
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
   //值改变后,通知订阅者见./dep.js
      dep.notify()
    }
//MDN中Object.is()中也有一段polyfill
if (!Object.is) {
  Object.is = function(x, y) {
    // SameValue algorithm
    if (x === y) { // Steps 1-5, 7-10
      // Steps 6.b-6.e: +0 != -0
      return x !== 0 || 1 / x === 1 / y;
    } else {
      // Step 6.a: NaN == NaN
      return x !== x && y !== y;
    }
  };
}

3.13. 隐式转换

[1,null,3].map(x=>x+1)//[2, 1, 4]
[1,undefined,3].map(x=>x+1)//[2, NaN, 4]
//下边这句代码显示的是undefined*1,但是使用arr变量接收并显示,却是undefined
//这个*n应该指的是有多少个undefined的
[1,,3].map(x=>x+1)//[2, undefined × 1, 4]

这里写图片描述

3.14. !!

如上图所示,共有6个值相当于false:falseundefinedNaN""0(包括+0、-0)、null,在判断时默认会进行隐式类型转换,但无论是从实际意义还是在后续操作中,我们都经常需要真正的true、false,这时候就可以使用!!来进行强制类型转换了,即将上述6中值转为false,其它转为true

3.15. &&和||

鉴于JavaScript的逻辑运算特性,可以用来替换过长的if...else...
缺点:这种方式将原本多行的if-else浓缩成一行,可读性大大降低,因此在实际开发中要慎用,以防止时间太久,连自己都不清楚代码的意图。

在Java中,&&和||最后总会返回一个布尔值。在JavaScript中却不仅仅是这样。
首先需要知道的是,JavaScript中的true、false并不是“绝对的”。默认的falseundefinedNaN""0null都会被当做false
下边是一段常用代码,来自于jquery源码。这段代码意思是说:如果Array.isArrayundefined,则使用后边的function。从这段代码看,||并不是返回true或者false,而是返回了一个Array.isArray或者function,只是这个返回值在if或者其它逻辑表达中代表着true而已

isArray: Array.isArray || function(obj) {
     return jQuery.type(obj) === "array";
    },

下边是vue中的一段源码,其中的e && e.__ob__ && e.__ob__.dep.depend()则是巧妙的使用&&来避免了if的嵌套

function dependArray (value: Array<any>) {
  for (let e, i = 0, l = value.length; i < l; i++) {
    e = value[i]
 //巧用&&化解多个if
 //递归订阅?
    e && e.__ob__ && e.__ob__.dep.depend()
    if (Array.isArray(e)) {
      dependArray(e)
    }
  }
}

下边是vue源码中使用&&来赋值

const getter = property && property.get

总结一句话:在JavaScript中,逻辑表达式的返回值并不只是true或者false,而是一个值,这个值可以转为true或者false而已,明白这句话,也就明白了所谓的||&&的妙用本质了。

3.16. null和{}的差别

这个其实可以延伸到比如说null和’’(空字符串)的差别等。
网上有一个比喻说的好:一个是无家可归,另一个是家徒四壁(O(∩_∩)O哈哈~)

3.17. 去除空格

' a  bc  '.replace(/ /g,'');//去除字符串中的所有空格
' a  bc  '.replace(/\s*/g,'');//去除字符串中的所有空白字符
' a  bc  '.replace(/^\s*/g,'');//去除字符串中左边所有空白字符
' a  bc  '.replace(/\s*$/g,'');//去除字符串中右边所有空白字符
' a  bc  '.replace(/^\s*|\s*$/g,'');//去除字符串中开头和结尾空白字符
' a  bc  '.trim();//去除字符串中开头和结尾空白字符

3.18. 获取url中的参数部分

//这只是一个思路,事实上需要很多必要的判断。
//可以使用的方法大都是关于Array,这里需要复习Array的es6 API
let param={};
paramsArr = 'http://www.runoob.com/jquery/misc-trim.html?channelid=12333&name=xiaoming&age=23'.split('?')[1].split('&');
//1.使用map
paramsArr.map(function(ele){
	param[ele.split('=')[0]]=ele.split('=')[1]
});
param={};
//2.使用filter
paramsArr.filter((ele)=>{
	param[ele.split('=')[0]]=ele.split('=')[1];
})

4. DOM操作

4.1. 事件

  • 事件分为三个阶段: 事件捕获 --> 事件目标 --> 事件冒泡
  • 事件捕获:事件发生时(onclick,onmouseover……)首先发生在document上,然后依次传递给body、……最后到达目的节点(即事件目标)。
  • 事件冒泡:事件到达事件目标之后不会结束,会逐层向上冒泡,直至document对象,跟事件捕获相反

1、onlick -->事件冒泡,重写onlick会覆盖之前属性,没有兼容性问题

ele.onclik = null;   //解绑单击事件,将onlick属性设为null即可

2、addEventListener(event.type, handle, boolean); IE8及以下不支持,属于DOM2级的方法,可添加多个方法不被覆盖,触发时会按照添加顺序依次调用。

//事件类型没有on
//false 表示在事件第三阶段(冒泡)触发
//true表示在事件第一阶段(捕获)触发。 如果handle是同一个方法,只执行一次。
ele.addEventListener('click', function(){ }, false);  
//解绑事件,参数和绑定一样
//不能移除匿名添加的函数。
ele.removeEventListener(event.type, handle, boolean);

3、attachEvent(event.type, handle ); IE特有,兼容IE8及以下,可添加多个事件处理程序,只支持冒泡阶段

//如果handle是同一个方法,绑定几次执行几次,这点和addEventListener不同,事件类型要加on,例如onclick而不是click
ele.attachEvent('onclick', function(){ }); 
//解绑事件,参数和绑定一样
ele.detachEvent("onclick", function(){ });

4、默认事件行为:href=""链接,submit表单提交等

● 阻止默认事件:

(1)return false; 阻止独享属性(通过on这种方式)绑定的事件的默认事件

ele.onclick = function() {
    ……                         //你的代码
    return false;              //通过返回false值阻止默认事件行为
};

(2)event.preventDefault( ); 阻止通过 addEventListener( ) 添加的事件的默认事件

element.addEventListener("click", function(e){
    var event = e || window.event;
    ……
    event.preventDefault( );      //阻止默认事件
},false);

(3)event.returnValue = false; 阻止通过 attachEvent( ) 添加的事件的默认事件

element.attachEvent("onclick", function(e){
    var event = e || window.event;
    ……
    event.returnValue = false;       //阻止默认事件
},false);

5、接下来我们把事件绑定以及事件解绑封装成为一个函数,兼容浏览器,包括IE6及以上

// 事件绑定
function addEvent(element, eType, handle, bol) {
    if(element.addEventListener){           //如果支持addEventListener
        element.addEventListener(eType, handle, bol);
    }else if(element.attachEvent){          //如果支持attachEvent
        element.attachEvent("on"+eType, handle);
    }else{                                  //否则使用兼容的onclick绑定
        element["on"+eType] = handle;
    }
}
// 事件解绑
function removeEvent(element, eType, handle, bol) {
    if(element.addEventListener){
        element.removeEventListener(eType, handle, bol);
    }else if(element.attachEvent){
        element.detachEvent("on"+eType, handle);
    }else{
        element["on"+eType] = null;
    }
}

A.● 事件冒泡、事件捕获阻止:

event.stopPropagation( );                // 阻止事件的进一步传播,包括(冒泡,捕获),无参数
event.cancelBubble = true;             // true 为阻止冒泡

B.● 事件委托:利用事件冒泡的特性,将里层的事件委托给外层事件,根据event对象的属性进行事件委托,改善性能。
使用事件委托能够避免对特定的每个节点添加事件监听器;事件监听器是被添加到它们的父元素上。事件监听器会分析从子元素冒泡上来的事件,找到是哪个子元素的事件。

4.1.1. 示例

4.1.1.1. JavaScript获取html元素的相关内容
<div id='secondpage' class="con show signup_box2"></div>
var ele = document.getElementById('secondpage');
//获取元素标签名称
ele.tagName;//DIV
//获取元素属性
ele.getAttribute('id');//secondpage
//获取元素的class
ele.className;//"con show signup_box2"
4.1.1.2. javascript获取form表单中的字段值
<html>
<body>
    <form id='test-form'>
        用户名:<input name='userName' value="123" /><br> 密码:
        <input name='pass' /><br>
    </form>
    <script type="text/javascript">
        var testForm1 = document.forms['test-form'];
        console.log('testForm1=' + testForm1);
        var testForm2 = document.getElementById('test-form');
        console.log('testForm2=' + testForm2);
        var userName1 = testForm1.userName;
        console.log('userName1=' + userName1);
        var userName2 = testForm1.elements[0];
        console.log('userName2=' + userName2);
        var userNameVal = userName1.value;
        console.log('userNameVal=' + userNameVal);
    </script>
</body>
</html>
4.1.1.3. javascript手动触发控件的click事件
<html>
	<body>
		<input type='button' id='test' onclick='alert(1)' value='按钮'/>
		<script type="text/javascript">
		//也可以使用document.getElementById('').click();
		function simulation_click(){
			var e = document.createEvent("MouseEvents");//创建新的MouseEvents事件
			
			//初始化新创建的 Event 对象的属性。
			//event.initEvent(eventType,canBubble,cancelable)
			//eventType		字符串值。事件的类型。
			//canBubble		事件是否起泡。
			//cancelable	是否可以用 preventDefault() 方法取消事件。
			e.initEvent("click", true, true);
			
			//dispatchEvent给节点分派一个合成事件。
			//如果在事件传播过程中调用了 evt 的 preventDefault() 方法,则返回 false,否则返回 true。
			document.getElementById("test").dispatchEvent(e);
		}
		simulation_dialog();
		</script>
	</body>
</html>

4.2. DOM

//1.创建dom
var div = document.createElement('div');//创建一个元素
var textNode = document.createTextNode('hello');//创建一个文本节点
div.appendChild(textNode);//添加子元素
div.removeChild(textNode)//移除子元素
var newText = document.createTextNode('world');
div.replaceChild(newText,textNode);//注意是new在前,old在后,否则报错
div.insertBefore(newText,textNode);//注意,新的在前,原有的在后
console.log(div);//<div>hello</div>
//2.给元素添加属性
//第一种方法,使用document.createAttribute,然后再使用setAttributeNode
var styleAttr = document.createAttribute('style');
styleAttr.value='border:1px solid black;background-color:red;';
div.setAttributeNode(styleAttr);
//第二种方法
div.setAttribute('id','test');//<div style="border:1px solid black" id="test">hello</div>
//3.获取属性
div.style;//该方法很可能获取到的不是我们需要的内容
div.getAttribute('style');
//4.添加到页面
document.write('<div>hello world</div>');
document.body.appendChild(div);
//5.获取元素内容
document.getElementById('test').outerHTML;//获取元素内容,包括本身
document.getElementById('test').outerHTML='<span>123</span>';//设置元素内容,包括本身

document.getElementById('test').innerHTML;//获取元素内容,不包括本身
document.getElementById('test').innerHTML='<span>123</span>';//设置元素内容,不包括本身

element.textContent = "this is some sample text";
document.getElementById('test').innerText;//获取元素中的文本
document.getElementById('test').outerText;//该特性是非标准的,请尽量不要在生产环境中使用它!用于获取元素中的文本时,和innerText效果相当;用于设置时,则比innerText多了元素本身。
document.getElementById('test').innerHTML='<span>123</span>';//设置元素中的文本
//6.获取元素
document.getElementById('test');
document.getElementsByTagName('div');
document.getElementsByName('name');

4.3. textConent

4.3.1. textConent与innerText的差别

  • textContent 会获取所有元素的内容,包括 <script><style> 元素,然而 innerText 不会。
  • innerText 受 CSS 样式的影响,并且不会返回隐藏元素的文本,而textContent会。
    由于 innerText 受 CSS 样式的影响,它会触发重排(reflow),但textContent 不会。
  • 与 textContent 不同的是, 在 Internet Explorer (对于小于等于 IE11 的版本) 中对 innerText 进行修改, 不仅会移除当前元素的子节点,而且还会永久性地破坏所有后代文本节点(所以不可能再次将节点再次插入到任何其他元素或同一元素中)。

4.3.2. 与innerHTML的区别

正如其名称,innerHTML 返回 HTML 文本。通常,为了在元素中检索或写入文本,人们使用innerHTML。但是,textContent通常具有更好的性能,因为文本不会被解析为HTML。此外,使用textContent可以防止 XSS 攻击。

5. apply、call

JavaScript中的每一个Function对象都有一个apply()方法和一个call()方法,它们的语法分别为:

/*apply()方法*/
function.apply(thisObj[, argArray])

/*call()方法*/
function.call(thisObj[, arg1[, arg2[, [,...argN]]]]);

它们各自的定义:

apply:调用一个对象的一个方法,用另一个对象替换当前对象。例如:B.apply(A, arguments);即A对象应用B对象的方法。

call:调用一个对象的一个方法,用另一个对象替换当前对象。例如:B.call(A, args1,args2);即A对象调用B对象的方法。

它们的共同之处:

都“可以用来代替另一个对象调用一个方法,将一个函数的对象上下文从初始的上下文改变为由thisObj指定的新对象”。

它们的不同之处:

apply:最多只能有两个参数——新this对象和一个数组argArray。如果给该方法传递多个参数,则把参数都写进这个数组里面,当然,即使只有一个参数,也要写进数组里。如果argArray不是一个有效的数组或arguments对象,那么将导致一个TypeError。如果没有提供argArray和thisObj任何一个参数,那么Global对象将被用作thisObj,并且无法被传递任何参数。

call:它可以接受多个参数,第一个参数与apply一样,后面则是一串参数列表。这个方法主要用在js对象各方法相互调用的时候,使当前this实例指针保持一致,或者在特殊情况下需要改变this指针。如果没有提供thisObj参数,那么 Global 对象被用作thisObj。

实际上,apply和call的功能是一样的,只是传入的参数列表形式不同。

6. typeof与instanceof

相同点:

JavaScript 中 typeof 和 instanceof 常用来判断一个变量是否为空,或者是什么类型的。

typeof

(1)、typeof 一般只能返回如下几个结果:number,boolean,string,function,object,undefined。
(2)、typeof 来获取一个变量是否存在,如 if(typeof a!=“undefined”){alert(“ok”)},而不要去使用 if(a) 因为如果 a 不存在(未声明)则会出错。
(3)、对于 Array,Null 等特殊对象使用 typeof 一律返回 object,这正是 typeof 的局限性。

instanceof

instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。

7. 闭包

闭包是在一个函数里声明了另外一个函数,并且这个函数访问了父函数作用域里的变量。
它访问了三个域的变量

  • 它自己作用域的变量
  • 父函数作用域的变量
  • 全局作用域的变量

我的理解

对变量作用域的重新定义。最典型的例子是在for循环中使用闭包。

//今天记录for循环和闭包的使用:
 for(var i = 0 ; i < lis.length ; i++) {
    lis[i].onclick=function(i){
        return function(){
        console.log(i)
        }
    }(i);
}
在for循环里面执行闭包的时候,将循环体的代码储存在一个内存中,对应的i值也储存在了内存中(闭包不销毁变量)。事件点击的时候是执行return之后的函数,在执行的时候,因为作用域的原因,使用的是事件之后的函数中的i值,这个i值在循环的时候已经储存为了对应的值,因此一次事件执行取到的i值都不一样。

例子1

var name = 'World!';
(function () {
    if (typeof name === 'undefined') {
        var name = 'Jack';
        console.log('Goodbye ' + name);
    } else {
        console.log('Hello ' + name);
    }
})();

解析:这个虽然是闭包的例子,但也有变量提升的关系。
在闭包中,if中的var name=‘Jack’;导致了变量的提升,这样typeof name==='undefined'就是true了。
因为闭包持有父级变量,这将导致这些变量有可能得不到释放,因此有可能造成内存泄漏

例子2

(function(){
  var a = b = 3;
})();
 
console.log("a defined? " + (typeof a !== 'undefined'));//a defined? false
console.log("b defined? " + (typeof b !== 'undefined'));//b defined? true

解析:

依然和变量的作用域有关系。在闭包中的var a=b=3事实上相当于b=3;var a=b;所以,a就是闭包中的局部变量,而b则提升为全局变量。

例子3

var myObject = {
    foo: "bar",
    func: function() {
        var self = this;
        console.log("outer func:  this.foo = " + this.foo);
        console.log("outer func:  self.foo = " + self.foo);
        (function() {
            console.log("inner func:  this.foo = " + this.foo);
            console.log("inner func:  self.foo = " + self.foo);
        }());
    }
};
myObject.func();
//输出
outer func:  this.foo = bar
outer func:  self.foo = bar
inner func:  this.foo = undefined
inner func:  self.foo = bar

解析

在外部函数中, this 和self 两者都指向了 myObject,因此两者都可以正确地引用和访问 foo。
在内部函数中, this 不再指向 myObject。其结果是,this.foo 没有在内部函数中被定义,相反,指向到本地的变量self 保持在范围内,并且可以访问。 (在ECMA 5之前,在内部函数中的this 将指向全局的 window 对象;反之,因为作为ECMA 5,内部函数中的功能this 是未定义的。)

8. 跨域

跨域

由于浏览器同源策略,凡是发送请求url的协议、域名、端口三者之间任意一与当前页面地址不同即为跨域。存在跨域的情况:
    网络协议不同,如http协议访问https协议。
    端口不同,如80端口访问8080端口。
    域名不同,如qianduanblog.com访问baidu.com
    子域名不同,如abc.qianduanblog.com访问def.qianduanblog.com
    域名和域名对应ip,如www.a.com访问20.205.28.90.

2、跨域请求资源的方法:

(1)、porxy代理

定义和用法:proxy代理用于将请求发送给后台服务器,通过服务器来发送请求,然后将请求的结果传递给前端。

实现方法:通过nginx代理;

注意点:1、如果你代理的是https协议的请求,那么你的proxy首先需要信任该证书(尤其是自定义证书)或者忽略证书检查,否则你的请求无法成功。

(2)、CORS 【Cross-Origin Resource Sharing】

定义和用法:是现代浏览器支持跨域资源请求的一种最常用的方式。

使用方法:一般需要后端人员在处理请求数据的时候,添加允许跨域的相关操作。如下:

res.writeHead(200, {
    "Content-Type": "text/html; charset=UTF-8",
    "Access-Control-Allow-Origin":'http://localhost',
    'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
    'Access-Control-Allow-Headers': 'X-Requested-With, Content-Type'
});
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值