你不知道的JavaScript(上)

时间:2021-09-16 13:14:52

一、Object.create与new Object区别

var obj = new Object();
console.log(obj.__proto__); //{} 其实是Object.prototype

var o = Object.create(null);
console.log(o.__proto__); //undefined

从上面可以得出,第一个参数传递null,相当于o对象没有Object.prototype的委托,比new Object更”空”

下面来介绍一下 Object.create(proto,[ propertiesObject ])

定义:使用指定的原型对象和属性创建一个新的对象。(如果为null,则代表没有原型对象)

第一个参数:指定的原型对象。如果不是 null 或一个对象值,则抛出一个 TypeError 异常。

第二个参数:属性描述符,与Object.defineProperty()第二个参数一样。

var a = {
"name" : "suoz"
};

a.__proto__.color = ["red","blue"];

var b = Object.create(a.__proto__,{"age":{
value : 20,
writable : true
}});

console.log(b.name); //undefined
console.log(b.age); //20

console.log(b.__proto__.constructor); //function Object(){}

console.log(b.__proto__ == a.__proto__); //true

//原型链
//b.__proto__(a.__proto__) => Object.prototype

二、for与forEach的区别?

昨天在看《你不知道的JS》(上篇)的时候,书中有涉及forEach,函数参数是传递一个函数,觉得好新颖。

代码一

function foo(){
for(var i=0;i<arguments.length;i++){
alert(arguemnts[i]);
}

}
foo(1,2,3,4,5)

代码二

function fn(value,index,arr){
alert(value);
}

[1,2,3,4,5].forEach(fn);

如果是上面的代码一、二,你会选择哪种?要是我肯定选择后者,毕竟在前面已经讲过访问arguments这个“大”对象是一个很昂贵的操作,会影响现代浏览器的性能。

那么我们来判断一下,什么时候用for?什么时候又用forEach?
1. 在固定长度 或者 长度不需要计算的时候用for比forEach效率更高
2. 在不确定长度 或者 计算长度损耗性能的时候用forEach
3. 遍历一个集合的值用forEach
4. 如果要对一个集合进行修改相关的操作,则用for

三、JS中参数是按值传递还是按引用传递?

在JS高程中,讲到了无论是基本类型实参还是引用类型的实参,都是按值传递。
这里我只讲解引用类型值参数,下面来看段代码:

function foo(obj){
obj.name = "suoz";
}

var obj = {};
foo(obj);
alert(obj.name); //suoz

看到这段代码,你肯定会觉得,形参的变化影响实参的值,那肯定是引用传递啦。答案是“不对”。

很多开发者错误的认为:在局部作用域中修改的对象会在全局作用域中反映出来,就说明参数是按引用传递的。

让我们再来看段代码:

function foo(obj){
var o = new Object();
obj = o;
o.name = "two";
}

var obj = {
"name" : "one"
};
foo(obj);
alert(obj.name); //one

这里弹出”one”,形参的变化并没有引起实参的变化。也验证了以上的观点:引用类型参数也是”按值传递的”。
其实实参obj隐式传递给形参obj,只是将实参在栈中的地址传递给形参,从而让形参也能对同一个对象的引用。
obj = o,这里又将新创建对象o在栈中的地址赋值给形参obj,这个时候形参不再指向实参对象,而是指向了新对象o。

四、为什么弃用arguments.callee

在ES5模式下,禁止使用callee、caller。
原因是访问arguments是一个昂贵的操作,因为它本身是个很”大”的对象,每次递归调用都要重新创建,影响现代浏览器的性能,还会影响闭包

五、arguments是一个伪数组对象

伪数组对象就是类似数组,同样可以使用数组的length属性判断长度,但是不能调用数组的方法,比如slice()。

如果想调用调用数组的方法,解决方案:Array.prototype.slice.call(arguments,0)

slice函数内部其实是遍历每一个数组对象,然后push进新数组,从而返回一个新数组,这个方法并不改变初始数组。

六、Function.prototype.bind()

说实话bind好像只有在JQuery中用过,在原生还真的没有接触过。

