数据类型
原始类型
ECMAScript 有 5 种原始类型(primitive type),即 Undefined、Null、Boolean、Number 和 String以及symbol(ES6)。
引用数据类型
对象(Object)、数组(Array)、函数(Function)
typeof 运算符
- undefined - 如果变量是 Undefined 类型的
- boolean - 如果变量是 Boolean 类型的
- number - 如果变量是 Number 类型的
- string - 如果变量是 String 类型的
- object - 如果变量是一种引用类型或 Null 类型的
instanceof
从原型的角度,来判断某引用属于哪个构造函数,从而判定它的数据类型。
可以判断数据类型,还可以在继承关系中用来判断一个实例是否属于它的父类型。
Object instanceof Object //true
Function instanceof Function //true
Function instanceof Object //true
判断是对象还是数组
//原理:
var a = [1,4,6]
var b = {a:1}
a instanceof Array //true
a instanceof Object //true
b instanceof Array //false
b instanceof Object //true
Array.isArray(a);//true
Array.isArray(b)//false
//判断是否为数组
function isArray(obj) {
return obj instanceof Array;
}
//判断是否为对象(json对象)
function isObject(obj) {
if (!(obj instanceof Array) && (obj instanceof Object)) {
return true;
}
return false;
}
this
- 在全局环境中,this都是指向全局对象windows
- 在函数调用时,this指向取决于调用它的方式
script 标签
//脚本将会异步运行,HTML5新特性:
<script type="text/javascript" src="demo_async.js" async="async"></script>
//defer 属性规定是否对脚本执行进行延迟,直到页面加载为止,只有ie支持:
<script type="text/javascript" defer="defer">
alert(document.getElementById("p1").firstChild.nodeValue);
</script>
//定义外部脚本文件中所使用的字符编码:
<script type="text/javascript" src="myscripts.js" charset="UTF-8"></script>
replace()
replace() 方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串,该方法不改变字符串本身
var str = 'bob'
var rep = str.replace('b','a')
console.log(str) //bob
console.log(rep) //aob
正则表达式对象或字面量(支持gi模式) g全局匹配 i忽略大小
var str = 'bob'
var rep = str.replace(/b/g,'a')
console.log(rep) //aoa
也可使用function做为第二个参数
name = 'aaa bbb ccc';
uw=name.replace(/\b\w+\b/g, function(word){
return word.substring(0,1).toUpperCase()+word.substring(1);}
);
//"Aaa Bbb Ccc"
call apply bind
call apply bind 都是改变 this 指向的,第一个参数都是this 要指向的对象,也就是要指定的上下文。
bind 是返回对应函数,便于稍后调用;apply 、call 则是立即调用 。
func.call(this, arg1, arg2);
func.apply(this, [arg1, arg2]);
func.bind(this, arg1, arg2)();
数组去重
function a (arr){
return Array.from(new Set (arr));
}
原型原型链
var Person = function(){
this.old = '18' //实例成员
}
Person.nn = 'cookie' //静态成员
Person.prototype.sing = function () {
console.log('我爱唱歌);
};
var person = new Person()
console.log(person.__proto__ === Person.prototype); //true
person.nn //undefined 实例无法访问静态成员
person.old//18
Person.old//undefined 通过构造函数无法直接访问实例成员
Person.nn //cookie 通过构造函数可直接访问静态成员
每一个构造函数都有它自己的对象,通过prototype指向它的原型对象。
此构造函数new出来的实例,通过_proto_指向构造函数的原型对象。
原型与原型层层相链接的过程即为原型链。
原型的作用,就是共享方法。
原型中this的指向是实例。
实例成员: 实例成员就是在构造函数内部,通过this添加的成员。实例成员只能通过实例化的对象来访问。
静态成员: 在构造函数本身上添加的成员,只能通过构造函数来访问
定义构造函数的规则:公共属性定义到构造函数里面,公共方法我们放到原型对象身上。
查找属性
-
首先在对象自身上查找是否有该属性,如果有,返回结果
-
如果没有,就去对象的原型上进行查找, 如果有,返回结果
-
如果没有,就沿着原型链继续往上查找,直到Object.prototype原型上即可,如果有,返回结果
-
如果Object.prototype原型上也没有,返回undefined
继承
- 原型链继承
function a() {
this.name = 'hello'
this.colors = ['red', 'green', 'blue']
}
function b() {
this.old = '17'
}
b.prototype = new a();
var b1 = new b();
console.log(b1 instanceof a); //true
a.prototype.isPrototypeOf(b1); // true
b1.colors.push('black');
var b2 = new b();
b2.colors; // ["red", "green", "blue", "black"]
缺点:
1)有引用类型时,各个实例对该引用类型的操作会影响其它实例。
2)没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。
- 借用构造函数继承
function a(){
this.colors = ['red', 'green', 'blue']
}
function b(){
a.call(this)
}
var b1 = new b();
b1.colors.push('black');
console.log(b1.colors); //["red", "green", "blue", "black"]
var b2 = new b();
console.log(b2.colors); //["red", "green", "blue"]
function a(name){
this.colors = ['red', 'green', 'blue']
this.name = name
}
function b(){
a.call(this,'cookie')
}
var b1 = new b();
console.log(b1.name)
利用call或者apply把父类中通过this指定的属性和方法复制(借用)到子类创建的实例中。
优势:相对于原型链而言,借用构造函数有一个很大的优势,即可以在子类型构造函数中向超类型构造函数传递参数。因为属性是绑定到this上面的,所以调用的时候才赋到相应的实例中,各个实例的值就不会互相影响了。
有引用类型时,各个实例对该引用类型的操作不会影响其它实例。
缺点:如果仅仅是借用构造函数,那么也将无法避免构造函数模式存在的问题——方法都在构造函数中定义,因此函数复用就无从谈起了。而且,在超类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。考虑到这些问题,借用构造函数的技术也是很少单独使用的。
- 组合继承
是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。
思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。
function a(name) {
this.name = name
this.colors = ['red', 'green', 'blue']
}
a.prototype.sayName = function(){
console.log(this.name)
}
function b(name, age){
//继承属性
a.call(this, name)
this.age = age
}
//继承方法
b.prototype = new a();
b.prototype.constructor = b
b.prototype.sayAge = function(){
console.log(this.age)
}
var b1 = new b('cookie', 18);
b1.colors.push('black');
console.log(b1.colors);//["red", "green", "blue", "black"]
b1.sayName();//'cookie'
b1.sayAge(); //18
var b2 = new b('frank', 19);
console.log(b2.colors);//["red", "green", "blue"]
b2.sayName();//'frank'
b2.sayAge(); //19
缺点:组合继承最大的问题就是无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。虽然子类型最终会包含超类型对象的全部实例属性,但我们不得不在调用子类型构造函数时重写这些属性。
- Class继承
Class 可以通过extends关键字实现继承。子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
toString() {
returnthis.color + ' ' + super.toString(); // 调用父类的toString()
}
}
参考资料:https://mp.weixin.qq.com/s/psY8M9eMpWrZygHgfVYJCg
递归深拷贝
// 定义一个深拷贝函数 接收目标target参数
function deepClone(target) {
// 定义一个变量
let result;
// 如果当前需要深拷贝的是一个对象的话
if (typeof target === 'object') {
// 如果是一个数组的话
if (Array.isArray(target)) {
result = []; // 将result赋值为一个数组,并且执行遍历
for (let i in target) {
// 递归克隆数组中的每一项
result.push(deepClone(target[i]))
}
// 判断如果当前的值是null的话;直接赋值为null
} else if(target===null) {
result = null;
// 判断如果当前的值是一个RegExp对象的话,直接赋值
} else if(target.constructor===RegExp){
result = target;
}else {
// 否则是普通对象,直接for in循环,递归赋值对象的所有值
result = {};
for (let i in target) {
result[i] = deepClone(target[i]);
}
}
// 如果不是对象的话,就是基本数据类型,那么直接赋值
} else {
result = target;
}
// 返回最终结果
return result;
}
遍历对象数组
对象
var obj = {a: 3, v: 4, g: 'p'}