之前的一些关于js的博客
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
;使用new时,
当代码 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。
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
//当一个属性被设置为不可设置,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操作符并不是影响数组的长度。
例子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
undefined 和 not defined 的区别
JavaScript 未声明变量直接使用会抛出异常:var name is not defined,如果没有处理异常,代码就停止运行了。
但是,使用typeof undeclared_variable并不会产生异常,会直接返回 undefined。
undefined和null的区别
undefined说明没有这个属性(no house);null表示有属性,但这个属性没值(家徒四壁)
值得注意的时候typeof null
的结果为object
typeof的结果如下
number,boolean,string,function,object,undefined
typeof ''==='string';
typeof('') === 'string';
''.constructor===String
typeof null //object
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]
``
例子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]完全相同。
在vue的源码中看到如下一行代码引起了我的好奇
export const emptyObject = Object.freeze({})
查阅MDN后备忘如下:
Object.freeze() 方法可以冻结一个对象,冻结指的是不能向这个对象添加新的属性,不能修改其已有属性的值,不能删除已有属性,以及不能修改该对象已有属性的可枚举性、可配置性、可写性。也就是说,这个对象永远是不可变的。该方法返回被冻结的对象。
要使对象不可变,需要递归冻结每个类型为对象的属性(深冻结)。当你知道对象在引用图中不包含任何 环 (循环引用)时,将根据你的设计逐个使用该模式,否则将触发无限循环。对 deepFreeze() 的增强将是具有接收路径(例如Array)参数的内部函数,以便当对象进入不变时,可以递归地调用 deepFreeze() 。你仍然有冻结不应冻结的对象的风险,例如[window]。
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返回的是"给定对象的自身可枚举属性"
concat() – 将两个或多个字符的文本组合起来,返回一个新的字符串。
indexOf() – 返回字符串中一个子串第一处出现的索引。如果没有匹配项,返回 -1 。
charAt() – 返回指定位置的字符。
lastIndexOf() – 返回字符串中一个子串最后一处出现的索引,如果没有匹配项,返回 -1 。
match() – 检查一个字符串是否匹配一个正则表达式。
substr() 函数 -- 返回从string的startPos位置,长度为length的字符串
substring() – 返回字符串的一个子串。传入参数是起始位置和结束位置。
slice() – 提取字符串的一部分,并返回一个新字符串。
replace() – 用来查找匹配一个正则表达式的字符串,然后使用新字符串代替匹配的字符串。
search() – 执行一个正则表达式匹配查找。如果查找成功,返回字符串中匹配的索引值。否则返回 -1 。
split() – 通过将字符串划分成子串,将一个字符串做成一个字符串数组。
length – 返回字符串的长度,所谓字符串的长度是指其包含的字符的个数。
toLowerCase() – 将整个字符串转成小写字母。
toUpperCase() – 将整个字符串转成大写字母。
var arrayList = ['a','b','c','d','e','f'];
//1
arrayList = [];
//2
arrayList.length=0;
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下标表示数组中的最后一个元素
//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.还有就是
搜集了一些有意思的东西。
参考:js代码常见技巧总结
可以使用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
大括号的使用主要是2个方面:
比如:
for(var i =1;i<10 ;i++)
console.log(i) //此处原则上可以忽略大括号
上述语句并没有问题,但是如果后期函数体内增加了其他语句的时候,很容易忘记补上大括号,因此建议都带上大括号;
这个问题非常容易被忽略,通常我们都觉得大括号是跟在语句的同一行还是下一行只是习惯问题,但是实际上不是的!看下面这个例子:
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'}
如下代码
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已经成为了全局变量
这个主要是因为在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
特别是dom遍历,这样做效率更好
function test(){
var arr = [];
for(var len = arr.length,i=0;i
这个主要是因为for…in循环会循环原型链上的内容,使用hasOwnProperty可以减少 不必要的麻烦.当然也可以使用Object.keys()/Object.prototype.values()[上述两个方法不包括原型链和不可枚举的]/Object.getOwnPropertyNames()[不包括原型链,但是包括不可枚举的]
类型的隐式转换问题
避免以0开头的字符串被错认为八进制(es3)
标准内置对象的分类
这些全局属性返回一个简单值,这些值没有自己的属性和方法。
全局函数可以直接调用,不需要在调用时指定所属对象,执行结束后会将结果直接返回给调用者。
Finite
指的是有限数。因此,当isFinite()的参数是一个大于Number.MIN_VALUE小于Number.MAX_VALUE的数字、或者是可以转换为这个区间的数字时,isFinite()将返回true,否则返回false
javascript中void
void该运算符指定要计算一个表达式但是不论该表达式原来是否有自己的返回值,其返回值都为undefined。void运算符的操作数可以是任意类型。
返回undefined,(对于为什么不直接使用undefined,是因为undefined不是关键字,意味着它随时可能被篡改成其他值。。。)。
在es5中有一些有特殊意义的字符串,如undefined
,null
,Infinity
,NaN
等。在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
}
如上,因此这些类型就不再“可靠”了,需要使用更可靠的值或者表达式来替代
这个本质上是运算符的优先级问题。void 5+5
等价于(void 5)+5
在javascript中一个神奇的属性。NaN
与所有值都不相等,包括它自己。NaN
不是常量,可以把它设置为其他值
判断是否是一个数字可以用isNaN()
方法,但是有时候,我们希望NaN
和NaN
是相等的,要怎么做呢?
//下边是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;
}
};
}
[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]
如上图所示,共有6个值相当于false:false
、undefined
、NaN
、""
、0
(包括+0、-0)、null
,在判断时默认会进行隐式类型转换,但无论是从实际意义还是在后续操作中,我们都经常需要真正的true、false
,这时候就可以使用!!
来进行强制类型转换了,即将上述6中值转为false
,其它转为true
鉴于JavaScript的逻辑运算特性,可以用来替换过长的if...else...
。
缺点:这种方式将原本多行的if-else浓缩成一行,可读性大大降低,因此在实际开发中要慎用,以防止时间太久,连自己都不清楚代码的意图。
在Java中,&&和||最后总会返回一个布尔值。在JavaScript中却不仅仅是这样。
首先需要知道的是,JavaScript中的true、false并不是“绝对的”。默认的false
、undefined
、NaN
、""
、0
、null
都会被当做false
。
下边是一段常用代码,来自于jquery源码。这段代码意思是说:如果Array.isArray
为undefined
,则使用后边的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而已,明白这句话,也就明白了所谓的||
、&&
的妙用本质了。
这个其实可以延伸到比如说null和’’(空字符串)的差别等。
网上有一个比喻说的好:一个是无家可归,另一个是家徒四壁(O(∩_∩)O哈哈~)
' 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();//去除字符串中开头和结尾空白字符
//这只是一个思路,事实上需要很多必要的判断。
//可以使用的方法大都是关于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];
})
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对象的属性进行事件委托,改善性能。
使用事件委托能够避免对特定的每个节点添加事件监听器;事件监听器是被添加到它们的父元素上。事件监听器会分析从子元素冒泡上来的事件,找到是哪个子元素的事件。
<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"
<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>
<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>
//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);//hello
//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');//hello
//3.获取属性
div.style;//该方法很可能获取到的不是我们需要的内容
div.getAttribute('style');
//4.添加到页面
document.write('hello world');
document.body.appendChild(div);
//5.获取元素内容
document.getElementById('test').outerHTML;//获取元素内容,包括本身
document.getElementById('test').outerHTML='123';//设置元素内容,包括本身
document.getElementById('test').innerHTML;//获取元素内容,不包括本身
document.getElementById('test').innerHTML='123';//设置元素内容,不包括本身
element.textContent = "this is some sample text";
document.getElementById('test').innerText;//获取元素中的文本
document.getElementById('test').outerText;//该特性是非标准的,请尽量不要在生产环境中使用它!用于获取元素中的文本时,和innerText效果相当;用于设置时,则比innerText多了元素本身。
document.getElementById('test').innerHTML='123';//设置元素中的文本
//6.获取元素
document.getElementById('test');
document.getElementsByTagName('div');
document.getElementsByName('name');
- textContent 会获取所有元素的内容,包括
和
元素,然而 innerText 不会。
正如其名称,innerHTML 返回 HTML 文本。通常,为了在元素中检索或写入文本,人们使用innerHTML。但是,textContent通常具有更好的性能,因为文本不会被解析为HTML。此外,使用textContent可以防止 XSS 攻击。
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的功能是一样的,只是传入的参数列表形式不同。
相同点:
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 属性。
闭包是在一个函数里声明了另外一个函数,并且这个函数访问了父函数作用域里的变量。
它访问了三个域的变量
我的理解
对变量作用域的重新定义。最典型的例子是在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 是未定义的。)
跨域
由于浏览器同源策略,凡是发送请求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'
});