bind()第一个作用:用来创建一个函数,并且该函数内部的this指针绑定好传入的对象。

相对于call()和apply()方法来说,它们都可以改变一个对象内部的this指向。但是bind()返回的是一个函数,要实现内部的代码需要返回的函数执行,而call()和apply()方法是立即执行的。

JavaScript新手经常会犯的一个错误:将一个方法从对象中提取出来,然后再调用,希望该方法中的this指向原来的对象。比如:在回调函数中传入这个方法,如果不做特殊的处理的话,this一般会丢失原来的对象。

如果这个时候有一种方法将原来的函数和原来的对象绑定好放在一个绑定函数内部,这就解决了上述的问题。

this.x = 10;
var obj = {
x : 100;
foo : function(){
alert(this.x);
}
};
obj.foo(); //100
var new_foo = obj.foo;
new_foo(); //10 这里的this指向全局对象window

//创建一个新对象,将函数内部的"this"始终硬绑定到对象obj上,无论怎么调用,在哪里调用都不会改变this的指向了(此处除了我所知道的new绑定可以改变以外)
var bind_foo = new_foo.bind(obj); //此处称bind_foo为“绑定函数”
bind_foo(); //100

bind还有一个作用就是使一个函数拥有预设的初始参数。
bind()第二个参数是可选的,如果有的话,它们会被插入到目标函数参数列表的初始位置,传递给绑定函数的参数会跟在它们后面(即除了第一个参数之外,将其余的参数传递给下层函数,这种技术叫”部分应用”,属于”柯里化”的一种)

function showList(){
return Array.prototype.slice.call(arguments);
}

var bind_list = showList.bind(null,4); //不建议第一个参数使用null
bind_list(); //[4]
bind_list(1,2,3); //[4,1,2,3]

为什么在代码段中我不建议第一个参数使用null呢?
因为老是用null代替传递的对象,这种做法并不可取。如果某个函数确实用到了this(比如第三方库中的一个函数),那么默认绑定规则会把this绑定到全局对象中,万一把全局对象中的属性不小心改变了,这时候会导致不可预计的后果。
所以建议用{}代替null。
当然在call和apply中也是同样的道理。

七、硬绑定和软绑定

硬绑定就是第六大点的系统内置函数bind(),就是说通过硬(强制)绑定this的指向。以后无论在哪里调用,何时调用,以及传入的参数是什么,都不会再改变this的指向。(当然,”new绑定”比”显示绑定”优先级更高,所以可以改变this指向)

function foo(){
alert(this.value);
}

var obj1 = {"value":"one"};
var obj2 = {"value":"two"};

var bind_foo = foo.bind(obj1);

bind_foo(); // one

obj2.foo = foo;
obj2.foo(); //two

bind_foo.call(obj2); //one

//硬绑定,此处的foo中的this始终绑定到obj1对象上
obj2.foo = foo.bind(obj1);
obj2.foo(); //one

以下来介绍软绑定:

function foo(){
alert(this.value);
}

var obj1 = {"value":"one"};
var obj2 = {"value":"two"};

var bind_foo = foo.bind(obj1);

bind_foo(); // one

obj2.foo = foo;
obj2.foo(); //two

//软绑定
obj2.foo = foo.softBind(obj1);
obj2.foo(); //two
//自定义函数:软绑定 Function.prototype.softBind()
if(!Function.prototype.softBind){
Function.prototype.softBind = function(obj){
var fn = this; //这里this指调用softBind的函数
var arr = [].slice.call(arguments,1);
var bound = function(){
//console.log(this);//这里的this指代传入softBind()的对象参数
return fn.apply((!this||this===(window||global))?obj:this,
arr.concat.apply(arr,arguments);
);
};
bound.prototype = Object.create(fn.prototype);
return bound;
};
}

var obj = {"name":"one"};
var obj = {"name":"two"};
function foo(){}

var o = foo.softBind(obj);
alert(o); //这里o=>闭包bound里面返回的函数
o.call(obj2); //two